component_preview.rs

  1#![allow(missing_docs)]
  2use crate::prelude::*;
  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(_cx: &WindowContext) -> Vec<ComponentExampleGroup<Self>>;
 34
 35    fn component_previews(cx: &WindowContext) -> Vec<AnyElement> {
 36        Self::examples(cx)
 37            .into_iter()
 38            .map(|example| Self::render_example_group(example))
 39            .collect()
 40    }
 41
 42    fn render_component_previews(cx: &WindowContext) -> AnyElement {
 43        let title = Self::title();
 44        let (source, title) = title
 45            .rsplit_once("::")
 46            .map_or((None, title), |(s, t)| (Some(s), t));
 47        let description = Self::description().into();
 48
 49        v_flex()
 50            .gap_3()
 51            .p_4()
 52            .border_1()
 53            .border_color(cx.theme().colors().border)
 54            .rounded_md()
 55            .child(
 56                v_flex()
 57                    .gap_1()
 58                    .child(
 59                        h_flex()
 60                            .gap_1()
 61                            .child(Headline::new(title).size(HeadlineSize::Small))
 62                            .when_some(source, |this, source| {
 63                                this.child(Label::new(format!("({})", source)).color(Color::Muted))
 64                            }),
 65                    )
 66                    .when_some(description, |this, description| {
 67                        this.child(
 68                            div()
 69                                .text_ui_sm(cx)
 70                                .text_color(cx.theme().colors().text_muted)
 71                                .max_w(px(600.0))
 72                                .child(description),
 73                        )
 74                    }),
 75            )
 76            .children(Self::component_previews(cx))
 77            .into_any_element()
 78    }
 79
 80    fn render_example_group(group: ComponentExampleGroup<Self>) -> AnyElement {
 81        v_flex()
 82            .gap_2()
 83            .when_some(group.title, |this, title| {
 84                this.child(Label::new(title).size(LabelSize::Small))
 85            })
 86            .child(
 87                h_flex()
 88                    .gap_6()
 89                    .children(group.examples.into_iter().map(Self::render_example))
 90                    .into_any_element(),
 91            )
 92            .into_any_element()
 93    }
 94
 95    fn render_example(example: ComponentExample<Self>) -> AnyElement {
 96        let base = div().flex();
 97
 98        let base = match Self::example_label_side() {
 99            ExampleLabelSide::Right => base.flex_row(),
100            ExampleLabelSide::Left => base.flex_row_reverse(),
101            ExampleLabelSide::Bottom => base.flex_col(),
102            ExampleLabelSide::Top => base.flex_col_reverse(),
103        };
104
105        base.gap_1()
106            .child(example.element)
107            .child(
108                Label::new(example.variant_name)
109                    .size(LabelSize::XSmall)
110                    .color(Color::Muted),
111            )
112            .into_any_element()
113    }
114}
115
116/// A single example of a component.
117pub struct ComponentExample<T> {
118    variant_name: SharedString,
119    element: T,
120}
121
122impl<T> ComponentExample<T> {
123    /// Create a new example with the given variant name and example value.
124    pub fn new(variant_name: impl Into<SharedString>, example: T) -> Self {
125        Self {
126            variant_name: variant_name.into(),
127            element: example,
128        }
129    }
130}
131
132/// A group of component examples.
133pub struct ComponentExampleGroup<T> {
134    pub title: Option<SharedString>,
135    pub examples: Vec<ComponentExample<T>>,
136}
137
138impl<T> ComponentExampleGroup<T> {
139    /// Create a new group of examples with the given title.
140    pub fn new(examples: Vec<ComponentExample<T>>) -> Self {
141        Self {
142            title: None,
143            examples,
144        }
145    }
146
147    pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample<T>>) -> Self {
148        Self {
149            title: Some(title.into()),
150            examples,
151        }
152    }
153}
154
155/// Create a single example
156pub fn single_example<T>(variant_name: impl Into<SharedString>, example: T) -> ComponentExample<T> {
157    ComponentExample::new(variant_name, example)
158}
159
160/// Create a group of examples without a title
161pub fn example_group<T>(examples: Vec<ComponentExample<T>>) -> ComponentExampleGroup<T> {
162    ComponentExampleGroup::new(examples)
163}
164
165/// Create a group of examples with a title
166pub fn example_group_with_title<T>(
167    title: impl Into<SharedString>,
168    examples: Vec<ComponentExample<T>>,
169) -> ComponentExampleGroup<T> {
170    ComponentExampleGroup::with_title(title, examples)
171}