Unify Story/StoryContainers (#17114)

Nate Butler created

Unify the various Story containers, and use gpui default colors over the
custom `StoryColors`.

Release Notes:

- N/A

Change summary

crates/collab_ui/src/notifications/stories/collab_notification.rs |  70 
crates/story/src/story.rs                                         | 222 
crates/storybook/src/stories/text.rs                              | 174 
crates/title_bar/src/stories/application_menu.rs                  |  16 
crates/ui/src/components/stories/avatar.rs                        |   5 
crates/ui/src/components/stories/icon_button.rs                   |  66 
crates/ui/src/components/stories/keybinding.rs                    |  97 
crates/ui/src/components/stories/toggle_button.rs                 | 168 
crates/ui/src/components/stories/tool_strip.rs                    |  48 
9 files changed, 356 insertions(+), 510 deletions(-)

Detailed changes

crates/collab_ui/src/notifications/stories/collab_notification.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::prelude::*;
-use story::{StoryContainer, StoryItem, StorySection};
+use story::{Story, StoryItem, StorySection};
 use ui::prelude::*;
 
 use crate::notifications::collab_notification::CollabNotification;
@@ -10,41 +10,39 @@ impl Render for CollabNotificationStory {
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
         let window_container = |width, height| div().w(px(width)).h(px(height));
 
-        StoryContainer::new(
-            "CollabNotification Story",
-            "crates/collab_ui/src/notifications/stories/collab_notification.rs",
-        )
-        .child(
-            StorySection::new().child(StoryItem::new(
-                "Incoming Call Notification",
-                window_container(400., 72.).child(
-                    CollabNotification::new(
-                        "https://avatars.githubusercontent.com/u/1486634?v=4",
-                        Button::new("accept", "Accept"),
-                        Button::new("decline", "Decline"),
-                    )
-                    .child(
-                        v_flex()
-                            .overflow_hidden()
-                            .child(Label::new("maxdeviant is sharing a project in Zed")),
+        Story::container()
+            .child(Story::title_for::<CollabNotification>())
+            .child(
+                StorySection::new().child(StoryItem::new(
+                    "Incoming Call Notification",
+                    window_container(400., 72.).child(
+                        CollabNotification::new(
+                            "https://avatars.githubusercontent.com/u/1486634?v=4",
+                            Button::new("accept", "Accept"),
+                            Button::new("decline", "Decline"),
+                        )
+                        .child(
+                            v_flex()
+                                .overflow_hidden()
+                                .child(Label::new("maxdeviant is sharing a project in Zed")),
+                        ),
                     ),
-                ),
-            )),
-        )
-        .child(
-            StorySection::new().child(StoryItem::new(
-                "Project Shared Notification",
-                window_container(400., 72.).child(
-                    CollabNotification::new(
-                        "https://avatars.githubusercontent.com/u/1714999?v=4",
-                        Button::new("open", "Open"),
-                        Button::new("dismiss", "Dismiss"),
-                    )
-                    .child(Label::new("iamnbutler"))
-                    .child(Label::new("is sharing a project in Zed:"))
-                    .child(Label::new("zed")),
-                ),
-            )),
-        )
+                )),
+            )
+            .child(
+                StorySection::new().child(StoryItem::new(
+                    "Project Shared Notification",
+                    window_container(400., 72.).child(
+                        CollabNotification::new(
+                            "https://avatars.githubusercontent.com/u/1714999?v=4",
+                            Button::new("open", "Open"),
+                            Button::new("dismiss", "Dismiss"),
+                        )
+                        .child(Label::new("iamnbutler"))
+                        .child(Label::new("is sharing a project in Zed:"))
+                        .child(Label::new("zed")),
+                    ),
+                )),
+            )
     }
 }

crates/story/src/story.rs 🔗

@@ -1,123 +1,16 @@
 use gpui::{
-    div, hsla, prelude::*, px, rems, AnyElement, Div, ElementId, Hsla, SharedString, WindowContext,
+    div, prelude::*, px, rems, AnyElement, DefaultColor, DefaultColors, Div, SharedString,
+    WindowContext,
 };
 use itertools::Itertools;
 use smallvec::SmallVec;
 
-use std::path::PathBuf;
-use std::sync::atomic::{AtomicUsize, Ordering};
-use std::time::{SystemTime, UNIX_EPOCH};
-
-static COUNTER: AtomicUsize = AtomicUsize::new(0);
-
-pub fn reasonably_unique_id() -> String {
-    let now = SystemTime::now();
-    let timestamp = now.duration_since(UNIX_EPOCH).unwrap();
-
-    let cnt = COUNTER.fetch_add(1, Ordering::Relaxed);
-
-    let id = format!("{}_{}", timestamp.as_nanos(), cnt);
-
-    id
-}
-
-pub struct StoryColor {
-    pub primary: Hsla,
-    pub secondary: Hsla,
-    pub border: Hsla,
-    pub background: Hsla,
-    pub card_background: Hsla,
-    pub divider: Hsla,
-    pub link: Hsla,
-}
-
-impl StoryColor {
-    pub fn new() -> Self {
-        Self {
-            primary: hsla(216. / 360., 11. / 100., 0. / 100., 1.),
-            secondary: hsla(216. / 360., 11. / 100., 16. / 100., 1.),
-            border: hsla(216. / 360., 11. / 100., 91. / 100., 1.),
-            background: hsla(0. / 360., 0. / 100., 1., 1.),
-            card_background: hsla(0. / 360., 0. / 100., 96. / 100., 1.),
-            divider: hsla(216. / 360., 11. / 100., 86. / 100., 1.),
-            link: hsla(206. / 360., 1., 50. / 100., 1.),
-        }
-    }
-}
-
-pub fn story_color() -> StoryColor {
-    StoryColor::new()
-}
-
-#[derive(IntoElement)]
-pub struct StoryContainer {
-    title: SharedString,
-    relative_path: &'static str,
-    children: SmallVec<[AnyElement; 2]>,
-}
-
-impl StoryContainer {
-    pub fn new(title: impl Into<SharedString>, relative_path: &'static str) -> Self {
-        Self {
-            title: title.into(),
-            relative_path,
-            children: SmallVec::new(),
-        }
-    }
-}
-
-impl ParentElement for StoryContainer {
-    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
-        self.children.extend(elements)
-    }
-}
-
-impl RenderOnce for StoryContainer {
-    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
-        div()
-            .size_full()
-            .flex()
-            .flex_col()
-            .id("story_container")
-            .bg(story_color().background)
-            .child(
-                div()
-                    .flex()
-                    .flex_none()
-                    .w_full()
-                    .justify_between()
-                    .p_2()
-                    .bg(story_color().background)
-                    .border_b_1()
-                    .border_color(story_color().border)
-                    .child(Story::title(self.title))
-                    .child(
-                        div()
-                            .text_xs()
-                            .text_color(story_color().primary)
-                            .child(Story::open_story_link(self.relative_path)),
-                    ),
-            )
-            .child(
-                div()
-                    .w_full()
-                    .h_px()
-                    .flex_1()
-                    .id("story_body")
-                    .overflow_x_hidden()
-                    .overflow_y_scroll()
-                    .flex()
-                    .flex_col()
-                    .pb_4()
-                    .children(self.children),
-            )
-    }
-}
-
 pub struct Story {}
 
 impl Story {
     pub fn container() -> gpui::Stateful<Div> {
+        let colors = DefaultColors::light();
+
         div()
             .id("story_container")
             .overflow_y_scroll()
@@ -125,70 +18,16 @@ impl Story {
             .min_h_full()
             .flex()
             .flex_col()
-            .bg(story_color().background)
-    }
-
-    // TODO: Move all stories to container2, then rename
-    pub fn container2<T>(relative_path: &'static str) -> Div {
-        div().size_full().child(
-            div()
-                .size_full()
-                .id("story_container")
-                .overflow_y_scroll()
-                .flex()
-                .flex_col()
-                .flex_none()
-                .child(
-                    div()
-                        .flex()
-                        .justify_between()
-                        .p_2()
-                        .border_b_1()
-                        .border_color(story_color().border)
-                        .child(Story::title_for::<T>())
-                        .child(
-                            div()
-                                .text_xs()
-                                .text_color(story_color().primary)
-                                .child(Story::open_story_link(relative_path)),
-                        ),
-                )
-                .child(
-                    div()
-                        .w_full()
-                        .min_h_full()
-                        .flex()
-                        .flex_col()
-                        .bg(story_color().background),
-                ),
-        )
-    }
-
-    pub fn open_story_link(relative_path: &'static str) -> impl Element {
-        let path = PathBuf::from_iter([relative_path]);
-
-        div()
-            .flex()
-            .gap_2()
-            .text_xs()
-            .text_color(story_color().primary)
-            .id(SharedString::from(format!("id_{}", relative_path)))
-            .on_click({
-                let path = path.clone();
-
-                move |_event, _cx| {
-                    let path = format!("{}:0:0", path.to_string_lossy());
-
-                    std::process::Command::new("zed").arg(path).spawn().ok();
-                }
-            })
-            .children(vec![div().child(Story::link("Open in Zed →"))])
+            .text_color(DefaultColor::Text.hsla(&colors))
+            .bg(DefaultColor::Background.hsla(&colors))
     }
 
     pub fn title(title: impl Into<SharedString>) -> impl Element {
+        let colors = DefaultColors::light();
+
         div()
             .text_xs()
-            .text_color(story_color().primary)
+            .text_color(DefaultColor::Text.hsla(&colors))
             .child(title.into())
     }
 
@@ -197,59 +36,66 @@ impl Story {
     }
 
     pub fn section() -> Div {
+        let colors = DefaultColors::light();
+
         div()
             .p_4()
             .m_4()
             .border_1()
-            .border_color(story_color().border)
+            .border_color(DefaultColor::Separator.hsla(&colors))
     }
 
     pub fn section_title() -> Div {
-        div().text_lg().text_color(story_color().primary)
+        let colors = DefaultColors::light();
+
+        div().text_lg().text_color(DefaultColor::Text.hsla(&colors))
     }
 
     pub fn group() -> Div {
-        div().my_2().bg(story_color().background)
+        let colors = DefaultColors::light();
+        div().my_2().bg(DefaultColor::Container.hsla(&colors))
     }
 
     pub fn code_block(code: impl Into<SharedString>) -> Div {
+        let colors = DefaultColors::light();
+
         div()
             .size_full()
             .p_2()
             .max_w(rems(36.))
-            .bg(gpui::black())
+            .bg(DefaultColor::Container.hsla(&colors))
             .rounded_md()
             .text_sm()
-            .text_color(gpui::white())
+            .text_color(DefaultColor::Text.hsla(&colors))
             .overflow_hidden()
             .child(code.into())
     }
 
     pub fn divider() -> Div {
-        div().my_2().h(px(1.)).bg(story_color().divider)
-    }
+        let colors = DefaultColors::light();
 
-    pub fn link(link: impl Into<SharedString>) -> impl Element {
         div()
-            .id(ElementId::from(SharedString::from(reasonably_unique_id())))
-            .text_xs()
-            .text_color(story_color().link)
-            .cursor(gpui::CursorStyle::PointingHand)
-            .child(link.into())
+            .my_2()
+            .h(px(1.))
+            .bg(DefaultColor::Separator.hsla(&colors))
     }
 
     pub fn description(description: impl Into<SharedString>) -> impl Element {
+        let colors = DefaultColors::light();
+
         div()
             .text_sm()
-            .text_color(story_color().secondary)
+            .text_color(DefaultColor::Text.hsla(&colors))
             .min_w_96()
             .child(description.into())
     }
 
     pub fn label(label: impl Into<SharedString>) -> impl Element {
+        let colors = DefaultColors::light();
+
         div()
             .text_xs()
-            .text_color(story_color().primary)
+            .text_color(DefaultColor::Text.hsla(&colors))
             .child(label.into())
     }
 
@@ -290,6 +136,8 @@ impl StoryItem {
 
 impl RenderOnce for StoryItem {
     fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
+        let colors = DefaultColors::light();
+
         div()
             .my_2()
             .flex()
@@ -304,9 +152,9 @@ impl RenderOnce for StoryItem {
                     .child(
                         div()
                             .rounded_md()
-                            .bg(story_color().card_background)
+                            .bg(DefaultColor::Background.hsla(&colors))
                             .border_1()
-                            .border_color(story_color().border)
+                            .border_color(DefaultColor::Border.hsla(&colors))
                             .py_1()
                             .px_2()
                             .overflow_hidden()

crates/storybook/src/stories/text.rs 🔗

@@ -15,98 +15,106 @@ impl TextStory {
 
 impl Render for TextStory {
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
-        StoryContainer::new("Text Story", "crates/storybook/src/stories/text.rs")
-            .children(
-                vec![
-
-            StorySection::new()
-                .child(
-                    StoryItem::new("Default", div().bg(gpui::blue()).child("Hello World!"))
+        Story::container()
+            .child(Story::title("Text"))
+            .children(vec![
+                StorySection::new()
+                    .child(
+                        StoryItem::new("Default", div().bg(gpui::blue()).child("Hello World!"))
+                            .usage(indoc! {r##"
+                                div()
+                                    .child("Hello World!")
+                                "##
+                            }),
+                    )
+                    .child(
+                        StoryItem::new(
+                            "Wrapping Text",
+                            div().max_w_96().child(concat!(
+                                "The quick brown fox jumps over the lazy dog. ",
+                                "Meanwhile, the lazy dog decided it was time for a change. ",
+                                "He started daily workout routines, ate healthier and became the fastest dog in town.",
+                            )),
+                        )
+                        .description("Set a width or max-width to enable text wrapping.")
                         .usage(indoc! {r##"
                             div()
-                                .child("Hello World!")
+                                .max_w_96()
+                                .child("Some text that you want to wrap.")
                             "##
                         }),
-                )
-                .child(
-                    StoryItem::new("Wrapping Text",
-                        div().max_w_96()
-                            .child(
-                                concat!(
-                                    "The quick brown fox jumps over the lazy dog. ",
+                    )
+                    .child(
+                        StoryItem::new(
+                            "tbd",
+                            div().flex().w_96().child(
+                                div().overflow_hidden().child(concat!(
+                                    "flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ",
                                     "Meanwhile, the lazy dog decided it was time for a change. ",
                                     "He started daily workout routines, ate healthier and became the fastest dog in town.",
-                                )
-                            )
+                                )),
+                            ),
+                        ),
                     )
-                    .description("Set a width or max-width to enable text wrapping.")
-                    .usage(indoc! {r##"
-                        div()
-                            .max_w_96()
-                            .child("Some text that you want to wrap.")
-                        "##
-                    })
-                )
-                .child(
-                    StoryItem::new("tbd",
-                    div().flex().w_96().child(div().overflow_hidden().child(concat!(
-                            "flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ",
-                            "Meanwhile, the lazy dog decided it was time for a change. ",
-                            "He started daily workout routines, ate healthier and became the fastest dog in town.",
-                        )))
-                    )
-                )
-                .child(
-                    StoryItem::new("Text in Horizontal Flex",
-                        div().flex().w_96().bg(red()).child(concat!(
-                                        "flex-row. width 96. The quick brown fox jumps over the lazy dog. ",
-                                        "Meanwhile, the lazy dog decided it was time for a change. ",
-                                        "He started daily workout routines, ate healthier and became the fastest dog in town.",
-                                    ))
-                    )
-                    .usage(indoc! {r##"
-                        // NOTE: When rendering text in a horizontal flex container,
-                        // Taffy will not pass width constraints down from the parent.
-                        // To fix this, render text in a parent with overflow: hidden
-
-                        div()
-                            .max_w_96()
-                            .child("Some text that you want to wrap.")
-                        "##
-                    })
-                )
-                .child(
-                    StoryItem::new("Interactive Text",
-                        InteractiveText::new(
-                            "interactive",
-                            StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
-                                (6..11, HighlightStyle {
-                                    background_color: Some(green()),
-                                    ..Default::default()
-                                }),
-                            ]),
+                    .child(
+                        StoryItem::new(
+                            "Text in Horizontal Flex",
+                            div().flex().w_96().bg(red()).child(concat!(
+                                "flex-row. width 96. The quick brown fox jumps over the lazy dog. ",
+                                "Meanwhile, the lazy dog decided it was time for a change. ",
+                                "He started daily workout routines, ate healthier and became the fastest dog in town.",
+                            )),
                         )
-                        .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
-                            println!("Clicked range {range_ix}");
-                        })
+                        .usage(indoc! {r##"
+                            // NOTE: When rendering text in a horizontal flex container,
+                            // Taffy will not pass width constraints down from the parent.
+                            // To fix this, render text in a parent with overflow: hidden
+
+                            div()
+                                .max_w_96()
+                                .child("Some text that you want to wrap.")
+                            "##
+                        }),
                     )
-                    .usage(indoc! {r##"
-                        InteractiveText::new(
-                            "interactive",
-                            StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
-                                (6..11, HighlightStyle {
-                                    background_color: Some(green()),
-                                    ..Default::default()
-                                }),
-                            ]),
+                    .child(
+                        StoryItem::new(
+                            "Interactive Text",
+                            InteractiveText::new(
+                                "interactive",
+                                StyledText::new("Hello world, how is it going?").with_highlights(
+                                    &cx.text_style(),
+                                    [
+                                        (
+                                            6..11,
+                                            HighlightStyle {
+                                                background_color: Some(green()),
+                                                ..Default::default()
+                                            },
+                                        ),
+                                    ],
+                                ),
+                            )
+                            .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
+                                println!("Clicked range {range_ix}");
+                            }),
                         )
-                        .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
-                            println!("Clicked range {range_ix}");
-                        })
-                        "##
-                    })
-                )
-        ]
-            ).into_element()
+                        .usage(indoc! {r##"
+                            InteractiveText::new(
+                                "interactive",
+                                StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
+                                    (6..11, HighlightStyle {
+                                        background_color: Some(green()),
+                                        ..Default::default()
+                                    }),
+                                ]),
+                            )
+                            .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
+                                println!("Clicked range {range_ix}");
+                            })
+                            "##
+                        }),
+                    ),
+            ])
+            .into_element()
     }
 }

