Colocate component stories with their components

Marshall Bowers created

Change summary

Cargo.lock                                                    |   1 
crates/storybook2/src/stories.rs                              |   1 
crates/storybook2/src/stories/components.rs                   |  23 -
crates/storybook2/src/stories/components/breadcrumb.rs        |  54 --
crates/storybook2/src/stories/components/buffer.rs            |  46 --
crates/storybook2/src/stories/components/chat_panel.rs        |  56 --
crates/storybook2/src/stories/components/collab_panel.rs      |  26 -
crates/storybook2/src/stories/components/command_palette.rs   |  26 -
crates/storybook2/src/stories/components/context_menu.rs      |  30 -
crates/storybook2/src/stories/components/facepile.rs          |  35 -
crates/storybook2/src/stories/components/keybinding.rs        |  74 ---
crates/storybook2/src/stories/components/language_selector.rs |  26 -
crates/storybook2/src/stories/components/multi_buffer.rs      |  34 -
crates/storybook2/src/stories/components/palette.rs           |  63 --
crates/storybook2/src/stories/components/panel.rs             |  35 -
crates/storybook2/src/stories/components/project_panel.rs     |  30 -
crates/storybook2/src/stories/components/recent_projects.rs   |  26 -
crates/storybook2/src/stories/components/tab.rs               | 101 ----
crates/storybook2/src/stories/components/tab_bar.rs           |  56 --
crates/storybook2/src/stories/components/terminal.rs          |  26 -
crates/storybook2/src/stories/components/theme_selector.rs    |  26 -
crates/storybook2/src/stories/components/title_bar.rs         |  26 -
crates/storybook2/src/stories/components/toast.rs             |  30 -
crates/storybook2/src/stories/components/toolbar.rs           |  79 ---
crates/storybook2/src/stories/components/traffic_lights.rs    |  28 -
crates/storybook2/src/stories/components/workspace.rs         |  22 -
crates/storybook2/src/story_selector.rs                       |  54 +-
crates/ui2/Cargo.toml                                         |   3 
crates/ui2/src/components/assistant_panel.rs                  |   2 
crates/ui2/src/components/breadcrumb.rs                       |  58 ++
crates/ui2/src/components/buffer.rs                           |  51 ++
crates/ui2/src/components/chat_panel.rs                       |  61 ++
crates/ui2/src/components/collab_panel.rs                     |  30 +
crates/ui2/src/components/command_palette.rs                  |  30 +
crates/ui2/src/components/context_menu.rs                     |  36 +
crates/ui2/src/components/facepile.rs                         |  39 +
crates/ui2/src/components/keybinding.rs                       |  78 +++
crates/ui2/src/components/language_selector.rs                |  30 +
crates/ui2/src/components/multi_buffer.rs                     |  38 +
crates/ui2/src/components/palette.rs                          |  69 +++
crates/ui2/src/components/panel.rs                            |  39 +
crates/ui2/src/components/project_panel.rs                    |  34 +
crates/ui2/src/components/recent_projects.rs                  |  30 +
crates/ui2/src/components/tab.rs                              | 106 +++++
crates/ui2/src/components/tab_bar.rs                          |  60 ++
crates/ui2/src/components/terminal.rs                         |  30 +
crates/ui2/src/components/theme_selector.rs                   |  30 +
crates/ui2/src/components/title_bar.rs                        |  30 +
crates/ui2/src/components/toast.rs                            |  36 +
crates/ui2/src/components/toolbar.rs                          |  85 ++++
crates/ui2/src/components/traffic_lights.rs                   |  32 +
crates/ui2/src/components/workspace.rs                        |  26 +
52 files changed, 1,085 insertions(+), 1,012 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9060,6 +9060,7 @@ dependencies = [
  "anyhow",
  "chrono",
  "gpui3",
+ "itertools 0.11.0",
  "rand 0.8.5",
  "serde",
  "settings",

crates/storybook2/src/stories/components.rs 🔗

@@ -1,23 +0,0 @@
-pub mod breadcrumb;
-pub mod buffer;
-pub mod chat_panel;
-pub mod collab_panel;
-pub mod command_palette;
-pub mod context_menu;
-pub mod facepile;
-pub mod keybinding;
-pub mod language_selector;
-pub mod multi_buffer;
-pub mod palette;
-pub mod panel;
-pub mod project_panel;
-pub mod recent_projects;
-pub mod tab;
-pub mod tab_bar;
-pub mod terminal;
-pub mod theme_selector;
-pub mod title_bar;
-pub mod toast;
-pub mod toolbar;
-pub mod traffic_lights;
-pub mod workspace;

crates/storybook2/src/stories/components/breadcrumb.rs 🔗

@@ -1,54 +0,0 @@
-use std::marker::PhantomData;
-use std::path::PathBuf;
-use std::str::FromStr;
-
-use ui::prelude::*;
-use ui::{Breadcrumb, HighlightedText, Symbol};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct BreadcrumbStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> BreadcrumbStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        let theme = theme(cx);
-
-        Story::container(cx)
-            .child(Story::title_for::<_, Breadcrumb<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(Breadcrumb::new(
-                PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
-                vec![
-                    Symbol(vec![
-                        HighlightedText {
-                            text: "impl ".to_string(),
-                            color: HighlightColor::Keyword.hsla(&theme),
-                        },
-                        HighlightedText {
-                            text: "BreadcrumbStory".to_string(),
-                            color: HighlightColor::Function.hsla(&theme),
-                        },
-                    ]),
-                    Symbol(vec![
-                        HighlightedText {
-                            text: "fn ".to_string(),
-                            color: HighlightColor::Keyword.hsla(&theme),
-                        },
-                        HighlightedText {
-                            text: "render".to_string(),
-                            color: HighlightColor::Function.hsla(&theme),
-                        },
-                    ]),
-                ],
-            ))
-    }
-}

crates/storybook2/src/stories/components/buffer.rs 🔗

