commit 022c1e7a50653ed78630ac8a3e76b3140efa40c6
parent 483bc94c4331c1fdd63f7400a4845ff8384c396e
Author: triesap <tyson@radroots.org>
Date: Sun, 19 Apr 2026 02:20:00 +0000
ui: restyle shared text inputs
Diffstat:
4 files changed, 173 insertions(+), 35 deletions(-)
diff --git a/crates/launchers/desktop/src/window.rs b/crates/launchers/desktop/src/window.rs
@@ -6,9 +6,9 @@ use gpui::{
transparent_black,
};
use gpui_component::{
- IconName, Root, Sizable, Size as ComponentSize,
+ IconName, Root,
button::{Button, ButtonCustomVariant, ButtonRounded, ButtonVariants},
- input::{Input, InputEvent, InputState},
+ input::{InputEvent, InputState},
};
use radroots_app_i18n::AppTextKey;
pub use radroots_app_models::SettingsSection as SettingsPanelViewKey;
@@ -35,7 +35,7 @@ use radroots_app_state::{
use radroots_app_ui::{
APP_UI_THEME, AppCheckboxFieldSpec, IconSegmentButtonSpec, LabelValueRow, action_button,
action_button_compact, action_button_primary, action_button_primary_disabled,
- action_icon_button, app_checkbox_field, app_shared_label_text, app_shared_text,
+ action_icon_button, app_checkbox_field, app_shared_label_text, app_shared_text, app_text_input,
app_window_shell, icon_segment_button, label_value_list, section_divider, status_indicator,
utility_title_row,
};
@@ -4280,8 +4280,7 @@ fn startup_signer_entry_surface(
.max_w(px(APP_UI_THEME.layout.home_card_max_width_px))
.id("home-signer-source-input")
.child(
- Input::new(&signer_entry.input)
- .with_size(ComponentSize::Large)
+ app_text_input(&signer_entry.input, !source_input_is_editable)
.disabled(!source_input_is_editable)
.w_full(),
),
@@ -4991,8 +4990,7 @@ fn products_controls_card(
.gap(px(APP_UI_THEME.layout.home_stack_gap_px))
.when_some(products_search, |this, products_search| {
this.child(
- Input::new(&products_search.input)
- .with_size(ComponentSize::Large)
+ app_text_input(&products_search.input, false)
.cleanable(true)
.w_full(),
)
@@ -5449,11 +5447,7 @@ fn products_stock_editor_card(
.text_color(rgb(APP_UI_THEME.text.secondary))
.child(app_shared_text(AppTextKey::ProductsStockEditorFieldLabel)),
)
- .child(
- Input::new(&editor.input)
- .with_size(ComponentSize::Large)
- .w_full(),
- )
+ .child(app_text_input(&editor.input, false).w_full())
.when_some(validation_key, |this, key| {
this.child(
div()
@@ -5641,7 +5635,7 @@ fn products_editor_text_field(
.items_start()
.gap(px(8.0))
.child(home_farm_setup_field_label(field_label_key))
- .child(Input::new(input).with_size(ComponentSize::Large).w_full())
+ .child(app_text_input(input, false).w_full())
.when_some(validation_key, |this, validation_key| {
this.child(home_body_text(app_shared_text(validation_key)))
})
@@ -6005,12 +5999,7 @@ fn home_farm_setup_text_field(
.items_start()
.gap(px(6.0))
.child(home_farm_setup_field_label(field_label_key))
- .child(
- Input::new(input)
- .with_size(ComponentSize::Large)
- .w_full()
- .into_any_element(),
- )
+ .child(app_text_input(input, false).w_full().into_any_element())
.when_some(blocker_key, |this, blocker_key| {
this.child(home_farm_setup_blocker(blocker_key))
}),
@@ -6134,12 +6123,7 @@ fn settings_text_field(label_key: AppTextKey, input: &Entity<InputState>) -> imp
.flex_col()
.gap(px(6.0))
.child(home_farm_setup_field_label(label_key))
- .child(
- Input::new(input)
- .with_size(ComponentSize::Large)
- .w_full()
- .into_any_element(),
- )
+ .child(app_text_input(input, false).w_full().into_any_element())
}
fn settings_pickup_location_title(
diff --git a/crates/shared/ui/src/lib.rs b/crates/shared/ui/src/lib.rs
@@ -6,8 +6,9 @@ mod theme;
pub use primitives::{
AppCheckboxFieldSpec, IconSegmentButtonSpec, LabelValueRow, action_button,
- action_button_compact, action_icon_button, app_card, app_center_stage, app_checkbox,
- app_checkbox_field, app_window_shell, icon_segment_button, label_value_list, section_divider,
+ action_button_compact, action_button_primary, action_button_primary_disabled,
+ action_icon_button, app_card, app_center_stage, app_checkbox, app_checkbox_field,
+ app_text_input, app_window_shell, icon_segment_button, label_value_list, section_divider,
status_indicator, utility_title_row,
};
pub use text::{
@@ -18,5 +19,5 @@ pub use theme::{
APP_UI_THEME, ActionButtonColors, ActionButtonSizing, ActionButtonTokens, AppControlTokens,
AppLayoutTokens, AppSurfaceTokens, AppTextTokens, AppTypographyTokens, AppUiTheme,
AppWindowTokens, CheckboxTokens, IconSegmentButtonColors, IconSegmentButtonSizing,
- IconSegmentButtonTokens, StatusIndicatorTokens,
+ IconSegmentButtonTokens, StatusIndicatorTokens, TextInputTokens,
};
diff --git a/crates/shared/ui/src/primitives.rs b/crates/shared/ui/src/primitives.rs
@@ -1,15 +1,22 @@
use gpui::{
- App, ClickEvent, IntoElement, ParentElement, SharedString, Styled, Window, div,
+ App, ClickEvent, Entity, IntoElement, ParentElement, SharedString, Styled, Window, div,
prelude::FluentBuilder, px, relative, rgb, transparent_black,
};
use gpui_component::{
Icon, IconName, Sizable, Size,
button::{Button, ButtonCustomVariant, ButtonRounded, ButtonVariants},
+ input::{Input, InputState},
};
use std::rc::Rc;
use crate::APP_UI_THEME;
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum ActionButtonVariant {
+ Standard,
+ Primary,
+}
+
pub struct IconSegmentButtonSpec {
pub id: &'static str,
pub label: SharedString,
@@ -303,6 +310,24 @@ pub fn icon_segment_button(
)
}
+pub fn app_text_input(input: &Entity<InputState>, disabled: bool) -> Input {
+ let tokens = APP_UI_THEME.controls.text_input;
+ let background = if disabled {
+ tokens.disabled_background
+ } else {
+ tokens.background
+ };
+
+ Input::new(input)
+ .with_size(Size::Medium)
+ .disabled(disabled)
+ .focus_bordered(false)
+ .bg(rgb(background))
+ .border_color(rgb(tokens.border))
+ .border_1()
+ .rounded(px(tokens.corner_radius_px))
+}
+
pub fn action_button(
id: &'static str,
label: impl Into<SharedString>,
@@ -310,13 +335,49 @@ pub fn action_button(
cx: &App,
) -> impl IntoElement {
action_button_label(
- action_button_base(id, on_click, cx),
+ action_button_base(id, ActionButtonVariant::Standard, on_click, cx),
+ label.into(),
+ APP_UI_THEME
+ .controls
+ .action_button
+ .sizing
+ .horizontal_padding_px,
+ ActionButtonVariant::Standard,
+ )
+}
+
+pub fn action_button_primary(
+ id: &'static str,
+ label: impl Into<SharedString>,
+ on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ cx: &App,
+) -> impl IntoElement {
+ action_button_label(
+ action_button_base(id, ActionButtonVariant::Primary, on_click, cx),
+ label.into(),
+ APP_UI_THEME
+ .controls
+ .action_button
+ .sizing
+ .horizontal_padding_px,
+ ActionButtonVariant::Primary,
+ )
+}
+
+pub fn action_button_primary_disabled(
+ id: &'static str,
+ label: impl Into<SharedString>,
+ cx: &App,
+) -> impl IntoElement {
+ action_button_label(
+ action_button_base_disabled(id, ActionButtonVariant::Primary, cx),
label.into(),
APP_UI_THEME
.controls
.action_button
.sizing
.horizontal_padding_px,
+ ActionButtonVariant::Primary,
)
}
@@ -327,13 +388,14 @@ pub fn action_button_compact(
cx: &App,
) -> impl IntoElement {
action_button_label(
- action_button_base(id, on_click, cx),
+ action_button_base(id, ActionButtonVariant::Standard, on_click, cx),
label.into(),
APP_UI_THEME
.controls
.action_button
.sizing
.compact_horizontal_padding_px,
+ ActionButtonVariant::Standard,
)
}
@@ -341,9 +403,10 @@ fn action_button_label(
button: Button,
label: SharedString,
horizontal_padding_px: f32,
+ variant: ActionButtonVariant,
) -> impl IntoElement {
let sizing = APP_UI_THEME.controls.action_button.sizing;
- let colors = APP_UI_THEME.controls.action_button.colors;
+ let colors = action_button_colors(variant);
button.child(
div()
.h_full()
@@ -364,9 +427,9 @@ pub fn action_icon_button(
cx: &App,
) -> impl IntoElement {
let sizing = APP_UI_THEME.controls.action_button.sizing;
- let colors = APP_UI_THEME.controls.action_button.colors;
+ let colors = action_button_colors(ActionButtonVariant::Standard);
- action_button_base(id, on_click, cx)
+ action_button_base(id, ActionButtonVariant::Standard, on_click, cx)
.with_size(Size::Size(px(sizing.square_width_px)))
.icon(
Icon::new(icon)
@@ -386,11 +449,12 @@ pub fn status_indicator(color: u32) -> impl IntoElement {
fn action_button_base(
id: &'static str,
+ variant: ActionButtonVariant,
on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
cx: &App,
) -> Button {
let sizing = APP_UI_THEME.controls.action_button.sizing;
- let colors = APP_UI_THEME.controls.action_button.colors;
+ let colors = action_button_colors(variant);
let hover_background = if colors.hover_changes_background {
colors.hover_background
} else {
@@ -411,6 +475,38 @@ fn action_button_base(
.on_click(on_click)
}
+fn action_button_base_disabled(id: &'static str, variant: ActionButtonVariant, cx: &App) -> Button {
+ let sizing = APP_UI_THEME.controls.action_button.sizing;
+ let colors = action_button_disabled_colors(variant);
+
+ Button::new(id)
+ .custom(
+ ButtonCustomVariant::new(cx)
+ .color(rgb(colors.background).into())
+ .foreground(rgb(colors.foreground).into())
+ .border(transparent_black())
+ .hover(rgb(colors.hover_background).into())
+ .active(rgb(colors.active_background).into()),
+ )
+ .rounded(ButtonRounded::Size(px(sizing.corner_radius_px)))
+ .h(px(sizing.height_px))
+}
+
+fn action_button_colors(variant: ActionButtonVariant) -> crate::ActionButtonColors {
+ match variant {
+ ActionButtonVariant::Standard => APP_UI_THEME.controls.action_button.colors,
+ ActionButtonVariant::Primary => APP_UI_THEME.controls.action_button.primary_colors,
+ }
+}
+
+fn action_button_disabled_colors(variant: ActionButtonVariant) -> crate::ActionButtonColors {
+ match variant {
+ ActionButtonVariant::Standard | ActionButtonVariant::Primary => {
+ APP_UI_THEME.controls.action_button.disabled_colors
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use gpui_component::IconName;
diff --git a/crates/shared/ui/src/theme.rs b/crates/shared/ui/src/theme.rs
@@ -37,6 +37,8 @@ pub struct AppTypographyTokens {
pub utility_title_text_px: f32,
pub body_text_px: f32,
pub brand_text_px: f32,
+ pub startup_title_text_px: f32,
+ pub startup_tagline_text_px: f32,
pub settings_row_text_px: f32,
pub settings_account_identity_text_px: f32,
pub settings_account_detail_text_px: f32,
@@ -50,6 +52,7 @@ pub struct AppLayoutTokens {
pub home_card_max_width_px: f32,
pub home_card_padding_px: f32,
pub home_stack_gap_px: f32,
+ pub startup_stack_gap_px: f32,
pub metadata_row_gap_px: f32,
pub utility_title_row_height_px: f32,
pub settings_chrome_height_px: f32,
@@ -84,6 +87,7 @@ pub struct AppLayoutTokens {
pub struct AppControlTokens {
pub icon_segment_button: IconSegmentButtonTokens,
pub action_button: ActionButtonTokens,
+ pub text_input: TextInputTokens,
pub checkbox: CheckboxTokens,
pub status_indicator: StatusIndicatorTokens,
}
@@ -115,6 +119,8 @@ pub struct IconSegmentButtonColors {
pub struct ActionButtonTokens {
pub sizing: ActionButtonSizing,
pub colors: ActionButtonColors,
+ pub primary_colors: ActionButtonColors,
+ pub disabled_colors: ActionButtonColors,
}
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -149,6 +155,14 @@ pub struct CheckboxTokens {
}
#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct TextInputTokens {
+ pub background: u32,
+ pub disabled_background: u32,
+ pub border: u32,
+ pub corner_radius_px: f32,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
pub struct StatusIndicatorTokens {
pub size_px: f32,
pub online: u32,
@@ -179,6 +193,8 @@ pub const APP_UI_THEME: AppUiTheme = AppUiTheme {
utility_title_text_px: 12.0,
body_text_px: 14.0,
brand_text_px: 14.0,
+ startup_title_text_px: 18.0,
+ startup_tagline_text_px: 16.0,
settings_row_text_px: 13.0,
settings_account_identity_text_px: 14.0,
settings_account_detail_text_px: 14.0,
@@ -190,6 +206,7 @@ pub const APP_UI_THEME: AppUiTheme = AppUiTheme {
home_card_max_width_px: 1080.0,
home_card_padding_px: 24.0,
home_stack_gap_px: 12.0,
+ startup_stack_gap_px: 6.0,
metadata_row_gap_px: 12.0,
utility_title_row_height_px: 24.0,
settings_chrome_height_px: 88.0,
@@ -252,6 +269,26 @@ pub const APP_UI_THEME: AppUiTheme = AppUiTheme {
hover_background: 0xDCDCE1,
active_background: 0xD1D1D6,
},
+ primary_colors: ActionButtonColors {
+ background: 0x0A84FF,
+ foreground: 0xFFFFFF,
+ hover_changes_background: true,
+ hover_background: 0x007AFF,
+ active_background: 0x0062CC,
+ },
+ disabled_colors: ActionButtonColors {
+ background: 0xA7C8F8,
+ foreground: 0xFFFFFF,
+ hover_changes_background: false,
+ hover_background: 0xA7C8F8,
+ active_background: 0xA7C8F8,
+ },
+ },
+ text_input: TextInputTokens {
+ background: 0xFFFFFF,
+ disabled_background: 0xF2F2F7,
+ border: 0xD1D1D6,
+ corner_radius_px: 10.0,
},
checkbox: CheckboxTokens {
size_px: 16.0,
@@ -314,6 +351,7 @@ mod tests {
fn control_tokens_match_the_frozen_budget() {
let segmented = APP_UI_THEME.controls.icon_segment_button.sizing;
let action = APP_UI_THEME.controls.action_button.sizing;
+ let text_input = APP_UI_THEME.controls.text_input;
let checkbox = APP_UI_THEME.controls.checkbox;
let status = APP_UI_THEME.controls.status_indicator;
@@ -323,6 +361,9 @@ mod tests {
assert_eq!(action.height_px, 24.0);
assert_eq!(action.corner_radius_px, 8.0);
assert_eq!(action.square_width_px, 24.0);
+ assert_eq!(text_input.corner_radius_px, 10.0);
+ assert_eq!(text_input.background, 0xFFFFFF);
+ assert_eq!(text_input.disabled_background, 0xF2F2F7);
assert_eq!(checkbox.size_px, 16.0);
assert_eq!(checkbox.corner_radius_px, 5.0);
assert_eq!(status.size_px, 12.0);
@@ -331,6 +372,22 @@ mod tests {
#[test]
fn accent_and_status_colors_match_the_current_shell_contract() {
assert_eq!(APP_UI_THEME.text.accent, 0x0A84FF);
+ assert_eq!(
+ APP_UI_THEME
+ .controls
+ .action_button
+ .primary_colors
+ .background,
+ 0x0A84FF
+ );
+ assert_eq!(
+ APP_UI_THEME
+ .controls
+ .action_button
+ .primary_colors
+ .foreground,
+ 0xFFFFFF
+ );
assert_eq!(APP_UI_THEME.controls.checkbox.checked_background, 0x0A84FF);
assert_eq!(APP_UI_THEME.controls.status_indicator.online, 0x34C759);
assert_eq!(APP_UI_THEME.controls.status_indicator.offline, 0xFFD60A);