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}