@@ -1,46 +0,0 @@
-use std::marker::PhantomData;
-
-use gpui3::rems;
-use ui::prelude::*;
-use ui::{
-    empty_buffer_example, hello_world_rust_buffer_example,
-    hello_world_rust_buffer_with_status_example, Buffer,
-};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct BufferStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> BufferStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        let theme = theme(cx);
-
-        Story::container(cx)
-            .child(Story::title_for::<_, Buffer<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(div().w(rems(64.)).h_96().child(empty_buffer_example()))
-            .child(Story::label(cx, "Hello World (Rust)"))
-            .child(
-                div()
-                    .w(rems(64.))
-                    .h_96()
-                    .child(hello_world_rust_buffer_example(&theme)),
-            )
-            .child(Story::label(cx, "Hello World (Rust) with Status"))
-            .child(
-                div()
-                    .w(rems(64.))
-                    .h_96()
-                    .child(hello_world_rust_buffer_with_status_example(&theme)),
-            )
-    }
-}

crates/storybook2/src/stories/components/chat_panel.rs 🔗

@@ -1,56 +0,0 @@
-use std::marker::PhantomData;
-
-use chrono::DateTime;
-use ui::prelude::*;
-use ui::{ChatMessage, ChatPanel, Panel};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct ChatPanelStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> ChatPanelStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, ChatPanel<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(Panel::new(
-                ScrollState::default(),
-                |_, _| vec![ChatPanel::new(ScrollState::default()).into_any()],
-                Box::new(()),
-            ))
-            .child(Story::label(cx, "With Mesages"))
-            .child(Panel::new(
-                ScrollState::default(),
-                |_, _| {
-                    vec![ChatPanel::new(ScrollState::default())
-                        .with_messages(vec![
-                            ChatMessage::new(
-                                "osiewicz".to_string(),
-                                "is this thing on?".to_string(),
-                                DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
-                                    .unwrap()
-                                    .naive_local(),
-                            ),
-                            ChatMessage::new(
-                                "maxdeviant".to_string(),
-                                "Reading you loud and clear!".to_string(),
-                                DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
-                                    .unwrap()
-                                    .naive_local(),
-                            ),
-                        ])
-                        .into_any()]
-                },
-                Box::new(()),
-            ))
-    }
-}

crates/storybook2/src/stories/components/collab_panel.rs 🔗

@@ -1,26 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::CollabPanel;
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct CollabPanelStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> CollabPanelStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, CollabPanel<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(CollabPanel::new(ScrollState::default()))
-    }
-}

crates/storybook2/src/stories/components/command_palette.rs 🔗

@@ -1,26 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::CommandPalette;
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct CommandPaletteStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> CommandPaletteStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, CommandPalette<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(CommandPalette::new(ScrollState::default()))
-    }
-}

crates/storybook2/src/stories/components/context_menu.rs 🔗

@@ -1,30 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::{ContextMenu, ContextMenuItem, Label};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct ContextMenuStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> ContextMenuStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, ContextMenu<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(ContextMenu::new([
-                ContextMenuItem::header("Section header"),
-                ContextMenuItem::Separator,
-                ContextMenuItem::entry(Label::new("Some entry")),
-            ]))
-    }
-}

crates/storybook2/src/stories/components/facepile.rs 🔗

@@ -1,35 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::{static_players, Facepile};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct FacepileStory<S: 'static + Send + Sync> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync> FacepileStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        let players = static_players();
-
-        Story::container(cx)
-            .child(Story::title_for::<_, Facepile<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(
-                div()
-                    .flex()
-                    .gap_3()
-                    .child(Facepile::new(players.clone().into_iter().take(1)))
-                    .child(Facepile::new(players.clone().into_iter().take(2)))
-                    .child(Facepile::new(players.clone().into_iter().take(3))),
-            )
-    }
-}

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

@@ -1,74 +0,0 @@
-use std::marker::PhantomData;
-
-use itertools::Itertools;
-use strum::IntoEnumIterator;
-use ui::prelude::*;
-use ui::{Keybinding, ModifierKey, ModifierKeys};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct KeybindingStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> KeybindingStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        let all_modifier_permutations = ModifierKey::iter().permutations(2);
-
-        Story::container(cx)
-            .child(Story::title_for::<_, Keybinding<S>>(cx))
-            .child(Story::label(cx, "Single Key"))
-            .child(Keybinding::new("Z".to_string(), ModifierKeys::new()))
-            .child(Story::label(cx, "Single Key with Modifier"))
-            .child(
-                div()
-                    .flex()
-                    .gap_3()
-                    .children(ModifierKey::iter().map(|modifier| {
-                        Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier))
-                    })),
-            )
-            .child(Story::label(cx, "Single Key with Modifier (Permuted)"))
-            .child(
-                div().flex().flex_col().children(
-                    all_modifier_permutations
-                        .chunks(4)
-                        .into_iter()
-                        .map(|chunk| {
-                            div()
-                                .flex()
-                                .gap_4()
-                                .py_3()
-                                .children(chunk.map(|permutation| {
-                                    let mut modifiers = ModifierKeys::new();
-
-                                    for modifier in permutation {
-                                        modifiers = modifiers.add(modifier);
-                                    }
-
-                                    Keybinding::new("X".to_string(), modifiers)
-                                }))
-                        }),
-                ),
-            )
-            .child(Story::label(cx, "Single Key with All Modifiers"))
-            .child(Keybinding::new("Z".to_string(), ModifierKeys::all()))
-            .child(Story::label(cx, "Chord"))
-            .child(Keybinding::new_chord(
-                ("A".to_string(), ModifierKeys::new()),
-                ("Z".to_string(), ModifierKeys::new()),
-            ))
-            .child(Story::label(cx, "Chord with Modifier"))
-            .child(Keybinding::new_chord(
-                ("A".to_string(), ModifierKeys::new().control(true)),
-                ("Z".to_string(), ModifierKeys::new().shift(true)),
-            ))
-    }
-}

crates/storybook2/src/stories/components/language_selector.rs 🔗

@@ -1,26 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::LanguageSelector;
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct LanguageSelectorStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> LanguageSelectorStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, LanguageSelector<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(LanguageSelector::new())
-    }
-}

crates/storybook2/src/stories/components/multi_buffer.rs 🔗

@@ -1,34 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::{hello_world_rust_buffer_example, MultiBuffer};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct MultiBufferStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> MultiBufferStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        let theme = theme(cx);
-
-        Story::container(cx)
-            .child(Story::title_for::<_, MultiBuffer<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(MultiBuffer::new(vec![
-                hello_world_rust_buffer_example(&theme),
-                hello_world_rust_buffer_example(&theme),
-                hello_world_rust_buffer_example(&theme),
-                hello_world_rust_buffer_example(&theme),
-                hello_world_rust_buffer_example(&theme),
-            ]))
-    }
-}

