1use gpui::{
2 AnyElement, App, DefaultColor, DefaultColors, Div, SharedString, Window, div, prelude::*, px,
3 rems,
4};
5use itertools::Itertools;
6use smallvec::SmallVec;
7
8pub struct Story {}
9
10impl Story {
11 pub fn container() -> gpui::Stateful<Div> {
12 let colors = DefaultColors::light();
13
14 div()
15 .id("story_container")
16 .overflow_y_scroll()
17 .w_full()
18 .min_h_full()
19 .flex()
20 .flex_col()
21 .text_color(DefaultColor::Text.hsla(&colors))
22 .bg(DefaultColor::Background.hsla(&colors))
23 }
24
25 pub fn title(title: impl Into<SharedString>) -> impl Element {
26 let colors = DefaultColors::light();
27
28 div()
29 .text_xs()
30 .text_color(DefaultColor::Text.hsla(&colors))
31 .child(title.into())
32 }
33
34 pub fn title_for<T>() -> impl Element {
35 Self::title(std::any::type_name::<T>())
36 }
37
38 pub fn section() -> Div {
39 let colors = DefaultColors::light();
40
41 div()
42 .p_4()
43 .m_4()
44 .border_1()
45 .border_color(DefaultColor::Separator.hsla(&colors))
46 }
47
48 pub fn section_title() -> Div {
49 let colors = DefaultColors::light();
50
51 div().text_lg().text_color(DefaultColor::Text.hsla(&colors))
52 }
53
54 pub fn group() -> Div {
55 let colors = DefaultColors::light();
56 div().my_2().bg(DefaultColor::Container.hsla(&colors))
57 }
58
59 pub fn code_block(code: impl Into<SharedString>) -> Div {
60 let colors = DefaultColors::light();
61
62 div()
63 .size_full()
64 .p_2()
65 .max_w(rems(36.))
66 .bg(DefaultColor::Container.hsla(&colors))
67 .rounded_sm()
68 .text_sm()
69 .text_color(DefaultColor::Text.hsla(&colors))
70 .overflow_hidden()
71 .child(code.into())
72 }
73
74 pub fn divider() -> Div {
75 let colors = DefaultColors::light();
76
77 div()
78 .my_2()
79 .h(px(1.))
80 .bg(DefaultColor::Separator.hsla(&colors))
81 }
82
83 pub fn description(description: impl Into<SharedString>) -> impl Element {
84 let colors = DefaultColors::light();
85
86 div()
87 .text_sm()
88 .text_color(DefaultColor::Text.hsla(&colors))
89 .min_w_96()
90 .child(description.into())
91 }
92
93 pub fn label(label: impl Into<SharedString>) -> impl Element {
94 let colors = DefaultColors::light();
95
96 div()
97 .text_xs()
98 .text_color(DefaultColor::Text.hsla(&colors))
99 .child(label.into())
100 }
101
102 /// Note: Not `ui::v_flex` as the `story` crate doesn't depend on the `ui` crate.
103 pub fn v_flex() -> Div {
104 div().flex().flex_col().gap_1()
105 }
106}
107
108#[derive(IntoElement)]
109pub struct StoryItem {
110 label: SharedString,
111 item: AnyElement,
112 description: Option<SharedString>,
113 usage: Option<SharedString>,
114}
115
116impl StoryItem {
117 pub fn new(label: impl Into<SharedString>, item: impl IntoElement) -> Self {
118 Self {
119 label: label.into(),
120 item: item.into_any_element(),
121 description: None,
122 usage: None,
123 }
124 }
125
126 pub fn description(mut self, description: impl Into<SharedString>) -> Self {
127 self.description = Some(description.into());
128 self
129 }
130
131 pub fn usage(mut self, code: impl Into<SharedString>) -> Self {
132 self.usage = Some(code.into());
133 self
134 }
135}
136
137impl RenderOnce for StoryItem {
138 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
139 let colors = DefaultColors::light();
140
141 div()
142 .my_2()
143 .flex()
144 .gap_4()
145 .w_full()
146 .child(
147 Story::v_flex()
148 .px_2()
149 .w_1_2()
150 .min_h_px()
151 .child(Story::label(self.label))
152 .child(
153 div()
154 .rounded_sm()
155 .bg(DefaultColor::Background.hsla(&colors))
156 .border_1()
157 .border_color(DefaultColor::Border.hsla(&colors))
158 .py_1()
159 .px_2()
160 .overflow_hidden()
161 .child(self.item),
162 )
163 .when_some(self.description, |this, description| {
164 this.child(Story::description(description))
165 }),
166 )
167 .child(
168 Story::v_flex()
169 .px_2()
170 .flex_none()
171 .w_1_2()
172 .min_h_px()
173 .when_some(self.usage, |this, usage| {
174 this.child(Story::label("Example Usage"))
175 .child(Story::code_block(usage))
176 }),
177 )
178 }
179}
180
181#[derive(IntoElement)]
182pub struct StorySection {
183 description: Option<SharedString>,
184 children: SmallVec<[AnyElement; 2]>,
185}
186
187impl Default for StorySection {
188 fn default() -> Self {
189 Self::new()
190 }
191}
192
193impl StorySection {
194 pub fn new() -> Self {
195 Self {
196 description: None,
197 children: SmallVec::new(),
198 }
199 }
200
201 pub fn description(mut self, description: impl Into<SharedString>) -> Self {
202 self.description = Some(description.into());
203 self
204 }
205}
206
207impl RenderOnce for StorySection {
208 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
209 let children: SmallVec<[AnyElement; 2]> = SmallVec::from_iter(Itertools::intersperse_with(
210 self.children.into_iter(),
211 || Story::divider().into_any_element(),
212 ));
213
214 Story::section()
215 // Section title
216 .py_2()
217 // Section description
218 .when_some(self.description.clone(), |section, description| {
219 section.child(Story::description(description))
220 })
221 .child(div().flex().flex_col().gap_2().children(children))
222 .child(Story::divider())
223 }
224}
225
226impl ParentElement for StorySection {
227 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
228 self.children.extend(elements)
229 }
230}