app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 022c1e7a50653ed78630ac8a3e76b3140efa40c6
parent 483bc94c4331c1fdd63f7400a4845ff8384c396e
Author: triesap <tyson@radroots.org>
Date:   Sun, 19 Apr 2026 02:20:00 +0000

ui: restyle shared text inputs

Diffstat:
Mcrates/launchers/desktop/src/window.rs | 34+++++++++-------------------------
Mcrates/shared/ui/src/lib.rs | 7++++---
Mcrates/shared/ui/src/primitives.rs | 110++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mcrates/shared/ui/src/theme.rs | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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);