gpui: Add a standard text example (#30747)

Nate Butler created

This is a dumb first pass at a standard text example. We'll use this to
start digging in to some text/scale rendering issues.

There will be a ton of follow-up features to this, but starting simple.

Release Notes:

- N/A

Change summary

crates/collab_ui/src/notifications/stories/collab_notification.rs |   6 
crates/gpui/Cargo.toml                                            |  12 
crates/gpui/examples/text.rs                                      | 333 +
crates/gpui/src/app.rs                                            |  11 
crates/gpui/src/colors.rs                                         | 122 
crates/gpui/src/elements/common.rs                                | 115 
crates/gpui/src/elements/mod.rs                                   |   2 
crates/gpui/src/gpui.rs                                           |   2 
crates/story/src/story.rs                                         |  93 
crates/storybook/src/stories.rs                                   |   2 
crates/storybook/src/stories/cursor.rs                            |   8 
crates/storybook/src/stories/default_colors.rs                    |  89 
crates/storybook/src/stories/indent_guides.rs                     |   6 
crates/storybook/src/stories/kitchen_sink.rs                      |   6 
crates/storybook/src/stories/overflow_scroll.rs                   |  10 
crates/storybook/src/stories/text.rs                              |   6 
crates/storybook/src/stories/viewport_units.rs                    |   4 
crates/storybook/src/stories/with_rem_size.rs                     |   4 
crates/storybook/src/story_selector.rs                            |   2 
crates/title_bar/src/stories/application_menu.rs                  |   6 
crates/ui/src/components/stories/context_menu.rs                  |   4 
crates/ui/src/components/stories/disclosure.rs                    |   6 
crates/ui/src/components/stories/icon_button.rs                   |   6 
crates/ui/src/components/stories/keybinding.rs                    |  28 
crates/ui/src/components/stories/list.rs                          |  10 
crates/ui/src/components/stories/list_header.rs                   |  14 
crates/ui/src/components/stories/list_item.rs                     |  27 
crates/ui/src/components/stories/tab.rs                           |  20 
crates/ui/src/components/stories/tab_bar.rs                       |   8 
crates/ui/src/components/stories/toggle_button.rs                 |   6 
30 files changed, 606 insertions(+), 362 deletions(-)

Detailed changes

crates/collab_ui/src/notifications/stories/collab_notification.rs ๐Ÿ”—

@@ -7,11 +7,11 @@ use crate::notifications::collab_notification::CollabNotification;
 pub struct CollabNotificationStory;
 
 impl Render for CollabNotificationStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let window_container = |width, height| div().w(px(width)).h(px(height));
 
-        Story::container()
-            .child(Story::title_for::<CollabNotification>())
+        Story::container(cx)
+            .child(Story::title_for::<CollabNotification>(cx))
             .child(
                 StorySection::new().child(StoryItem::new(
                     "Incoming Call Notification",

crates/gpui/Cargo.toml ๐Ÿ”—

@@ -257,6 +257,10 @@ path = "examples/image/image.rs"
 name = "input"
 path = "examples/input.rs"
 
+[[example]]
+name = "on_window_close_quit"
+path = "examples/on_window_close_quit.rs"
+
 [[example]]
 name = "opacity"
 path = "examples/opacity.rs"
@@ -277,6 +281,10 @@ path = "examples/shadow.rs"
 name = "svg"
 path = "examples/svg/svg.rs"
 
+[[example]]
+name = "text"
+path = "examples/text.rs"
+
 [[example]]
 name = "text_wrapper"
 path = "examples/text_wrapper.rs"
@@ -288,7 +296,3 @@ path = "examples/uniform_list.rs"
 [[example]]
 name = "window_shadow"
 path = "examples/window_shadow.rs"
-
-[[example]]
-name = "on_window_close_quit"
-path = "examples/on_window_close_quit.rs"

crates/gpui/examples/text.rs ๐Ÿ”—

@@ -0,0 +1,333 @@
+use std::{
+    ops::{Deref, DerefMut},
+    sync::Arc,
+};
+
+use gpui::{
+    AbsoluteLength, App, Application, Context, DefiniteLength, ElementId, Global, Hsla, Menu,
+    SharedString, TextStyle, TitlebarOptions, Window, WindowBounds, WindowOptions, bounds,
+    colors::DefaultColors, div, point, prelude::*, px, relative, rgb, size,
+};
+use std::iter;
+
+#[derive(Clone, Debug)]
+pub struct TextContext {
+    font_size: f32,
+    line_height: f32,
+    type_scale: f32,
+}
+
+impl Default for TextContext {
+    fn default() -> Self {
+        TextContext {
+            font_size: 16.0,
+            line_height: 1.3,
+            type_scale: 1.33,
+        }
+    }
+}
+
+impl TextContext {
+    pub fn get_global(cx: &App) -> &Arc<TextContext> {
+        &cx.global::<GlobalTextContext>().0
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct GlobalTextContext(pub Arc<TextContext>);
+
+impl Deref for GlobalTextContext {
+    type Target = Arc<TextContext>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for GlobalTextContext {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl Global for GlobalTextContext {}
+
+pub trait ActiveTextContext {
+    fn text_context(&self) -> &Arc<TextContext>;
+}
+
+impl ActiveTextContext for App {
+    fn text_context(&self) -> &Arc<TextContext> {
+        &self.global::<GlobalTextContext>().0
+    }
+}
+
+#[derive(Clone, PartialEq)]
+pub struct SpecimenTheme {
+    pub bg: Hsla,
+    pub fg: Hsla,
+}
+
+impl Default for SpecimenTheme {
+    fn default() -> Self {
+        Self {
+            bg: gpui::white(),
+            fg: gpui::black(),
+        }
+    }
+}
+
+impl SpecimenTheme {
+    pub fn invert(&self) -> Self {
+        Self {
+            bg: self.fg,
+            fg: self.bg,
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, IntoElement)]
+struct Specimen {
+    id: ElementId,
+    scale: f32,
+    text_style: Option<TextStyle>,
+    string: SharedString,
+    invert: bool,
+}
+
+impl Specimen {
+    pub fn new(id: usize) -> Self {
+        let string = SharedString::new_static("The quick brown fox jumps over the lazy dog");
+        let id_string = format!("specimen-{}", id);
+        let id = ElementId::Name(id_string.into());
+        Self {
+            id,
+            scale: 1.0,
+            text_style: None,
+            string,
+            invert: false,
+        }
+    }
+
+    pub fn invert(mut self) -> Self {
+        self.invert = !self.invert;
+        self
+    }
+
+    pub fn scale(mut self, scale: f32) -> Self {
+        self.scale = scale;
+        self
+    }
+}
+
+impl RenderOnce for Specimen {
+    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
+        let rem_size = window.rem_size();
+        let scale = self.scale;
+        let global_style = cx.text_context();
+
+        let style_override = self.text_style;
+
+        let mut font_size = global_style.font_size;
+        let mut line_height = global_style.line_height;
+
+        if let Some(style_override) = style_override {
+            font_size = style_override.font_size.to_pixels(rem_size).0;
+            line_height = match style_override.line_height {
+                DefiniteLength::Absolute(absolute_len) => match absolute_len {
+                    AbsoluteLength::Rems(absolute_len) => absolute_len.to_pixels(rem_size).0,
+                    AbsoluteLength::Pixels(absolute_len) => absolute_len.0,
+                },
+                DefiniteLength::Fraction(value) => value,
+            };
+        }
+
+        let mut theme = SpecimenTheme::default();
+
+        if self.invert {
+            theme = theme.invert();
+        }
+
+        div()
+            .id(self.id)
+            .bg(theme.bg)
+            .text_color(theme.fg)
+            .text_size(px(font_size * scale))
+            .line_height(relative(line_height))
+            .p(px(10.0))
+            .child(self.string.clone())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, IntoElement)]
+struct CharacterGrid {
+    scale: f32,
+    invert: bool,
+    text_style: Option<TextStyle>,
+}
+
+impl CharacterGrid {
+    pub fn new() -> Self {
+        Self {
+            scale: 1.0,
+            invert: false,
+            text_style: None,
+        }
+    }
+
+    pub fn scale(mut self, scale: f32) -> Self {
+        self.scale = scale;
+        self
+    }
+}
+
+impl RenderOnce for CharacterGrid {
+    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
+        let mut theme = SpecimenTheme::default();
+
+        if self.invert {
+            theme = theme.invert();
+        }
+
+        let characters = vec![
+            "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "A", "B", "C", "D", "E", "F", "G",
+            "H", "I", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
+            "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "p", "q",
+            "r", "s", "t", "u", "v", "w", "x", "y", "z", "แบž", "ลฟ", "รŸ", "รฐ", "รž", "รพ", "ฮฑ", "ฮฒ",
+            "ฮ“", "ฮณ", "ฮ”", "ฮด", "ฮท", "ฮธ", "ฮน", "ฮบ", "ฮ›", "ฮป", "ฮผ", "ฮฝ", "ฮพ", "ฯ€", "ฯ„", "ฯ…", "ฯ†",
+            "ฯ‡", "ฯˆ", "โˆ‚", "ะฐ", "ะฒ", "ะ–", "ะถ", "ะ—", "ะท", "ะš", "ะบ", "ะป", "ะผ", "ะ", "ะฝ", "ะ ", "ั€",
+            "ะฃ", "ัƒ", "ั„", "ั‡", "ัŒ", "ั‹", "ะญ", "ั", "ะฏ", "ั", "ij", "รถแบ‹", ".,", "โฃโฃ‘", "~", "*",
+            "_", "^", "`", "'", "(", "{", "ยซ", "#", "&", "@", "$", "ยข", "%", "|", "?", "ยถ", "ยต",
+            "โฎ", "<=", "!=", "==", "--", "++", "=>", "->",
+        ];
+
+        let columns = 11;
+        let rows = characters.len().div_ceil(columns);
+
+        let grid_rows = (0..rows).map(|row_idx| {
+            let start_idx = row_idx * columns;
+            let end_idx = (start_idx + columns).min(characters.len());
+
+            div()
+                .w_full()
+                .flex()
+                .flex_row()
+                .children((start_idx..end_idx).map(|i| {
+                    div()
+                        .text_center()
+                        .size(px(62.))
+                        .bg(theme.bg)
+                        .text_color(theme.fg)
+                        .text_size(px(24.0))
+                        .line_height(relative(1.0))
+                        .child(characters[i])
+                }))
+                .when(end_idx - start_idx < columns, |d| {
+                    d.children(
+                        iter::repeat_with(|| div().flex_1()).take(columns - (end_idx - start_idx)),
+                    )
+                })
+        });
+
+        div().p_4().gap_2().flex().flex_col().children(grid_rows)
+    }
+}
+
+struct TextExample {
+    next_id: usize,
+}
+
+impl TextExample {
+    fn next_id(&mut self) -> usize {
+        self.next_id += 1;
+        self.next_id
+    }
+}
+
+impl Render for TextExample {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        let tcx = cx.text_context();
+        let colors = cx.default_colors().clone();
+
+        let type_scale = tcx.type_scale;
+
+        let step_down_2 = 1.0 / (type_scale * type_scale);
+        let step_down_1 = 1.0 / type_scale;
+        let base = 1.0;
+        let step_up_1 = base * type_scale;
+        let step_up_2 = step_up_1 * type_scale;
+        let step_up_3 = step_up_2 * type_scale;
+        let step_up_4 = step_up_3 * type_scale;
+        let step_up_5 = step_up_4 * type_scale;
+        let step_up_6 = step_up_5 * type_scale;
+
+        div()
+            .size_full()
+            .child(
+                div()
+                    .id("text-example")
+                    .overflow_y_scroll()
+                    .overflow_x_hidden()
+                    .bg(rgb(0xffffff))
+                    .size_full()
+                    .child(div().child(CharacterGrid::new().scale(base)))
+                    .child(
+                        div()
+                            .child(Specimen::new(self.next_id()).scale(step_down_2))
+                            .child(Specimen::new(self.next_id()).scale(step_down_2).invert())
+                            .child(Specimen::new(self.next_id()).scale(step_down_1))
+                            .child(Specimen::new(self.next_id()).scale(step_down_1).invert())
+                            .child(Specimen::new(self.next_id()).scale(base))
+                            .child(Specimen::new(self.next_id()).scale(base).invert())
+                            .child(Specimen::new(self.next_id()).scale(step_up_1))
+                            .child(Specimen::new(self.next_id()).scale(step_up_1).invert())
+                            .child(Specimen::new(self.next_id()).scale(step_up_2))
+                            .child(Specimen::new(self.next_id()).scale(step_up_2).invert())
+                            .child(Specimen::new(self.next_id()).scale(step_up_3))
+                            .child(Specimen::new(self.next_id()).scale(step_up_3).invert())
+                            .child(Specimen::new(self.next_id()).scale(step_up_4))
+                            .child(Specimen::new(self.next_id()).scale(step_up_4).invert())
+                            .child(Specimen::new(self.next_id()).scale(step_up_5))
+                            .child(Specimen::new(self.next_id()).scale(step_up_5).invert())
+                            .child(Specimen::new(self.next_id()).scale(step_up_6))
+                            .child(Specimen::new(self.next_id()).scale(step_up_6).invert()),
+                    ),
+            )
+            .child(div().w(px(240.)).h_full().bg(colors.container))
+    }
+}
+
+fn main() {
+    Application::new().run(|cx: &mut App| {
+        cx.set_menus(vec![Menu {
+            name: "GPUI Typography".into(),
+            items: vec![],
+        }]);
+
+        cx.init_colors();
+        cx.set_global(GlobalTextContext(Arc::new(TextContext::default())));
+
+        let window = cx
+            .open_window(
+                WindowOptions {
+                    titlebar: Some(TitlebarOptions {
+                        title: Some("GPUI Typography".into()),
+                        ..Default::default()
+                    }),
+                    window_bounds: Some(WindowBounds::Windowed(bounds(
+                        point(px(0.0), px(0.0)),
+                        size(px(920.), px(720.)),
+                    ))),
+                    ..Default::default()
+                },
+                |_window, cx| cx.new(|_cx| TextExample { next_id: 0 }),
+            )
+            .unwrap();
+
+        window
+            .update(cx, |_view, _window, cx| {
+                cx.activate(true);
+            })
+            .unwrap();
+    });
+}

crates/gpui/src/app.rs ๐Ÿ”—

@@ -38,7 +38,9 @@ use crate::{
     PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptHandle, PromptLevel,
     Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString,
     SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
-    WindowHandle, WindowId, WindowInvalidator, current_platform, hash, init_app_menus,
+    WindowHandle, WindowId, WindowInvalidator,
+    colors::{Colors, GlobalColors},
+    current_platform, hash, init_app_menus,
 };
 
 mod async_context;
@@ -1656,6 +1658,13 @@ impl App {
             _ = window.drop_image(image);
         }
     }
+
+    /// Initializes gpui's default colors for the application.
+    ///
+    /// These colors can be accessed through `cx.default_colors()`.
+    pub fn init_colors(&mut self) {
+        self.set_global(GlobalColors(Arc::new(Colors::default())));
+    }
 }
 
 impl AppContext for App {

crates/gpui/src/colors.rs ๐Ÿ”—

@@ -0,0 +1,122 @@
+use crate::{App, Global, Rgba, Window, WindowAppearance, rgb};
+use std::ops::Deref;
+use std::sync::Arc;
+
+/// The default set of colors for gpui.
+///
+/// These are used for styling base components, examples and more.
+#[derive(Clone, Debug)]
+pub struct Colors {
+    /// Text color
+    pub text: Rgba,
+    /// Selected text color
+    pub selected_text: Rgba,
+    /// Background color
+    pub background: Rgba,
+    /// Disabled color
+    pub disabled: Rgba,
+    /// Selected color
+    pub selected: Rgba,
+    /// Border color
+    pub border: Rgba,
+    /// Separator color
+    pub separator: Rgba,
+    /// Container color
+    pub container: Rgba,
+}
+
+impl Default for Colors {
+    fn default() -> Self {
+        Self::light()
+    }
+}
+
+impl Colors {
+    /// Returns the default colors for the given window appearance.
+    pub fn for_appearance(window: &Window) -> Self {
+        match window.appearance() {
+            WindowAppearance::Light | WindowAppearance::VibrantLight => Self::light(),
+            WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::dark(),
+        }
+    }
+
+    /// Returns the default dark colors.
+    pub fn dark() -> Self {
+        Self {
+            text: rgb(0xffffff),
+            selected_text: rgb(0xffffff),
+            disabled: rgb(0x565656),
+            selected: rgb(0x2457ca),
+            background: rgb(0x222222),
+            border: rgb(0x000000),
+            separator: rgb(0xd9d9d9),
+            container: rgb(0x262626),
+        }
+    }
+
+    /// Returns the default light colors.
+    pub fn light() -> Self {
+        Self {
+            text: rgb(0x252525),
+            selected_text: rgb(0xffffff),
+            background: rgb(0xffffff),
+            disabled: rgb(0xb0b0b0),
+            selected: rgb(0x2a63d9),
+            border: rgb(0xd9d9d9),
+            separator: rgb(0xe6e6e6),
+            container: rgb(0xf4f5f5),
+        }
+    }
+
+    /// Get [Colors] from the global state
+    pub fn get_global(cx: &App) -> &Arc<Colors> {
+        &cx.global::<GlobalColors>().0
+    }
+}
+
+/// Get [Colors] from the global state
+#[derive(Clone, Debug)]
+pub struct GlobalColors(pub Arc<Colors>);
+
+impl Deref for GlobalColors {
+    type Target = Arc<Colors>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl Global for GlobalColors {}
+
+/// Implement this trait to allow global [Color] access via `cx.default_colors()`.
+pub trait DefaultColors {
+    /// Returns the default [`gpui::Colors`]
+    fn default_colors(&self) -> &Arc<Colors>;
+}
+
+impl DefaultColors for App {
+    fn default_colors(&self) -> &Arc<Colors> {
+        &self.global::<GlobalColors>().0
+    }
+}
+
+/// The appearance of the base GPUI colors, used to style GPUI elements
+///
+/// Varies based on the system's current [`WindowAppearance`].
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
+pub enum DefaultAppearance {
+    /// Use the set of colors for light appearances.
+    #[default]
+    Light,
+    /// Use the set of colors for dark appearances.
+    Dark,
+}
+
+impl From<WindowAppearance> for DefaultAppearance {
+    fn from(appearance: WindowAppearance) -> Self {
+        match appearance {
+            WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
+            WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
+        }
+    }
+}

crates/gpui/src/elements/common.rs ๐Ÿ”—

@@ -1,115 +0,0 @@
-use crate::{Hsla, Rgba, WindowAppearance, rgb};
-
-/// The appearance of the base GPUI colors, used to style GPUI elements
-///
-/// Varies based on the system's current [`WindowAppearance`].
-#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
-pub enum DefaultThemeAppearance {
-    /// Use the set of colors for light appearances.
-    #[default]
-    Light,
-    /// Use the set of colors for dark appearances.
-    Dark,
-}
-
-impl From<WindowAppearance> for DefaultThemeAppearance {
-    fn from(appearance: WindowAppearance) -> Self {
-        match appearance {
-            WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
-            WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
-        }
-    }
-}
-
-/// Returns the default colors for the given appearance.
-pub fn colors(appearance: DefaultThemeAppearance) -> DefaultColors {
-    match appearance {
-        DefaultThemeAppearance::Light => DefaultColors::light(),
-        DefaultThemeAppearance::Dark => DefaultColors::dark(),
-    }
-}
-
-/// A collection of colors.
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct DefaultColors {
-    text: Rgba,
-    selected_text: Rgba,
-    background: Rgba,
-    disabled: Rgba,
-    selected: Rgba,
-    border: Rgba,
-    separator: Rgba,
-    container: Rgba,
-}
-
-impl DefaultColors {
-    /// Returns the default dark colors.
-    pub fn dark() -> Self {
-        Self {
-            text: rgb(0xffffff),
-            selected_text: rgb(0xffffff),
-            disabled: rgb(0x565656),
-            selected: rgb(0x2457ca),
-            background: rgb(0x222222),
-            border: rgb(0x000000),
-            separator: rgb(0xd9d9d9),
-            container: rgb(0x262626),
-        }
-    }
-
-    /// Returns the default light colors.
-    pub fn light() -> Self {
-        Self {
-            text: rgb(0x252525),
-            selected_text: rgb(0xffffff),
-            background: rgb(0xffffff),
-            disabled: rgb(0xb0b0b0),
-            selected: rgb(0x2a63d9),
-            border: rgb(0xd9d9d9),
-            separator: rgb(0xe6e6e6),
-            container: rgb(0xf4f5f5),
-        }
-    }
-}
-
-/// A default GPUI color.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
-pub enum DefaultColor {
-    /// Text color
-    Text,
-    /// Selected text color
-    SelectedText,
-    /// Background color
-    Background,
-    /// Disabled color
-    Disabled,
-    /// Selected color
-    Selected,
-    /// Border color
-    Border,
-    /// Separator color
-    Separator,
-    /// Container color
-    Container,
-}
-
-impl DefaultColor {
-    /// Returns the RGBA color for the given color type.
-    pub fn color(&self, colors: &DefaultColors) -> Rgba {
-        match self {
-            DefaultColor::Text => colors.text,
-            DefaultColor::SelectedText => colors.selected_text,
-            DefaultColor::Background => colors.background,
-            DefaultColor::Disabled => colors.disabled,
-            DefaultColor::Selected => colors.selected,
-            DefaultColor::Border => colors.border,
-            DefaultColor::Separator => colors.separator,
-            DefaultColor::Container => colors.container,
-        }
-    }
-
-    /// Returns the HSLA color for the given color type.
-    pub fn hsla(&self, colors: &DefaultColors) -> Hsla {
-        self.color(colors).into()
-    }
-}

crates/gpui/src/elements/mod.rs ๐Ÿ”—

@@ -1,7 +1,6 @@
 mod anchored;
 mod animation;
 mod canvas;
-mod common;
 mod deferred;
 mod div;
 mod image_cache;
@@ -15,7 +14,6 @@ mod uniform_list;
 pub use anchored::*;
 pub use animation::*;
 pub use canvas::*;
-pub use common::*;
 pub use deferred::*;
 pub use div::*;
 pub use image_cache::*;

crates/gpui/src/gpui.rs ๐Ÿ”—

@@ -73,6 +73,8 @@ mod asset_cache;
 mod assets;
 mod bounds_tree;
 mod color;
+/// The default colors used by GPUI.
+pub mod colors;
 mod element;
 mod elements;
 mod executor;

crates/story/src/story.rs ๐Ÿ”—

@@ -1,6 +1,5 @@
 use gpui::{
-    AnyElement, App, DefaultColor, DefaultColors, Div, SharedString, Window, div, prelude::*, px,
-    rems,
+    AnyElement, App, Div, SharedString, Window, colors::DefaultColors, div, prelude::*, px, rems,
 };
 use itertools::Itertools;
 use smallvec::SmallVec;
@@ -8,9 +7,7 @@ use smallvec::SmallVec;
 pub struct Story {}
 
 impl Story {
-    pub fn container() -> gpui::Stateful<Div> {
-        let colors = DefaultColors::light();
-
+    pub fn container(cx: &App) -> gpui::Stateful<Div> {
         div()
             .id("story_container")
             .overflow_y_scroll()
@@ -18,84 +15,66 @@ impl Story {
             .min_h_full()
             .flex()
             .flex_col()
-            .text_color(DefaultColor::Text.hsla(&colors))
-            .bg(DefaultColor::Background.hsla(&colors))
+            .text_color(cx.default_colors().text)
+            .bg(cx.default_colors().background)
     }
 
-    pub fn title(title: impl Into<SharedString>) -> impl Element {
-        let colors = DefaultColors::light();
-
+    pub fn title(title: impl Into<SharedString>, cx: &App) -> impl Element {
         div()
             .text_xs()
-            .text_color(DefaultColor::Text.hsla(&colors))
+            .text_color(cx.default_colors().text)
             .child(title.into())
     }
 
-    pub fn title_for<T>() -> impl Element {
-        Self::title(std::any::type_name::<T>())
+    pub fn title_for<T>(cx: &App) -> impl Element {
+        Self::title(std::any::type_name::<T>(), cx)
     }
 
-    pub fn section() -> Div {
-        let colors = DefaultColors::light();
-
+    pub fn section(cx: &App) -> Div {
         div()
             .p_4()
             .m_4()
             .border_1()
-            .border_color(DefaultColor::Separator.hsla(&colors))
+            .border_color(cx.default_colors().separator)
     }
 
-    pub fn section_title() -> Div {
-        let colors = DefaultColors::light();
-
-        div().text_lg().text_color(DefaultColor::Text.hsla(&colors))
+    pub fn section_title(cx: &App) -> Div {
+        div().text_lg().text_color(cx.default_colors().text)
     }
 
-    pub fn group() -> Div {
-        let colors = DefaultColors::light();
-        div().my_2().bg(DefaultColor::Container.hsla(&colors))
+    pub fn group(cx: &App) -> Div {
+        div().my_2().bg(cx.default_colors().container)
     }
 
-    pub fn code_block(code: impl Into<SharedString>) -> Div {
-        let colors = DefaultColors::light();
-
+    pub fn code_block(code: impl Into<SharedString>, cx: &App) -> Div {
         div()
             .size_full()
             .p_2()
             .max_w(rems(36.))
-            .bg(DefaultColor::Container.hsla(&colors))
+            .bg(cx.default_colors().container)
             .rounded_sm()
             .text_sm()
-            .text_color(DefaultColor::Text.hsla(&colors))
+            .text_color(cx.default_colors().text)
             .overflow_hidden()
             .child(code.into())
     }
 
-    pub fn divider() -> Div {
-        let colors = DefaultColors::light();
-
-        div()
-            .my_2()
-            .h(px(1.))
-            .bg(DefaultColor::Separator.hsla(&colors))
+    pub fn divider(cx: &App) -> Div {
+        div().my_2().h(px(1.)).bg(cx.default_colors().separator)
     }
 
-    pub fn description(description: impl Into<SharedString>) -> impl Element {
-        let colors = DefaultColors::light();
-
+    pub fn description(description: impl Into<SharedString>, cx: &App) -> impl Element {
         div()
             .text_sm()
-            .text_color(DefaultColor::Text.hsla(&colors))
+            .text_color(cx.default_colors().text)
             .min_w_96()
             .child(description.into())
     }
 
-    pub fn label(label: impl Into<SharedString>) -> impl Element {
-        let colors = DefaultColors::light();
-
+    pub fn label(label: impl Into<SharedString>, cx: &App) -> impl Element {
         div()
             .text_xs()
-            .text_color(DefaultColor::Text.hsla(&colors))
+            .text_color(cx.default_colors().text)
             .child(label.into())
     }
 
@@ -135,8 +114,8 @@ impl StoryItem {
 }
 
 impl RenderOnce for StoryItem {
-    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
-        let colors = DefaultColors::light();
+    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
+        let colors = cx.default_colors();
 
         div()
             .my_2()
@@ -148,20 +127,20 @@ impl RenderOnce for StoryItem {
                     .px_2()
                     .w_1_2()
                     .min_h_px()
-                    .child(Story::label(self.label))
+                    .child(Story::label(self.label, cx))
                     .child(
                         div()
                             .rounded_sm()
-                            .bg(DefaultColor::Background.hsla(&colors))
+                            .bg(colors.background)
                             .border_1()
-                            .border_color(DefaultColor::Border.hsla(&colors))
+                            .border_color(colors.border)
                             .py_1()
                             .px_2()
                             .overflow_hidden()
                             .child(self.item),
                     )
                     .when_some(self.description, |this, description| {
-                        this.child(Story::description(description))
+                        this.child(Story::description(description, cx))
                     }),
             )
             .child(
@@ -171,8 +150,8 @@ impl RenderOnce for StoryItem {
                     .w_1_2()
                     .min_h_px()
                     .when_some(self.usage, |this, usage| {
-                        this.child(Story::label("Example Usage"))
-                            .child(Story::code_block(usage))
+                        this.child(Story::label("Example Usage", cx))
+                            .child(Story::code_block(usage, cx))
                     }),
             )
     }
@@ -205,21 +184,21 @@ impl StorySection {
 }
 
 impl RenderOnce for StorySection {
-    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
+    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
         let children: SmallVec<[AnyElement; 2]> = SmallVec::from_iter(Itertools::intersperse_with(
             self.children.into_iter(),
-            || Story::divider().into_any_element(),
+            || Story::divider(cx).into_any_element(),
         ));
 
-        Story::section()
+        Story::section(cx)
             // Section title
             .py_2()
             // Section description
             .when_some(self.description.clone(), |section, description| {
-                section.child(Story::description(description))
+                section.child(Story::description(description, cx))
             })
             .child(div().flex().flex_col().gap_2().children(children))
-            .child(Story::divider())
+            .child(Story::divider(cx))
     }
 }
 

crates/storybook/src/stories.rs ๐Ÿ”—

@@ -1,6 +1,5 @@
 mod auto_height_editor;
 mod cursor;
-mod default_colors;
 mod focus;
 mod kitchen_sink;
 mod overflow_scroll;
@@ -12,7 +11,6 @@ mod with_rem_size;
 
 pub use auto_height_editor::*;
 pub use cursor::*;
-pub use default_colors::*;
 pub use focus::*;
 pub use kitchen_sink::*;
 pub use overflow_scroll::*;

crates/storybook/src/stories/cursor.rs ๐Ÿ”—

@@ -5,7 +5,7 @@ use ui::prelude::*;
 pub struct CursorStory;
 
 impl Render for CursorStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let all_cursors: [(&str, Box<dyn Fn(Stateful<Div>) -> Stateful<Div>>); 19] = [
             (
                 "cursor_default",
@@ -85,10 +85,10 @@ impl Render for CursorStory {
             ),
         ];
 
-        Story::container()
+        Story::container(cx)
             .flex()
             .gap_1()
-            .child(Story::title("cursor"))
+            .child(Story::title("cursor", cx))
             .children(all_cursors.map(|(name, apply_cursor)| {
                 div().gap_1().flex().text_color(gpui::white()).child(
                     div()
@@ -102,7 +102,7 @@ impl Render for CursorStory {
                         .bg(gpui::red())
                         .active(|style| style.bg(gpui::green()))
                         .text_sm()
-                        .child(Story::label(name)),
+                        .child(Story::label(name, cx)),
                 )
             }))
     }

crates/storybook/src/stories/default_colors.rs ๐Ÿ”—

@@ -1,89 +0,0 @@
-use gpui::{
-    App, Context, DefaultColor, DefaultThemeAppearance, Entity, Hsla, Render, Window, colors, div,
-    prelude::*,
-};
-use story::Story;
-use strum::IntoEnumIterator;
-use ui::{ActiveTheme, h_flex};
-
-pub struct DefaultColorsStory;
-
-impl DefaultColorsStory {
-    pub fn model(cx: &mut App) -> Entity<Self> {
-        cx.new(|_| Self)
-    }
-}
-
-impl Render for DefaultColorsStory {
-    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let appearances = [DefaultThemeAppearance::Light, DefaultThemeAppearance::Dark];
-
-        Story::container()
-            .child(Story::title("Default Colors"))
-            .children(appearances.iter().map(|&appearance| {
-                let colors = colors(appearance);
-                let color_types = DefaultColor::iter()
-                    .map(|color| {
-                        let name = format!("{:?}", color);
-                        let rgba = color.hsla(&colors);
-                        (name, rgba)
-                    })
-                    .collect::<Vec<_>>();
-
-                div()
-                    .flex()
-                    .flex_col()
-                    .gap_4()
-                    .p_4()
-                    .child(Story::label(format!("{:?} Appearance", appearance)))
-                    .children(color_types.iter().map(|(name, color)| {
-                        let color: Hsla = *color;
-
-                        div()
-                            .flex()
-                            .items_center()
-                            .gap_2()
-                            .child(
-                                div()
-                                    .w_12()
-                                    .h_12()
-                                    .bg(color)
-                                    .border_1()
-                                    .border_color(cx.theme().colors().border),
-                            )
-                            .child(Story::label(format!("{}: {:?}", name, color.clone())))
-                    }))
-                    .child(
-                        h_flex()
-                            .gap_1()
-                            .child(
-                                h_flex()
-                                    .bg(DefaultColor::Background.hsla(&colors))
-                                    .h_8()
-                                    .p_2()
-                                    .text_sm()
-                                    .text_color(DefaultColor::Text.hsla(&colors))
-                                    .child("Default Text"),
-                            )
-                            .child(
-                                h_flex()
-                                    .bg(DefaultColor::Container.hsla(&colors))
-                                    .h_8()
-                                    .p_2()
-                                    .text_sm()
-                                    .text_color(DefaultColor::Text.hsla(&colors))
-                                    .child("Text on Container"),
-                            )
-                            .child(
-                                h_flex()
-                                    .bg(DefaultColor::Selected.hsla(&colors))
-                                    .h_8()
-                                    .p_2()
-                                    .text_sm()
-                                    .text_color(DefaultColor::SelectedText.hsla(&colors))
-                                    .child("Selected Text"),
-                            ),
-                    )
-            }))
-    }
-}

crates/storybook/src/stories/indent_guides.rs ๐Ÿ”—

@@ -1,12 +1,12 @@
 use std::fmt::format;
 
 use gpui::{
-    colors, div, prelude::*, uniform_list, DefaultColor, DefaultThemeAppearance, Hsla, Render,
+    DefaultColor, DefaultThemeAppearance, Hsla, Render, colors, div, prelude::*, uniform_list,
 };
 use story::Story;
 use strum::IntoEnumIterator;
 use ui::{
-    h_flex, px, v_flex, AbsoluteLength, ActiveTheme, Color, DefiniteLength, Label, LabelCommon,
+    AbsoluteLength, ActiveTheme, Color, DefiniteLength, Label, LabelCommon, h_flex, px, v_flex,
 };
 
 const LENGTH: usize = 100;
@@ -34,7 +34,7 @@ impl IndentGuidesStory {
 
 impl Render for IndentGuidesStory {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container()
+        Story::container(cx)
             .child(Story::title("Indent guides"))
             .child(
                 v_flex().size_full().child(

crates/storybook/src/stories/kitchen_sink.rs ๐Ÿ”—

@@ -19,11 +19,11 @@ impl Render for KitchenSinkStory {
             .map(|selector| selector.story(window, cx))
             .collect::<Vec<_>>();
 
-        Story::container()
+        Story::container(cx)
             .id("kitchen-sink")
             .overflow_y_scroll()
-            .child(Story::title("Kitchen Sink"))
-            .child(Story::label("Components"))
+            .child(Story::title("Kitchen Sink", cx))
+            .child(Story::label("Components", cx))
             .child(div().flex().flex_col().children(component_stories))
             // Add a bit of space at the bottom of the kitchen sink so elements
             // don't end up squished right up against the bottom of the screen.

crates/storybook/src/stories/overflow_scroll.rs ๐Ÿ”—

@@ -6,10 +6,10 @@ use ui::prelude::*;
 pub struct OverflowScrollStory;
 
 impl Render for OverflowScrollStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container()
-            .child(Story::title("Overflow Scroll"))
-            .child(Story::label("`overflow_x_scroll`"))
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        Story::container(cx)
+            .child(Story::title("Overflow Scroll", cx))
+            .child(Story::label("`overflow_x_scroll`", cx))
             .child(
                 h_flex()
                     .id("overflow_x_scroll")
@@ -22,7 +22,7 @@ impl Render for OverflowScrollStory {
                             .child(SharedString::from(format!("Child {}", i + 1)))
                     })),
             )
-            .child(Story::label("`overflow_y_scroll`"))
+            .child(Story::label("`overflow_y_scroll`", cx))
             .child(
                 v_flex()
                     .w_full()

crates/storybook/src/stories/text.rs ๐Ÿ”—

@@ -14,9 +14,9 @@ impl TextStory {
 }
 
 impl Render for TextStory {
-    fn render(&mut self, window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
-        Story::container()
-            .child(Story::title("Text"))
+    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        Story::container(cx)
+            .child(Story::title("Text", cx))
             .children(vec![
                 StorySection::new()
                     .child(

crates/storybook/src/stories/viewport_units.rs ๐Ÿ”—

@@ -6,8 +6,8 @@ use ui::prelude::*;
 pub struct ViewportUnitsStory;
 
 impl Render for ViewportUnitsStory {
-    fn render(&mut self, window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
-        Story::container().child(
+    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        Story::container(cx).child(
             div()
                 .flex()
                 .flex_row()

crates/storybook/src/stories/with_rem_size.rs ๐Ÿ”—

@@ -6,8 +6,8 @@ use ui::{prelude::*, utils::WithRemSize};
 pub struct WithRemSizeStory;
 
 impl Render for WithRemSizeStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container().child(
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        Story::container(cx).child(
             Example::new(16., gpui::red())
                 .child(
                     Example::new(24., gpui::green())

crates/storybook/src/story_selector.rs ๐Ÿ”—

@@ -17,7 +17,6 @@ pub enum ComponentStory {
     CollabNotification,
     ContextMenu,
     Cursor,
-    DefaultColors,
     Focus,
     IconButton,
     Keybinding,
@@ -47,7 +46,6 @@ impl ComponentStory {
                 .into(),
             Self::ContextMenu => cx.new(|_| ui::ContextMenuStory).into(),
             Self::Cursor => cx.new(|_| crate::stories::CursorStory).into(),
-            Self::DefaultColors => DefaultColorsStory::model(cx).into(),
             Self::Focus => FocusStory::model(window, cx).into(),
             Self::IconButton => cx.new(|_| ui::IconButtonStory).into(),
             Self::Keybinding => cx.new(|_| ui::KeybindingStory).into(),

crates/title_bar/src/stories/application_menu.rs ๐Ÿ”—

@@ -18,9 +18,9 @@ impl ApplicationMenuStory {
 }
 
 impl Render for ApplicationMenuStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container()
-            .child(Story::title_for::<ApplicationMenu>())
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        Story::container(cx)
+            .child(Story::title_for::<ApplicationMenu>(cx))
             .child(StorySection::new().child(StoryItem::new(
                 "Application Menu",
                 h_flex().child(self.menu.clone()),

crates/ui/src/components/stories/context_menu.rs ๐Ÿ”—

@@ -26,8 +26,8 @@ fn build_menu(
 pub struct ContextMenuStory;
 
 impl Render for ContextMenuStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container()
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        Story::container(cx)
             .on_action(|_: &PrintCurrentDate, _, _| {
                 println!("printing unix time!");
                 if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() {

crates/ui/src/components/stories/disclosure.rs ๐Ÿ”—

@@ -7,9 +7,9 @@ use crate::prelude::*;
 pub struct DisclosureStory;
 
 impl Render for DisclosureStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container()
-            .child(Story::title_for::<Disclosure>())
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        Story::container(cx)
+            .child(Story::title_for::<Disclosure>(cx))
             .child(Story::label("Toggled"))
             .child(Disclosure::new("toggled", true))
             .child(Story::label("Not Toggled"))

crates/ui/src/components/stories/icon_button.rs ๐Ÿ”—

@@ -7,7 +7,7 @@ use crate::{IconButtonShape, Tooltip, prelude::*};
 pub struct IconButtonStory;
 
 impl Render for IconButtonStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let default_button = StoryItem::new(
             "Default",
             IconButton::new("default_icon_button", IconName::Hash),
@@ -113,8 +113,8 @@ impl Render for IconButtonStory {
             selected_with_tooltip_button,
         ];
 
-        Story::container()
-            .child(Story::title_for::<IconButton>())
+        Story::container(cx)
+            .child(Story::title_for::<IconButton>(cx))
             .child(StorySection::new().children(buttons))
             .child(
                 StorySection::new().child(StoryItem::new(

crates/ui/src/components/stories/keybinding.rs ๐Ÿ”—

@@ -15,11 +15,11 @@ impl Render for KeybindingStory {
     fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
 
-        Story::container()
-            .child(Story::title_for::<KeyBinding>())
-            .child(Story::label("Single Key"))
+        Story::container(cx)
+            .child(Story::title_for::<KeyBinding>(cx))
+            .child(Story::label("Single Key", cx))
             .child(KeyBinding::new(binding("Z"), cx))
-            .child(Story::label("Single Key with Modifier"))
+            .child(Story::label("Single Key with Modifier", cx))
             .child(
                 div()
                     .flex()
@@ -29,7 +29,7 @@ impl Render for KeybindingStory {
                     .child(KeyBinding::new(binding("cmd-c"), cx))
                     .child(KeyBinding::new(binding("shift-c"), cx)),
             )
-            .child(Story::label("Single Key with Modifier (Permuted)"))
+            .child(Story::label("Single Key with Modifier (Permuted)", cx))
             .child(
                 div().flex().flex_col().children(
                     all_modifier_permutations
@@ -46,33 +46,33 @@ impl Render for KeybindingStory {
                         }),
                 ),
             )
-            .child(Story::label("Single Key with All Modifiers"))
+            .child(Story::label("Single Key with All Modifiers", cx))
             .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx))
-            .child(Story::label("Chord"))
+            .child(Story::label("Chord", cx))
             .child(KeyBinding::new(binding("a z"), cx))
-            .child(Story::label("Chord with Modifier"))
+            .child(Story::label("Chord with Modifier", cx))
             .child(KeyBinding::new(binding("ctrl-a shift-z"), cx))
             .child(KeyBinding::new(binding("fn-s"), cx))
-            .child(Story::label("Single Key with All Modifiers (Linux)"))
+            .child(Story::label("Single Key with All Modifiers (Linux)", cx))
             .child(
                 KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx)
                     .platform_style(PlatformStyle::Linux),
             )
-            .child(Story::label("Chord (Linux)"))
+            .child(Story::label("Chord (Linux)", cx))
             .child(KeyBinding::new(binding("a z"), cx).platform_style(PlatformStyle::Linux))
-            .child(Story::label("Chord with Modifier (Linux)"))
+            .child(Story::label("Chord with Modifier (Linux)", cx))
             .child(
                 KeyBinding::new(binding("ctrl-a shift-z"), cx).platform_style(PlatformStyle::Linux),
             )
             .child(KeyBinding::new(binding("fn-s"), cx).platform_style(PlatformStyle::Linux))
-            .child(Story::label("Single Key with All Modifiers (Windows)"))
+            .child(Story::label("Single Key with All Modifiers (Windows)", cx))
             .child(
                 KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx)
                     .platform_style(PlatformStyle::Windows),
             )
-            .child(Story::label("Chord (Windows)"))
+            .child(Story::label("Chord (Windows)", cx))
             .child(KeyBinding::new(binding("a z"), cx).platform_style(PlatformStyle::Windows))
-            .child(Story::label("Chord with Modifier (Windows)"))
+            .child(Story::label("Chord with Modifier (Windows)", cx))
             .child(
                 KeyBinding::new(binding("ctrl-a shift-z"), cx)
                     .platform_style(PlatformStyle::Windows),

crates/ui/src/components/stories/list.rs ๐Ÿ”—

@@ -7,17 +7,17 @@ use crate::{ListHeader, ListSeparator, ListSubHeader, prelude::*};
 pub struct ListStory;
 
 impl Render for ListStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container()
-            .child(Story::title_for::<List>())
-            .child(Story::label("Default"))
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        Story::container(cx)
+            .child(Story::title_for::<List>(cx))
+            .child(Story::label("Default", cx))
             .child(
                 List::new()
                     .child(ListItem::new("apple").child("Apple"))
                     .child(ListItem::new("banana").child("Banana"))
                     .child(ListItem::new("cherry").child("Cherry")),
             )
-            .child(Story::label("With sections"))
+            .child(Story::label("With sections", cx))
             .child(
                 List::new()
                     .header(ListHeader::new("Produce"))

crates/ui/src/components/stories/list_header.rs ๐Ÿ”—

@@ -7,20 +7,20 @@ use crate::{IconName, ListHeader};
 pub struct ListHeaderStory;
 
 impl Render for ListHeaderStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container()
-            .child(Story::title_for::<ListHeader>())
-            .child(Story::label("Default"))
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        Story::container(cx)
+            .child(Story::title_for::<ListHeader>(cx))
+            .child(Story::label("Default", cx))
             .child(ListHeader::new("Section 1"))
-            .child(Story::label("With left icon"))
+            .child(Story::label("With left icon", cx))
             .child(ListHeader::new("Section 2").start_slot(Icon::new(IconName::Bell)))
-            .child(Story::label("With left icon and meta"))
+            .child(Story::label("With left icon and meta", cx))
             .child(
                 ListHeader::new("Section 3")
                     .start_slot(Icon::new(IconName::BellOff))
                     .end_slot(IconButton::new("action_1", IconName::Bolt)),
             )
-            .child(Story::label("With multiple meta"))
+            .child(Story::label("With multiple meta", cx))
             .child(
                 ListHeader::new("Section 4")
                     .end_slot(IconButton::new("action_1", IconName::Bolt))

crates/ui/src/components/stories/list_item.rs ๐Ÿ”—

@@ -10,12 +10,12 @@ pub struct ListItemStory;
 
 impl Render for ListItemStory {
     fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container()
+        Story::container(cx)
             .bg(cx.theme().colors().background)
-            .child(Story::title_for::<ListItem>())
-            .child(Story::label("Default"))
+            .child(Story::title_for::<ListItem>(cx))
+            .child(Story::label("Default", cx))
             .child(ListItem::new("hello_world").child("Hello, world!"))
-            .child(Story::label("Inset"))
+            .child(Story::label("Inset", cx))
             .child(
                 ListItem::new("inset_list_item")
                     .inset(true)
@@ -31,7 +31,7 @@ impl Render for ListItemStory {
                             .color(Color::Muted),
                     ),
             )
-            .child(Story::label("With start slot icon"))
+            .child(Story::label("With start slot icon", cx))
             .child(
                 ListItem::new("with start slot_icon")
                     .child("Hello, world!")
@@ -41,7 +41,7 @@ impl Render for ListItemStory {
                             .color(Color::Muted),
                     ),
             )
-            .child(Story::label("With start slot avatar"))
+            .child(Story::label("With start slot avatar", cx))
             .child(
                 ListItem::new("with_start slot avatar")
                     .child("Hello, world!")
@@ -49,7 +49,7 @@ impl Render for ListItemStory {
                         "https://avatars.githubusercontent.com/u/1714999?v=4",
                     )),
             )
-            .child(Story::label("With end slot"))
+            .child(Story::label("With end slot", cx))
             .child(
                 ListItem::new("with_left_avatar")
                     .child("Hello, world!")
@@ -57,7 +57,7 @@ impl Render for ListItemStory {
                         "https://avatars.githubusercontent.com/u/1714999?v=4",
                     )),
             )
-            .child(Story::label("With end hover slot"))
+            .child(Story::label("With end hover slot", cx))
             .child(
                 ListItem::new("with_end_hover_slot")
                     .child("Hello, world!")
@@ -84,13 +84,13 @@ impl Render for ListItemStory {
                         "https://avatars.githubusercontent.com/u/1714999?v=4",
                     )),
             )
-            .child(Story::label("With `on_click`"))
+            .child(Story::label("With `on_click`", cx))
             .child(ListItem::new("with_on_click").child("Click me").on_click(
                 |_event, _window, _cx| {
                     println!("Clicked!");
                 },
             ))
-            .child(Story::label("With `on_secondary_mouse_down`"))
+            .child(Story::label("With `on_secondary_mouse_down`", cx))
             .child(
                 ListItem::new("with_on_secondary_mouse_down")
                     .child("Right click me")
@@ -98,7 +98,10 @@ impl Render for ListItemStory {
                         println!("Right mouse down!");
                     }),
             )
-            .child(Story::label("With overflowing content in the `end_slot`"))
+            .child(Story::label(
+                "With overflowing content in the `end_slot`",
+                cx,
+            ))
             .child(
                 ListItem::new("with_overflowing_content_in_end_slot")
                     .child("An excerpt")
@@ -106,6 +109,7 @@ impl Render for ListItemStory {
             )
             .child(Story::label(
                 "`inset` with overflowing content in the `end_slot`",
+                cx,
             ))
             .child(
                 ListItem::new("inset_with_overflowing_content_in_end_slot")
@@ -115,6 +119,7 @@ impl Render for ListItemStory {
             )
             .child(Story::label(
                 "`inset` with overflowing content in `children` and `end_slot`",
+                cx,
             ))
             .child(
                 ListItem::new("inset_with_overflowing_content_in_children_and_end_slot")

crates/ui/src/components/stories/tab.rs ๐Ÿ”—

@@ -9,12 +9,12 @@ use crate::{Indicator, Tab};
 pub struct TabStory;
 
 impl Render for TabStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container()
-            .child(Story::title_for::<Tab>())
-            .child(Story::label("Default"))
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        Story::container(cx)
+            .child(Story::title_for::<Tab>(cx))
+            .child(Story::label("Default", cx))
             .child(h_flex().child(Tab::new("tab_1").child("Tab 1")))
-            .child(Story::label("With indicator"))
+            .child(Story::label("With indicator", cx))
             .child(
                 h_flex().child(
                     Tab::new("tab_1")
@@ -22,7 +22,7 @@ impl Render for TabStory {
                         .child("Tab 1"),
                 ),
             )
-            .child(Story::label("With close button"))
+            .child(Story::label("With close button", cx))
             .child(
                 h_flex().child(
                     Tab::new("tab_1")
@@ -37,13 +37,13 @@ impl Render for TabStory {
                         .child("Tab 1"),
                 ),
             )
-            .child(Story::label("List of tabs"))
+            .child(Story::label("List of tabs", cx))
             .child(
                 h_flex()
                     .child(Tab::new("tab_1").child("Tab 1"))
                     .child(Tab::new("tab_2").child("Tab 2")),
             )
-            .child(Story::label("List of tabs with first tab selected"))
+            .child(Story::label("List of tabs with first tab selected", cx))
             .child(
                 h_flex()
                     .child(
@@ -64,7 +64,7 @@ impl Render for TabStory {
                     )
                     .child(Tab::new("tab_4").position(TabPosition::Last).child("Tab 4")),
             )
-            .child(Story::label("List of tabs with last tab selected"))
+            .child(Story::label("List of tabs with last tab selected", cx))
             .child(
                 h_flex()
                     .child(
@@ -89,7 +89,7 @@ impl Render for TabStory {
                             .child("Tab 4"),
                     ),
             )
-            .child(Story::label("List of tabs with second tab selected"))
+            .child(Story::label("List of tabs with second tab selected", cx))
             .child(
                 h_flex()
                     .child(

crates/ui/src/components/stories/tab_bar.rs ๐Ÿ”—

@@ -6,7 +6,7 @@ use crate::{Tab, TabBar, TabPosition, prelude::*};
 pub struct TabBarStory;
 
 impl Render for TabBarStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let tab_count = 20;
         let selected_tab_index = 3;
 
@@ -31,9 +31,9 @@ impl Render for TabBarStory {
             })
             .collect::<Vec<_>>();
 
-        Story::container()
-            .child(Story::title_for::<TabBar>())
-            .child(Story::label("Default"))
+        Story::container(cx)
+            .child(Story::title_for::<TabBar>(cx))
+            .child(Story::label("Default", cx))
             .child(
                 h_flex().child(
                     TabBar::new("tab_bar_1")

crates/ui/src/components/stories/toggle_button.rs ๐Ÿ”—

@@ -6,9 +6,9 @@ use crate::{ToggleButton, prelude::*};
 pub struct ToggleButtonStory;
 
 impl Render for ToggleButtonStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container()
-            .child(Story::title_for::<ToggleButton>())
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        Story::container(cx)
+            .child(Story::title_for::<ToggleButton>(cx))
             .child(
                 StorySection::new().child(
                     StoryItem::new(