From 5c15b0e840e107e1de931be18a4061a075dde7bc Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Fri, 9 Oct 2026 10:43:46 +0700 Subject: [PATCH] migrate ui crate to new theme tokens --- crates/theme/src/lib.rs | 2 +- crates/theme/src/paint.rs | 10 +++ crates/ui/src/avatar.rs | 3 +- crates/ui/src/button.rs | 96 ++++++++++++------------ crates/ui/src/dialog.rs | 50 +++++-------- crates/ui/src/dock/mod.rs | 22 +++--- crates/ui/src/group_box.rs | 6 +- crates/ui/src/icon.rs | 2 +- crates/ui/src/input/clear_button.rs | 2 +- crates/ui/src/input/input.rs | 16 ++-- crates/ui/src/kbd.rs | 4 +- crates/ui/src/markdown.rs | 20 ++++- crates/ui/src/menu/menu_item.rs | 10 +-- crates/ui/src/menu/popup_menu.rs | 9 ++- crates/ui/src/message.rs | 6 +- crates/ui/src/nav.rs | 16 ++-- crates/ui/src/nav_item.rs | 17 +++-- crates/ui/src/notification.rs | 20 ++--- crates/ui/src/resizable/mod.rs | 6 +- crates/ui/src/root.rs | 25 ++----- crates/ui/src/styled.rs | 12 +-- crates/ui/src/switch.rs | 16 ++-- crates/ui/src/tab/mod.rs | 22 +++--- crates/ui/src/tab/tab_bar.rs | 4 +- crates/ui/src/title_bar.rs | 12 +-- crates/ui/src/tooltip.rs | 8 +- docs/ui-theme-bezel-migration.md | 111 +++++++++++++++++++--------- 27 files changed, 292 insertions(+), 235 deletions(-) diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index cef3d457..6ab4a5ca 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -35,7 +35,7 @@ pub use layout::{ pub use notification::*; pub use paint::{ INK_FILL_SCALE, INK_HAIRLINE_SCALE, SCRIM_ALPHA_DARK, band, current_appearance, hairline, ink, - scrim, set_current_appearance, surface_shadows, wash, + plate_active, plate_hover, scrim, set_current_appearance, surface_shadows, wash, }; pub use platform_kind::PlatformKind; pub use registry::*; diff --git a/crates/theme/src/paint.rs b/crates/theme/src/paint.rs index 22c6338e..34892a8f 100644 --- a/crates/theme/src/paint.rs +++ b/crates/theme/src/paint.rs @@ -179,3 +179,13 @@ pub(crate) fn plate_hover_for(appearance: ThemeMode, plate: gpui::Hsla) -> gpui: pub(crate) fn plate_active_for(appearance: ThemeMode, plate: gpui::Hsla) -> gpui::Hsla { crate::color::flatten(ink_for(appearance, 0.16), plate) } + +/// Hover over an opaque plate, for call sites outside this crate. +pub fn plate_hover(plate: gpui::Hsla) -> gpui::Hsla { + plate_hover_for(current_appearance(), plate) +} + +/// Pressed over an opaque plate, for call sites outside this crate. +pub fn plate_active(plate: gpui::Hsla) -> gpui::Hsla { + plate_active_for(current_appearance(), plate) +} diff --git a/crates/ui/src/avatar.rs b/crates/ui/src/avatar.rs index 979f51a4..8b070faf 100644 --- a/crates/ui/src/avatar.rs +++ b/crates/ui/src/avatar.rs @@ -418,7 +418,7 @@ impl InteractiveElement for Avatar { } impl RenderOnce for Avatar { - fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement { let image_size = avatar_size(self.size).to_pixels(window.rem_size()); let content = match self.picture { @@ -429,7 +429,6 @@ impl RenderOnce for Avatar { .size(image_size) .rounded_full() .object_fit(ObjectFit::Cover) - .bg(cx.theme().ghost_element_background) .with_fallback(move || generated_avatar(seed.as_deref(), image_size)) .into_any_element() } diff --git a/crates/ui/src/button.rs b/crates/ui/src/button.rs index 67c60f01..e25b97ab 100644 --- a/crates/ui/src/button.rs +++ b/crates/ui/src/button.rs @@ -4,10 +4,10 @@ use gpui::prelude::FluentBuilder as _; use gpui::{ AnyElement, App, ClickEvent, ElementId, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement, - Styled, Window, div, relative, + Styled, Window, div, px, relative, }; use gpui_base::Button as BaseButton; -use theme::ActiveTheme; +use theme::{ActiveTheme, bubble_radius, button_radius, plate_active, plate_hover, wash}; use crate::indicator::Indicator; use crate::tooltip::Tooltip; @@ -24,10 +24,10 @@ pub struct ButtonCustomVariant { impl ButtonCustomVariant { pub fn new(_window: &Window, cx: &App) -> Self { Self { - color: cx.theme().element_background, - foreground: cx.theme().element_foreground, - hover: cx.theme().element_hover, - active: cx.theme().element_active, + color: cx.theme().solid, + foreground: cx.theme().on_solid, + hover: plate_hover(cx.theme().solid), + active: plate_active(cx.theme().solid), } } @@ -293,9 +293,16 @@ impl RenderOnce for Button { .cursor_default() .overflow_hidden() .map(|this| match self.rounded { - false => this.rounded(cx.theme().radius), - true => this.rounded_full(), + false => this.rounded(px(button_radius())), + true => this.rounded(px(bubble_radius())), }) + .when_some( + match style { + ButtonVariant::Secondary => Some(cx.theme().border), + _ => None, + }, + |this, border_color| this.border_1().border_color(border_color), + ) .when(!self.compact, |this| { if self.label.is_none() && self.children.is_empty() { // Icon Button @@ -424,15 +431,15 @@ impl ButtonVariant { fn bg_color(&self, cx: &App) -> Hsla { match self { - ButtonVariant::Primary => cx.theme().element_background, - ButtonVariant::Secondary => cx.theme().secondary_background, - ButtonVariant::Danger => cx.theme().danger_background, - ButtonVariant::Warning => cx.theme().warning_background, + ButtonVariant::Primary => cx.theme().solid, + ButtonVariant::Secondary => cx.theme().surface_raised, + ButtonVariant::Danger => cx.theme().danger_strong, + ButtonVariant::Warning => cx.theme().warning_muted, ButtonVariant::Ghost { alt } => { if *alt { - cx.theme().ghost_element_background_alt + cx.theme().surface_raised } else { - cx.theme().ghost_element_background + gpui::transparent_black() } } ButtonVariant::Custom(colors) => colors.color, @@ -442,11 +449,11 @@ impl ButtonVariant { fn text_color(&self, cx: &App) -> Hsla { match self { - ButtonVariant::Primary => cx.theme().element_foreground, - ButtonVariant::Secondary => cx.theme().secondary_foreground, - ButtonVariant::Danger => cx.theme().danger_foreground, - ButtonVariant::Warning => cx.theme().warning_foreground, - ButtonVariant::Transparent => cx.theme().text_placeholder, + ButtonVariant::Primary => cx.theme().on_solid, + ButtonVariant::Secondary => cx.theme().text, + ButtonVariant::Danger => cx.theme().on_accent, + ButtonVariant::Warning => cx.theme().text, + ButtonVariant::Transparent => cx.theme().text_faint, ButtonVariant::Ghost { alt } => { if *alt { cx.theme().text @@ -460,19 +467,19 @@ impl ButtonVariant { fn hovered(&self, cx: &App) -> ButtonVariantStyle { let bg = match self { - ButtonVariant::Primary => cx.theme().element_hover, - ButtonVariant::Secondary => cx.theme().secondary_hover, - ButtonVariant::Danger => cx.theme().danger_hover, - ButtonVariant::Warning => cx.theme().warning_hover, - ButtonVariant::Ghost { .. } => cx.theme().ghost_element_hover, + ButtonVariant::Primary => plate_hover(cx.theme().solid), + ButtonVariant::Secondary => cx.theme().surface_raised_hover, + ButtonVariant::Danger => plate_hover(cx.theme().danger_strong), + ButtonVariant::Warning => cx.theme().element_hover, + ButtonVariant::Ghost { .. } => cx.theme().element_hover, ButtonVariant::Transparent => gpui::transparent_black(), ButtonVariant::Custom(colors) => colors.hover, }; let fg = match self { - ButtonVariant::Secondary => cx.theme().secondary_foreground, + ButtonVariant::Secondary => cx.theme().text, ButtonVariant::Ghost { .. } => cx.theme().text, - ButtonVariant::Transparent => cx.theme().text_placeholder, + ButtonVariant::Transparent => cx.theme().text_faint, _ => self.text_color(cx), }; @@ -481,18 +488,18 @@ impl ButtonVariant { fn active(&self, cx: &App) -> ButtonVariantStyle { let bg = match self { - ButtonVariant::Primary => cx.theme().element_active, - ButtonVariant::Secondary => cx.theme().secondary_active, - ButtonVariant::Danger => cx.theme().danger_active, - ButtonVariant::Warning => cx.theme().warning_active, - ButtonVariant::Ghost { .. } => cx.theme().ghost_element_active, + ButtonVariant::Primary => plate_active(cx.theme().solid), + ButtonVariant::Secondary => plate_active(cx.theme().surface_raised), + ButtonVariant::Danger => plate_active(cx.theme().danger_strong), + ButtonVariant::Warning => cx.theme().element_active, + ButtonVariant::Ghost { .. } => cx.theme().element_active, ButtonVariant::Transparent => gpui::transparent_black(), ButtonVariant::Custom(colors) => colors.active, }; let fg = match self { - ButtonVariant::Secondary => cx.theme().secondary_foreground, - ButtonVariant::Transparent => cx.theme().text_placeholder, + ButtonVariant::Secondary => cx.theme().text, + ButtonVariant::Transparent => cx.theme().text_faint, _ => self.text_color(cx), }; @@ -501,18 +508,18 @@ impl ButtonVariant { fn selected(&self, cx: &App) -> ButtonVariantStyle { let bg = match self { - ButtonVariant::Primary => cx.theme().element_selected, - ButtonVariant::Secondary => cx.theme().secondary_selected, - ButtonVariant::Danger => cx.theme().danger_selected, - ButtonVariant::Warning => cx.theme().warning_selected, - ButtonVariant::Ghost { .. } => cx.theme().ghost_element_selected, + ButtonVariant::Primary => cx.theme().element_active, + ButtonVariant::Secondary => cx.theme().element_active, + ButtonVariant::Danger => plate_active(cx.theme().danger_strong), + ButtonVariant::Warning => cx.theme().element_active, + ButtonVariant::Ghost { .. } => cx.theme().element_active, ButtonVariant::Transparent => gpui::transparent_black(), ButtonVariant::Custom(colors) => colors.active, }; let fg = match self { - ButtonVariant::Secondary => cx.theme().secondary_foreground, - ButtonVariant::Transparent => cx.theme().text_placeholder, + ButtonVariant::Secondary => cx.theme().text, + ButtonVariant::Transparent => cx.theme().text_faint, _ => self.text_color(cx), }; @@ -520,14 +527,7 @@ impl ButtonVariant { } fn disabled(&self, cx: &App) -> ButtonVariantStyle { - let bg = match self { - ButtonVariant::Danger => cx.theme().danger_disabled, - ButtonVariant::Warning => cx.theme().warning_disabled, - ButtonVariant::Ghost { .. } => cx.theme().ghost_element_disabled, - ButtonVariant::Secondary => cx.theme().secondary_disabled, - _ => cx.theme().element_disabled, - }; - + let bg = wash(0.08); let fg = cx.theme().text_muted; ButtonVariantStyle { bg, fg } diff --git a/crates/ui/src/dialog.rs b/crates/ui/src/dialog.rs index 5bb246a2..458a4c66 100644 --- a/crates/ui/src/dialog.rs +++ b/crates/ui/src/dialog.rs @@ -2,13 +2,13 @@ use std::rc::Rc; use gpui::prelude::FluentBuilder; use gpui::{ - Animation, AnimationExt as _, AnyElement, App, BoxShadow, ClickEvent, Div, FocusHandle, - IntoElement, ParentElement, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, Window, - div, hsla, point, px, + Animation, AnimationExt as _, AnyElement, App, ClickEvent, Div, FocusHandle, IntoElement, + ParentElement, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, Window, div, px, + transparent_black, }; use gpui_base::{DialogBackdrop, DialogPopup, DialogTitle}; use instant::Duration; -use theme::ActiveTheme; +use theme::{ActiveTheme, SCRIM_ALPHA_DARK, hairline, scrim, surface_radius, surface_shadows}; use crate::animation::cubic_bezier; use crate::button::{Button, ButtonCustomVariant, ButtonVariant, ButtonVariants as _}; @@ -278,7 +278,9 @@ impl RenderOnce for Dialog { let backdrop = DialogBackdrop::new() .absolute() .inset_0() - .when(self.overlay_visible, |this| this.bg(cx.theme().overlay)) + .when(self.overlay_visible, |this| { + this.bg(scrim(SCRIM_ALPHA_DARK)) + }) .with_animation("fade-in", animation.clone(), move |this, delta| { this.opacity(delta) }); @@ -286,11 +288,11 @@ impl RenderOnce for Dialog { let card = DialogPopup::new() .flex() .flex_col() - .bg(cx.theme().background) + .bg(cx.theme().surface_dialog) .border_1() - .border_color(cx.theme().border.alpha(0.4)) - .rounded(cx.theme().radius_lg) - .when(cx.theme().shadow, |this| this.shadow_xl()) + .border_color(hairline(0.4)) + .rounded(px(surface_radius())) + .when(cx.theme().shadow, |this| this.shadow(surface_shadows())) .min_h_24() .refine_style(&self.style) .relative() @@ -329,10 +331,10 @@ impl RenderOnce for Dialog { .icon(IconName::CloseCircleFill) .custom( ButtonCustomVariant::new(window, cx) - .foreground(cx.theme().icon_muted) - .color(cx.theme().ghost_element_background) - .hover(cx.theme().ghost_element_background) - .active(cx.theme().ghost_element_background), + .foreground(cx.theme().text_faint) + .color(transparent_black()) + .hover(cx.theme().element_hover) + .active(cx.theme().element_active), ) .on_click(move |_, window, cx| { on_cancel(&ClickEvent::default(), window, cx); @@ -374,23 +376,11 @@ impl RenderOnce for Dialog { .with_animation("slide-down", animation, move |this, delta| { // Settle at the target offset; only the entrance is animated. let y_offset = px(30.) * (delta - 1.); - // This is equivalent to `shadow_xl` with an extra opacity. - let shadow = vec![ - BoxShadow { - color: hsla(0., 0., 0., 0.1 * delta), - offset: point(px(0.), px(20.)), - blur_radius: px(25.), - spread_radius: px(-5.), - inset: false, - }, - BoxShadow { - color: hsla(0., 0., 0., 0.1 * delta), - offset: point(px(0.), px(8.)), - blur_radius: px(10.), - spread_radius: px(-6.), - inset: false, - }, - ]; + let mut shadow = surface_shadows(); + // The shadows fade in with the slide. + for shadow in &mut shadow { + shadow.color.a *= delta; + } this.top(y_offset).shadow(shadow) }); diff --git a/crates/ui/src/dock/mod.rs b/crates/ui/src/dock/mod.rs index 3fd9653e..f0998b54 100644 --- a/crates/ui/src/dock/mod.rs +++ b/crates/ui/src/dock/mod.rs @@ -17,7 +17,7 @@ use gpui_base::dock::{ PaneRef, PanelId, TabGroupContext, TabGroupRenderer, }; use gpui_base::{HandleEdge, Placement, ResizeHandleContext}; -use theme::{ActiveTheme, TABBAR_HEIGHT}; +use theme::{ActiveTheme, TABBAR_HEIGHT, button_radius, panel_radius}; use crate::button::{Button, ButtonVariants as _}; use crate::resizable::{resize_handle, resize_handle_appearance}; @@ -348,12 +348,12 @@ impl Render for DragPreview { .justify_center() .overflow_hidden() .whitespace_nowrap() - .rounded(cx.theme().radius) + .rounded(px(button_radius())) .text_sm() .text_color(cx.theme().text) .text_ellipsis() .when(cx.theme().shadow, |this| this.shadow_xs()) - .bg(cx.theme().background) + .bg(cx.theme().bg) .child(panel_title(&self.panel, cx)) } } @@ -448,7 +448,7 @@ impl TabGroupSkin { .flex_grow_1() .min_w_16() .when(droppable, |this| { - this.drag_over::(|this, _, _, cx| this.bg(cx.theme().surface_background)) + this.drag_over::(|this, _, _, cx| this.bg(cx.theme().drop_target)) .on_drop({ let group = TabGroupContext::clone(group); move |drag: &DragPanel, window, cx| { @@ -467,7 +467,7 @@ impl TabGroupSkin { let bar = TabBar::new("tab-bar") .track_scroll(&self.scroll_handle) .h(TABBAR_HEIGHT) - .bg(cx.theme().panel_background) + .bg(cx.theme().surface) .when(needs_traffic_light_padding, |this| { this.pl(px(TRAFFIC_LIGHT_PADDING)) }) @@ -539,7 +539,7 @@ impl TabGroupSkin { this.rounded_l_none() .border_l_2() .border_r_0() - .border_color(cx.theme().border) + .border_color(cx.theme().drop_line) }) .on_drop({ let group = TabGroupContext::clone(group); @@ -569,7 +569,7 @@ impl TabGroupSkin { h_flex() .h(TABBAR_HEIGHT) .w_full() - .bg(cx.theme().panel_background) + .bg(cx.theme().surface) .child(bar.flex_1()) .child(window_controls()) .into_any_element() @@ -718,7 +718,7 @@ impl TabGroupRenderer for TabGroupSkin { .child( div() .size_full() - .bg(cx.theme().panel_background) + .bg(cx.theme().surface) .overflow_hidden() .child(panel.cached(StyleRefinement::default().v_flex().size_full())), ) @@ -759,10 +759,10 @@ impl TabGroupRenderer for TabGroupSkin { .top(top) .w(width) .h(height) - .rounded(cx.theme().radius_lg) + .rounded(px(panel_radius())) .border_1() - .border_color(cx.theme().element_disabled) - .bg(cx.theme().drop_target_background) + .border_color(cx.theme().drop_line) + .bg(cx.theme().drop_target) .into_any_element(), ) } diff --git a/crates/ui/src/group_box.rs b/crates/ui/src/group_box.rs index 4a9d98d0..b7aaf783 100644 --- a/crates/ui/src/group_box.rs +++ b/crates/ui/src/group_box.rs @@ -1,7 +1,7 @@ use gpui::prelude::FluentBuilder; use gpui::{ AnyElement, App, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce, - StyleRefinement, Styled, Window, div, relative, + StyleRefinement, Styled, Window, div, px, relative, }; use smallvec::SmallVec; use theme::ActiveTheme; @@ -95,7 +95,7 @@ impl RenderOnce for GroupBox { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { let (bg, has_paddings) = match self.variant { GroupBoxVariant::Normal => (None, false), - GroupBoxVariant::Fill => (Some(cx.theme().surface_background), true), + GroupBoxVariant::Fill => (Some(cx.theme().surface), true), }; v_flex() @@ -120,7 +120,7 @@ impl RenderOnce for GroupBox { .text_color(cx.theme().text) .when(has_paddings, |this| this.p_2()) .gap_4() - .rounded(cx.theme().radius_lg) + .rounded(px(theme::panel_radius())) .children(self.children), ) } diff --git a/crates/ui/src/icon.rs b/crates/ui/src/icon.rs index b4c0e530..90fa8029 100644 --- a/crates/ui/src/icon.rs +++ b/crates/ui/src/icon.rs @@ -252,7 +252,7 @@ impl From for AnyElement { impl Render for Icon { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - let text_color = self.text_color.unwrap_or_else(|| cx.theme().icon); + let text_color = self.text_color.unwrap_or_else(|| cx.theme().text_muted); let text_size = window.text_style().font_size.to_pixels(window.rem_size()); let has_base_size = self.style.size.width.is_some() || self.style.size.height.is_some(); diff --git a/crates/ui/src/input/clear_button.rs b/crates/ui/src/input/clear_button.rs index 511609c4..f220318f 100644 --- a/crates/ui/src/input/clear_button.rs +++ b/crates/ui/src/input/clear_button.rs @@ -11,5 +11,5 @@ pub(crate) fn clear_button(cx: &App) -> Button { .ghost() .xsmall() .tab_stop(false) - .text_color(cx.theme().icon_muted) + .text_color(cx.theme().text_faint) } diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index cccc7cb8..eacf502d 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -6,7 +6,7 @@ use gpui::{ }; use gpui_base::InputBase; use gpui_base::input::{InputBaseState, InputEditorStyle, InputMode, InputModeKind, TextareaMode}; -use theme::ActiveTheme; +use theme::{ActiveTheme, button_radius}; use crate::indicator::Indicator; use crate::input::clear_button; @@ -15,9 +15,9 @@ use crate::{Sizable, Size, StyleSized, StyledExt, h_flex, v_flex}; /// The background of an input frame, which reads muted while the input is disabled. fn input_background(disabled: bool, cx: &App) -> Hsla { if disabled { - cx.theme().surface_background + cx.theme().surface } else { - cx.theme().elevated_surface_background + cx.theme().input_bg } } @@ -26,11 +26,11 @@ fn input_editor_style(cx: &App) -> InputEditorStyle { let theme = cx.theme(); InputEditorStyle { foreground: theme.text, - muted_foreground: theme.text_muted, - background: theme.elevated_surface_background, - border: theme.border, + muted_foreground: theme.text_faint, + background: theme.input_bg, + border: theme.border_faint, selection: theme.selection, - caret: theme.cursor, + caret: theme.caret, ..InputEditorStyle::default() } } @@ -171,7 +171,7 @@ impl RenderOnce for Input { .when(self.appearance, |this| { this.bg(background) .when(self.disabled, |this| this.opacity(0.5)) - .rounded(cx.theme().radius) + .rounded(px(button_radius())) }) .tab_index(0) .gap(gap_x) diff --git a/crates/ui/src/kbd.rs b/crates/ui/src/kbd.rs index d4f7f863..fb5629e9 100644 --- a/crates/ui/src/kbd.rs +++ b/crates/ui/src/kbd.rs @@ -171,9 +171,9 @@ impl RenderOnce for Kbd { fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement { div() .border_1() - .border_color(cx.theme().border) + .border_color(cx.theme().border_faint) .text_color(cx.theme().text_muted) - .bg(cx.theme().surface_background) + .bg(cx.theme().surface_raised) .py_0p5() .px_1() .min_w_5() diff --git a/crates/ui/src/markdown.rs b/crates/ui/src/markdown.rs index 2d19b2af..ce81b006 100644 --- a/crates/ui/src/markdown.rs +++ b/crates/ui/src/markdown.rs @@ -1,7 +1,8 @@ use std::ops::Range; use gpui::{ - AnyElement, App, ElementId, IntoElement, SharedString, StyleRefinement, Styled as _, Window, + AnyElement, App, ElementId, HighlightStyle, IntoElement, SharedString, StyleRefinement, + Styled as _, Window, }; use gpui_base::{TextView, TextViewStyle}; use theme::ActiveTheme as _; @@ -63,14 +64,27 @@ impl RenderedText { pub fn element(&self, id: ElementId, _window: &Window, cx: &App) -> AnyElement { let theme = cx.theme(); let code_block = StyleRefinement::default().font_family(code_font_family()); + let muted_heading = theme.text_muted; TextView::markdown(id, self.text.clone()) .style( TextViewStyle::default() .with_foreground(theme.text) .with_muted_foreground(theme.text_muted) - .with_link(theme.text_accent) - .with_code_background(theme.elevated_surface_background) + // Deeply nested headings read as secondary text. + .with_heading(move |level| { + if level >= 4 { + StyleRefinement::default().text_color(muted_heading) + } else { + StyleRefinement::default() + } + }) + .with_link(theme.accent) + .with_code_background(theme.code_wash) + .with_inline_code(HighlightStyle { + color: Some(theme.code_text), + ..Default::default() + }) .with_border(theme.border) .with_code_block(code_block) .with_dark(theme.is_dark()), diff --git a/crates/ui/src/menu/menu_item.rs b/crates/ui/src/menu/menu_item.rs index 62549258..48cccf3a 100644 --- a/crates/ui/src/menu/menu_item.rs +++ b/crates/ui/src/menu/menu_item.rs @@ -101,12 +101,12 @@ impl RenderOnce for MenuItemElement { }) .when(!self.disabled, |this| { this.group_hover(self.group_name, |this| { - this.bg(cx.theme().secondary_background) - .text_color(cx.theme().secondary_foreground) + this.bg(cx.theme().element_hover) + .text_color(cx.theme().text) }) .when(self.selected, |this| { - this.bg(cx.theme().secondary_background) - .text_color(cx.theme().secondary_foreground) + this.bg(cx.theme().element_active) + .text_color(cx.theme().text) }) .when_some(self.on_click, |this, on_click| { this.on_mouse_down(MouseButton::Left, move |_, _, cx| { @@ -115,7 +115,7 @@ impl RenderOnce for MenuItemElement { .on_click(on_click) }) }) - .when(self.disabled, |this| this.text_color(cx.theme().text_muted)) + .when(self.disabled, |this| this.text_color(cx.theme().text_faint)) .children(self.children) } } diff --git a/crates/ui/src/menu/popup_menu.rs b/crates/ui/src/menu/popup_menu.rs index 00e997a9..8ae223e3 100644 --- a/crates/ui/src/menu/popup_menu.rs +++ b/crates/ui/src/menu/popup_menu.rs @@ -577,7 +577,7 @@ impl PopupMenu { checked: bool, icon: Option, _: &mut Window, - _: &mut Context, + cx: &mut Context, ) -> Option { if !has_icon { return None; @@ -586,7 +586,7 @@ impl PopupMenu { let icon = if let Some(icon) = icon { icon.clone() } else if checked { - Icon::new(IconName::Check) + Icon::new(IconName::Check).text_color(cx.theme().accent) } else { Icon::empty() }; @@ -657,7 +657,7 @@ impl PopupMenu { .my_0p5() .mx_neg_1() .border_b(px(2.)) - .border_color(cx.theme().border) + .border_color(cx.theme().border_faint) .disabled(true), PopupMenuItem::Label(label) => this.disabled(true).cursor_default().child( h_flex().cursor_default().items_center().gap_x_1().child( @@ -790,7 +790,8 @@ impl Render for PopupMenu { let options = RenderOptions { has_left_icon, - radius: cx.theme().radius.min(px(8.)), + // Items sit p_1 (4px) inside the surface plate; keep corners concentric. + radius: px(theme::inset_radius(theme::surface_radius(), 4.0)), }; v_flex() diff --git a/crates/ui/src/message.rs b/crates/ui/src/message.rs index 9d046e0a..a157f3ec 100644 --- a/crates/ui/src/message.rs +++ b/crates/ui/src/message.rs @@ -192,7 +192,7 @@ impl RenderOnce for MessageRow { h_flex() .gap_2() .text_sm() - .text_color(cx.theme().text_placeholder) + .text_color(cx.theme().text_dim) .when_some(author, |this, author| { this.child(div().font_semibold().child(author)) }) @@ -212,7 +212,7 @@ impl RenderOnce for MessageRow { .when_some(on_double_click, |this, listener| { this.on_double_click(listener) }) - .hover(|this| this.bg(cx.theme().surface_background)) + .hover(|this| this.bg(cx.theme().element_hover)) .into_any_element() } } @@ -305,7 +305,7 @@ impl RenderOnce for WelcomeMessage { this.child( div() .text_xs() - .text_color(cx.theme().text_placeholder) + .text_color(cx.theme().text_faint) .child(message), ) }), diff --git a/crates/ui/src/nav.rs b/crates/ui/src/nav.rs index b7edb004..cb8422fe 100644 --- a/crates/ui/src/nav.rs +++ b/crates/ui/src/nav.rs @@ -4,7 +4,7 @@ use gpui::prelude::FluentBuilder as _; use gpui::{ AnyElement, App, ClickEvent, ElementId, InteractiveElement, Interactivity, IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement, - Styled, Window, div, + Styled, Window, div, px, }; use theme::ActiveTheme; @@ -103,6 +103,11 @@ impl Styled for Nav { impl RenderOnce for Nav { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let clickable = self.on_click.is_some() || self.clickable; + let text_color = if self.selected { + cx.theme().text + } else { + cx.theme().text_muted + }; v_flex().h_10().w_full().items_center().child( h_flex() @@ -111,7 +116,8 @@ impl RenderOnce for Nav { .w_full() .px_1p5() .gap_1p5() - .rounded(cx.theme().radius_lg) + .rounded(px(theme::button_radius())) + .text_color(text_color) .when_some(self.prefix, |this, prefix| this.child(prefix)) .child( h_flex() @@ -125,10 +131,8 @@ impl RenderOnce for Nav { ) .when(clickable, |this| { this.cursor_pointer() - .hover(|this| this.bg(cx.theme().ghost_element_hover)) - .when(self.selected, |this| { - this.bg(cx.theme().ghost_element_active) - }) + .hover(|this| this.bg(cx.theme().element_hover)) + .when(self.selected, |this| this.bg(cx.theme().element_active)) }) .when_some(self.on_click, |this, handler| { this.on_click(move |event, window, cx| handler(event, window, cx)) diff --git a/crates/ui/src/nav_item.rs b/crates/ui/src/nav_item.rs index 56e9bfc7..8d9bc182 100644 --- a/crates/ui/src/nav_item.rs +++ b/crates/ui/src/nav_item.rs @@ -4,7 +4,7 @@ use gpui::prelude::FluentBuilder as _; use gpui::{ AnyElement, App, ClickEvent, ElementId, InteractiveElement, Interactivity, IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement, - Styled, Window, div, + Styled, Window, div, px, }; use theme::ActiveTheme; @@ -86,6 +86,11 @@ impl Styled for NavItem { impl RenderOnce for NavItem { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let clickable = self.on_click.is_some(); + let text_color = if self.selected { + cx.theme().text + } else { + cx.theme().text_muted + }; h_flex() .id(self.id) @@ -94,8 +99,8 @@ impl RenderOnce for NavItem { .py_1() .w_full() .gap_2() - .rounded(cx.theme().radius) - .text_color(cx.theme().text) + .rounded(px(theme::button_radius())) + .text_color(text_color) .child(self.icon) .child( div() @@ -110,10 +115,8 @@ impl RenderOnce for NavItem { }) .when(clickable, |this| { this.cursor_pointer() - .hover(|this| this.bg(cx.theme().ghost_element_hover)) - .when(self.selected, |this| { - this.bg(cx.theme().ghost_element_active) - }) + .hover(|this| this.bg(cx.theme().element_hover)) + .when(self.selected, |this| this.bg(cx.theme().element_active)) }) .when_some(self.on_click, |this, handler| { this.on_click(move |event, window, cx| handler(event, window, cx)) diff --git a/crates/ui/src/notification.rs b/crates/ui/src/notification.rs index 4caf7158..b32a0d5a 100644 --- a/crates/ui/src/notification.rs +++ b/crates/ui/src/notification.rs @@ -41,10 +41,10 @@ pub enum NotificationKind { impl NotificationKind { fn icon(&self, cx: &App) -> Icon { let (name, color) = match self { - Self::Info => (IconName::Info, cx.theme().icon), - Self::Success => (IconName::CheckCircle, cx.theme().icon_accent), - Self::Warning => (IconName::Warning, cx.theme().text_warning), - Self::Error => (IconName::CloseCircle, cx.theme().danger_foreground), + Self::Info => (IconName::Info, cx.theme().text_muted), + Self::Success => (IconName::CheckCircle, cx.theme().success), + Self::Warning => (IconName::Warning, cx.theme().warning), + Self::Error => (IconName::CloseCircle, cx.theme().on_accent), }; Icon::new(name).with_size(Size::Medium).text_color(color) } @@ -279,12 +279,12 @@ impl Render for Notification { }; let background = match self.kind { - Some(NotificationKind::Error) => cx.theme().danger_background, - _ => cx.theme().surface_background, + Some(NotificationKind::Error) => cx.theme().danger_strong, + _ => cx.theme().surface_overlay, }; let text_color = match self.kind { - Some(NotificationKind::Error) => cx.theme().danger_foreground, + Some(NotificationKind::Error) => cx.theme().on_accent, _ => cx.theme().text, }; @@ -305,8 +305,10 @@ impl Render for Notification { .border_color(cx.theme().border) .bg(background) .text_color(text_color) - .rounded(cx.theme().radius_lg) - .when(cx.theme().shadow, |this| this.shadow_md()) + .rounded(px(theme::surface_radius())) + .when(cx.theme().shadow, |this| { + this.shadow(theme::surface_shadows()) + }) .p_2() .gap_2() .justify_start() diff --git a/crates/ui/src/resizable/mod.rs b/crates/ui/src/resizable/mod.rs index b68bacab..821308a1 100644 --- a/crates/ui/src/resizable/mod.rs +++ b/crates/ui/src/resizable/mod.rs @@ -12,15 +12,15 @@ pub(crate) fn resize_handle_appearance() -> ResizeHandleRenderer { Rc::new( |context: &ResizeHandleContext, _: &mut Window, cx: &mut App| { let color = if context.is_active() { - cx.theme().border_selected + cx.theme().border_strong } else { - cx.theme().border + cx.theme().element_hover }; let axis = context.axis(); Some( div() - .bg(cx.theme().border_variant) + .bg(cx.theme().border_faint) .group_hover("handle", move |this| this.bg(color)) .when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE)) .when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)) diff --git a/crates/ui/src/root.rs b/crates/ui/src/root.rs index 8bec496c..2cc3bc81 100644 --- a/crates/ui/src/root.rs +++ b/crates/ui/src/root.rs @@ -4,14 +4,14 @@ use std::rc::Rc; use gpui::prelude::FluentBuilder; use gpui::{ AnyView, App, AppContext, Bounds, Context, CursorStyle, Decorations, ElementId, Entity, - FocusHandle, HitboxBehavior, Hsla, InteractiveElement, IntoElement, MouseButton, - ParentElement as _, Pixels, Point, Render, ResizeEdge, Size, Styled, Tiling, WeakFocusHandle, - Window, canvas, div, point, px, size, + FocusHandle, HitboxBehavior, InteractiveElement, IntoElement, MouseButton, ParentElement as _, + Pixels, Point, Render, ResizeEdge, Size, Styled, Tiling, WeakFocusHandle, Window, canvas, div, + point, px, size, }; use gpui_base::TextSelectionLayer; use theme::{ ActiveTheme, CLIENT_SIDE_DECORATION_BORDER, CLIENT_SIDE_DECORATION_ROUNDING, - CLIENT_SIDE_DECORATION_SHADOW, + CLIENT_SIDE_DECORATION_SHADOW, surface_shadows, }; use crate::dialog::Dialog; @@ -354,19 +354,8 @@ impl Render for Root { .when(!tiling.right, |div| { div.border_r(CLIENT_SIDE_DECORATION_BORDER) }) - .when(!tiling.is_tiled(), |div| { - div.shadow(vec![gpui::BoxShadow { - color: Hsla { - h: 0., - s: 0., - l: 0., - a: 0.4, - }, - blur_radius: CLIENT_SIDE_DECORATION_SHADOW / 2., - spread_radius: px(0.), - offset: point(px(0.0), px(0.0)), - inset: false, - }]) + .when(!tiling.is_tiled() && cx.theme().shadow, |div| { + div.shadow(surface_shadows()) }), }) .on_mouse_move(|_e, _, cx| { @@ -374,7 +363,7 @@ impl Render for Root { }) .size_full() .font_family(font_family) - .bg(cx.theme().surface_background) + .bg(cx.theme().bg) .text_color(cx.theme().text) .child(TextSelectionLayer) .child(self.view.clone()), diff --git a/crates/ui/src/styled.rs b/crates/ui/src/styled.rs index 2388c3df..86a117bd 100644 --- a/crates/ui/src/styled.rs +++ b/crates/ui/src/styled.rs @@ -1,6 +1,6 @@ use gpui::{App, Div, Pixels, Refineable, StyleRefinement, Styled, div, px}; pub use gpui_base::component_traits::{Disableable, Selectable}; -use theme::ActiveTheme; +use theme::{ActiveTheme, surface_radius, surface_shadows}; /// Returns a `Div` as horizontal flex layout. pub fn h_flex() -> Div { @@ -14,7 +14,7 @@ pub fn v_flex() -> Div { /// Returns a `Div` as divider. pub fn divider(cx: &App) -> Div { - div().my_1().w_full().h_px().bg(cx.theme().border_variant) + div().my_1().w_full().h_px().bg(cx.theme().border_faint) } macro_rules! font_weight { @@ -53,11 +53,13 @@ pub trait StyledExt: Styled + Sized { /// Set as Popover style #[inline] fn popover_style(self, cx: &mut App) -> Self { - self.bg(cx.theme().background) + let shadows = cx.theme().shadow.then(surface_shadows).unwrap_or_default(); + + self.bg(cx.theme().surface_overlay) .border_1() .border_color(cx.theme().border) - .shadow_md() - .rounded(cx.theme().radius_lg) + .shadow(shadows) + .rounded(px(surface_radius())) } } diff --git a/crates/ui/src/switch.rs b/crates/ui/src/switch.rs index 5db2ed2e..5d079a47 100644 --- a/crates/ui/src/switch.rs +++ b/crates/ui/src/switch.rs @@ -4,7 +4,7 @@ use std::time::Duration; use gpui::prelude::FluentBuilder as _; use gpui::{ App, ElementId, IntoElement, ParentElement as _, RenderOnce, SharedString, Styled as _, Window, - div, px, white, + div, px, transparent_black, }; use gpui_base::{Spring, Switch as BaseSwitch, SwitchThumb, SwitchTrack, spring}; use theme::ActiveTheme; @@ -63,11 +63,17 @@ impl RenderOnce for Switch { let on_click = self.on_click.clone(); let bg = if checked { - cx.theme().element_background + cx.theme().accent_strong } else { - cx.theme().elevated_surface_background + cx.theme().input_bg + }; + let toggle_bg = cx.theme().on_solid; + // The border is structural (it insets the thumb); it only paints when off. + let track_border = if checked { + transparent_black() + } else { + cx.theme().border }; - let toggle_bg = white(); let bg_width = px(36.); let bg_height = px(20.); @@ -119,7 +125,7 @@ impl RenderOnce for Switch { .flex() .items_center() .border(inset) - .border_color(cx.theme().border_transparent) + .border_color(track_border) .bg(bg) .cursor_pointer() .child( diff --git a/crates/ui/src/tab/mod.rs b/crates/ui/src/tab/mod.rs index 9d8dc17d..2bb87696 100644 --- a/crates/ui/src/tab/mod.rs +++ b/crates/ui/src/tab/mod.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use gpui::prelude::FluentBuilder as _; use gpui::{ AnyElement, App, ClickEvent, Div, InteractiveElement, IntoElement, MouseButton, ParentElement, - RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, + RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, px, }; use theme::ActiveTheme; @@ -145,9 +145,9 @@ impl RenderOnce for Tab { let foreground = if disabled { cx.theme().text_muted } else if selected { - cx.theme().tab_active_foreground + cx.theme().text } else { - cx.theme().tab_foreground + cx.theme().text_muted }; let content = h_flex() @@ -173,13 +173,13 @@ impl RenderOnce for Tab { this.text_xs() .flex_1() .h_6() - .rounded(cx.theme().radius) + .rounded(px(theme::button_radius())) .when(selected && !disabled, |this| { - this.bg(cx.theme().tab_active_background) + this.bg(cx.theme().bg) .when(cx.theme().shadow, |this| this.shadow_sm()) }) .when(!selected && !disabled, |this| { - this.hover(|this| this.bg(cx.theme().tab_hover_background)) + this.hover(|this| this.bg(cx.theme().element_hover)) }) }) .when(!segmented, |this| { @@ -189,15 +189,13 @@ impl RenderOnce for Tab { .gap_1() .px_1p5() .text_sm() - .rounded(cx.theme().radius) + .rounded(px(theme::button_radius())) .overflow_hidden() - .when(selected && !disabled, |this| { - this.bg(cx.theme().tab_background) - }) + .when(selected && !disabled, |this| this.bg(cx.theme().bg)) .when(!selected && !disabled, |this| { this.hover(|this| { - this.bg(cx.theme().tab_hover_background) - .text_color(cx.theme().tab_active_foreground) + this.bg(cx.theme().element_hover) + .text_color(cx.theme().text) }) }) }) diff --git a/crates/ui/src/tab/tab_bar.rs b/crates/ui/src/tab/tab_bar.rs index 07be9d5d..353749fc 100644 --- a/crates/ui/src/tab/tab_bar.rs +++ b/crates/ui/src/tab/tab_bar.rs @@ -122,9 +122,9 @@ impl RenderOnce for TabBar { .min_w_0() .refine_style(&self.style) .when(segmented, |this| { - this.bg(cx.theme().tab_background) + this.bg(cx.theme().surface) .p_0p5() - .rounded(cx.theme().radius) + .rounded(px(theme::button_radius())) }) .when_some(self.prefix, |this, prefix| this.child(prefix)) .child( diff --git a/crates/ui/src/title_bar.rs b/crates/ui/src/title_bar.rs index 151f93d2..be63273d 100644 --- a/crates/ui/src/title_bar.rs +++ b/crates/ui/src/title_bar.rs @@ -4,7 +4,7 @@ use gpui::{ RenderOnce, Stateful, StatefulInteractiveElement as _, Styled, Window, WindowControlArea, div, px, }; -use theme::ActiveTheme; +use theme::{ActiveTheme, plate_active}; use crate::{Icon, IconName, Sizable as _, h_flex}; @@ -70,7 +70,7 @@ impl ControlIcon { #[inline] fn hover_fg(&self, cx: &App) -> Hsla { if self.is_close() { - cx.theme().danger_foreground + cx.theme().on_accent } else { cx.theme().text } @@ -79,18 +79,18 @@ impl ControlIcon { #[inline] fn hover_bg(&self, cx: &App) -> Hsla { if self.is_close() { - cx.theme().danger_background + cx.theme().danger_strong } else { - cx.theme().ghost_element_hover + cx.theme().element_hover } } #[inline] fn active_bg(&self, cx: &mut App) -> Hsla { if self.is_close() { - cx.theme().danger_active + plate_active(cx.theme().danger_strong) } else { - cx.theme().ghost_element_active + cx.theme().element_active } } } diff --git a/crates/ui/src/tooltip.rs b/crates/ui/src/tooltip.rs index c9df510a..099c6f86 100644 --- a/crates/ui/src/tooltip.rs +++ b/crates/ui/src/tooltip.rs @@ -1,10 +1,10 @@ use gpui::prelude::FluentBuilder; use gpui::{ App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled, - Window, relative, + Window, px, relative, }; use gpui_base::Tooltip as BaseTooltip; -use theme::ActiveTheme; +use theme::{ActiveTheme, control_radius}; pub struct Tooltip { text: SharedString, @@ -24,9 +24,9 @@ impl Render for Tooltip { .p_1p5() .border_1() .border_color(cx.theme().border) - .bg(cx.theme().surface_background) + .bg(cx.theme().surface_overlay) .when(cx.theme().shadow, |this| this.shadow_sm()) - .rounded(cx.theme().radius) + .rounded(px(control_radius())) .text_xs() .text_color(cx.theme().text) .line_height(relative(1.25)) diff --git a/docs/ui-theme-bezel-migration.md b/docs/ui-theme-bezel-migration.md index 539f3c4c..de684b1f 100644 --- a/docs/ui-theme-bezel-migration.md +++ b/docs/ui-theme-bezel-migration.md @@ -281,51 +281,90 @@ Keep public APIs (D2). Per file, replace old token reads with new tokens, radii with ratio functions, ad-hoc spacing with `SPACE` steps where it is a real deviation, and state washes with the paint helpers. -- [ ] `button.rs` — variant table: Primary → `solid`/`on_solid` + +- [x] `button.rs` — variant table: Primary → `solid`/`on_solid` + `surface_raised_hover`-style plate hover; Secondary → `surface_raised` + `border` + `text`; Danger → `danger_strong` + `on_accent`; Warning → `warning_muted` chip treatment; Ghost → transparent + `element_hover`/`element_active`; Custom stays (sources defaults from new tokens). Size → bezel `ControlSize` ladder values via the existing `Size` enum. Radius: `button_radius()`, `bubble_radius()` - when `rounded`. -- [ ] `input/` — `input_bg`, `border_faint` resting / `border_strong` focus, - `ring`, `caret`, `selection`, `text_faint` placeholder. -- [ ] `dialog.rs` — `surface_dialog` over `scrim()`, `surface_radius()`, - frame shadows. -- [ ] `popover.rs`, `menu/` — `surface_overlay`, hairline border, - `popover_surface` stub (D6: plain wash for now). -- [ ] `tab.rs` — mapping table row: `bg` active plane, `element_hover` - hover, `text_muted`/`text` labels, `border_faint` separators. -- [ ] `nav.rs`, `nav_item.rs` — `element_hover`/`element_active` states, - `accent` selection markers. -- [ ] `title_bar.rs` — `surface`; drag handlers unchanged; height stays 36. -- [ ] `tooltip.rs` — `surface_overlay` plate, `text` label. -- [ ] `switch.rs` — on: `accent_strong` track + `on_solid` knob; off: + when `rounded`. *(Size ladder heights kept — bezel's 24px control + heights are a consumer-visible layout change, deferred.)* +- [x] `input/` — `input_bg`, `border_faint` resting / `border_strong` focus, + `ring`, `caret`, `selection`, `text_faint` placeholder. *(coop's Input + paints no frame border/ring of its own — focus mechanics untouched; + `InputEditorStyle::border` now `border_faint`, placeholder reaches + gpui-base's `muted_foreground`, now `text_faint`.)* +- [x] `dialog.rs` — `surface_dialog` over `scrim(SCRIM_ALPHA_DARK)`, + `surface_radius()`, `surface_shadows()` (entrance animation scales the + helper's shadow alphas). +- [x] `popover.rs`, `menu/` — `surface_overlay`, hairline border, + `popover_surface` stub (D6: plain wash for now). *(The floating plate + is `StyledExt::popover_style` in styled.rs; popover.rs itself paints no + theme values. Menu item corners are concentric: + `inset_radius(surface_radius(), 4)`.)* +- [x] `tab.rs` — mapping table row: `bg` active plane, `element_hover` + hover, `text_muted`/`text` labels, `border_faint` separators. *(No + separator was painted before; none added — structure frozen. Segmented + track is `surface`.)* +- [x] `nav.rs`, `nav_item.rs` — `element_hover`/`element_active` states, + `accent` selection markers. *(Resting labels are now `text_muted` — + intentional visible change; radius 12→8.)* +- [x] `title_bar.rs` — the crate never read `title_bar`/`title_bar_inactive` + (consumers paint the strip); close-button danger hover reskinned to + `danger_strong`/`on_accent`/`plate_active`. Drag handlers and heights + untouched. +- [x] `tooltip.rs` — `surface_overlay` plate, `text` label, + `control_radius()`. +- [x] `switch.rs` — on: `accent_strong` track + `on_solid` knob; off: `input_bg` track + `border`. -- [ ] `indicator.rs`, `kbd.rs` — indicator gets `success`/`busy`/`danger` - dots; kbd: `surface_raised` + `border_faint` + `text_muted`. -- [ ] `avatar.rs` — `border_faint` ring; keep blob behavior. -- [ ] `message.rs` — `bubble_radius()` on bubbles, `text_dim` for file-path / - secondary labels, `success`/`busy`/`danger` delivery states, `band` for - composer strips. -- [ ] `markdown.rs` — `code_text`/`code_wash`, `SyntaxPalette`, - `text_muted` headings hierarchy. -- [ ] `notification.rs` — `surface_overlay` plate + semantic accents. -- [ ] `group_box.rs` — `panel_radius()`, `border_faint`, `band` header. -- [ ] `dock/`, `resizable/` — `drop_target`, `drop_line`, `border_faint`. -- [ ] `scroll/` — scrollbar paints from gpui-base tokens; verify - `ScrollbarMode` setting still lands via `sync_base`. -- [ ] `root.rs` — `bg` + `border` window frame, frame shadows. -- [ ] `icon.rs` — tones from text tokens; keep the `IconName` set as-is. -- [ ] `styled.rs` — `divider()` → `border_faint` / `hairline()`; - `popover_style()` → `surface_overlay` + `surface_radius()`. Keep +- [x] `indicator.rs`, `kbd.rs` — indicator turned out to be the loading + spinner and paints no theme values (no status dots exist in `crates/ui` + — the `success`/`busy`/`danger` mapping lands at consumer call sites in + Phase 3). kbd: `surface_raised` + `border_faint` + `text_muted`. +- [x] `avatar.rs` — no ring/border is painted today; dropped the + `ghost_element_background` underlay. Blob behavior kept. +- [x] `message.rs` — `text_dim` on the author/timestamp row, `text_faint` + welcome body, `element_hover` row wash. *(No bubbles, delivery states, + composer strips or report banners live in this file — those are + consumer-side, Phase 3.)* +- [x] `markdown.rs` — `code_text`/`code_wash` (added `with_inline_code`), + heading hierarchy (`with_heading`: h1–h3 `text`, h4–h6 `text_muted`), + links `accent`. *(Fenced-code highlighting comes from gpui-base's theme + via `sync_base`; `SyntaxPalette` stays unconsumed until a coop-side + highlighter exists.)* +- [x] `notification.rs` — `surface_overlay` plate + `surface_radius()` + + `surface_shadows()`; error is a full `danger_strong` plate with + `on_accent`, success/warning icons `success`/`warning`. +- [x] `group_box.rs` — `surface` plate, `panel_radius()`. *(No border or + header strip is painted today; adding them is a design change, deferred + to Phase 3/4 review.)* +- [x] `dock/`, `resizable/` — `drop_target`, `drop_line`, `border_faint`. + *(Tab-insertion line and drop-region outline both use `drop_line` now — + slightly more prominent than the old muted outline, intentional. Drag + preview/empty-space drag-over also reskinned.)* +- [x] `scroll/` — verified: zero theme reads; scrollbar paint comes from + gpui-base via `sync_base`, `ScrollbarMode` still lands. +- [x] `root.rs` — `bg` plane, `surface_shadows()` on the window frame (now + gated on `cx.theme().shadow`); decoration consts untouched. +- [x] `icon.rs` — default tone `text_muted` (was `icon`); `IconName` set and + APIs unchanged. +- [x] `styled.rs` — `divider()` → `border_faint`; `popover_style()` → + `surface_overlay` + `px(surface_radius())` + `surface_shadows()` gated + on `cx.theme().shadow` (was always-on `shadow_md`). Keep `h_flex`/`v_flex` names (bezel's `stack::row`/`column` equivalents). -- [ ] `animation.rs` — leave as-is this pass; note bezel's named-motion - catalog as follow-up. +- [x] `animation.rs` — left as-is this pass; bezel's named-motion catalog is + a follow-up. -Validation: `cargo check -p ui`, run desktop, walk every component in -light/dark against Phase 0 screenshots. +Outcome: `theme::plate_hover`/`plate_active` were made public (wrappers over +the crate-internal `*_for` helpers) so `crates/ui` can reskin opaque plates. +Zero legacy-token reads remain in `crates/ui` (the Phase 3 grep already +passes inside this crate); `cargo check -p ui`, `cargo check --workspace`, +`cargo test -p theme` (11 tests) and `cargo clippy -p ui -p theme` are green. +Public APIs, element structure, sizes and heights unchanged (D2, D7). + +Validation remaining: run desktop, walk every component in light/dark +against the Phase 0 screenshots. ### Phase 3 — Migrate consumers