Skip to main content

guise/
blockquote.rs

1//! `Blockquote` — a quoted passage behind a left accent border, with an
2//! optional icon and citation.
3//!
4//! ```ignore
5//! Blockquote::new()
6//!     .icon(IconName::Info)
7//!     .text("Life is like an npm install — you never know what you are going to get.")
8//!     .cite("– Forrest Gump")
9//! ```
10
11use gpui::prelude::*;
12use gpui::{div, px, AnyElement, App, IntoElement, SharedString, Window};
13
14use crate::devtools::Probed;
15use crate::icon::{Icon, IconName};
16use crate::theme::{theme, ColorName, Size};
17
18/// A quote block.
19///
20/// Content is either [`Blockquote::text`], `ParentElement` children
21/// (`.child(..)`), or both — text renders first.
22#[derive(IntoElement)]
23pub struct Blockquote {
24  children: Vec<AnyElement>,
25  text: Option<SharedString>,
26  color: ColorName,
27  cite: Option<SharedString>,
28  icon: Option<IconName>,
29  padding: Size,
30  radius: Option<Size>,
31}
32
33impl Blockquote {
34  pub fn new() -> Self {
35    Blockquote {
36      children: Vec::new(),
37      text: None,
38      color: ColorName::Blue,
39      cite: None,
40      icon: None,
41      padding: Size::Lg,
42      radius: None,
43    }
44  }
45
46  /// The quoted text (shorthand for a single themed text child).
47  pub fn text(mut self, text: impl Into<SharedString>) -> Self {
48    self.text = Some(text.into());
49    self
50  }
51
52  /// The accent color for the border, icon, and background wash.
53  pub fn color(mut self, color: ColorName) -> Self {
54    self.color = color;
55    self
56  }
57
58  /// Attribution line, rendered dimmed below the quote (include your own
59  /// dash, e.g. `"– Forrest Gump"`).
60  pub fn cite(mut self, cite: impl Into<SharedString>) -> Self {
61    self.cite = Some(cite.into());
62    self
63  }
64
65  /// A glyph shown above the quote in the accent color.
66  pub fn icon(mut self, icon: IconName) -> Self {
67    self.icon = Some(icon);
68    self
69  }
70
71  pub fn padding(mut self, padding: Size) -> Self {
72    self.padding = padding;
73    self
74  }
75
76  pub fn radius(mut self, radius: Size) -> Self {
77    self.radius = Some(radius);
78    self
79  }
80}
81
82impl Default for Blockquote {
83  fn default() -> Self {
84    Blockquote::new()
85  }
86}
87
88impl ParentElement for Blockquote {
89  fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
90    self.children.extend(elements);
91  }
92}
93
94impl RenderOnce for Blockquote {
95  fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
96    let t = theme(cx);
97    let dark = t.scheme.is_dark();
98    let accent = t.color(self.color, if dark { 4 } else { 6 }).hsla();
99    let wash = t.color(self.color, if dark { 5 } else { 6 }).alpha(0.06);
100    let text_color = t.text().hsla();
101    let dimmed = t.dimmed().hsla();
102    let padding = t.spacing(self.padding);
103    let gap = t.spacing(Size::Sm);
104    let radius = t.radius(self.radius.unwrap_or(t.default_radius));
105    let font_md = t.font_size(Size::Md);
106    let font_sm = t.font_size(Size::Sm);
107
108    let mut el = div()
109      .flex()
110      .flex_col()
111      .gap(px(gap))
112      .p(px(padding))
113      .border_l(px(3.0))
114      .border_color(accent)
115      .rounded_r(px(radius))
116      .bg(wash)
117      .text_size(px(font_md))
118      .text_color(text_color);
119
120    if let Some(icon) = self.icon {
121      el = el.child(
122        div()
123          .flex()
124          .text_color(accent)
125          .child(Icon::new(icon).size(Size::Sm)),
126      );
127    }
128    if let Some(text) = self.text {
129      el = el.child(div().child(text));
130    }
131    el = el.children(self.children);
132    if let Some(cite) = self.cite {
133      el = el.child(div().text_size(px(font_sm)).text_color(dimmed).child(cite));
134    }
135    el.probe("Blockquote")
136  }
137}