mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-24 20:41:53 +03:00
Add checkboxes and their stories
This commit is contained in:
parent
b243de407e
commit
1bce5dcc69
1
assets/icons/dash.svg
Normal file
1
assets/icons/dash.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-minus"><path d="M5 12h14"/></svg>
|
After Width: | Height: | Size: 229 B |
@ -19,6 +19,7 @@ pub enum ComponentStory {
|
||||
Buffer,
|
||||
Button,
|
||||
ChatPanel,
|
||||
Checkbox,
|
||||
CollabPanel,
|
||||
Colors,
|
||||
CommandPalette,
|
||||
@ -61,6 +62,7 @@ impl ComponentStory {
|
||||
Self::Buffer => cx.build_view(|_| ui::BufferStory).into(),
|
||||
Self::Button => cx.build_view(|_| ButtonStory).into(),
|
||||
Self::ChatPanel => cx.build_view(|_| ui::ChatPanelStory).into(),
|
||||
Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
|
||||
Self::CollabPanel => cx.build_view(|_| ui::CollabPanelStory).into(),
|
||||
Self::Colors => cx.build_view(|_| ColorsStory).into(),
|
||||
Self::CommandPalette => cx.build_view(|_| ui::CommandPaletteStory).into(),
|
||||
|
@ -54,7 +54,9 @@ pub struct ThemeColors {
|
||||
pub border: Hsla,
|
||||
pub border_variant: Hsla,
|
||||
pub border_focused: Hsla,
|
||||
pub border_selected: Hsla,
|
||||
pub border_transparent: Hsla,
|
||||
pub border_disabled: Hsla,
|
||||
pub elevated_surface: Hsla,
|
||||
pub surface: Hsla,
|
||||
pub background: Hsla,
|
||||
|
@ -205,6 +205,8 @@ impl ThemeColors {
|
||||
border: neutral().light().step_6(),
|
||||
border_variant: neutral().light().step_5(),
|
||||
border_focused: blue().light().step_5(),
|
||||
border_disabled: neutral().light().step_3(),
|
||||
border_selected: blue().light().step_5(),
|
||||
border_transparent: system.transparent,
|
||||
elevated_surface: neutral().light().step_2(),
|
||||
surface: neutral().light().step_2(),
|
||||
@ -250,6 +252,8 @@ impl ThemeColors {
|
||||
border: neutral().dark().step_6(),
|
||||
border_variant: neutral().dark().step_5(),
|
||||
border_focused: blue().dark().step_5(),
|
||||
border_disabled: neutral().dark().step_3(),
|
||||
border_selected: blue().dark().step_5(),
|
||||
border_transparent: system.transparent,
|
||||
elevated_surface: neutral().dark().step_2(),
|
||||
surface: neutral().dark().step_2(),
|
||||
|
@ -2,6 +2,16 @@
|
||||
|
||||
## Common patterns
|
||||
|
||||
### Method ordering
|
||||
|
||||
- id
|
||||
- Flex properties
|
||||
- Position properties
|
||||
- Size properties
|
||||
- Style properties
|
||||
- Handlers
|
||||
- State properties
|
||||
|
||||
### Using the Label Component to Create UI Text
|
||||
|
||||
The `Label` component helps in displaying text on user interfaces. It creates an interface where specific parameters such as label color, line height style, and strikethrough can be set.
|
||||
|
@ -1,5 +1,6 @@
|
||||
mod avatar;
|
||||
mod button;
|
||||
mod checkbox;
|
||||
mod context_menu;
|
||||
mod details;
|
||||
mod facepile;
|
||||
@ -25,6 +26,7 @@ mod tool_divider;
|
||||
|
||||
pub use avatar::*;
|
||||
pub use button::*;
|
||||
pub use checkbox::*;
|
||||
pub use context_menu::*;
|
||||
pub use details::*;
|
||||
pub use facepile::*;
|
||||
|
217
crates/ui2/src/components/checkbox.rs
Normal file
217
crates/ui2/src/components/checkbox.rs
Normal file
@ -0,0 +1,217 @@
|
||||
///! # Checkbox
|
||||
///!
|
||||
///! Checkboxes are used for multiple choices, not for mutually exclusive choices.
|
||||
///! Each checkbox works independently from other checkboxes in the list,
|
||||
///! therefore checking an additional box does not affect any other selections.
|
||||
use gpui2::{
|
||||
div, Component, ParentElement, SharedString, StatelessInteractive, Styled, ViewContext,
|
||||
};
|
||||
use theme2::ActiveTheme;
|
||||
|
||||
use crate::{Icon, IconColor, IconElement, Selected};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Checkbox {
|
||||
id: SharedString,
|
||||
checked: Selected,
|
||||
disabled: bool,
|
||||
}
|
||||
|
||||
impl Checkbox {
|
||||
pub fn new(id: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
checked: Selected::Unselected,
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(mut self) -> Self {
|
||||
self.checked = match self.checked {
|
||||
Selected::Selected => Selected::Unselected,
|
||||
Selected::Unselected => Selected::Selected,
|
||||
Selected::Indeterminate => Selected::Selected,
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_indeterminate(mut self) -> Self {
|
||||
self.checked = Selected::Indeterminate;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_disabled(mut self, disabled: bool) -> Self {
|
||||
self.disabled = disabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
let group_id = format!("checkbox_group_{}", self.id);
|
||||
|
||||
// The icon is different depending on the state of the checkbox.
|
||||
//
|
||||
// We need the match to return all the same type,
|
||||
// so we wrap the eatch result in a div.
|
||||
//
|
||||
// We are still exploring the best way to handle this.
|
||||
let icon = match self.checked {
|
||||
// When selected, we show a checkmark.
|
||||
Selected::Selected => {
|
||||
div().child(
|
||||
IconElement::new(Icon::Check)
|
||||
.size(crate::IconSize::Small)
|
||||
.color(
|
||||
// If the checkbox is disabled we change the color of the icon.
|
||||
if self.disabled {
|
||||
IconColor::Disabled
|
||||
} else {
|
||||
IconColor::Selected
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
// In an indeterminate state, we show a dash.
|
||||
Selected::Indeterminate => {
|
||||
div().child(
|
||||
IconElement::new(Icon::Dash)
|
||||
.size(crate::IconSize::Small)
|
||||
.color(
|
||||
// If the checkbox is disabled we change the color of the icon.
|
||||
if self.disabled {
|
||||
IconColor::Disabled
|
||||
} else {
|
||||
IconColor::Selected
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
// When unselected, we show nothing.
|
||||
Selected::Unselected => div(),
|
||||
};
|
||||
|
||||
// A checkbox could be in an indeterminate state,
|
||||
// for example the indeterminate state could represent:
|
||||
// - a group of options of which only some are selected
|
||||
// - an enabled option that is no longer available
|
||||
// - a previously agreed to license that has been updated
|
||||
//
|
||||
// For the sake of styles we treat the indeterminate state as selected,
|
||||
// but it's icon will be different.
|
||||
let selected =
|
||||
self.checked == Selected::Selected || self.checked == Selected::Indeterminate;
|
||||
|
||||
// We could use something like this to make the checkbox background when selected:
|
||||
//
|
||||
// ~~~rust
|
||||
// ...
|
||||
// .when(selected, |this| {
|
||||
// this.bg(cx.theme().colors().element_selected)
|
||||
// })
|
||||
// ~~~
|
||||
//
|
||||
// But we use a match instead here because the checkbox might be disabled,
|
||||
// and it could be disabled _while_ it is selected, as well as while it is not selected.
|
||||
let (bg_color, border_color) = match (self.disabled, selected) {
|
||||
(true, _) => (
|
||||
cx.theme().colors().ghost_element_disabled,
|
||||
cx.theme().colors().border_disabled,
|
||||
),
|
||||
(false, true) => (
|
||||
cx.theme().colors().element_selected,
|
||||
cx.theme().colors().border,
|
||||
),
|
||||
(false, false) => (cx.theme().colors().element, cx.theme().colors().border),
|
||||
};
|
||||
|
||||
div()
|
||||
// Rather than adding `px_1()` to add some space around the checkbox,
|
||||
// we use a larger parent element to create a slightly larger
|
||||
// click area for the checkbox.
|
||||
.size_5()
|
||||
// Because we've enlarged the click area, we need to create a
|
||||
// `group` to pass down interaction events to the checkbox.
|
||||
.group(group_id.clone())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
// This prevent the flex element from growing
|
||||
// or shrinking in response to any size changes
|
||||
.flex_none()
|
||||
// The combo of `justify_center()` and `items_center()`
|
||||
// is used frequently to center elements in a flex container.
|
||||
//
|
||||
// We use this to center the icon in the checkbox.
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.m_1()
|
||||
.size_4()
|
||||
.rounded_sm()
|
||||
.bg(bg_color)
|
||||
.border()
|
||||
.border_color(border_color)
|
||||
// We only want the interaction states to fire when we
|
||||
// are in a checkbox that isn't disabled.
|
||||
.when(!self.disabled, |this| {
|
||||
// Here instead of `hover()` we use `group_hover()`
|
||||
// to pass it the group id.
|
||||
this.group_hover(group_id.clone(), |el| {
|
||||
el.bg(cx.theme().colors().element_hover)
|
||||
})
|
||||
})
|
||||
.child(icon),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories {
|
||||
use super::*;
|
||||
use crate::{h_stack, Story};
|
||||
use gpui2::{Div, Render};
|
||||
|
||||
pub struct CheckboxStory;
|
||||
|
||||
impl Render for CheckboxStory {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Checkbox>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(
|
||||
h_stack()
|
||||
.p_2()
|
||||
.gap_2()
|
||||
.rounded_md()
|
||||
.border()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(Checkbox::new("checkbox-enabled"))
|
||||
.child(Checkbox::new("checkbox-intermediate").set_indeterminate())
|
||||
.child(Checkbox::new("checkbox-selected").toggle()),
|
||||
)
|
||||
.child(Story::label(cx, "Disabled"))
|
||||
.child(
|
||||
h_stack()
|
||||
.p_2()
|
||||
.gap_2()
|
||||
.rounded_md()
|
||||
.border()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(Checkbox::new("checkbox-disabled").set_disabled(true))
|
||||
.child(
|
||||
Checkbox::new("checkbox-disabled-intermediate")
|
||||
.set_disabled(true)
|
||||
.set_indeterminate(),
|
||||
)
|
||||
.child(
|
||||
Checkbox::new("checkbox-disabled-selected")
|
||||
.set_disabled(true)
|
||||
.toggle(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ pub enum IconColor {
|
||||
Warning,
|
||||
Success,
|
||||
Info,
|
||||
Selected,
|
||||
}
|
||||
|
||||
impl IconColor {
|
||||
@ -36,6 +37,7 @@ impl IconColor {
|
||||
IconColor::Warning => cx.theme().status().warning,
|
||||
IconColor::Success => cx.theme().status().success,
|
||||
IconColor::Info => cx.theme().status().info,
|
||||
IconColor::Selected => cx.theme().colors().icon_accent,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,6 +57,7 @@ pub enum Icon {
|
||||
ChevronRight,
|
||||
ChevronUp,
|
||||
Close,
|
||||
Dash,
|
||||
Exit,
|
||||
ExclamationTriangle,
|
||||
File,
|
||||
@ -112,6 +115,7 @@ impl Icon {
|
||||
Icon::ChevronRight => "icons/chevron_right.svg",
|
||||
Icon::ChevronUp => "icons/chevron_up.svg",
|
||||
Icon::Close => "icons/x.svg",
|
||||
Icon::Dash => "icons/dash.svg",
|
||||
Icon::Exit => "icons/exit.svg",
|
||||
Icon::ExclamationTriangle => "icons/warning.svg",
|
||||
Icon::File => "icons/file.svg",
|
||||
|
@ -154,10 +154,10 @@ impl InteractionState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq)]
|
||||
pub enum SelectedState {
|
||||
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum Selected {
|
||||
#[default]
|
||||
Unselected,
|
||||
PartiallySelected,
|
||||
Indeterminate,
|
||||
Selected,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user