crates/title_bar/src/stories/application_menu.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::Render;
-use story::{StoryContainer, StoryItem, StorySection};
+use story::{Story, StoryItem, StorySection};
 
 use ui::prelude::*;
 
@@ -9,13 +9,11 @@ pub struct ApplicationMenuStory;
 
 impl Render for ApplicationMenuStory {
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        StoryContainer::new(
-            "ApplicationMenu Story",
-            "crates/title_bar/src/stories/application_menu.rs",
-        )
-        .child(StorySection::new().child(StoryItem::new(
-            "Application Menu",
-            h_flex().child(ApplicationMenu::new()),
-        )))
+        Story::container()
+            .child(Story::title_for::<ApplicationMenu>())
+            .child(StorySection::new().child(StoryItem::new(
+                "Application Menu",
+                h_flex().child(ApplicationMenu::new()),
+            )))
     }
 }

crates/ui/src/components/stories/avatar.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::Render;
-use story::{StoryContainer, StoryItem, StorySection};
+use story::{Story, StoryItem, StorySection};
 
 use crate::{prelude::*, AudioStatus, Availability, AvatarAvailabilityIndicator};
 use crate::{Avatar, AvatarAudioStatusIndicator};
@@ -8,7 +8,8 @@ pub struct AvatarStory;
 
 impl Render for AvatarStory {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        StoryContainer::new("Avatar", "crates/ui/src/components/stories/avatar.rs")
+        Story::container()
+            .child(Story::title_for::<Avatar>())
             .child(
                 StorySection::new()
                     .child(StoryItem::new(

crates/ui/src/components/stories/icon_button.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::Render;
-use story::{StoryContainer, StoryItem, StorySection};
+use story::{Story, StoryItem, StorySection};
 
 use crate::{prelude::*, IconButtonShape, Tooltip};
 use crate::{IconButton, IconName};
@@ -111,38 +111,36 @@ impl Render for IconButtonStory {
             selected_with_tooltip_button,
         ];
 
-        StoryContainer::new(
-            "Icon Button",
-            "crates/ui/src/components/stories/icon_button.rs",
-        )
-        .child(StorySection::new().children(buttons))
-        .child(
-            StorySection::new().child(StoryItem::new(
-                "Square",
-                h_flex()
-                    .gap_2()
-                    .child(
-                        IconButton::new("square-medium", IconName::Close)
-                            .shape(IconButtonShape::Square)
-                            .icon_size(IconSize::Medium),
-                    )
-                    .child(
-                        IconButton::new("square-small", IconName::Close)
-                            .shape(IconButtonShape::Square)
-                            .icon_size(IconSize::Small),
-                    )
-                    .child(
-                        IconButton::new("square-xsmall", IconName::Close)
-                            .shape(IconButtonShape::Square)
-                            .icon_size(IconSize::XSmall),
-                    )
-                    .child(
-                        IconButton::new("square-indicator", IconName::Close)
-                            .shape(IconButtonShape::Square)
-                            .icon_size(IconSize::Indicator),
-                    ),
-            )),
-        )
-        .into_element()
+        Story::container()
+            .child(Story::title_for::<IconButton>())
+            .child(StorySection::new().children(buttons))
+            .child(
+                StorySection::new().child(StoryItem::new(
+                    "Square",
+                    h_flex()
+                        .gap_2()
+                        .child(
+                            IconButton::new("square-medium", IconName::Close)
+                                .shape(IconButtonShape::Square)
+                                .icon_size(IconSize::Medium),
+                        )
+                        .child(
+                            IconButton::new("square-small", IconName::Close)
+                                .shape(IconButtonShape::Square)
+                                .icon_size(IconSize::Small),
+                        )
+                        .child(
+                            IconButton::new("square-xsmall", IconName::Close)
+                                .shape(IconButtonShape::Square)
+                                .icon_size(IconSize::XSmall),
+                        )
+                        .child(
+                            IconButton::new("square-indicator", IconName::Close)
+                                .shape(IconButtonShape::Square)
+                                .icon_size(IconSize::Indicator),
+                        ),
+                )),
+            )
+            .into_element()
     }
 }

crates/ui/src/components/stories/keybinding.rs 🔗

@@ -1,7 +1,7 @@
 use gpui::NoAction;
 use gpui::Render;
 use itertools::Itertools;
-use story::{Story, StoryContainer};
+use story::Story;
 
 use crate::{prelude::*, KeyBinding};
 
@@ -15,28 +15,23 @@ impl Render for KeybindingStory {
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
         let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
 
-        StoryContainer::new(
-            "KeyBinding",
-            "crates/ui/src/components/stories/keybinding.rs",
-        )
-        .child(Story::label("Single Key"))
-        .child(KeyBinding::new(binding("Z")))
-        .child(Story::label("Single Key with Modifier"))
-        .child(
-            div()
-                .flex()
-                .gap_3()
-                .child(KeyBinding::new(binding("ctrl-c")))
-                .child(KeyBinding::new(binding("alt-c")))
-                .child(KeyBinding::new(binding("cmd-c")))
-                .child(KeyBinding::new(binding("shift-c"))),
-        )
-        .child(Story::label("Single Key with Modifier (Permuted)"))
-        .child(
-            div()
-                .flex()
-                .flex_col()
-                .children(
+        Story::container()
+            .child(Story::title_for::<KeyBinding>())
+            .child(Story::label("Single Key"))
+            .child(KeyBinding::new(binding("Z")))
+            .child(Story::label("Single Key with Modifier"))
+            .child(
+                div()
+                    .flex()
+                    .gap_3()
+                    .child(KeyBinding::new(binding("ctrl-c")))
+                    .child(KeyBinding::new(binding("alt-c")))
+                    .child(KeyBinding::new(binding("cmd-c")))
+                    .child(KeyBinding::new(binding("shift-c"))),
+            )
+            .child(Story::label("Single Key with Modifier (Permuted)"))
+            .child(
+                div().flex().flex_col().children(
                     all_modifier_permutations
                         .chunks(4)
                         .into_iter()
@@ -50,31 +45,35 @@ impl Render for KeybindingStory {
                                 }))
                         }),
                 ),
-        )
-        .child(Story::label("Single Key with All Modifiers"))
-        .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
-        .child(Story::label("Chord"))
-        .child(KeyBinding::new(binding("a z")))
-        .child(Story::label("Chord with Modifier"))
-        .child(KeyBinding::new(binding("ctrl-a shift-z")))
-        .child(KeyBinding::new(binding("fn-s")))
-        .child(Story::label("Single Key with All Modifiers (Linux)"))
-        .child(
-            KeyBinding::new(binding("ctrl-alt-cmd-shift-z")).platform_style(PlatformStyle::Linux),
-        )
-        .child(Story::label("Chord (Linux)"))
-        .child(KeyBinding::new(binding("a z")).platform_style(PlatformStyle::Linux))
-        .child(Story::label("Chord with Modifier (Linux)"))
-        .child(KeyBinding::new(binding("ctrl-a shift-z")).platform_style(PlatformStyle::Linux))
-        .child(KeyBinding::new(binding("fn-s")).platform_style(PlatformStyle::Linux))
-        .child(Story::label("Single Key with All Modifiers (Windows)"))
-        .child(
-            KeyBinding::new(binding("ctrl-alt-cmd-shift-z")).platform_style(PlatformStyle::Windows),
-        )
-        .child(Story::label("Chord (Windows)"))
-        .child(KeyBinding::new(binding("a z")).platform_style(PlatformStyle::Windows))
-        .child(Story::label("Chord with Modifier (Windows)"))
-        .child(KeyBinding::new(binding("ctrl-a shift-z")).platform_style(PlatformStyle::Windows))
-        .child(KeyBinding::new(binding("fn-s")).platform_style(PlatformStyle::Windows))
+            )
+            .child(Story::label("Single Key with All Modifiers"))
+            .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
+            .child(Story::label("Chord"))
+            .child(KeyBinding::new(binding("a z")))
+            .child(Story::label("Chord with Modifier"))
+            .child(KeyBinding::new(binding("ctrl-a shift-z")))
+            .child(KeyBinding::new(binding("fn-s")))
+            .child(Story::label("Single Key with All Modifiers (Linux)"))
+            .child(
+                KeyBinding::new(binding("ctrl-alt-cmd-shift-z"))
+                    .platform_style(PlatformStyle::Linux),
+            )
+            .child(Story::label("Chord (Linux)"))
+            .child(KeyBinding::new(binding("a z")).platform_style(PlatformStyle::Linux))
+            .child(Story::label("Chord with Modifier (Linux)"))
+            .child(KeyBinding::new(binding("ctrl-a shift-z")).platform_style(PlatformStyle::Linux))
+            .child(KeyBinding::new(binding("fn-s")).platform_style(PlatformStyle::Linux))
+            .child(Story::label("Single Key with All Modifiers (Windows)"))
+            .child(
+                KeyBinding::new(binding("ctrl-alt-cmd-shift-z"))
+                    .platform_style(PlatformStyle::Windows),
+            )
+            .child(Story::label("Chord (Windows)"))
+            .child(KeyBinding::new(binding("a z")).platform_style(PlatformStyle::Windows))
+            .child(Story::label("Chord with Modifier (Windows)"))
+            .child(
+                KeyBinding::new(binding("ctrl-a shift-z")).platform_style(PlatformStyle::Windows),
+            )
+            .child(KeyBinding::new(binding("fn-s")).platform_style(PlatformStyle::Windows))
     }
 }