crates/storybook2/src/stories/components/palette.rs 🔗

@@ -1,63 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::{Keybinding, ModifierKeys, Palette, PaletteItem};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct PaletteStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> PaletteStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, Palette<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(Palette::new(ScrollState::default()))
-            .child(Story::label(cx, "With Items"))
-            .child(
-                Palette::new(ScrollState::default())
-                    .placeholder("Execute a command...")
-                    .items(vec![
-                        PaletteItem::new("theme selector: toggle").keybinding(
-                            Keybinding::new_chord(
-                                ("k".to_string(), ModifierKeys::new().command(true)),
-                                ("t".to_string(), ModifierKeys::new().command(true)),
-                            ),
-                        ),
-                        PaletteItem::new("assistant: inline assist").keybinding(Keybinding::new(
-                            "enter".to_string(),
-                            ModifierKeys::new().command(true),
-                        )),
-                        PaletteItem::new("assistant: quote selection").keybinding(Keybinding::new(
-                            ">".to_string(),
-                            ModifierKeys::new().command(true),
-                        )),
-                        PaletteItem::new("assistant: toggle focus").keybinding(Keybinding::new(
-                            "?".to_string(),
-                            ModifierKeys::new().command(true),
-                        )),
-                        PaletteItem::new("auto update: check"),
-                        PaletteItem::new("auto update: view release notes"),
-                        PaletteItem::new("branches: open recent").keybinding(Keybinding::new(
-                            "b".to_string(),
-                            ModifierKeys::new().command(true).alt(true),
-                        )),
-                        PaletteItem::new("chat panel: toggle focus"),
-                        PaletteItem::new("cli: install"),
-                        PaletteItem::new("client: sign in"),
-                        PaletteItem::new("client: sign out"),
-                        PaletteItem::new("editor: cancel")
-                            .keybinding(Keybinding::new("escape".to_string(), ModifierKeys::new())),
-                    ]),
-            )
-    }
-}

crates/storybook2/src/stories/components/panel.rs 🔗

@@ -1,35 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::{Label, Panel};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct PanelStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> PanelStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, Panel<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(Panel::new(
-                ScrollState::default(),
-                |_, _| {
-                    vec![div()
-                        .overflow_y_scroll(ScrollState::default())
-                        .children((0..100).map(|ix| Label::new(format!("Item {}", ix + 1))))
-                        .into_any()]
-                },
-                Box::new(()),
-            ))
-    }
-}

crates/storybook2/src/stories/components/project_panel.rs 🔗

@@ -1,30 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::{Panel, ProjectPanel};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct ProjectPanelStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> ProjectPanelStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, ProjectPanel<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(Panel::new(
-                ScrollState::default(),
-                |_, _| vec![ProjectPanel::new(ScrollState::default()).into_any()],
-                Box::new(()),
-            ))
-    }
-}

crates/storybook2/src/stories/components/recent_projects.rs 🔗

@@ -1,26 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::RecentProjects;
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct RecentProjectsStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> RecentProjectsStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, RecentProjects<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(RecentProjects::new())
-    }
-}

crates/storybook2/src/stories/components/tab.rs 🔗

@@ -1,101 +0,0 @@
-use std::marker::PhantomData;
-
-use strum::IntoEnumIterator;
-use ui::prelude::*;
-use ui::{h_stack, v_stack, Tab};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct TabStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> TabStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        let git_statuses = GitStatus::iter();
-        let fs_statuses = FileSystemStatus::iter();
-
-        Story::container(cx)
-            .child(Story::title_for::<_, Tab<S>>(cx))
-            .child(
-                h_stack().child(
-                    v_stack()
-                        .gap_2()
-                        .child(Story::label(cx, "Default"))
-                        .child(Tab::new()),
-                ),
-            )
-            .child(
-                h_stack().child(
-                    v_stack().gap_2().child(Story::label(cx, "Current")).child(
-                        h_stack()
-                            .gap_4()
-                            .child(Tab::new().title("Current".to_string()).current(true))
-                            .child(Tab::new().title("Not Current".to_string()).current(false)),
-                    ),
-                ),
-            )
-            .child(
-                h_stack().child(
-                    v_stack()
-                        .gap_2()
-                        .child(Story::label(cx, "Titled"))
-                        .child(Tab::new().title("label".to_string())),
-                ),
-            )
-            .child(
-                h_stack().child(
-                    v_stack()
-                        .gap_2()
-                        .child(Story::label(cx, "With Icon"))
-                        .child(
-                            Tab::new()
-                                .title("label".to_string())
-                                .icon(Some(ui::Icon::Envelope)),
-                        ),
-                ),
-            )
-            .child(
-                h_stack().child(
-                    v_stack()
-                        .gap_2()
-                        .child(Story::label(cx, "Close Side"))
-                        .child(
-                            h_stack()
-                                .gap_4()
-                                .child(
-                                    Tab::new()
-                                        .title("Left".to_string())
-                                        .close_side(IconSide::Left),
-                                )
-                                .child(Tab::new().title("Right".to_string())),
-                        ),
-                ),
-            )
-            .child(
-                v_stack()
-                    .gap_2()
-                    .child(Story::label(cx, "Git Status"))
-                    .child(h_stack().gap_4().children(git_statuses.map(|git_status| {
-                        Tab::new()
-                            .title(git_status.to_string())
-                            .git_status(git_status)
-                    }))),
-            )
-            .child(
-                v_stack()
-                    .gap_2()
-                    .child(Story::label(cx, "File System Status"))
-                    .child(h_stack().gap_4().children(fs_statuses.map(|fs_status| {
-                        Tab::new().title(fs_status.to_string()).fs_status(fs_status)
-                    }))),
-            )
-    }
-}

crates/storybook2/src/stories/components/tab_bar.rs 🔗

