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