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}