@@ -1,56 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::{Tab, TabBar};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct TabBarStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> TabBarStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, TabBar<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(TabBar::new(vec![
-                Tab::new()
-                    .title("Cargo.toml".to_string())
-                    .current(false)
-                    .git_status(GitStatus::Modified),
-                Tab::new()
-                    .title("Channels Panel".to_string())
-                    .current(false),
-                Tab::new()
-                    .title("channels_panel.rs".to_string())
-                    .current(true)
-                    .git_status(GitStatus::Modified),
-                Tab::new()
-                    .title("workspace.rs".to_string())
-                    .current(false)
-                    .git_status(GitStatus::Modified),
-                Tab::new()
-                    .title("icon_button.rs".to_string())
-                    .current(false),
-                Tab::new()
-                    .title("storybook.rs".to_string())
-                    .current(false)
-                    .git_status(GitStatus::Created),
-                Tab::new().title("theme.rs".to_string()).current(false),
-                Tab::new()
-                    .title("theme_registry.rs".to_string())
-                    .current(false),
-                Tab::new()
-                    .title("styleable_helpers.rs".to_string())
-                    .current(false),
-            ]))
-    }
-}

crates/storybook2/src/stories/components/terminal.rs 🔗

@@ -1,26 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::Terminal;
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct TerminalStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> TerminalStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, Terminal<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(Terminal::new())
-    }
-}

crates/storybook2/src/stories/components/theme_selector.rs 🔗

@@ -1,26 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::ThemeSelector;
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct ThemeSelectorStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> ThemeSelectorStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, ThemeSelector<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(ThemeSelector::new())
-    }
-}

crates/storybook2/src/stories/components/title_bar.rs 🔗

@@ -1,26 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::TitleBar;
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct TitleBarStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> TitleBarStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, TitleBar<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(TitleBar::new(cx))
-    }
-}

crates/storybook2/src/stories/components/toast.rs 🔗

@@ -1,30 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::{Label, Toast, ToastOrigin};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct ToastStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> ToastStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, Toast<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(Toast::new(
-                ToastOrigin::Bottom,
-                |_, _| vec![Label::new("label").into_any()],
-                Box::new(()),
-            ))
-    }
-}

crates/storybook2/src/stories/components/toolbar.rs 🔗

@@ -1,79 +0,0 @@
-use std::marker::PhantomData;
-use std::path::PathBuf;
-use std::str::FromStr;
-use std::sync::Arc;
-
-use ui::prelude::*;
-use ui::{theme, Breadcrumb, HighlightColor, HighlightedText, Icon, IconButton, Symbol, Toolbar};
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct ToolbarStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> ToolbarStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        let theme = theme(cx);
-
-        struct LeftItemsPayload {
-            pub theme: Arc<Theme>,
-        }
-
-        Story::container(cx)
-            .child(Story::title_for::<_, Toolbar<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(Toolbar::new(
-                |_, payload| {
-                    let payload = payload.downcast_ref::<LeftItemsPayload>().unwrap();
-
-                    let theme = payload.theme.clone();
-
-                    vec![Breadcrumb::new(
-                        PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
-                        vec![
-                            Symbol(vec![
-                                HighlightedText {
-                                    text: "impl ".to_string(),
-                                    color: HighlightColor::Keyword.hsla(&theme),
-                                },
-                                HighlightedText {
-                                    text: "ToolbarStory".to_string(),
-                                    color: HighlightColor::Function.hsla(&theme),
-                                },
-                            ]),
-                            Symbol(vec![
-                                HighlightedText {
-                                    text: "fn ".to_string(),
-                                    color: HighlightColor::Keyword.hsla(&theme),
-                                },
-                                HighlightedText {
-                                    text: "render".to_string(),
-                                    color: HighlightColor::Function.hsla(&theme),
-                                },
-                            ]),
-                        ],
-                    )
-                    .into_any()]
-                },
-                Box::new(LeftItemsPayload {
-                    theme: theme.clone(),
-                }),
-                |_, _| {
-                    vec![
-                        IconButton::new(Icon::InlayHint).into_any(),
-                        IconButton::new(Icon::MagnifyingGlass).into_any(),
-                        IconButton::new(Icon::MagicWand).into_any(),
-                    ]
-                },
-                Box::new(()),
-            ))
-    }
-}

crates/storybook2/src/stories/components/traffic_lights.rs 🔗

@@ -1,28 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::TrafficLights;
-
-use crate::story::Story;
-
-#[derive(Element)]
-pub struct TrafficLightsStory<S: 'static + Send + Sync> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync> TrafficLightsStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        Story::container(cx)
-            .child(Story::title_for::<_, TrafficLights<S>>(cx))
-            .child(Story::label(cx, "Default"))
-            .child(TrafficLights::new())
-            .child(Story::label(cx, "Unfocused"))
-            .child(TrafficLights::new().window_has_focus(false))
-    }
-}

crates/storybook2/src/stories/components/workspace.rs 🔗

@@ -1,22 +0,0 @@
-use std::marker::PhantomData;
-
-use ui::prelude::*;
-use ui::WorkspaceElement;
-
-#[derive(Element)]
-pub struct WorkspaceStory<S: 'static + Send + Sync + Clone> {
-    state_type: PhantomData<S>,
-}
-
-impl<S: 'static + Send + Sync + Clone> WorkspaceStory<S> {
-    pub fn new() -> Self {
-        Self {
-            state_type: PhantomData,
-        }
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
-        // Just render the workspace without any story boilerplate.
-        WorkspaceElement::new()
-    }
-}

crates/storybook2/src/story_selector.rs 🔗

