From 976fc3ee977cdd6c395c6ea2211291b9fca3f2f7 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 10 Mar 2025 16:19:02 -0400 Subject: [PATCH] git_ui: Design Polish (#26361) Polish PR - [ ] Horizontal scrollbar for git panel - [ ] Allow shift clicking a checkbox in any section to stage the whole section - [ ] Clean up design of no changes/pending push state in panel - [x] Ensure checkbox placeholder dot is centered in the checkbox - [x] Improve spacing between elements in git panel entries - [x] Update git branch icon to match branch selector text when disabled - [x] Truncate last commit message less aggressively in panel - [x] Clean up new panel header design - [x] Remove `_background` version control keys (backgrounds are derived from the foreground colors) ### Previous message truncation: Before: ![CleanShot 2025-03-10 at 11 54 32@2x](https://github.com/user-attachments/assets/46b18f66-bb5c-435e-a0da-6cc931bd8a15) After: ![CleanShot 2025-03-10 at 11 55 24@2x](https://github.com/user-attachments/assets/fcf688c7-b949-41a2-a7b8-1a198eb7fa4a) ### Make branch icon match when menu is disabled Before: ![CleanShot 2025-03-10 at 12 02 14@2x](https://github.com/user-attachments/assets/1990f4b3-c2f0-4e02-89ad-211aaebb3821) After: ![CleanShot 2025-03-10 at 12 02 53@2x](https://github.com/user-attachments/assets/9b1caf65-c48f-44c9-924b-484892fb543f) Release Notes: - N/A *or* Added/Fixed/Improved ... --------- Co-authored-by: Cole Miller Co-authored-by: Cole Miller Co-authored-by: Max Brunsfeld --- crates/git_ui/src/git_panel.rs | 96 +++++++++++++------- crates/git_ui/src/git_ui.rs | 106 ++++++++++++++++------ crates/panel/src/panel.rs | 2 - crates/theme/src/default_colors.rs | 8 -- crates/theme/src/fallback_themes.rs | 4 - crates/theme/src/schema.rs | 32 ------- crates/theme/src/styles/colors.rs | 22 ----- crates/ui/src/components/button/button.rs | 5 +- crates/ui/src/components/toggle.rs | 61 ++++++++----- 9 files changed, 180 insertions(+), 156 deletions(-) diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 7bc6740099..8e279063ba 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -41,7 +41,8 @@ use language_model::{ use menu::{Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrevious}; use multi_buffer::ExcerptInfo; use panel::{ - panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button, PanelHeader, + panel_button, panel_editor_container, panel_editor_style, panel_filled_button, + panel_icon_button, PanelHeader, }; use project::{ git::{GitEvent, Repository}, @@ -103,13 +104,13 @@ enum TrashCancel { } fn git_panel_context_menu( - focus_handle: Option, + focus_handle: FocusHandle, window: &mut Window, cx: &mut App, ) -> Entity { ContextMenu::build(window, cx, |context_menu, _, _| { context_menu - .when_some(focus_handle, |el, focus_handle| el.context(focus_handle)) + .context(focus_handle) .action("Stage All", StageAll.boxed_clone()) .action("Unstage All", UnstageAll.boxed_clone()) .separator() @@ -2309,6 +2310,18 @@ impl GitPanel { self.has_staged_changes() } + fn render_overflow_menu(&self, id: impl Into) -> impl IntoElement { + let focus_handle = self.focus_handle.clone(); + PopoverMenu::new(id.into()) + .trigger( + IconButton::new("overflow-menu-trigger", IconName::EllipsisVertical) + .icon_size(IconSize::Small) + .icon_color(Color::Muted), + ) + .menu(move |window, cx| Some(git_panel_context_menu(focus_handle.clone(), window, cx))) + .anchor(Corner::TopRight) + } + pub(crate) fn render_generate_commit_message_button( &self, cx: &Context, @@ -2458,9 +2471,17 @@ impl GitPanel { tooltip = "git add --all ." } + let change_string = match self.entry_count { + 0 => "No Changes".to_string(), + 1 => "1 Change".to_string(), + _ => format!("{} Changes", self.entry_count), + }; + self.panel_header_container(window, cx) + .px_2() .child( - Button::new("diff", "Open Diff") + panel_button(change_string) + .color(Color::Muted) .tooltip(Tooltip::for_action_title_in( "Open diff", &Diff, @@ -2473,8 +2494,9 @@ impl GitPanel { }), ) .child(div().flex_grow()) // spacer + .child(self.render_overflow_menu("overflow_menu")) .child( - Button::new("stage-unstage-all", text) + panel_filled_button(text) .tooltip(Tooltip::for_action_title_in( tooltip, action.as_ref(), @@ -2631,15 +2653,14 @@ impl GitPanel { .items_center() .py_2() .px(px(8.)) - // .bg(cx.theme().colors().background) - // .border_t_1() .border_color(cx.theme().colors().border) .gap_1p5() .child( div() .flex_grow() .overflow_hidden() - .max_w(relative(0.6)) + .items_center() + .max_w(relative(0.85)) .h_full() .child( Label::new(commit.subject.clone()) @@ -2946,7 +2967,7 @@ impl GitPanel { window: &mut Window, cx: &mut Context, ) { - let context_menu = git_panel_context_menu(Some(self.focus_handle.clone()), window, cx); + let context_menu = git_panel_context_menu(self.focus_handle.clone(), window, cx); self.set_context_menu(context_menu, position, window, cx); } @@ -2993,6 +3014,9 @@ impl GitPanel { let marked = self.marked_entries.contains(&ix); let status_style = GitPanelSettings::get_global(cx).status_style; let status = entry.status; + let modifiers = self.current_modifiers; + let shift_held = modifiers.shift; + let has_conflict = status.is_conflicted(); let is_modified = status.is_modified(); let is_deleted = status.is_deleted(); @@ -3078,7 +3102,7 @@ impl GitPanel { .px(rems(0.75)) // ~12px .overflow_hidden() .flex_none() - .gap(DynamicSpacing::Base04.rems(cx)) + .gap_1p5() .bg(base_bg) .hover(|this| this.bg(hover_bg)) .active(|this| this.bg(active_bg)) @@ -3123,6 +3147,7 @@ impl GitPanel { .flex_none() .occlude() .cursor_pointer() + .ml_neg_0p5() .child( Checkbox::new(checkbox_id, is_staged) .disabled(!has_write_access) @@ -3144,17 +3169,35 @@ impl GitPanel { }) }) .tooltip(move |window, cx| { - let tooltip_name = if entry_staging.is_fully_staged() { - "Unstage" + let is_staged = entry_staging.is_fully_staged(); + + let action = if is_staged { "Unstage" } else { "Stage" }; + let tooltip_name = if shift_held { + format!("{} section", action) } else { - "Stage" + action.to_string() }; - Tooltip::for_action(tooltip_name, &ToggleStaged, window, cx) + let meta = if shift_held { + format!( + "Release shift to {} single entry", + action.to_lowercase() + ) + } else { + format!("Shift click to {} section", action.to_lowercase()) + }; + + Tooltip::with_meta( + tooltip_name, + Some(&ToggleStaged), + meta, + window, + cx, + ) }), ), ) - .child(git_status_icon(status, cx)) + .child(git_status_icon(status)) .child( h_flex() .items_center() @@ -3456,27 +3499,11 @@ impl PanelRepoFooter { git_panel: None, } } - - fn render_overflow_menu(&self, id: impl Into, cx: &App) -> impl IntoElement { - let focus_handle = self - .git_panel - .as_ref() - .map(|git_panel| git_panel.focus_handle(cx)); - PopoverMenu::new(id.into()) - .trigger( - IconButton::new("overflow-menu-trigger", IconName::EllipsisVertical) - .icon_size(IconSize::Small) - .icon_color(Color::Muted), - ) - .menu(move |window, cx| Some(git_panel_context_menu(focus_handle.clone(), window, cx))) - .anchor(Corner::TopRight) - } } impl RenderOnce for PanelRepoFooter { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let active_repo = self.active_repository.clone(); - let overflow_menu_id: SharedString = format!("overflow-menu-{}", active_repo).into(); let repo_selector_trigger = Button::new("repo-selector", active_repo) .style(ButtonStyle::Transparent) .size(ButtonSize::None) @@ -3565,7 +3592,11 @@ impl RenderOnce for PanelRepoFooter { div().child( Icon::new(IconName::GitBranchSmall) .size(IconSize::Small) - .color(Color::Muted), + .color(if single_repo { + Color::Disabled + } else { + Color::Muted + }), ), ) .child(repo_selector) @@ -3584,7 +3615,6 @@ impl RenderOnce for PanelRepoFooter { .gap_1() .flex_shrink_0() .children(spinner) - .child(self.render_overflow_menu(overflow_menu_id, cx)) .when_some(branch, |this, branch| { let mut focus_handle = None; if let Some(git_panel) = self.git_panel.as_ref() { diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index 1e543c9a25..d153177b2d 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -1,13 +1,13 @@ use ::settings::Settings; use git::{ repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus}, - status::FileStatus, + status::{FileStatus, StatusCode, UnmergedStatus, UnmergedStatusCode}, }; use git_panel_settings::GitPanelSettings; use gpui::{App, Entity, FocusHandle}; use project::Project; use project_diff::ProjectDiff; -use ui::{ActiveTheme, Color, Icon, IconName, IntoElement, SharedString}; +use ui::prelude::*; use workspace::Workspace; mod askpass_modal; @@ -86,30 +86,8 @@ pub fn init(cx: &mut App) { .detach(); } -// TODO: Add updated status colors to theme -pub fn git_status_icon(status: FileStatus, cx: &App) -> impl IntoElement { - let (icon_name, color) = if status.is_conflicted() { - ( - IconName::Warning, - cx.theme().colors().version_control_conflict, - ) - } else if status.is_deleted() { - ( - IconName::SquareMinus, - cx.theme().colors().version_control_deleted, - ) - } else if status.is_modified() { - ( - IconName::SquareDot, - cx.theme().colors().version_control_modified, - ) - } else { - ( - IconName::SquarePlus, - cx.theme().colors().version_control_added, - ) - }; - Icon::new(icon_name).color(Color::Custom(color)) +pub fn git_status_icon(status: FileStatus) -> impl IntoElement { + GitStatusIcon::new(status) } fn can_push_and_pull(project: &Entity, cx: &App) -> bool { @@ -465,3 +443,79 @@ mod remote_button { } } } + +#[derive(IntoElement, IntoComponent)] +#[component(scope = "Version Control")] +pub struct GitStatusIcon { + status: FileStatus, +} + +impl GitStatusIcon { + pub fn new(status: FileStatus) -> Self { + Self { status } + } +} + +impl RenderOnce for GitStatusIcon { + fn render(self, _window: &mut ui::Window, cx: &mut App) -> impl IntoElement { + let status = self.status; + + let (icon_name, color) = if status.is_conflicted() { + ( + IconName::Warning, + cx.theme().colors().version_control_conflict, + ) + } else if status.is_deleted() { + ( + IconName::SquareMinus, + cx.theme().colors().version_control_deleted, + ) + } else if status.is_modified() { + ( + IconName::SquareDot, + cx.theme().colors().version_control_modified, + ) + } else { + ( + IconName::SquarePlus, + cx.theme().colors().version_control_added, + ) + }; + + Icon::new(icon_name).color(Color::Custom(color)) + } +} + +// View this component preview using `workspace: open component-preview` +impl ComponentPreview for GitStatusIcon { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { + fn tracked_file_status(code: StatusCode) -> FileStatus { + FileStatus::Tracked(git::status::TrackedStatus { + index_status: code, + worktree_status: code, + }) + } + + let modified = tracked_file_status(StatusCode::Modified); + let added = tracked_file_status(StatusCode::Added); + let deleted = tracked_file_status(StatusCode::Deleted); + let conflict = UnmergedStatus { + first_head: UnmergedStatusCode::Updated, + second_head: UnmergedStatusCode::Updated, + } + .into(); + + v_flex() + .gap_6() + .children(vec![example_group(vec![ + single_example("Modified", GitStatusIcon::new(modified).into_any_element()), + single_example("Added", GitStatusIcon::new(added).into_any_element()), + single_example("Deleted", GitStatusIcon::new(deleted).into_any_element()), + single_example( + "Conflicted", + GitStatusIcon::new(conflict).into_any_element(), + ), + ])]) + .into_any_element() + } +} diff --git a/crates/panel/src/panel.rs b/crates/panel/src/panel.rs index aecde61e2d..b1c55777c7 100644 --- a/crates/panel/src/panel.rs +++ b/crates/panel/src/panel.rs @@ -18,8 +18,6 @@ pub trait PanelHeader: workspace::Panel { .w_full() .px_1() .flex_none() - .border_b_1() - .border_color(cx.theme().colors().border) } } diff --git a/crates/theme/src/default_colors.rs b/crates/theme/src/default_colors.rs index 45c70cb7cf..1801a84294 100644 --- a/crates/theme/src/default_colors.rs +++ b/crates/theme/src/default_colors.rs @@ -136,14 +136,10 @@ impl ThemeColors { terminal_ansi_dim_white: neutral().light().step_11(), link_text_hover: orange().light().step_10(), version_control_added: ADDED_COLOR, - version_control_added_background: ADDED_COLOR.opacity(0.1), version_control_deleted: REMOVED_COLOR, - version_control_deleted_background: REMOVED_COLOR.opacity(0.1), version_control_modified: MODIFIED_COLOR, - version_control_modified_background: MODIFIED_COLOR.opacity(0.1), version_control_renamed: MODIFIED_COLOR, version_control_conflict: orange().light().step_12(), - version_control_conflict_background: orange().light().step_12().opacity(0.1), version_control_ignored: gray().light().step_12(), } } @@ -253,14 +249,10 @@ impl ThemeColors { terminal_ansi_dim_white: neutral().dark().step_10(), link_text_hover: orange().dark().step_10(), version_control_added: ADDED_COLOR, - version_control_added_background: ADDED_COLOR.opacity(0.1), version_control_deleted: REMOVED_COLOR, - version_control_deleted_background: REMOVED_COLOR.opacity(0.1), version_control_modified: MODIFIED_COLOR, - version_control_modified_background: MODIFIED_COLOR.opacity(0.1), version_control_renamed: MODIFIED_COLOR, version_control_conflict: orange().dark().step_12(), - version_control_conflict_background: orange().dark().step_12().opacity(0.1), version_control_ignored: gray().dark().step_12(), } } diff --git a/crates/theme/src/fallback_themes.rs b/crates/theme/src/fallback_themes.rs index 861f2bc7dd..92f7215741 100644 --- a/crates/theme/src/fallback_themes.rs +++ b/crates/theme/src/fallback_themes.rs @@ -190,14 +190,10 @@ pub(crate) fn zed_default_dark() -> Theme { editor_foreground: hsla(218. / 360., 14. / 100., 71. / 100., 1.), link_text_hover: blue, version_control_added: ADDED_COLOR, - version_control_added_background: ADDED_COLOR.opacity(0.1), version_control_deleted: REMOVED_COLOR, - version_control_deleted_background: REMOVED_COLOR.opacity(0.1), version_control_modified: MODIFIED_COLOR, - version_control_modified_background: MODIFIED_COLOR.opacity(0.1), version_control_renamed: MODIFIED_COLOR, version_control_conflict: crate::orange().light().step_12(), - version_control_conflict_background: crate::orange().light().step_12().opacity(0.1), version_control_ignored: crate::gray().light().step_12(), }, status: StatusColors { diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index 8ebcab2f88..69df3d0b79 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -557,26 +557,14 @@ pub struct ThemeColorsContent { #[serde(rename = "version_control.added")] pub version_control_added: Option, - /// Added version control background color. - #[serde(rename = "version_control.added_background")] - pub version_control_added_background: Option, - /// Deleted version control color. #[serde(rename = "version_control.deleted")] pub version_control_deleted: Option, - /// Deleted version control background color. - #[serde(rename = "version_control.deleted_background")] - pub version_control_deleted_background: Option, - /// Modified version control color. #[serde(rename = "version_control.modified")] pub version_control_modified: Option, - /// Modified version control background color. - #[serde(rename = "version_control.modified_background")] - pub version_control_modified_background: Option, - /// Renamed version control color. #[serde(rename = "version_control.renamed")] pub version_control_renamed: Option, @@ -585,10 +573,6 @@ pub struct ThemeColorsContent { #[serde(rename = "version_control.conflict")] pub version_control_conflict: Option, - /// Conflict version control background color. - #[serde(rename = "version_control.conflict_background")] - pub version_control_conflict_background: Option, - /// Ignored version control color. #[serde(rename = "version_control.ignored")] pub version_control_ignored: Option, @@ -1000,26 +984,14 @@ impl ThemeColorsContent { .version_control_added .as_ref() .and_then(|color| try_parse_color(color).ok()), - version_control_added_background: self - .version_control_added_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), version_control_deleted: self .version_control_deleted .as_ref() .and_then(|color| try_parse_color(color).ok()), - version_control_deleted_background: self - .version_control_deleted_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), version_control_modified: self .version_control_modified .as_ref() .and_then(|color| try_parse_color(color).ok()), - version_control_modified_background: self - .version_control_modified_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), version_control_renamed: self .version_control_renamed .as_ref() @@ -1028,10 +1000,6 @@ impl ThemeColorsContent { .version_control_conflict .as_ref() .and_then(|color| try_parse_color(color).ok()), - version_control_conflict_background: self - .version_control_conflict_background - .as_ref() - .and_then(|color| try_parse_color(color).ok()), version_control_ignored: self .version_control_ignored .as_ref() diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index 04f65ca807..fa639a03a9 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -246,22 +246,14 @@ pub struct ThemeColors { /// Represents an added entry or hunk in vcs, like git. pub version_control_added: Hsla, - /// Represents the line background of an added entry or hunk in vcs, like git. - pub version_control_added_background: Hsla, /// Represents a deleted entry in version control systems. pub version_control_deleted: Hsla, - /// Represents the background color for deleted entries in version control systems. - pub version_control_deleted_background: Hsla, /// Represents a modified entry in version control systems. pub version_control_modified: Hsla, - /// Represents the background color for modified entries in version control systems. - pub version_control_modified_background: Hsla, /// Represents a renamed entry in version control systems. pub version_control_renamed: Hsla, /// Represents a conflicting entry in version control systems. pub version_control_conflict: Hsla, - /// Represents the background color for conflicting entries in version control systems. - pub version_control_conflict_background: Hsla, /// Represents an ignored entry in version control systems. pub version_control_ignored: Hsla, } @@ -366,14 +358,10 @@ pub enum ThemeColorField { TerminalAnsiDimWhite, LinkTextHover, VersionControlAdded, - VersionControlAddedBackground, VersionControlDeleted, - VersionControlDeletedBackground, VersionControlModified, - VersionControlModifiedBackground, VersionControlRenamed, VersionControlConflict, - VersionControlConflictBackground, VersionControlIgnored, } @@ -485,20 +473,10 @@ impl ThemeColors { ThemeColorField::TerminalAnsiDimWhite => self.terminal_ansi_dim_white, ThemeColorField::LinkTextHover => self.link_text_hover, ThemeColorField::VersionControlAdded => self.version_control_added, - ThemeColorField::VersionControlAddedBackground => self.version_control_added_background, ThemeColorField::VersionControlDeleted => self.version_control_deleted, - ThemeColorField::VersionControlDeletedBackground => { - self.version_control_deleted_background - } ThemeColorField::VersionControlModified => self.version_control_modified, - ThemeColorField::VersionControlModifiedBackground => { - self.version_control_modified_background - } ThemeColorField::VersionControlRenamed => self.version_control_renamed, ThemeColorField::VersionControlConflict => self.version_control_conflict, - ThemeColorField::VersionControlConflictBackground => { - self.version_control_conflict_background - } ThemeColorField::VersionControlIgnored => self.version_control_ignored, } } diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index 27c7509944..6dcdaea3a6 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -6,9 +6,7 @@ use crate::{ prelude::*, Color, DynamicSpacing, ElevationIndex, IconPosition, KeyBinding, KeybindingPosition, TintColor, }; -use crate::{ - ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle, -}; +use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label}; use super::button_icon::ButtonIcon; @@ -448,7 +446,6 @@ impl RenderOnce for Button { .color(label_color) .size(self.label_size.unwrap_or_default()) .when_some(self.alpha, |this, alpha| this.alpha(alpha)) - .line_height_style(LineHeightStyle::UiLabel) .when(self.truncate, |this| this.truncate()), ) .children(self.key_binding), diff --git a/crates/ui/src/components/toggle.rs b/crates/ui/src/components/toggle.rs index f188d0d86b..666bff99fe 100644 --- a/crates/ui/src/components/toggle.rs +++ b/crates/ui/src/components/toggle.rs @@ -1,6 +1,5 @@ use gpui::{ - div, hsla, prelude::*, AnyElement, AnyView, CursorStyle, ElementId, Hsla, IntoElement, Styled, - Window, + div, hsla, prelude::*, AnyElement, AnyView, ElementId, Hsla, IntoElement, Styled, Window, }; use std::sync::Arc; @@ -141,14 +140,14 @@ impl Checkbox { match self.style.clone() { ToggleStyle::Ghost => cx.theme().colors().border, - ToggleStyle::ElevationBased(elevation) => elevation.on_elevation_bg(cx), + ToggleStyle::ElevationBased(_) => cx.theme().colors().border, ToggleStyle::Custom(color) => color.opacity(0.3), } } /// container size - pub fn container_size(cx: &App) -> Rems { - DynamicSpacing::Base20.rems(cx) + pub fn container_size() -> Pixels { + px(20.0) } } @@ -157,21 +156,21 @@ impl RenderOnce for Checkbox { let group_id = format!("checkbox_group_{:?}", self.id); let color = if self.disabled { Color::Disabled - } else if self.placeholder { - Color::Placeholder } else { Color::Selected }; let icon = match self.toggle_state { - ToggleState::Selected => Some(if self.placeholder { - Icon::new(IconName::Circle) - .size(IconSize::XSmall) - .color(color) - } else { - Icon::new(IconName::Check) - .size(IconSize::Small) - .color(color) - }), + ToggleState::Selected => { + if self.placeholder { + None + } else { + Some( + Icon::new(IconName::Check) + .size(IconSize::Small) + .color(color), + ) + } + } ToggleState::Indeterminate => { Some(Icon::new(IconName::Dash).size(IconSize::Small).color(color)) } @@ -180,8 +179,9 @@ impl RenderOnce for Checkbox { let bg_color = self.bg_color(cx); let border_color = self.border_color(cx); + let hover_border_color = border_color.alpha(0.7); - let size = Self::container_size(cx); + let size = Self::container_size(); let checkbox = h_flex() .id(self.id.clone()) @@ -195,22 +195,27 @@ impl RenderOnce for Checkbox { .flex_none() .justify_center() .items_center() - .m(DynamicSpacing::Base04.px(cx)) - .size(DynamicSpacing::Base16.rems(cx)) + .m_1() + .size_4() .rounded_xs() .bg(bg_color) .border_1() .border_color(border_color) - .when(self.disabled, |this| { - this.cursor(CursorStyle::OperationNotAllowed) - }) + .when(self.disabled, |this| this.cursor_not_allowed()) .when(self.disabled, |this| { this.bg(cx.theme().colors().element_disabled.opacity(0.6)) }) .when(!self.disabled, |this| { - this.group_hover(group_id.clone(), |el| { - el.bg(cx.theme().colors().element_hover) - }) + this.group_hover(group_id.clone(), |el| el.border_color(hover_border_color)) + }) + .when(self.placeholder, |this| { + this.child( + div() + .flex_none() + .rounded_full() + .bg(color.color(cx).alpha(0.5)) + .size(px(4.)), + ) }) .children(icon), ); @@ -522,6 +527,12 @@ impl ComponentPreview for Checkbox { Checkbox::new("checkbox_unselected", ToggleState::Unselected) .into_any_element(), ), + single_example( + "Placeholder", + Checkbox::new("checkbox_indeterminate", ToggleState::Selected) + .placeholder(true) + .into_any_element(), + ), single_example( "Indeterminate", Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate)