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}