@@ -68,39 +68,31 @@ pub enum ComponentStory {
 
 impl ComponentStory {
     pub fn story<S: 'static + Send + Sync + Clone>(&self) -> AnyElement<S> {
-        use crate::stories::components;
-
         match self {
             Self::AssistantPanel => ui::AssistantPanelStory::new().into_any(),
-            Self::Buffer => components::buffer::BufferStory::new().into_any(),
-            Self::Breadcrumb => components::breadcrumb::BreadcrumbStory::new().into_any(),
-            Self::ChatPanel => components::chat_panel::ChatPanelStory::new().into_any(),
-            Self::CollabPanel => components::collab_panel::CollabPanelStory::new().into_any(),
-            Self::CommandPalette => {
-                components::command_palette::CommandPaletteStory::new().into_any()
-            }
-            Self::ContextMenu => components::context_menu::ContextMenuStory::new().into_any(),
-            Self::Facepile => components::facepile::FacepileStory::new().into_any(),
-            Self::Keybinding => components::keybinding::KeybindingStory::new().into_any(),
-            Self::LanguageSelector => {
-                components::language_selector::LanguageSelectorStory::new().into_any()
-            }
-            Self::MultiBuffer => components::multi_buffer::MultiBufferStory::new().into_any(),
-            Self::Palette => components::palette::PaletteStory::new().into_any(),
-            Self::Panel => components::panel::PanelStory::new().into_any(),
-            Self::ProjectPanel => components::project_panel::ProjectPanelStory::new().into_any(),
-            Self::RecentProjects => {
-                components::recent_projects::RecentProjectsStory::new().into_any()
-            }
-            Self::Tab => components::tab::TabStory::new().into_any(),
-            Self::TabBar => components::tab_bar::TabBarStory::new().into_any(),
-            Self::Terminal => components::terminal::TerminalStory::new().into_any(),
-            Self::ThemeSelector => components::theme_selector::ThemeSelectorStory::new().into_any(),
-            Self::TitleBar => components::title_bar::TitleBarStory::new().into_any(),
-            Self::Toast => components::toast::ToastStory::new().into_any(),
-            Self::Toolbar => components::toolbar::ToolbarStory::new().into_any(),
-            Self::TrafficLights => components::traffic_lights::TrafficLightsStory::new().into_any(),
-            Self::Workspace => components::workspace::WorkspaceStory::new().into_any(),
+            Self::Buffer => ui::BufferStory::new().into_any(),
+            Self::Breadcrumb => ui::BreadcrumbStory::new().into_any(),
+            Self::ChatPanel => ui::ChatPanelStory::new().into_any(),
+            Self::CollabPanel => ui::CollabPanelStory::new().into_any(),
+            Self::CommandPalette => ui::CommandPaletteStory::new().into_any(),
+            Self::ContextMenu => ui::ContextMenuStory::new().into_any(),
+            Self::Facepile => ui::FacepileStory::new().into_any(),
+            Self::Keybinding => ui::KeybindingStory::new().into_any(),
+            Self::LanguageSelector => ui::LanguageSelectorStory::new().into_any(),
+            Self::MultiBuffer => ui::MultiBufferStory::new().into_any(),
+            Self::Palette => ui::PaletteStory::new().into_any(),
+            Self::Panel => ui::PanelStory::new().into_any(),
+            Self::ProjectPanel => ui::ProjectPanelStory::new().into_any(),
+            Self::RecentProjects => ui::RecentProjectsStory::new().into_any(),
+            Self::Tab => ui::TabStory::new().into_any(),
+            Self::TabBar => ui::TabBarStory::new().into_any(),
+            Self::Terminal => ui::TerminalStory::new().into_any(),
+            Self::ThemeSelector => ui::ThemeSelectorStory::new().into_any(),
+            Self::TitleBar => ui::TitleBarStory::new().into_any(),
+            Self::Toast => ui::ToastStory::new().into_any(),
+            Self::Toolbar => ui::ToolbarStory::new().into_any(),
+            Self::TrafficLights => ui::TrafficLightsStory::new().into_any(),
+            Self::Workspace => ui::WorkspaceStory::new().into_any(),
         }
     }
 }

crates/ui2/Cargo.toml 🔗

@@ -8,6 +8,7 @@ publish = false
 anyhow.workspace = true
 chrono = "0.4"
 gpui3 = { path = "../gpui3" }
+itertools = { version = "0.11.0", optional = true }
 serde.workspace = true
 settings = { path = "../settings" }
 smallvec.workspace = true
@@ -17,4 +18,4 @@ rand = "0.8"
 
 [features]
 default = ["stories"]
-stories = []
+stories = ["dep:itertools"]

crates/ui2/src/components/breadcrumb.rs 🔗

@@ -75,3 +75,61 @@ impl<S: 'static + Send + Sync + Clone> Breadcrumb<S> {
             )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use std::str::FromStr;
+
+    use crate::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct BreadcrumbStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> BreadcrumbStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            let theme = theme(cx);
+
+            Story::container(cx)
+                .child(Story::title_for::<_, Breadcrumb<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(Breadcrumb::new(
+                    PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
+                    vec![
+                        Symbol(vec![
+                            HighlightedText {
+                                text: "impl ".to_string(),
+                                color: HighlightColor::Keyword.hsla(&theme),
+                            },
+                            HighlightedText {
+                                text: "BreadcrumbStory".to_string(),
+                                color: HighlightColor::Function.hsla(&theme),
+                            },
+                        ]),
+                        Symbol(vec![
+                            HighlightedText {
+                                text: "fn ".to_string(),
+                                color: HighlightColor::Keyword.hsla(&theme),
+                            },
+                            HighlightedText {
+                                text: "render".to_string(),
+                                color: HighlightColor::Function.hsla(&theme),
+                            },
+                        ]),
+                    ],
+                ))
+        }
+    }
+}

crates/ui2/src/components/buffer.rs 🔗

@@ -236,3 +236,54 @@ impl<S: 'static + Send + Sync + Clone> Buffer<S> {
             .children(rows)
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use gpui3::rems;
+
+    use crate::{
+        empty_buffer_example, hello_world_rust_buffer_example,
+        hello_world_rust_buffer_with_status_example, Story,
+    };
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct BufferStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> BufferStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            let theme = theme(cx);
+
+            Story::container(cx)
+                .child(Story::title_for::<_, Buffer<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(div().w(rems(64.)).h_96().child(empty_buffer_example()))
+                .child(Story::label(cx, "Hello World (Rust)"))
+                .child(
+                    div()
+                        .w(rems(64.))
+                        .h_96()
+                        .child(hello_world_rust_buffer_example(&theme)),
+                )
+                .child(Story::label(cx, "Hello World (Rust) with Status"))
+                .child(
+                    div()
+                        .w(rems(64.))
+                        .h_96()
+                        .child(hello_world_rust_buffer_with_status_example(&theme)),
+                )
+        }
+    }
+}

crates/ui2/src/components/chat_panel.rs 🔗

