story.rs

  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}