component_preview.rs

  1#![allow(missing_docs)]
  2use crate::{prelude::*, KeyBinding};
  3use gpui::{AnyElement, SharedString};
  4
  5/// Which side of the preview to show labels on
  6#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
  7pub enum ExampleLabelSide {
  8    /// Left side
  9    Left,
 10    /// Right side
 11    Right,
 12    #[default]
 13    /// Top side
 14    Top,
 15    /// Bottom side
 16    Bottom,
 17}
 18
 19/// Implement this trait to enable rich UI previews with metadata in the Theme Preview tool.
 20pub trait ComponentPreview: IntoElement {
 21    fn title() -> &'static str {
 22        std::any::type_name::<Self>()
 23    }
 24
 25    fn description() -> impl Into<Option<&'static str>> {
 26        None
 27    }
 28
 29    fn example_label_side() -> ExampleLabelSide {
 30        ExampleLabelSide::default()
 31    }
 32
 33    fn examples(_window: &mut Window, _cx: &mut App) -> Vec<ComponentExampleGroup<Self>>;
 34
 35    fn custom_example(_window: &mut Window, _cx: &mut App) -> impl Into<Option<AnyElement>> {
 36        None::<AnyElement>
 37    }
 38
 39    fn component_previews(window: &mut Window, cx: &mut App) -> Vec<AnyElement> {
 40        Self::examples(window, cx)
 41            .into_iter()
 42            .map(|example| Self::render_example_group(example))
 43            .collect()
 44    }
 45
 46    fn render_component_previews(window: &mut Window, cx: &mut App) -> AnyElement {
 47        let title = Self::title();
 48        let (source, title) = title
 49            .rsplit_once("::")
 50            .map_or((None, title), |(s, t)| (Some(s), t));
 51        let description = Self::description().into();
 52
 53        v_flex()
 54            .w_full()
 55            .gap_6()
 56            .p_4()
 57            .border_1()
 58            .border_color(cx.theme().colors().border)
 59            .rounded_md()
 60            .child(
 61                v_flex()
 62                    .gap_1()
 63                    .child(
 64                        h_flex()
 65                            .gap_1()
 66                            .child(Headline::new(title).size(HeadlineSize::Small))
 67                            .when_some(source, |this, source| {
 68                                this.child(Label::new(format!("({})", source)).color(Color::Muted))
 69                            }),
 70                    )
 71                    .when_some(description, |this, description| {
 72                        this.child(
 73                            div()
 74                                .text_ui_sm(cx)
 75                                .text_color(cx.theme().colors().text_muted)
 76                                .max_w(px(600.0))
 77                                .child(description),
 78                        )
 79                    }),
 80            )
 81            .when_some(
 82                Self::custom_example(window, cx).into(),
 83                |this, custom_example| this.child(custom_example),
 84            )
 85            .children(Self::component_previews(window, cx))
 86            .into_any_element()
 87    }
 88
 89    fn render_example_group(group: ComponentExampleGroup<Self>) -> AnyElement {
 90        v_flex()
 91            .gap_6()
 92            .when(group.grow, |this| this.w_full().flex_1())
 93            .when_some(group.title, |this, title| {
 94                this.child(Label::new(title).size(LabelSize::Small))
 95            })
 96            .child(
 97                h_flex()
 98                    .w_full()
 99                    .gap_6()
100                    .children(group.examples.into_iter().map(Self::render_example))
101                    .into_any_element(),
102            )
103            .into_any_element()
104    }
105
106    fn render_example(example: ComponentExample<Self>) -> AnyElement {
107        let base = div().flex();
108
109        let base = match Self::example_label_side() {
110            ExampleLabelSide::Right => base.flex_row(),
111            ExampleLabelSide::Left => base.flex_row_reverse(),
112            ExampleLabelSide::Bottom => base.flex_col(),
113            ExampleLabelSide::Top => base.flex_col_reverse(),
114        };
115
116        base.gap_1()
117            .when(example.grow, |this| this.flex_1())
118            .child(example.element)
119            .child(
120                Label::new(example.variant_name)
121                    .size(LabelSize::XSmall)
122                    .color(Color::Muted),
123            )
124            .into_any_element()
125    }
126}
127
128/// A single example of a component.
129pub struct ComponentExample<T> {
130    variant_name: SharedString,
131    element: T,
132    grow: bool,
133}
134
135impl<T> ComponentExample<T> {
136    /// Create a new example with the given variant name and example value.
137    pub fn new(variant_name: impl Into<SharedString>, example: T) -> Self {
138        Self {
139            variant_name: variant_name.into(),
140            element: example,
141            grow: false,
142        }
143    }
144
145    /// Set the example to grow to fill the available horizontal space.
146    pub fn grow(mut self) -> Self {
147        self.grow = true;
148        self
149    }
150}
151
152/// A group of component examples.
153pub struct ComponentExampleGroup<T> {
154    pub title: Option<SharedString>,
155    pub examples: Vec<ComponentExample<T>>,
156    pub grow: bool,
157}
158
159impl<T> ComponentExampleGroup<T> {
160    /// Create a new group of examples with the given title.
161    pub fn new(examples: Vec<ComponentExample<T>>) -> Self {
162        Self {
163            title: None,
164            examples,
165            grow: false,
166        }
167    }
168
169    /// Create a new group of examples with the given title.
170    pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample<T>>) -> Self {
171        Self {
172            title: Some(title.into()),
173            examples,
174            grow: false,
175        }
176    }
177
178    /// Set the group to grow to fill the available horizontal space.
179    pub fn grow(mut self) -> Self {
180        self.grow = true;
181        self
182    }
183}
184
185/// Create a single example
186pub fn single_example<T>(variant_name: impl Into<SharedString>, example: T) -> ComponentExample<T> {
187    ComponentExample::new(variant_name, example)
188}
189
190/// Create a group of examples without a title
191pub fn example_group<T>(examples: Vec<ComponentExample<T>>) -> ComponentExampleGroup<T> {
192    ComponentExampleGroup::new(examples)
193}
194
195/// Create a group of examples with a title
196pub fn example_group_with_title<T>(
197    title: impl Into<SharedString>,
198    examples: Vec<ComponentExample<T>>,
199) -> ComponentExampleGroup<T> {
200    ComponentExampleGroup::with_title(title, examples)
201}
202
203pub fn theme_preview_keybinding(keystrokes: &str) -> KeyBinding {
204    KeyBinding::new(gpui::KeyBinding::new(keystrokes, gpui::NoAction {}, None))
205}