@@ -106,3 +106,64 @@ impl<S: 'static + Send + Sync + Clone> ChatMessage<S> {
             .child(div().child(Label::new(self.text.clone())))
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use chrono::DateTime;
+
+    use crate::{Panel, Story};
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct ChatPanelStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> ChatPanelStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, ChatPanel<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(Panel::new(
+                    ScrollState::default(),
+                    |_, _| vec![ChatPanel::new(ScrollState::default()).into_any()],
+                    Box::new(()),
+                ))
+                .child(Story::label(cx, "With Mesages"))
+                .child(Panel::new(
+                    ScrollState::default(),
+                    |_, _| {
+                        vec![ChatPanel::new(ScrollState::default())
+                            .with_messages(vec![
+                                ChatMessage::new(
+                                    "osiewicz".to_string(),
+                                    "is this thing on?".to_string(),
+                                    DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
+                                        .unwrap()
+                                        .naive_local(),
+                                ),
+                                ChatMessage::new(
+                                    "maxdeviant".to_string(),
+                                    "Reading you loud and clear!".to_string(),
+                                    DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
+                                        .unwrap()
+                                        .naive_local(),
+                                ),
+                            ])
+                            .into_any()]
+                    },
+                    Box::new(()),
+                ))
+        }
+    }
+}

crates/ui2/src/components/collab_panel.rs 🔗

@@ -158,3 +158,33 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
             )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct CollabPanelStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> CollabPanelStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, CollabPanel<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(CollabPanel::new(ScrollState::default()))
+        }
+    }
+}

crates/ui2/src/components/command_palette.rs 🔗

@@ -27,3 +27,33 @@ impl<S: 'static + Send + Sync + Clone> CommandPalette<S> {
         )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct CommandPaletteStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> CommandPaletteStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, CommandPalette<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(CommandPalette::new(ScrollState::default()))
+        }
+    }
+}

crates/ui2/src/components/context_menu.rs 🔗

@@ -65,3 +65,39 @@ impl<S: 'static + Send + Sync + Clone> ContextMenu<S> {
             )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use std::marker::PhantomData;
+
+    use crate::story::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct ContextMenuStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> ContextMenuStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, ContextMenu<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(ContextMenu::new([
+                    ContextMenuItem::header("Section header"),
+                    ContextMenuItem::Separator,
+                    ContextMenuItem::entry(Label::new("Some entry")),
+                ]))
+        }
+    }
+}

crates/ui2/src/components/facepile.rs 🔗

@@ -30,3 +30,42 @@ impl<S: 'static + Send + Sync> Facepile<S> {
         div().p_1().flex().items_center().children(player_list)
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::{static_players, Story};
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct FacepileStory<S: 'static + Send + Sync> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync> FacepileStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            let players = static_players();
+
+            Story::container(cx)
+                .child(Story::title_for::<_, Facepile<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(
+                    div()
+                        .flex()
+                        .gap_3()
+                        .child(Facepile::new(players.clone().into_iter().take(1)))
+                        .child(Facepile::new(players.clone().into_iter().take(2)))
+                        .child(Facepile::new(players.clone().into_iter().take(3))),
+                )
+        }
+    }
+}

crates/ui2/src/components/keybinding.rs 🔗

@@ -165,3 +165,81 @@ impl ModifierKeys {
         self
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use itertools::Itertools;
+
+    use crate::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct KeybindingStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> KeybindingStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            let all_modifier_permutations = ModifierKey::iter().permutations(2);
+
+            Story::container(cx)
+                .child(Story::title_for::<_, Keybinding<S>>(cx))
+                .child(Story::label(cx, "Single Key"))
+                .child(Keybinding::new("Z".to_string(), ModifierKeys::new()))
+                .child(Story::label(cx, "Single Key with Modifier"))
+                .child(
+                    div()
+                        .flex()
+                        .gap_3()
+                        .children(ModifierKey::iter().map(|modifier| {
+                            Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier))
+                        })),
+                )
+                .child(Story::label(cx, "Single Key with Modifier (Permuted)"))
+                .child(
+                    div().flex().flex_col().children(
+                        all_modifier_permutations
+                            .chunks(4)
+                            .into_iter()
+                            .map(|chunk| {
+                                div()
+                                    .flex()
+                                    .gap_4()
+                                    .py_3()
+                                    .children(chunk.map(|permutation| {
+                                        let mut modifiers = ModifierKeys::new();
+
+                                        for modifier in permutation {
+                                            modifiers = modifiers.add(modifier);
+                                        }
+
+                                        Keybinding::new("X".to_string(), modifiers)
+                                    }))
+                            }),
+                    ),
+                )
+                .child(Story::label(cx, "Single Key with All Modifiers"))
+                .child(Keybinding::new("Z".to_string(), ModifierKeys::all()))
+                .child(Story::label(cx, "Chord"))
+                .child(Keybinding::new_chord(
+                    ("A".to_string(), ModifierKeys::new()),
+                    ("Z".to_string(), ModifierKeys::new()),
+                ))
+                .child(Story::label(cx, "Chord with Modifier"))
+                .child(Keybinding::new_chord(
+                    ("A".to_string(), ModifierKeys::new().control(true)),
+                    ("Z".to_string(), ModifierKeys::new().shift(true)),
+                ))
+        }
+    }
+}

crates/ui2/src/components/language_selector.rs 🔗

@@ -38,3 +38,33 @@ impl<S: 'static + Send + Sync + Clone> LanguageSelector<S> {
         )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct LanguageSelectorStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> LanguageSelectorStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, LanguageSelector<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(LanguageSelector::new())
+        }
+    }
+}

crates/ui2/src/components/multi_buffer.rs 🔗

@@ -40,3 +40,41 @@ impl<S: 'static + Send + Sync + Clone> MultiBuffer<S> {
             }))
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::{hello_world_rust_buffer_example, Story};
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct MultiBufferStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> MultiBufferStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            let theme = theme(cx);
+
+            Story::container(cx)
+                .child(Story::title_for::<_, MultiBuffer<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(MultiBuffer::new(vec![
+                    hello_world_rust_buffer_example(&theme),
+                    hello_world_rust_buffer_example(&theme),
+                    hello_world_rust_buffer_example(&theme),
+                    hello_world_rust_buffer_example(&theme),
+                    hello_world_rust_buffer_example(&theme),
+                ]))
+        }
+    }
+}

