From 6886ae746e4d759c15f5ad414f22f797a9fc8e2a Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 3 Jul 2025 18:54:23 +0800 Subject: [PATCH] story: Add `Indicator`, `Skeleton` story. (#1038) --- crates/story/src/indicator_story.rs | 86 +++++++++++++++++++ crates/story/src/lib.rs | 4 + crates/story/src/main.rs | 2 + crates/story/src/progress_story.rs | 128 ++++++++++------------------ crates/story/src/skeleton_story.rs | 81 ++++++++++++++++++ 5 files changed, 220 insertions(+), 81 deletions(-) create mode 100644 crates/story/src/indicator_story.rs create mode 100644 crates/story/src/skeleton_story.rs diff --git a/crates/story/src/indicator_story.rs b/crates/story/src/indicator_story.rs new file mode 100644 index 00000000..1eb5692f --- /dev/null +++ b/crates/story/src/indicator_story.rs @@ -0,0 +1,86 @@ +use gpui::{ + px, App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled, + Window, +}; +use gpui_component::{ + blue_500, green_500, indicator::Indicator, sky_500, v_flex, IconName, Sizable, +}; + +use crate::section; + +pub struct IndicatorStory { + focus_handle: gpui::FocusHandle, + value: f32, +} + +impl super::Story for IndicatorStory { + fn title() -> &'static str { + "Indicator" + } + + fn description() -> &'static str { + "Displays an indicator showing the completion progress of a task." + } + + fn new_view(window: &mut Window, cx: &mut App) -> Entity { + Self::view(window, cx) + } +} + +impl IndicatorStory { + pub fn view(window: &mut Window, cx: &mut App) -> Entity { + cx.new(|cx| Self::new(window, cx)) + } + + fn new(_: &mut Window, cx: &mut Context) -> Self { + Self { + focus_handle: cx.focus_handle(), + value: 50., + } + } + + pub fn set_value(&mut self, value: f32) { + self.value = value; + } +} + +impl Focusable for IndicatorStory { + fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle { + self.focus_handle.clone() + } +} + +impl Render for IndicatorStory { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + v_flex() + .items_center() + .gap_y_3() + .child(section("Indicator").gap_x_2().child(Indicator::new())) + .child( + section("Indicator with color") + .gap_x_2() + .child(Indicator::new().color(blue_500())) + .child(Indicator::new().color(green_500())), + ) + .child( + section("Indicator with size") + .gap_x_2() + .child(Indicator::new().with_size(px(64.))) + .child(Indicator::new().large()) + .child(Indicator::new()) + .child(Indicator::new().small()) + .child(Indicator::new().xsmall()), + ) + .child( + section("Indicator with Icon") + .gap_x_2() + .child(Indicator::new().icon(IconName::LoaderCircle)) + .child( + Indicator::new() + .icon(IconName::LoaderCircle) + .large() + .color(sky_500()), + ), + ) + } +} diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index 2c8c217f..3e6abe9e 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -16,6 +16,7 @@ mod dropdown_story; mod form_story; mod icon_story; mod image_story; +mod indicator_story; mod input_story; mod kbd_story; mod label_story; @@ -31,6 +32,7 @@ mod radio_story; mod resizable_story; mod scrollable_story; mod sidebar_story; +mod skeleton_story; mod slider_story; mod switch_story; mod table_story; @@ -69,6 +71,7 @@ pub use dropdown_story::DropdownStory; pub use form_story::FormStory; pub use icon_story::IconStory; pub use image_story::ImageStory; +pub use indicator_story::IndicatorStory; pub use input_story::InputStory; pub use kbd_story::KbdStory; pub use label_story::LabelStory; @@ -85,6 +88,7 @@ pub use resizable_story::ResizableStory; pub use scrollable_story::ScrollableStory; use serde::{Deserialize, Serialize}; pub use sidebar_story::SidebarStory; +pub use skeleton_story::SkeletonStory; pub use slider_story::SliderStory; pub use switch_story::SwitchStory; pub use table_story::TableStory; diff --git a/crates/story/src/main.rs b/crates/story/src/main.rs index 6a67da0c..222027b9 100644 --- a/crates/story/src/main.rs +++ b/crates/story/src/main.rs @@ -53,6 +53,7 @@ impl Gallery { StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), + StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), @@ -69,6 +70,7 @@ impl Gallery { StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), + StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), diff --git a/crates/story/src/progress_story.rs b/crates/story/src/progress_story.rs index 96c2ec2f..6eb1e7fb 100644 --- a/crates/story/src/progress_story.rs +++ b/crates/story/src/progress_story.rs @@ -1,11 +1,7 @@ use gpui::{ - px, App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled, - Window, -}; -use gpui_component::{ - blue_500, button::Button, h_flex, indicator::Indicator, progress::Progress, skeleton::Skeleton, - v_flex, IconName, Sizable, + App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled, Window, }; +use gpui_component::{button::Button, h_flex, progress::Progress, v_flex, IconName, Sizable}; use crate::section; @@ -53,83 +49,53 @@ impl Focusable for ProgressStory { impl Render for ProgressStory { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - v_flex() - .items_center() - .gap_y_3() - .child( - section("Progress Bar").max_w_md().child( - v_flex() - .w_full() - .gap_3() - .justify_center() - .items_center() - .child( - h_flex() - .gap_2() - .child(Button::new("button-1").small().label("0%").on_click( - cx.listener(|this, _, _, _| { - this.set_value(0.); - }), - )) - .child(Button::new("button-2").small().label("25%").on_click( - cx.listener(|this, _, _, _| { - this.set_value(25.); - }), - )) - .child(Button::new("button-3").small().label("75%").on_click( - cx.listener(|this, _, _, _| { - this.set_value(75.); - }), - )) - .child(Button::new("button-4").small().label("100%").on_click( - cx.listener(|this, _, _, _| { - this.set_value(100.); - }), - )), - ) - .child(Progress::new().value(self.value)) - .child( - h_flex() - .gap_x_2() - .child(Button::new("button-5").icon(IconName::Minus).on_click( - cx.listener(|this, _, _, _| { - this.set_value((this.value - 1.).max(0.)); - }), - )) - .child(Button::new("button-6").icon(IconName::Plus).on_click( - cx.listener(|this, _, _, _| { - this.set_value((this.value + 1.).min(100.)); - }), - )), - ), - ), - ) - .child( - section("Indicator") - .gap_x_2() - .child(Indicator::new().xsmall()) - .child(Indicator::new().small()) - .child(Indicator::new()) - .child(Indicator::new().with_size(px(64.))), - ) - .child( - section("Indicator with Icon").gap_x_2().child( - Indicator::new() - .with_size(px(64.)) - .icon(IconName::LoaderCircle) - .color(blue_500()), - ), - ) - .child( - section("Skeleton") - .max_w_md() - .child(Skeleton::new().size_12().rounded_full()) + v_flex().items_center().gap_y_3().child( + section("Progress Bar").max_w_md().child( + v_flex() + .w_full() + .gap_3() + .justify_center() + .items_center() .child( - v_flex() + h_flex() .gap_2() - .child(Skeleton::new().w(px(250.)).h_4()) - .child(Skeleton::new().w(px(200.)).h_4()), + .child(Button::new("button-1").small().label("0%").on_click( + cx.listener(|this, _, _, _| { + this.set_value(0.); + }), + )) + .child(Button::new("button-2").small().label("25%").on_click( + cx.listener(|this, _, _, _| { + this.set_value(25.); + }), + )) + .child(Button::new("button-3").small().label("75%").on_click( + cx.listener(|this, _, _, _| { + this.set_value(75.); + }), + )) + .child(Button::new("button-4").small().label("100%").on_click( + cx.listener(|this, _, _, _| { + this.set_value(100.); + }), + )), + ) + .child(Progress::new().value(self.value)) + .child( + h_flex() + .gap_x_2() + .child(Button::new("button-5").icon(IconName::Minus).on_click( + cx.listener(|this, _, _, _| { + this.set_value((this.value - 1.).max(0.)); + }), + )) + .child(Button::new("button-6").icon(IconName::Plus).on_click( + cx.listener(|this, _, _, _| { + this.set_value((this.value + 1.).min(100.)); + }), + )), ), - ) + ), + ) } } diff --git a/crates/story/src/skeleton_story.rs b/crates/story/src/skeleton_story.rs new file mode 100644 index 00000000..af81984e --- /dev/null +++ b/crates/story/src/skeleton_story.rs @@ -0,0 +1,81 @@ +use gpui::{ + px, App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled, + Window, +}; +use gpui_component::{skeleton::Skeleton, v_flex}; + +use crate::section; + +pub struct SkeletonStory { + focus_handle: gpui::FocusHandle, + value: f32, +} + +impl super::Story for SkeletonStory { + fn title() -> &'static str { + "Skeleton" + } + + fn description() -> &'static str { + "Use to show a placeholder while content is loading." + } + + fn new_view(window: &mut Window, cx: &mut App) -> Entity { + Self::view(window, cx) + } +} + +impl SkeletonStory { + pub fn view(window: &mut Window, cx: &mut App) -> Entity { + cx.new(|cx| Self::new(window, cx)) + } + + fn new(_: &mut Window, cx: &mut Context) -> Self { + Self { + focus_handle: cx.focus_handle(), + value: 50., + } + } + + pub fn set_value(&mut self, value: f32) { + self.value = value; + } +} + +impl Focusable for SkeletonStory { + fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle { + self.focus_handle.clone() + } +} + +impl Render for SkeletonStory { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + v_flex() + .items_center() + .gap_y_3() + .child( + section("Skeleton") + .max_w_md() + .child(Skeleton::new().size_12().rounded_full()) + .child( + v_flex() + .gap_2() + .child(Skeleton::new().w(px(250.)).h_4().rounded_md()) + .child(Skeleton::new().w(px(200.)).h_4().rounded_md()), + ), + ) + .child( + section("Card").max_w_md().child( + v_flex() + .gap_2() + .child(Skeleton::new().w(px(250.)).h(px(125.)).rounded_md()) + .child( + v_flex() + .gap_2() + .child(Skeleton::new().w(px(250.)).h_4().rounded_md()) + .child(Skeleton::new().w(px(200.)).h_4().rounded_md()), + ), + ), + ) + } +}