story.rs

  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}