crates/ui2/src/components/palette.rs 🔗

@@ -150,3 +150,72 @@ impl<S: 'static + Send + Sync + Clone> PaletteItem<S> {
             .children(self.keybinding.clone())
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::{ModifierKeys, Story};
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct PaletteStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> PaletteStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, Palette<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(Palette::new(ScrollState::default()))
+                .child(Story::label(cx, "With Items"))
+                .child(
+                    Palette::new(ScrollState::default())
+                        .placeholder("Execute a command...")
+                        .items(vec![
+                            PaletteItem::new("theme selector: toggle").keybinding(
+                                Keybinding::new_chord(
+                                    ("k".to_string(), ModifierKeys::new().command(true)),
+                                    ("t".to_string(), ModifierKeys::new().command(true)),
+                                ),
+                            ),
+                            PaletteItem::new("assistant: inline assist").keybinding(
+                                Keybinding::new(
+                                    "enter".to_string(),
+                                    ModifierKeys::new().command(true),
+                                ),
+                            ),
+                            PaletteItem::new("assistant: quote selection").keybinding(
+                                Keybinding::new(">".to_string(), ModifierKeys::new().command(true)),
+                            ),
+                            PaletteItem::new("assistant: toggle focus").keybinding(
+                                Keybinding::new("?".to_string(), ModifierKeys::new().command(true)),
+                            ),
+                            PaletteItem::new("auto update: check"),
+                            PaletteItem::new("auto update: view release notes"),
+                            PaletteItem::new("branches: open recent").keybinding(Keybinding::new(
+                                "b".to_string(),
+                                ModifierKeys::new().command(true).alt(true),
+                            )),
+                            PaletteItem::new("chat panel: toggle focus"),
+                            PaletteItem::new("cli: install"),
+                            PaletteItem::new("client: sign in"),
+                            PaletteItem::new("client: sign out"),
+                            PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
+                                "escape".to_string(),
+                                ModifierKeys::new(),
+                            )),
+                        ]),
+                )
+        }
+    }
+}

crates/ui2/src/components/panel.rs 🔗

@@ -143,3 +143,42 @@ impl<S: 'static + Send + Sync> Panel<S> {
         panel_base.children_any((self.children)(cx, self.payload.as_ref()))
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::{Label, Story};
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct PanelStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> PanelStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, Panel<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(Panel::new(
+                    ScrollState::default(),
+                    |_, _| {
+                        vec![div()
+                            .overflow_y_scroll(ScrollState::default())
+                            .children((0..100).map(|ix| Label::new(format!("Item {}", ix + 1))))
+                            .into_any()]
+                    },
+                    Box::new(()),
+                ))
+        }
+    }
+}

crates/ui2/src/components/project_panel.rs 🔗

@@ -56,3 +56,37 @@ impl<S: 'static + Send + Sync + Clone> ProjectPanel<S> {
             )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::{Panel, Story};
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct ProjectPanelStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> ProjectPanelStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, ProjectPanel<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(Panel::new(
+                    ScrollState::default(),
+                    |_, _| vec![ProjectPanel::new(ScrollState::default()).into_any()],
+                    Box::new(()),
+                ))
+        }
+    }
+}

crates/ui2/src/components/recent_projects.rs 🔗

@@ -34,3 +34,33 @@ impl<S: 'static + Send + Sync + Clone> RecentProjects<S> {
         )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct RecentProjectsStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> RecentProjectsStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, RecentProjects<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(RecentProjects::new())
+        }
+    }
+}

crates/ui2/src/components/tab.rs 🔗

@@ -133,3 +133,109 @@ impl<S: 'static + Send + Sync + Clone> Tab<S> {
             )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use strum::IntoEnumIterator;
+
+    use crate::{h_stack, v_stack, Icon, Story};
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct TabStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> TabStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            let git_statuses = GitStatus::iter();
+            let fs_statuses = FileSystemStatus::iter();
+
+            Story::container(cx)
+                .child(Story::title_for::<_, Tab<S>>(cx))
+                .child(
+                    h_stack().child(
+                        v_stack()
+                            .gap_2()
+                            .child(Story::label(cx, "Default"))
+                            .child(Tab::new()),
+                    ),
+                )
+                .child(
+                    h_stack().child(
+                        v_stack().gap_2().child(Story::label(cx, "Current")).child(
+                            h_stack()
+                                .gap_4()
+                                .child(Tab::new().title("Current".to_string()).current(true))
+                                .child(Tab::new().title("Not Current".to_string()).current(false)),
+                        ),
+                    ),
+                )
+                .child(
+                    h_stack().child(
+                        v_stack()
+                            .gap_2()
+                            .child(Story::label(cx, "Titled"))
+                            .child(Tab::new().title("label".to_string())),
+                    ),
+                )
+                .child(
+                    h_stack().child(
+                        v_stack()
+                            .gap_2()
+                            .child(Story::label(cx, "With Icon"))
+                            .child(
+                                Tab::new()
+                                    .title("label".to_string())
+                                    .icon(Some(Icon::Envelope)),
+                            ),
+                    ),
+                )
+                .child(
+                    h_stack().child(
+                        v_stack()
+                            .gap_2()
+                            .child(Story::label(cx, "Close Side"))
+                            .child(
+                                h_stack()
+                                    .gap_4()
+                                    .child(
+                                        Tab::new()
+                                            .title("Left".to_string())
+                                            .close_side(IconSide::Left),
+                                    )
+                                    .child(Tab::new().title("Right".to_string())),
+                            ),
+                    ),
+                )
+                .child(
+                    v_stack()
+                        .gap_2()
+                        .child(Story::label(cx, "Git Status"))
+                        .child(h_stack().gap_4().children(git_statuses.map(|git_status| {
+                            Tab::new()
+                                .title(git_status.to_string())
+                                .git_status(git_status)
+                        }))),
+                )
+                .child(
+                    v_stack()
+                        .gap_2()
+                        .child(Story::label(cx, "File System Status"))
+                        .child(h_stack().gap_4().children(fs_statuses.map(|fs_status| {
+                            Tab::new().title(fs_status.to_string()).fs_status(fs_status)
+                        }))),
+                )
+        }
+    }
+}

crates/ui2/src/components/tab_bar.rs 🔗