crates/ui/src/components/stories/toggle_button.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::Render;
-use story::{StoryContainer, StoryItem, StorySection};
+use story::{Story, StoryItem, StorySection};
 
 use crate::{prelude::*, ToggleButton};
 
@@ -7,89 +7,87 @@ pub struct ToggleButtonStory;
 
 impl Render for ToggleButtonStory {
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        StoryContainer::new(
-            "Toggle Button",
-            "crates/ui/src/components/stories/toggle_button.rs",
-        )
-        .child(
-            StorySection::new().child(
-                StoryItem::new(
-                    "Default",
-                    ToggleButton::new("default_toggle_button", "Hello"),
-                )
-                .description("Displays a toggle button.")
-                .usage(""),
-            ),
-        )
-        .child(
-            StorySection::new().child(
-                StoryItem::new(
-                    "Toggle button group",
-                    h_flex()
-                        .child(
-                            ToggleButton::new(1, "Apple")
-                                .style(ButtonStyle::Filled)
-                                .size(ButtonSize::Large)
-                                .first(),
-                        )
-                        .child(
-                            ToggleButton::new(2, "Banana")
-                                .style(ButtonStyle::Filled)
-                                .size(ButtonSize::Large)
-                                .middle(),
-                        )
-                        .child(
-                            ToggleButton::new(3, "Cherry")
-                                .style(ButtonStyle::Filled)
-                                .size(ButtonSize::Large)
-                                .middle(),
-                        )
-                        .child(
-                            ToggleButton::new(4, "Dragonfruit")
-                                .style(ButtonStyle::Filled)
-                                .size(ButtonSize::Large)
-                                .last(),
-                        ),
-                )
-                .description("Displays a group of toggle buttons.")
-                .usage(""),
-            ),
-        )
-        .child(
-            StorySection::new().child(
-                StoryItem::new(
-                    "Toggle button group with selection",
-                    h_flex()
-                        .child(
-                            ToggleButton::new(1, "Apple")
-                                .style(ButtonStyle::Filled)
-                                .size(ButtonSize::Large)
-                                .first(),
-                        )
-                        .child(
-                            ToggleButton::new(2, "Banana")
-                                .style(ButtonStyle::Filled)
-                                .size(ButtonSize::Large)
-                                .selected(true)
-                                .middle(),
-                        )
-                        .child(
-                            ToggleButton::new(3, "Cherry")
-                                .style(ButtonStyle::Filled)
-                                .size(ButtonSize::Large)
-                                .middle(),
-                        )
-                        .child(
-                            ToggleButton::new(4, "Dragonfruit")
-                                .style(ButtonStyle::Filled)
-                                .size(ButtonSize::Large)
-                                .last(),
-                        ),
-                )
-                .description("Displays a group of toggle buttons.")
-                .usage(""),
-            ),
-        )
-        .into_element()
+        Story::container()
+            .child(Story::title_for::<ToggleButton>())
+            .child(
+                StorySection::new().child(
+                    StoryItem::new(
+                        "Default",
+                        ToggleButton::new("default_toggle_button", "Hello"),
+                    )
+                    .description("Displays a toggle button.")
+                    .usage(""),
+                ),
+            )
+            .child(
+                StorySection::new().child(
+                    StoryItem::new(
+                        "Toggle button group",
+                        h_flex()
+                            .child(
+                                ToggleButton::new(1, "Apple")
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Large)
+                                    .first(),
+                            )
+                            .child(
+                                ToggleButton::new(2, "Banana")
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Large)
+                                    .middle(),
+                            )
+                            .child(
+                                ToggleButton::new(3, "Cherry")
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Large)
+                                    .middle(),
+                            )
+                            .child(
+                                ToggleButton::new(4, "Dragonfruit")
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Large)
+                                    .last(),
+                            ),
+                    )
+                    .description("Displays a group of toggle buttons.")
+                    .usage(""),
+                ),
+            )
+            .child(
+                StorySection::new().child(
+                    StoryItem::new(
+                        "Toggle button group with selection",
+                        h_flex()
+                            .child(
+                                ToggleButton::new(1, "Apple")
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Large)
+                                    .first(),
+                            )
+                            .child(
+                                ToggleButton::new(2, "Banana")
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Large)
+                                    .selected(true)
+                                    .middle(),
+                            )
+                            .child(
+                                ToggleButton::new(3, "Cherry")
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Large)
+                                    .middle(),
+                            )
+                            .child(
+                                ToggleButton::new(4, "Dragonfruit")
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Large)
+                                    .last(),
+                            ),
+                    )
+                    .description("Displays a group of toggle buttons.")
+                    .usage(""),
+                ),
+            )
+            .into_element()
     }
 }

