From 4ace342cf0d47946b26f22c71cdbd67590856cc1 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 11:59:24 -0500 Subject: [PATCH 1/8] Fix typo --- crates/storybook2/src/stories/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index ccd13cb4d80b1e739cef0671a3bfd54b78264fea..b279e004c7b37bc3c1d626028d86239df6e14d65 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -50,7 +50,7 @@ impl Render for TextStory { )))) // NOTE: When rendering text in a horizonal flex container, // Taffy will not pass width constraints down from the parent. - // To fix this, render text in a praent with overflow: hidden, which + // To fix this, render text in a parent with overflow: hidden .child(div().h_5()) .child(div().flex().w_96().bg(red()).child(concat!( "flex-row. width 96. The quick brown fox jumps over the lazy dog. ", From c041799c6ad32c046ac51b5233cdfe227958bd7c Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 15:55:17 -0500 Subject: [PATCH 2/8] Extend Story components, allow linking to story file Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com> --- Cargo.lock | 2 + crates/story/Cargo.toml | 1 + crates/story/src/story.rs | 274 ++++++++++++++++++++++++-- crates/storybook2/Cargo.toml | 1 + crates/storybook2/src/stories/text.rs | 137 +++++++------ 5 files changed, 344 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4281553aff5f7100dc91be770eeda88bad9825ac..c5ec126de5934615c09f1e494b46b4f70808763e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9211,6 +9211,7 @@ name = "story" version = "0.1.0" dependencies = [ "gpui2", + "smallvec", ] [[package]] @@ -9224,6 +9225,7 @@ dependencies = [ "editor2", "fuzzy2", "gpui2", + "indoc", "itertools 0.11.0", "language2", "log", diff --git a/crates/story/Cargo.toml b/crates/story/Cargo.toml index 384447af8fe51472a7da9b614e23b364bb664d25..b042cbab807702b77610e885c997444ba5465fb8 100644 --- a/crates/story/Cargo.toml +++ b/crates/story/Cargo.toml @@ -8,3 +8,4 @@ publish = false [dependencies] gpui = { package = "gpui2", path = "../gpui2" } +smallvec.workspace = true diff --git a/crates/story/src/story.rs b/crates/story/src/story.rs index d95c879ce0068c00fd24ffb64179d4e3d46a5c7a..c656f3729db4c679ae81292f1ccaea10cb7eacaf 100644 --- a/crates/story/src/story.rs +++ b/crates/story/src/story.rs @@ -1,22 +1,109 @@ -use gpui::prelude::*; -use gpui::{div, hsla, Div, SharedString}; +use gpui::{div, hsla, AnyElement, Div, ElementId, Hsla, SharedString, Stateful, WindowContext}; +use gpui::{prelude::*, px}; +use smallvec::SmallVec; + +use std::path::PathBuf; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::{SystemTime, UNIX_EPOCH}; + +static COUNTER: AtomicUsize = AtomicUsize::new(0); + +pub fn reasonably_unique_id() -> String { + let now = SystemTime::now(); + let timestamp = now.duration_since(UNIX_EPOCH).unwrap(); + + let cnt = COUNTER.fetch_add(1, Ordering::Relaxed); + + let id = format!("{}_{}", timestamp.as_nanos(), cnt); + + id +} + +pub struct StoryColor { + pub primary: Hsla, + pub secondary: Hsla, + pub border: Hsla, + pub background: Hsla, + pub card_background: Hsla, + pub divider: Hsla, + pub link: Hsla, +} + +impl StoryColor { + pub fn new() -> Self { + Self { + primary: hsla(216. / 360., 11. / 100., 0. / 100., 1.), + secondary: hsla(216. / 360., 11. / 100., 16. / 100., 1.), + border: hsla(216. / 360., 11. / 100., 91. / 100., 1.), + background: hsla(0. / 360., 0. / 100., 100. / 100., 1.), + card_background: hsla(0. / 360., 0. / 100., 96. / 100., 1.), + divider: hsla(216. / 360., 11. / 100., 91. / 100., 1.), + link: hsla(206. / 360., 100. / 100., 50. / 100., 1.), + } + } +} + +pub fn story_color() -> StoryColor { + StoryColor::new() +} pub struct Story {} impl Story { pub fn container() -> Div { - div().size_full().flex().flex_col().pt_2().px_4().bg(hsla( - 0. / 360., - 0. / 100., - 100. / 100., - 1., - )) + div() + .size_full() + .flex() + .flex_col() + .bg(story_color().background) + } + + // TODO: Move all stories to container2, then rename + pub fn container2(relative_path: &'static str) -> Div { + div() + .size_full() + .flex() + .flex_col() + .bg(story_color().background) + .child( + div() + .flex() + .justify_between() + .p_2() + .border_b() + .border_color(story_color().border) + .child(Story::title_for::()) + .child( + div() + .text_xs() + .text_color(story_color().primary) + .child(Story::open_story_link(relative_path)), + ), + ) + } + + pub fn open_story_link(relative_path: &'static str) -> impl Element { + let path = PathBuf::from_iter([relative_path]); + div() + .id(SharedString::from(format!("id_{}", relative_path))) + .text_xs() + .text_color(story_color().primary) + .on_click({ + let path = path.clone(); + + move |_event, _cx| { + let path = format!("{}:0:0", path.to_string_lossy()); + + std::process::Command::new("zed").arg(path).spawn().ok(); + } + }) + .child(Story::link(path.to_string_lossy().to_string())) } pub fn title(title: impl Into) -> impl Element { div() - .text_xl() - .text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.)) + .text_xs() + .text_color(story_color().primary) .child(title.into()) } @@ -24,12 +111,173 @@ impl Story { Self::title(std::any::type_name::()) } + pub fn section() -> Div { + div().mt_4().mb_2() + } + + pub fn section_title() -> Div { + div().text_lg().text_color(story_color().primary) + } + + pub fn group() -> Div { + div().my_2().bg(story_color().background) + } + + pub fn code_block(code: impl Into) -> Div { + div() + .size_full() + .p_2() + .bg(gpui::black()) + .border() + .border_color(story_color().border) + .rounded_md() + .text_sm() + .text_color(gpui::white()) + .child(code.into()) + } + + pub fn divider() -> Div { + div().my_2().h(px(1.)).bg(story_color().divider) + } + + pub fn link(link: impl Into) -> impl Element { + div() + .id(ElementId::from(SharedString::from(reasonably_unique_id()))) + .text_xs() + .text_color(story_color().link) + .cursor(gpui::CursorStyle::PointingHand) + .child(link.into()) + } + + pub fn description(description: impl Into) -> impl Element { + div() + .text_sm() + .text_color(story_color().secondary) + .min_w_96() + .child(description.into()) + } + pub fn label(label: impl Into) -> impl Element { div() - .mt_4() - .mb_2() .text_xs() - .text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.)) + .text_color(story_color().primary) .child(label.into()) } + + /// Note: Not ui::v_stack() as the story crate doesn't depend on the ui crate. + pub fn v_stack() -> Div { + div().flex().flex_col().gap_1() + } +} + +#[derive(IntoElement)] +pub struct StoryItem { + label: SharedString, + item: AnyElement, + description: Option, + usage: Option, +} + +impl StoryItem { + pub fn new(label: impl Into, item: impl IntoElement) -> Self { + Self { + label: label.into(), + item: item.into_any_element(), + description: None, + usage: None, + } + } + + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + pub fn usage(mut self, code: impl Into) -> Self { + self.usage = Some(code.into()); + self + } +} + +impl RenderOnce for StoryItem { + type Rendered = Div; + + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + div() + .my_2() + .flex() + .w_full() + .child( + Story::v_stack() + .px_2() + .flex_none() + .w_1_2() + .min_h_px() + .child(Story::label(self.label)) + .child( + div() + .rounded_sm() + .bg(story_color().card_background) + .border() + .border_color(story_color().border) + .child(self.item), + ) + .when_some(self.description, |this, description| { + this.child(Story::description(description)) + }), + ) + .child( + Story::v_stack() + .px_2() + .flex_none() + .w_1_2() + .min_h_px() + .when_some(self.usage, |this, usage| { + this.child(Story::label("Usage")) + .child(Story::code_block(usage)) + }), + ) + } +} + +#[derive(IntoElement)] +pub struct StorySection { + description: Option, + children: SmallVec<[AnyElement; 2]>, +} + +impl StorySection { + pub fn new() -> Self { + Self { + description: None, + children: SmallVec::new(), + } + } + + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } +} + +impl RenderOnce for StorySection { + type Rendered = Div; + + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + Story::section() + // Section title + .py_2() + // Section description + .when_some(self.description.clone(), |section, description| { + section.child(Story::description(description)) + }) + .child(div().flex().flex_col().gap_2().children(self.children)) + .child(Story::divider()) + } +} + +impl ParentElement for StorySection { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } } diff --git a/crates/storybook2/Cargo.toml b/crates/storybook2/Cargo.toml index 949d07b06f83bc4b18aaa7faa6b6c4ebfa2899d0..672bebe20e1ea59124833c00615c4da44cc65057 100644 --- a/crates/storybook2/Cargo.toml +++ b/crates/storybook2/Cargo.toml @@ -22,6 +22,7 @@ editor = { package = "editor2", path = "../editor2" } chrono = "0.4" fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } +indoc.workspace = true itertools = "0.11.0" language = { package = "language2", path = "../language2" } log.workspace = true diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index b279e004c7b37bc3c1d626028d86239df6e14d65..279a2dbf4d24051aca3ec82263ea8821bb24ae7b 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,8 +1,6 @@ -use gpui::{ - blue, div, green, red, white, Div, HighlightStyle, InteractiveText, ParentElement, Render, - Styled, StyledText, View, VisualContext, WindowContext, -}; -use ui::v_stack; +use gpui::{div, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext}; +use indoc::indoc; +use story::*; pub struct TextStory; @@ -16,59 +14,82 @@ impl Render for TextStory { type Element = Div; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { - v_stack() - .bg(blue()) - .child( - div() - .flex() - .child(div().max_w_96().bg(white()).child(concat!( - "max-width: 96. The quick brown fox jumps over the lazy dog. ", - "Meanwhile, the lazy dog decided it was time for a change. ", - "He started daily workout routines, ate healthier and became the fastest dog in town.", - ))), - ) - .child(div().h_5()) - .child(div().flex().flex_col().w_96().bg(white()).child(concat!( - "flex-col. width: 96; The quick brown fox jumps over the lazy dog. ", - "Meanwhile, the lazy dog decided it was time for a change. ", - "He started daily workout routines, ate healthier and became the fastest dog in town.", - ))) - .child(div().h_5()) - .child( - div() - .flex() - .child(div().min_w_96().bg(white()).child(concat!( - "min-width: 96. The quick brown fox jumps over the lazy dog. ", - "Meanwhile, the lazy dog decided it was time for a change. ", - "He started daily workout routines, ate healthier and became the fastest dog in town.", -)))) - .child(div().h_5()) - .child(div().flex().w_96().bg(white()).child(div().overflow_hidden().child(concat!( - "flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ", - "Meanwhile, the lazy dog decided it was time for a change. ", - "He started daily workout routines, ate healthier and became the fastest dog in town.", - )))) - // NOTE: When rendering text in a horizonal flex container, - // Taffy will not pass width constraints down from the parent. - // To fix this, render text in a parent with overflow: hidden - .child(div().h_5()) - .child(div().flex().w_96().bg(red()).child(concat!( - "flex-row. width 96. The quick brown fox jumps over the lazy dog. ", - "Meanwhile, the lazy dog decided it was time for a change. ", - "He started daily workout routines, ate healthier and became the fastest dog in town.", - ))).child( - InteractiveText::new( - "interactive", - StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [ - (6..11, HighlightStyle { - background_color: Some(green()), - ..Default::default() - }), - ]), + // let # = "The quick brown fox jumps over the lazy dog. Meanwhile, the lazy dog decided it was time for a change. He started daily workout routines, ate healthier and became the fastest dog in town."; + + Story::container2::("crates/storybook2/src/stories/text.rs").child( + StorySection::new().child( + StoryItem::new( + "Default Text", + div().flex().child(div().max_w_96().child("foo")), ) - .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| { - println!("Clicked range {range_ix}"); - }) - ) + .description("Text with a max-width. Wraps based on set max-width.") + .usage(indoc! {r##" + div().max_w_96() + .child("Some text that you want to wrap.") + "## + }), + ), + ) } } + +// impl Render for TextStory { +// type Element = Div; + +// fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { +// v_stack() +// .bg(blue()) +// .child( +// div() +// .flex() +// .child(div().max_w_96().bg(white()).child(concat!( +// "max-width: 96. The quick brown fox jumps over the lazy dog. ", +// "Meanwhile, the lazy dog decided it was time for a change. ", +// "He started daily workout routines, ate healthier and became the fastest dog in town.", +// ))), +// ) +// .child(div().h_5()) +// .child(div().flex().flex_col().w_96().bg(white()).child(concat!( +// "flex-col. width: 96; The quick brown fox jumps over the lazy dog. ", +// "Meanwhile, the lazy dog decided it was time for a change. ", +// "He started daily workout routines, ate healthier and became the fastest dog in town.", +// ))) +// .child(div().h_5()) +// .child( +// div() +// .flex() +// .child(div().min_w_96().bg(white()).child(concat!( +// "min-width: 96. The quick brown fox jumps over the lazy dog. ", +// "Meanwhile, the lazy dog decided it was time for a change. ", +// "He started daily workout routines, ate healthier and became the fastest dog in town.", +// )))) +// .child(div().h_5()) +// .child(div().flex().w_96().bg(white()).child(div().overflow_hidden().child(concat!( +// "flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ", +// "Meanwhile, the lazy dog decided it was time for a change. ", +// "He started daily workout routines, ate healthier and became the fastest dog in town.", +// )))) +// // NOTE: When rendering text in a horizonal flex container, +// // Taffy will not pass width constraints down from the parent. +// // To fix this, render text in a parent with overflow: hidden +// .child(div().h_5()) +// .child(div().flex().w_96().bg(red()).child(concat!( +// "flex-row. width 96. The quick brown fox jumps over the lazy dog. ", +// "Meanwhile, the lazy dog decided it was time for a change. ", +// "He started daily workout routines, ate healthier and became the fastest dog in town.", +// ))).child( +// InteractiveText::new( +// "interactive", +// StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [ +// (6..11, HighlightStyle { +// background_color: Some(green()), +// ..Default::default() +// }), +// ]), +// ) +// .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| { +// println!("Clicked range {range_ix}"); +// }) +// ) +// } +// } From 63c3edfb83a223c0822a160fc60e7a7de9c0d155 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 16:52:05 -0500 Subject: [PATCH 3/8] Continue styling new story components --- Cargo.lock | 1 + crates/story/Cargo.toml | 1 + crates/story/src/story.rs | 34 +++++++++---- crates/storybook2/src/stories/text.rs | 72 +++++++++++++++++++++------ 4 files changed, 83 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5ec126de5934615c09f1e494b46b4f70808763e..1c74b49738e5703537ea5e43d0e0d067695b89c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9211,6 +9211,7 @@ name = "story" version = "0.1.0" dependencies = [ "gpui2", + "itertools 0.10.5", "smallvec", ] diff --git a/crates/story/Cargo.toml b/crates/story/Cargo.toml index b042cbab807702b77610e885c997444ba5465fb8..9c195f77f05f591474e739834a8b8965c3822fb4 100644 --- a/crates/story/Cargo.toml +++ b/crates/story/Cargo.toml @@ -9,3 +9,4 @@ publish = false [dependencies] gpui = { package = "gpui2", path = "../gpui2" } smallvec.workspace = true +itertools = {package = "itertools", version = "0.10"} diff --git a/crates/story/src/story.rs b/crates/story/src/story.rs index c656f3729db4c679ae81292f1ccaea10cb7eacaf..4985fb1961c3e4957d8ad9b8947929fee996fe52 100644 --- a/crates/story/src/story.rs +++ b/crates/story/src/story.rs @@ -1,5 +1,7 @@ -use gpui::{div, hsla, AnyElement, Div, ElementId, Hsla, SharedString, Stateful, WindowContext}; -use gpui::{prelude::*, px}; +use gpui::{ + div, hsla, prelude::*, px, rems, AnyElement, Div, ElementId, Hsla, SharedString, WindowContext, +}; +use itertools::Itertools; use smallvec::SmallVec; use std::path::PathBuf; @@ -37,7 +39,7 @@ impl StoryColor { border: hsla(216. / 360., 11. / 100., 91. / 100., 1.), background: hsla(0. / 360., 0. / 100., 100. / 100., 1.), card_background: hsla(0. / 360., 0. / 100., 96. / 100., 1.), - divider: hsla(216. / 360., 11. / 100., 91. / 100., 1.), + divider: hsla(216. / 360., 11. / 100., 86. / 100., 1.), link: hsla(206. / 360., 100. / 100., 50. / 100., 1.), } } @@ -112,7 +114,11 @@ impl Story { } pub fn section() -> Div { - div().mt_4().mb_2() + div() + .p_4() + .m_4() + .border() + .border_color(story_color().border) } pub fn section_title() -> Div { @@ -127,12 +133,12 @@ impl Story { div() .size_full() .p_2() + .max_w(rems(36.)) .bg(gpui::black()) - .border() - .border_color(story_color().border) .rounded_md() .text_sm() .text_color(gpui::white()) + .overflow_hidden() .child(code.into()) } @@ -206,20 +212,23 @@ impl RenderOnce for StoryItem { div() .my_2() .flex() + .gap_4() .w_full() .child( Story::v_stack() .px_2() - .flex_none() .w_1_2() .min_h_px() .child(Story::label(self.label)) .child( div() - .rounded_sm() + .rounded_md() .bg(story_color().card_background) .border() .border_color(story_color().border) + .py_1() + .px_2() + .overflow_hidden() .child(self.item), ) .when_some(self.description, |this, description| { @@ -233,7 +242,7 @@ impl RenderOnce for StoryItem { .w_1_2() .min_h_px() .when_some(self.usage, |this, usage| { - this.child(Story::label("Usage")) + this.child(Story::label("Example Usage")) .child(Story::code_block(usage)) }), ) @@ -264,6 +273,11 @@ impl RenderOnce for StorySection { type Rendered = Div; fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + let children: SmallVec<[AnyElement; 2]> = SmallVec::from_iter(Itertools::intersperse_with( + self.children.into_iter(), + || Story::divider().into_any_element(), + )); + Story::section() // Section title .py_2() @@ -271,7 +285,7 @@ impl RenderOnce for StorySection { .when_some(self.description.clone(), |section, description| { section.child(Story::description(description)) }) - .child(div().flex().flex_col().gap_2().children(self.children)) + .child(div().flex().flex_col().gap_2().children(children)) .child(Story::divider()) } } diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index 279a2dbf4d24051aca3ec82263ea8821bb24ae7b..b48b9dd4b5ea438564499ec80654e6f052aa4396 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,4 +1,4 @@ -use gpui::{div, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext}; +use gpui::{div, red, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext}; use indoc::indoc; use story::*; @@ -13,22 +13,64 @@ impl TextStory { impl Render for TextStory { type Element = Div; - fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { - // let # = "The quick brown fox jumps over the lazy dog. Meanwhile, the lazy dog decided it was time for a change. He started daily workout routines, ate healthier and became the fastest dog in town."; - + fn render(&mut self, _cx: &mut gpui::ViewContext) -> Self::Element { Story::container2::("crates/storybook2/src/stories/text.rs").child( - StorySection::new().child( - StoryItem::new( - "Default Text", - div().flex().child(div().max_w_96().child("foo")), + StorySection::new() + .child( + StoryItem::new("Default", div().bg(gpui::blue()).child("Hello World!")) + .usage(indoc! {r##" + div() + .child("Hello World!") + "## + }), + ) + .child( + StoryItem::new("Wrapping Text", + div().max_w_96() + .child( + concat!( + "The quick brown fox jumps over the lazy dog. ", + "Meanwhile, the lazy dog decided it was time for a change. ", + "He started daily workout routines, ate healthier and became the fastest dog in town.", + ) + ) + ) + .description("Set a width or max-width to enable text wrapping.") + .usage(indoc! {r##" + div() + .max_w_96() + .child("Some text that you want to wrap.") + "## + }) + ) + .child( + StoryItem::new("tbd", + div().flex().w_96().child(div().overflow_hidden().child(concat!( + "flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ", + "Meanwhile, the lazy dog decided it was time for a change. ", + "He started daily workout routines, ate healthier and became the fastest dog in town.", + ))) + ) + ) + .child( + StoryItem::new("Text in Horizontal Flex", + div().flex().w_96().bg(red()).child(concat!( + "flex-row. width 96. The quick brown fox jumps over the lazy dog. ", + "Meanwhile, the lazy dog decided it was time for a change. ", + "He started daily workout routines, ate healthier and became the fastest dog in town.", + )) + ) + .usage(indoc! {r##" + // NOTE: When rendering text in a horizonal flex container, + // Taffy will not pass width constraints down from the parent. + // To fix this, render text in a parent with overflow: hidden + + div() + .max_w_96() + .child("Some text that you want to wrap.") + "## + }) ) - .description("Text with a max-width. Wraps based on set max-width.") - .usage(indoc! {r##" - div().max_w_96() - .child("Some text that you want to wrap.") - "## - }), - ), ) } } From 3cf003763e3af497351f6294b10259cce081348c Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 17:38:22 -0500 Subject: [PATCH 4/8] Use updated story container in Text story --- crates/story/src/story.rs | 118 ++++++++++++++++++++++---- crates/storybook2/src/stories/text.rs | 50 +++++++++-- 2 files changed, 148 insertions(+), 20 deletions(-) diff --git a/crates/story/src/story.rs b/crates/story/src/story.rs index 4985fb1961c3e4957d8ad9b8947929fee996fe52..3419af95b099807609b52b40b9775b17457975a0 100644 --- a/crates/story/src/story.rs +++ b/crates/story/src/story.rs @@ -1,5 +1,6 @@ use gpui::{ - div, hsla, prelude::*, px, rems, AnyElement, Div, ElementId, Hsla, SharedString, WindowContext, + div, hsla, prelude::*, px, rems, AnyElement, Div, ElementId, Hsla, SharedString, Stateful, + WindowContext, }; use itertools::Itertools; use smallvec::SmallVec; @@ -49,47 +50,134 @@ pub fn story_color() -> StoryColor { StoryColor::new() } -pub struct Story {} +#[derive(IntoElement)] +pub struct StoryContainer { + title: SharedString, + relative_path: &'static str, + children: SmallVec<[AnyElement; 2]>, +} -impl Story { - pub fn container() -> Div { - div() - .size_full() - .flex() - .flex_col() - .bg(story_color().background) +impl StoryContainer { + pub fn new(title: impl Into, relative_path: &'static str) -> Self { + Self { + title: title.into(), + relative_path, + children: SmallVec::new(), + } } +} - // TODO: Move all stories to container2, then rename - pub fn container2(relative_path: &'static str) -> Div { +impl ParentElement for StoryContainer { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } +} + +impl RenderOnce for StoryContainer { + type Rendered = Stateful
; + + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { div() .size_full() .flex() .flex_col() + .id("story_container") .bg(story_color().background) .child( div() .flex() + .flex_none() + .w_full() .justify_between() .p_2() + .bg(story_color().background) .border_b() .border_color(story_color().border) - .child(Story::title_for::()) + .child(Story::title(self.title)) .child( div() .text_xs() .text_color(story_color().primary) - .child(Story::open_story_link(relative_path)), + .child(Story::open_story_link(self.relative_path)), ), ) + .child( + div() + .w_full() + .h_px() + .flex_1() + .id("story_body") + .overflow_hidden_x() + .overflow_y_scroll() + .flex() + .flex_col() + .pb_4() + .children(self.children), + ) + } +} + +pub struct Story {} + +impl Story { + pub fn container() -> Div { + div().size_full().overflow_hidden().child( + div() + .id("story_container") + .overflow_y_scroll() + .w_full() + .min_h_full() + .flex() + .flex_col() + .bg(story_color().background), + ) + } + + // TODO: Move all stories to container2, then rename + pub fn container2(relative_path: &'static str) -> Div { + div().size_full().child( + div() + .size_full() + .id("story_container") + .overflow_y_scroll() + .flex() + .flex_col() + .flex_none() + .child( + div() + .flex() + .justify_between() + .p_2() + .border_b() + .border_color(story_color().border) + .child(Story::title_for::()) + .child( + div() + .text_xs() + .text_color(story_color().primary) + .child(Story::open_story_link(relative_path)), + ), + ) + .child( + div() + .w_full() + .min_h_full() + .flex() + .flex_col() + .bg(story_color().background), + ), + ) } pub fn open_story_link(relative_path: &'static str) -> impl Element { let path = PathBuf::from_iter([relative_path]); + div() - .id(SharedString::from(format!("id_{}", relative_path))) + .flex() + .gap_2() .text_xs() .text_color(story_color().primary) + .id(SharedString::from(format!("id_{}", relative_path))) .on_click({ let path = path.clone(); @@ -99,7 +187,7 @@ impl Story { std::process::Command::new("zed").arg(path).spawn().ok(); } }) - .child(Story::link(path.to_string_lossy().to_string())) + .children(vec![div().child(Story::link("Open in Zed →"))]) } pub fn title(title: impl Into) -> impl Element { diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index b48b9dd4b5ea438564499ec80654e6f052aa4396..7c1d29a2693dd6ba5dd83dfdbbe0030dea261d42 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,4 +1,7 @@ -use gpui::{div, red, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext}; +use gpui::{ + div, green, red, Component, Div, HighlightStyle, InteractiveText, IntoElement, ParentElement, + Render, Styled, StyledText, View, VisualContext, WindowContext, +}; use indoc::indoc; use story::*; @@ -11,10 +14,13 @@ impl TextStory { } impl Render for TextStory { - type Element = Div; + type Element = Component; + + fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { + StoryContainer::new("Text Story", "crates/storybook2/src/stories/text.rs") + .children( + vec![ - fn render(&mut self, _cx: &mut gpui::ViewContext) -> Self::Element { - Story::container2::("crates/storybook2/src/stories/text.rs").child( StorySection::new() .child( StoryItem::new("Default", div().bg(gpui::blue()).child("Hello World!")) @@ -71,10 +77,44 @@ impl Render for TextStory { "## }) ) - ) + .child( + StoryItem::new("Interactive Text", + InteractiveText::new( + "interactive", + StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [ + (6..11, HighlightStyle { + background_color: Some(green()), + ..Default::default() + }), + ]), + ) + .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| { + println!("Clicked range {range_ix}"); + }) + ) + .usage(indoc! {r##" + InteractiveText::new( + "interactive", + StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [ + (6..11, HighlightStyle { + background_color: Some(green()), + ..Default::default() + }), + ]), + ) + .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| { + println!("Clicked range {range_ix}"); + }) + "## + }) + ) + ] + ).into_element() } } +// TODO: Check all were updated to new style and remove + // impl Render for TextStory { // type Element = Div; From 936c78be941cbf6948b93c81e6e9ca07def9f4f2 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 17:51:08 -0500 Subject: [PATCH 5/8] WIP IconButton story --- crates/storybook2/src/stories/text.rs | 2 +- .../ui2/src/components/stories/icon_button.rs | 214 +++++++++++++----- 2 files changed, 163 insertions(+), 53 deletions(-) diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index 7c1d29a2693dd6ba5dd83dfdbbe0030dea261d42..99d722988d086d932c6bd734992720273f02e2e3 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,5 +1,5 @@ use gpui::{ - div, green, red, Component, Div, HighlightStyle, InteractiveText, IntoElement, ParentElement, + div, green, red, Component, HighlightStyle, InteractiveText, IntoElement, ParentElement, Render, Styled, StyledText, View, VisualContext, WindowContext, }; use indoc::indoc; diff --git a/crates/ui2/src/components/stories/icon_button.rs b/crates/ui2/src/components/stories/icon_button.rs index 583f453d188b9df5aaaf71b5bb1897814c0a6f51..5391f0ac182c1a2a0d02bdead7350f46b45c1f6c 100644 --- a/crates/ui2/src/components/stories/icon_button.rs +++ b/crates/ui2/src/components/stories/icon_button.rs @@ -1,5 +1,5 @@ -use gpui::{Div, Render}; -use story::Story; +use gpui::{Component, Div, Render}; +use story::{Story, StoryContainer, StoryItem, StorySection}; use crate::{prelude::*, Tooltip}; use crate::{Icon, IconButton}; @@ -7,57 +7,167 @@ use crate::{Icon, IconButton}; pub struct IconButtonStory; impl Render for IconButtonStory { - type Element = Div; + type Element = Component; fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { - Story::container() - .child(Story::title_for::()) - .child(Story::label("Default")) - .child(div().w_8().child(IconButton::new("icon_a", Icon::Hash))) - .child(Story::label("Selected")) - .child( - div() - .w_8() - .child(IconButton::new("icon_a", Icon::Hash).selected(true)), - ) - .child(Story::label("Selected with `selected_icon`")) - .child( - div().w_8().child( - IconButton::new("icon_a", Icon::AudioOn) - .selected(true) - .selected_icon(Icon::AudioOff), - ), - ) - .child(Story::label("Disabled")) - .child( - div() - .w_8() - .child(IconButton::new("icon_a", Icon::Hash).disabled(true)), - ) - .child(Story::label("With `on_click`")) - .child( - div() - .w_8() - .child( - IconButton::new("with_on_click", Icon::Ai).on_click(|_event, _cx| { - println!("Clicked!"); - }), - ), - ) - .child(Story::label("With `tooltip`")) - .child( - div().w_8().child( - IconButton::new("with_tooltip", Icon::MessageBubbles) - .tooltip(|cx| Tooltip::text("Open messages", cx)), - ), - ) - .child(Story::label("Selected with `tooltip`")) - .child( - div().w_8().child( - IconButton::new("selected_with_tooltip", Icon::InlayHint) - .selected(true) - .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), - ), - ) + let default_button = StoryItem::new( + "Default", + IconButton::new("default_icon_button", Icon::Hash), + ) + .description("Displays an icon button.") + .usage( + r#" + IconButton::new("default_icon_button", Icon::Hash) + "#, + ); + + let selected_button = StoryItem::new( + "Selected", + IconButton::new("selected_icon_button", Icon::Hash).selected(true), + ) + .description("Displays an icon button that is selected.") + .usage( + r#" + IconButton::new("selected_icon_button", Icon::Hash).selected(true) + "#, + ); + + let selected_with_selected_icon = StoryItem::new( + "Selected with `selected_icon`", + IconButton::new("selected_with_selected_icon_button", Icon::AudioOn) + .selected(true) + .selected_icon(Icon::AudioOff), + ) + .description( + "Displays an icon button that is selected and shows a different icon when selected.", + ) + .usage( + r#" + IconButton::new("selected_with_selected_icon_button", Icon::AudioOn) + .selected(true) + .selected_icon(Icon::AudioOff) + "#, + ); + + let disabled_button = StoryItem::new( + "Disabled", + IconButton::new("disabled_icon_button", Icon::Hash).disabled(true), + ) + .description("Displays an icon button that is disabled.") + .usage( + r#" + IconButton::new("disabled_icon_button", Icon::Hash).disabled(true) + "#, + ); + + let with_on_click_button = StoryItem::new( + "With `on_click`", + IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| { + println!("Clicked!"); + }), + ) + .description("Displays an icon button which triggers an event on click.") + .usage( + r#" + IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| { + println!("Clicked!"); + }) + "#, + ); + + let with_tooltip_button = StoryItem::new( + "With `tooltip`", + IconButton::new("with_tooltip_button", Icon::MessageBubbles) + .tooltip(|cx| Tooltip::text("Open messages", cx)), + ) + .description("Displays an icon button that has a tooltip when hovered.") + .usage( + r#" + IconButton::new("with_tooltip_button", Icon::MessageBubbles) + .tooltip(|cx| Tooltip::text("Open messages", cx)) + "#, + ); + + let selected_with_tooltip_button = StoryItem::new( + "Selected with `tooltip`", + IconButton::new("selected_with_tooltip_button", Icon::InlayHint) + .selected(true) + .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), + ) + .description("Displays a selected icon button with tooltip.") + .usage( + r#" + IconButton::new("selected_with_tooltip_button", Icon::InlayHint) + .selected(true) + .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)) + "#, + ); + + let buttons = vec![ + default_button, + selected_button, + selected_with_selected_icon, + disabled_button, + with_on_click_button, + with_tooltip_button, + selected_with_tooltip_button, + ]; + + StoryContainer::new( + "Icon Button", + "crates/ui2/src/components/stories/icon_button.rs", + ) + .children(vec![StorySection::new().children(buttons)]) + .into_element() + + // Story::container() + // .child(Story::title_for::()) + // .child(Story::label("Default")) + // .child(div().w_8().child(IconButton::new("icon_a", Icon::Hash))) + // .child(Story::label("Selected")) + // .child( + // div() + // .w_8() + // .child(IconButton::new("icon_a", Icon::Hash).selected(true)), + // ) + // .child(Story::label("Selected with `selected_icon`")) + // .child( + // div().w_8().child( + // IconButton::new("icon_a", Icon::AudioOn) + // .selected(true) + // .selected_icon(Icon::AudioOff), + // ), + // ) + // .child(Story::label("Disabled")) + // .child( + // div() + // .w_8() + // .child(IconButton::new("icon_a", Icon::Hash).disabled(true)), + // ) + // .child(Story::label("With `on_click`")) + // .child( + // div() + // .w_8() + // .child( + // IconButton::new("with_on_click", Icon::Ai).on_click(|_event, _cx| { + // println!("Clicked!"); + // }), + // ), + // ) + // .child(Story::label("With `tooltip`")) + // .child( + // div().w_8().child( + // IconButton::new("with_tooltip", Icon::MessageBubbles) + // .tooltip(|cx| Tooltip::text("Open messages", cx)), + // ), + // ) + // .child(Story::label("Selected with `tooltip`")) + // .child( + // div().w_8().child( + // IconButton::new("selected_with_tooltip", Icon::InlayHint) + // .selected(true) + // .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), + // ), + // ) } } From e4f9bddbab32140f23840037a3d8989bc665aecc Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 14 Dec 2023 17:56:42 -0500 Subject: [PATCH 6/8] Remove unused imports --- crates/ui2/src/components/stories/icon_button.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ui2/src/components/stories/icon_button.rs b/crates/ui2/src/components/stories/icon_button.rs index 5391f0ac182c1a2a0d02bdead7350f46b45c1f6c..0d219efbc2fe72ac4e7695e2f86bf305d26c5537 100644 --- a/crates/ui2/src/components/stories/icon_button.rs +++ b/crates/ui2/src/components/stories/icon_button.rs @@ -1,5 +1,5 @@ -use gpui::{Component, Div, Render}; -use story::{Story, StoryContainer, StoryItem, StorySection}; +use gpui::{Component, Render}; +use story::{StoryContainer, StoryItem, StorySection}; use crate::{prelude::*, Tooltip}; use crate::{Icon, IconButton}; From 3d9e051b07d20ca5009da8d363dea7307dad1d05 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 15 Dec 2023 10:25:07 -0500 Subject: [PATCH 7/8] Update storybook2.rs --- crates/storybook2/src/bin/storybook2.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/storybook2/src/bin/storybook2.rs b/crates/storybook2/src/bin/storybook2.rs index 43f91db1a471f01d5d48e47161f6163f84dde62b..b77bbcb0666ac9f3ddc4930e72ad2c2fd5c8c6b5 100644 --- a/crates/storybook2/src/bin/storybook2.rs +++ b/crates/storybook2/src/bin/storybook2.rs @@ -11,6 +11,7 @@ use simplelog::SimpleLogger; use theme2::{ThemeRegistry, ThemeSettings}; use ui::prelude::*; +pub use indoc::indoc; use storybook2::assets::Assets; pub use storybook2::story_selector::*; // pub use crate::story_selector::{ComponentStory, StorySelector}; From f459fc5e27c5e905f6be516be134edeca1aa911c Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 15 Dec 2023 10:33:27 -0500 Subject: [PATCH 8/8] Fix import --- crates/storybook2/src/storybook2.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index 13d9a75e2451d607820c9df3c845b285a0f6c40c..523e93cf5267e310d691ee4ca19db1cc67cfbb4b 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -17,10 +17,9 @@ use strum::IntoEnumIterator; use theme2::{ThemeRegistry, ThemeSettings}; use ui::prelude::*; -pub use indoc::indoc; -use storybook2::assets::Assets; use crate::assets::Assets; use crate::story_selector::{ComponentStory, StorySelector}; +pub use indoc::indoc; // gpui::actions! { // storybook,