@@ -83,3 +83,63 @@ impl<S: 'static + Send + Sync + Clone> TabBar<S> {
             )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct TabBarStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> TabBarStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, TabBar<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(TabBar::new(vec![
+                    Tab::new()
+                        .title("Cargo.toml".to_string())
+                        .current(false)
+                        .git_status(GitStatus::Modified),
+                    Tab::new()
+                        .title("Channels Panel".to_string())
+                        .current(false),
+                    Tab::new()
+                        .title("channels_panel.rs".to_string())
+                        .current(true)
+                        .git_status(GitStatus::Modified),
+                    Tab::new()
+                        .title("workspace.rs".to_string())
+                        .current(false)
+                        .git_status(GitStatus::Modified),
+                    Tab::new()
+                        .title("icon_button.rs".to_string())
+                        .current(false),
+                    Tab::new()
+                        .title("storybook.rs".to_string())
+                        .current(false)
+                        .git_status(GitStatus::Created),
+                    Tab::new().title("theme.rs".to_string()).current(false),
+                    Tab::new()
+                        .title("theme_registry.rs".to_string())
+                        .current(false),
+                    Tab::new()
+                        .title("styleable_helpers.rs".to_string())
+                        .current(false),
+                ]))
+        }
+    }
+}

crates/ui2/src/components/terminal.rs 🔗

@@ -87,3 +87,33 @@ impl<S: 'static + Send + Sync + Clone> Terminal<S> {
             ))
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct TerminalStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> TerminalStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, Terminal<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(Terminal::new())
+        }
+    }
+}

crates/ui2/src/components/theme_selector.rs 🔗

@@ -39,3 +39,33 @@ impl<S: 'static + Send + Sync + Clone> ThemeSelector<S> {
         )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct ThemeSelectorStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> ThemeSelectorStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, ThemeSelector<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(ThemeSelector::new())
+        }
+    }
+}

crates/ui2/src/components/title_bar.rs 🔗

@@ -117,3 +117,33 @@ impl<S: 'static + Send + Sync + Clone> TitleBar<S> {
             )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct TitleBarStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> TitleBarStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, TitleBar<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(TitleBar::new(cx))
+        }
+    }
+}

crates/ui2/src/components/toast.rs 🔗

@@ -65,3 +65,39 @@ impl<S: 'static + Send + Sync> Toast<S> {
             .children_any((self.children)(cx, self.payload.as_ref()))
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use std::marker::PhantomData;
+
+    use crate::{Label, Story};
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct ToastStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> ToastStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, Toast<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(Toast::new(
+                    ToastOrigin::Bottom,
+                    |_, _| vec![Label::new("label").into_any()],
+                    Box::new(()),
+                ))
+        }
+    }
+}

crates/ui2/src/components/toolbar.rs 🔗

@@ -47,3 +47,88 @@ impl<S: 'static + Send + Sync> Toolbar<S> {
             )
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use std::marker::PhantomData;
+    use std::path::PathBuf;
+    use std::str::FromStr;
+    use std::sync::Arc;
+
+    use crate::{Breadcrumb, HighlightedText, Icon, IconButton, Story, Symbol};
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct ToolbarStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> ToolbarStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            let theme = theme(cx);
+
+            struct LeftItemsPayload {
+                pub theme: Arc<Theme>,
+            }
+
+            Story::container(cx)
+                .child(Story::title_for::<_, Toolbar<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(Toolbar::new(
+                    |_, payload| {
+                        let payload = payload.downcast_ref::<LeftItemsPayload>().unwrap();
+
+                        let theme = payload.theme.clone();
+
+                        vec![Breadcrumb::new(
+                            PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
+                            vec![
+                                Symbol(vec![
+                                    HighlightedText {
+                                        text: "impl ".to_string(),
+                                        color: HighlightColor::Keyword.hsla(&theme),
+                                    },
+                                    HighlightedText {
+                                        text: "ToolbarStory".to_string(),
+                                        color: HighlightColor::Function.hsla(&theme),
+                                    },
+                                ]),
+                                Symbol(vec![
+                                    HighlightedText {
+                                        text: "fn ".to_string(),
+                                        color: HighlightColor::Keyword.hsla(&theme),
+                                    },
+                                    HighlightedText {
+                                        text: "render".to_string(),
+                                        color: HighlightColor::Function.hsla(&theme),
+                                    },
+                                ]),
+                            ],
+                        )
+                        .into_any()]
+                    },
+                    Box::new(LeftItemsPayload {
+                        theme: theme.clone(),
+                    }),
+                    |_, _| {
+                        vec![
+                            IconButton::new(Icon::InlayHint).into_any(),
+                            IconButton::new(Icon::MagnifyingGlass).into_any(),
+                            IconButton::new(Icon::MagicWand).into_any(),
+                        ]
+                    },
+                    Box::new(()),
+                ))
+        }
+    }
+}

crates/ui2/src/components/traffic_lights.rs 🔗

@@ -82,3 +82,35 @@ impl<S: 'static + Send + Sync> TrafficLights<S> {
             ))
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use crate::Story;
+
+    use super::*;
+
+    #[derive(Element)]
+    pub struct TrafficLightsStory<S: 'static + Send + Sync> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync> TrafficLightsStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            Story::container(cx)
+                .child(Story::title_for::<_, TrafficLights<S>>(cx))
+                .child(Story::label(cx, "Default"))
+                .child(TrafficLights::new())
+                .child(Story::label(cx, "Unfocused"))
+                .child(TrafficLights::new().window_has_focus(false))
+        }
+    }
+}

crates/ui2/src/components/workspace.rs 🔗

@@ -195,3 +195,29 @@ impl<S: 'static + Send + Sync + Clone> WorkspaceElement<S> {
             ))
     }
 }
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use super::*;
+
+    #[derive(Element)]
+    pub struct WorkspaceStory<S: 'static + Send + Sync + Clone> {
+        state_type: PhantomData<S>,
+    }
+
+    impl<S: 'static + Send + Sync + Clone> WorkspaceStory<S> {
+        pub fn new() -> Self {
+            Self {
+                state_type: PhantomData,
+            }
+        }
+
+        fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+            // Just render the workspace without any story boilerplate.
+            WorkspaceElement::new()
+        }
+    }
+}