crates/ui/src/components/stories/tool_strip.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::Render;
-use story::{StoryContainer, StoryItem, StorySection};
+use story::{Story, StoryItem, StorySection};
 
 use crate::{prelude::*, ToolStrip, Tooltip};
 
@@ -7,29 +7,27 @@ pub struct ToolStripStory;
 
 impl Render for ToolStripStory {
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        StoryContainer::new(
-            "Tool Strip",
-            "crates/ui/src/components/stories/tool_strip.rs",
-        )
-        .child(
-            StorySection::new().child(StoryItem::new(
-                "Vertical Tool Strip",
-                h_flex().child(
-                    ToolStrip::vertical("tool_strip_example")
-                        .tool(
-                            IconButton::new("example_tool", IconName::AudioOn)
-                                .tooltip(|cx| Tooltip::text("Example tool", cx)),
-                        )
-                        .tool(
-                            IconButton::new("example_tool_2", IconName::MicMute)
-                                .tooltip(|cx| Tooltip::text("Example tool 2", cx)),
-                        )
-                        .tool(
-                            IconButton::new("example_tool_3", IconName::Screen)
-                                .tooltip(|cx| Tooltip::text("Example tool 3", cx)),
-                        ),
-                ),
-            )),
-        )
+        Story::container()
+            .child(Story::title_for::<ToolStrip>())
+            .child(
+                StorySection::new().child(StoryItem::new(
+                    "Vertical Tool Strip",
+                    h_flex().child(
+                        ToolStrip::vertical("tool_strip_example")
+                            .tool(
+                                IconButton::new("example_tool", IconName::AudioOn)
+                                    .tooltip(|cx| Tooltip::text("Example tool", cx)),
+                            )
+                            .tool(
+                                IconButton::new("example_tool_2", IconName::MicMute)
+                                    .tooltip(|cx| Tooltip::text("Example tool 2", cx)),
+                            )
+                            .tool(
+                                IconButton::new("example_tool_3", IconName::Screen)
+                                    .tooltip(|cx| Tooltip::text("Example tool 3", cx)),
+                            ),
+                    ),
+                )),
+            )
     }
 }