story: Fix story and merge checkbox, radio to switch story.

This commit is contained in:
Jason Lee 2025-01-21 10:43:10 +08:00
parent 20feab5f40
commit 493acdefab
3 changed files with 291 additions and 266 deletions

View file

@ -2,9 +2,8 @@ use std::cell::Cell;
use std::rc::Rc; use std::rc::Rc;
use gpui::{ use gpui::{
div, px, size, Entity, InteractiveElement, ParentElement, Pixels, Render, ScrollHandle, div, px, size, Entity, InteractiveElement, IntoElement, ParentElement, Pixels, Render,
SharedString, Size, StatefulInteractiveElement as _, Styled, View, ViewContext, VisualContext, ScrollHandle, SharedString, Size, Styled, View, ViewContext, VisualContext, WindowContext,
WindowContext,
}; };
use ui::button::Button; use ui::button::Button;
use ui::divider::Divider; use ui::divider::Divider;
@ -89,36 +88,8 @@ impl ScrollableStory {
self.message = SharedString::from(msg.to_string()); self.message = SharedString::from(msg.to_string());
cx.notify(); cx.notify();
} }
}
impl super::Story for ScrollableStory { fn render_buttons(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn title() -> &'static str {
"Scrollable"
}
fn description() -> &'static str {
"Add vertical or horizontal, or both scrollbars to a container, and use `virtual_list` to render a large number of items."
}
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
Self::view(cx)
}
}
impl gpui::FocusableView for ScrollableStory {
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ScrollableStory {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
let view = cx.view().clone();
v_flex()
.size_full()
.gap_4()
.child(
h_flex() h_flex()
.gap_2() .gap_2()
.justify_between() .justify_between()
@ -149,9 +120,11 @@ impl Render for ScrollableStory {
.child( .child(
Button::new("test-axis-both") Button::new("test-axis-both")
.label("Both Scrollbar") .label("Both Scrollbar")
.on_click(cx.listener(|view, _, cx| { .on_click(
cx.listener(|view, _, cx| {
view.change_axis(ScrollbarAxis::Both, cx) view.change_axis(ScrollbarAxis::Both, cx)
})), }),
),
) )
.child( .child(
Button::new("test-axis-vertical") Button::new("test-axis-vertical")
@ -168,8 +141,39 @@ impl Render for ScrollableStory {
})), })),
), ),
) )
.child(Label::new(self.message.clone())), .child(Label::new(self.message.clone()))
) }
}
impl super::Story for ScrollableStory {
fn title() -> &'static str {
"Scrollable"
}
fn description() -> &'static str {
"Add vertical or horizontal, or both scrollbars to a container, \
and use `virtual_list` to render a large number of items."
}
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
Self::view(cx)
}
}
impl gpui::FocusableView for ScrollableStory {
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ScrollableStory {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
let view = cx.view().clone();
v_flex()
.size_full()
.gap_4()
.child(self.render_buttons(cx))
.child( .child(
div().w_full().child( div().w_full().child(
div().relative().w_full().h(px(350.)).child( div().relative().w_full().h(px(350.)).child(
@ -257,17 +261,16 @@ impl Render for ScrollableStory {
.border_1() .border_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.w_full() .w_full()
.flex_1()
.overflow_hidden() .overflow_hidden()
.max_h(px(400.))
.child( .child(
v_flex() v_flex()
.id("test-1")
.scrollable(cx.view().entity_id(), ScrollbarAxis::Vertical)
.focusable()
.p_3() .p_3()
.w(self.test_width) .w(self.test_width)
.id("test-1")
.scrollable(cx.view().entity_id(), ScrollbarAxis::Vertical)
.gap_1() .gap_1()
.child("Hello world") .child("Scrollable Example")
.children(self.items.iter().take(500).map(|item| { .children(self.items.iter().take(500).map(|item| {
div() div()
.h(ITEM_HEIGHT) .h(ITEM_HEIGHT)

View file

@ -1,18 +1,25 @@
use gpui::{ use gpui::{
Div, IntoElement, ParentElement, Render, SharedString, Styled, View, ViewContext, div, px, Div, IntoElement, ParentElement, Render, SharedString, Styled, View, ViewContext,
VisualContext as _, WindowContext, VisualContext as _, WindowContext,
}; };
use ui::{ use ui::{
h_flex, label::Label, switch::Switch, theme::ActiveTheme, v_flex, Disableable as _, Side, checkbox::Checkbox, h_flex, label::Label, radio::Radio, switch::Switch, theme::ActiveTheme,
Sizable, StyledExt, v_flex, Disableable as _, Side, Sizable, StyledExt,
}; };
use crate::section;
pub struct SwitchStory { pub struct SwitchStory {
focus_handle: gpui::FocusHandle, focus_handle: gpui::FocusHandle,
switch1: bool, switch1: bool,
switch2: bool, switch2: bool,
switch3: bool, switch3: bool,
check1: bool,
check2: bool,
check3: bool,
radio_check1: bool,
radio_check2: bool,
} }
impl super::Story for SwitchStory { impl super::Story for SwitchStory {
@ -21,7 +28,7 @@ impl super::Story for SwitchStory {
} }
fn description() -> &'static str { fn description() -> &'static str {
"A control that allows the user to toggle between two states." "Switch, Radio, Checkbox components testing and examples"
} }
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> { fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
@ -40,6 +47,11 @@ impl SwitchStory {
switch1: true, switch1: true,
switch2: false, switch2: false,
switch3: true, switch3: true,
check1: false,
check2: false,
check3: true,
radio_check1: false,
radio_check2: true,
} }
} }
} }
@ -71,8 +83,7 @@ impl Render for SwitchStory {
.border_color(theme.border) .border_color(theme.border)
} }
v_flex().gap_6() v_flex().gap_6().child(
.child(
v_flex() v_flex()
.items_start() .items_start()
.justify_center() .justify_center()
@ -81,8 +92,11 @@ impl Render for SwitchStory {
card(cx) card(cx)
.child( .child(
title("Marketing emails").child( title("Marketing emails").child(
Label::new("Receive emails about new products, features, and more.").text_color(theme.muted_foreground) Label::new(
"Receive emails about new products, features, and more.",
) )
.text_color(theme.muted_foreground),
),
) )
.child( .child(
Switch::new("switch1") Switch::new("switch1")
@ -99,8 +113,12 @@ impl Render for SwitchStory {
card(cx) card(cx)
.child( .child(
title("Security emails").child( title("Security emails").child(
Label::new("Receive emails about your account security. When turn off, you never receive email again.").text_color(theme.muted_foreground) Label::new(
"Receive emails about your account security. \
When turn off, you never receive email again.",
) )
.text_color(theme.muted_foreground),
),
) )
.child( .child(
Switch::new("switch2") Switch::new("switch2")
@ -112,34 +130,136 @@ impl Render for SwitchStory {
), ),
) )
.child( .child(
card(cx).v_flex() card(cx)
.items_start().child(title("Disabled Switches")).child( .v_flex()
h_flex().items_center() .items_start()
.child(title("Disabled Switches"))
.child(
h_flex()
.items_center()
.gap_6() .gap_6()
.child(Switch::new("switch3").disabled(true).on_click(|v, _| { .child(Switch::new("switch3").disabled(true).on_click(|v, _| {
println!("Switch value changed: {:?}", v); println!("Switch value changed: {:?}", v);
})) }))
.child( .child(
Switch::new("switch3_1").label("Airplane Mode") Switch::new("switch3_1")
.label("Airplane Mode")
.checked(true) .checked(true)
.disabled(true) .disabled(true)
.on_click(|ev, _| { .on_click(|ev, _| {
println!("Switch value changed: {:?}", ev); println!("Switch value changed: {:?}", ev);
}), }),
)) ),
),
) )
.child( .child(
card(cx).v_flex() card(cx)
.items_start().child(title("Small Switches")).child( .v_flex()
h_flex().items_center() .items_start()
.gap_6() .child(title("Small Switches"))
.child(Switch::new("switch3").checked(self.switch3).label("Small Size").small().on_click(cx.listener(move |view, checked, cx| { .child(
h_flex().items_center().gap_6().child(
Switch::new("switch3")
.checked(self.switch3)
.label("Small Size")
.small()
.on_click(cx.listener(move |view, checked, cx| {
view.switch3 = *checked; view.switch3 = *checked;
cx.notify(); cx.notify();
})), })),
) ),
), ),
) )
.child(
section("Checkbox", cx).child(
h_flex()
.w_full()
.items_start()
.gap_6()
.child(Checkbox::new("check1").checked(self.check1).on_click(
cx.listener(|v, _, _| {
v.check1 = !v.check1;
}),
))
.child(
Checkbox::new("check2")
.checked(self.check2)
.label("Subscribe to newsletter")
.on_click(cx.listener(|v, _, _| {
v.check2 = !v.check2;
})),
)
.child(
Checkbox::new("check3")
.checked(self.check3)
.label("Remember me")
.on_click(cx.listener(|v, _, _| {
v.check3 = !v.check3;
})),
)
.child(div().w(px(300.)).child(
Checkbox::new("longlong-checkbox").label(
"The long long label text, \
it should ellipsis when the text is too long.",
),
)),
),
)
.child(
section("Disabled Checkbox", cx).child(
h_flex()
.w_full()
.items_center()
.gap_6()
.child(
Checkbox::new("check3")
.label("Disabled Checked")
.checked(true)
.disabled(true),
)
.child(
Checkbox::new("check3_1")
.label("Disabled Unchecked")
.checked(false)
.disabled(true),
),
),
)
.child(
section("Radio", cx).child(
h_flex()
.w_full()
.gap_4()
.items_start()
.child(Radio::new("radio1").checked(self.radio_check1).on_click(
cx.listener(|this, v, _cx| {
this.radio_check1 = *v;
}),
))
.child(
Radio::new("radio2")
.label("Radio")
.checked(self.radio_check2)
.on_click(cx.listener(|this, v, _cx| {
this.radio_check2 = *v;
})),
)
.child(
Radio::new("radio3")
.label("Disabled Radio")
.checked(true)
.disabled(true),
)
.child(
div().w(px(200.)).child(
Radio::new("radio3")
.label("A long long long text radio label")
.checked(true)
.disabled(true),
),
),
),
),
) )
} }
} }

View file

@ -5,25 +5,18 @@ use gpui::{
use ui::{ use ui::{
button::{Button, ButtonVariant, ButtonVariants as _}, button::{Button, ButtonVariant, ButtonVariants as _},
checkbox::Checkbox,
clipboard::Clipboard, clipboard::Clipboard,
h_flex, h_flex,
label::Label, label::Label,
link::Link, link::Link,
radio::Radio,
tag::Tag, tag::Tag,
v_flex, Disableable as _, IconName, Sizable, StyledExt, v_flex, IconName, Sizable, StyledExt,
}; };
use crate::section; use crate::section;
pub struct TextStory { pub struct TextStory {
focus_handle: gpui::FocusHandle, focus_handle: gpui::FocusHandle,
check1: bool,
check2: bool,
check3: bool,
radio_check1: bool,
radio_check2: bool,
masked: bool, masked: bool,
} }
@ -45,11 +38,6 @@ impl TextStory {
pub(crate) fn new(cx: &mut WindowContext) -> Self { pub(crate) fn new(cx: &mut WindowContext) -> Self {
Self { Self {
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
check1: false,
check2: false,
check3: true,
radio_check1: false,
radio_check2: true,
masked: false, masked: false,
} }
} }
@ -84,147 +72,42 @@ impl Render for TextStory {
.child(Label::new("Text align right").text_right()), .child(Label::new("Text align right").text_right()),
) )
.child(Label::new("Color Label").text_color(ui::red_500())) .child(Label::new("Color Label").text_color(ui::red_500()))
.child( .child(Label::new("Font Size Label").text_size(px(20.)).font_semibold().line_height(rems(1.8)))
Label::new("Font Size Label")
.text_size(px(20.))
.font_semibold()
.line_height(rems(1.8)),
)
.child( .child(
div().w(px(200.)).child( div().w(px(200.)).child(
Label::new("Label should support text wrap in default, if the text is too long, it should wrap to the next line.") Label::new("Label should support text wrap in default, if the text is too long, it should wrap to the next line.")
.line_height(rems(1.8)), .line_height(rems(1.8)),
), ),
),
) )
.child(
) h_flex()
.gap_3()
.child( .child(
section("Link", cx).child( section("Link", cx).child(
h_flex().items_start().gap_3() h_flex()
.items_start()
.gap_3()
.child(Link::new("link1").href("https://github.com").child("GitHub")) .child(Link::new("link1").href("https://github.com").child("GitHub"))
.child(Link::new("link2").href("https://github.com").text_color(ui::red_500()).text_decoration_color(ui::red_500()).child("Red Link")) .child(
.child(Link::new("link3").child(h_flex().gap_1().child(IconName::GitHub).child("GitHub")).on_click(cx.listener(|_, _, cx| { Link::new("link2")
cx.open_url("https://google.com") .href("https://github.com")
}))) .text_color(ui::red_500())
.child(div().w(px(250.)).child(Link::new("link4").child("https://github.com/longbridge/gpui-component").href("https://github.com/longbridge/gpui-component"))) .text_decoration_color(ui::red_500())
) .child("Red Link"),
) )
.child( .child(
section("Maksed Label", cx).child( Link::new("link3")
v_flex() .child(h_flex().gap_1().child(IconName::GitHub).child("GitHub"))
.w_full() .on_click(cx.listener(|_, _, cx| cx.open_url("https://google.com"))),
.gap_4() )
.child( .child(
h_flex() div().w(px(250.)).child(
.child(Label::new("9,182,1 USD").text_2xl().masked(self.masked)) Link::new("link4")
.child( .child("https://github.com/longbridge/gpui-component")
Button::new("btn-mask") .href("https://github.com/longbridge/gpui-component"),
.with_variant(ButtonVariant::Ghost)
.icon(if self.masked {
IconName::EyeOff
} else {
IconName::Eye
})
.on_click(cx.listener(|this, _, _| {
this.masked = !this.masked;
})),
), ),
)
.child(Label::new("500 USD").text_xl().masked(self.masked)),
), ),
)
.child(
section("Checkbox", cx).child(
h_flex()
.w_full()
.items_start()
.gap_6()
.child(
Checkbox::new("check1")
.checked(self.check1)
.on_click(cx.listener(|v, _, _| {
v.check1 = !v.check1;
})),
)
.child(
Checkbox::new("check2")
.checked(self.check2)
.label("Subscribe to newsletter")
.on_click(cx.listener(|v, _, _| {
v.check2 = !v.check2;
})),
)
.child(
Checkbox::new("check3")
.checked(self.check3)
.label("Remember me")
.on_click(cx.listener(|v, _, _| {
v.check3 = !v.check3;
})),
)
.child(
div().w(px(300.)).child(
Checkbox::new("longlong-checkbox")
.label("The long long label text, it should ellipsis when the text is too long.")
),
)
),
)
.child(
section("Disabled Checkbox", cx).child(
h_flex()
.w_full()
.items_center()
.gap_6()
.child(
Checkbox::new("check3")
.label("Disabled Checked")
.checked(true)
.disabled(true),
)
.child(
Checkbox::new("check3_1")
.label("Disabled Unchecked")
.checked(false)
.disabled(true),
)
),
)
.child(
section("Radio", cx).child(
h_flex()
.w_full()
.gap_4()
.items_start()
.child(
Radio::new("radio1")
.checked(self.radio_check1)
.on_click(cx.listener(|this, v, _cx| {
this.radio_check1 = *v;
})),
)
.child(
Radio::new("radio2")
.label("Radio")
.checked(self.radio_check2)
.on_click(cx.listener(|this, v, _cx| {
this.radio_check2 = *v;
})),
)
.child(
Radio::new("radio3")
.label("Disabled Radio")
.checked(true)
.disabled(true),
)
.child(
div().w(px(200.)).child(
Radio::new("radio3")
.label("A long long long text radio label")
.checked(true)
.disabled(true),
),
)
), ),
) )
.child( .child(
@ -237,9 +120,7 @@ impl Render for TextStory {
.content(|_| Label::new("Click icon to copy")) .content(|_| Label::new("Click icon to copy"))
.value_fn({ .value_fn({
let view = cx.view().clone(); let view = cx.view().clone();
move |cx| { move |cx| SharedString::from(format!("masked :{}", view.read(cx).masked))
SharedString::from(format!("Check1 :{}", view.read(cx).check1))
}
}) })
.on_copied(|value, _| println!("Copied value: {}", value)), .on_copied(|value, _| println!("Copied value: {}", value)),
) )
@ -248,27 +129,48 @@ impl Render for TextStory {
.content(|_| Link::new("link1").href("https://github.com").child("GitHub")) .content(|_| Link::new("link1").href("https://github.com").child("GitHub"))
.value("https://github.com") .value("https://github.com")
.on_copied(|value, _| println!("Copied value: {}", value)), .on_copied(|value, _| println!("Copied value: {}", value)),
),
),
),
) )
.child(
section("Maksed Label", cx).child(
v_flex()
.w_full()
.gap_4()
.child(
h_flex().child(Label::new("9,182,1 USD").text_2xl().masked(self.masked)).child(
Button::new("btn-mask")
.with_variant(ButtonVariant::Ghost)
.icon(if self.masked { IconName::EyeOff } else { IconName::Eye })
.on_click(cx.listener(|this, _, _| {
this.masked = !this.masked;
})),
),
)
.child(Label::new("500 USD").text_xl().masked(self.masked)),
), ),
) )
.child( .child(
section("Tag", cx) section("Tag", cx)
.child( .child(
h_flex().gap_2() h_flex()
.gap_2()
.child(Tag::primary().small().child("Tag")) .child(Tag::primary().small().child("Tag"))
.child(Tag::secondary().small().child("Secondary")) .child(Tag::secondary().small().child("Secondary"))
.child(Tag::outline().small().child("Outline")) .child(Tag::outline().small().child("Outline"))
.child(Tag::danger().small().child("danger")) .child(Tag::danger().small().child("danger"))
.child(Tag::custom(ui::yellow_500(), ui::yellow_800(), ui::yellow_500()).small().child("Custom")) .child(Tag::custom(ui::yellow_500(), ui::yellow_800(), ui::yellow_500()).small().child("Custom")),
) )
.child( .child(
h_flex().gap_2() h_flex()
.gap_2()
.child(Tag::primary().child("Tag")) .child(Tag::primary().child("Tag"))
.child(Tag::secondary().child("Secondary")) .child(Tag::secondary().child("Secondary"))
.child(Tag::outline().child("Outline")) .child(Tag::outline().child("Outline"))
.child(Tag::danger().child("danger")) .child(Tag::danger().child("danger"))
.child(Tag::custom(ui::yellow_500(), ui::yellow_800(), ui::yellow_500()).child("Custom")) .child(Tag::custom(ui::yellow_500(), ui::yellow_800(), ui::yellow_500()).child("Custom")),
) ),
) )
} }
} }