diff --git a/Cargo.lock b/Cargo.lock index 7e34f1e055..7ba4f1f6e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14684,6 +14684,7 @@ dependencies = [ "language", "log", "menu", + "notifications", "paths", "project", "schemars", @@ -14695,6 +14696,7 @@ dependencies = [ "tree-sitter-json", "tree-sitter-rust", "ui", + "ui_input", "util", "workspace", "workspace-hack", diff --git a/assets/icons/play_filled.svg b/assets/icons/play_filled.svg new file mode 100644 index 0000000000..387304ef04 --- /dev/null +++ b/assets/icons/play_filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs index 29f7a8f50d..3c24ee59f6 100644 --- a/crates/icons/src/icons.rs +++ b/crates/icons/src/icons.rs @@ -191,6 +191,7 @@ pub enum IconName { Play, PlayAlt, PlayBug, + PlayFilled, Plus, PocketKnife, Power, diff --git a/crates/settings_ui/Cargo.toml b/crates/settings_ui/Cargo.toml index 2791876117..4502d994e7 100644 --- a/crates/settings_ui/Cargo.toml +++ b/crates/settings_ui/Cargo.toml @@ -26,6 +26,7 @@ gpui.workspace = true language.workspace = true log.workspace = true menu.workspace = true +notifications.workspace = true paths.workspace = true project.workspace = true schemars.workspace = true @@ -37,6 +38,7 @@ theme.workspace = true tree-sitter-json.workspace = true tree-sitter-rust.workspace = true ui.workspace = true +ui_input.workspace = true util.workspace = true workspace-hack.workspace = true workspace.workspace = true diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 210ec827cc..a5008e17a0 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -10,20 +10,22 @@ use feature_flags::FeatureFlagViewExt; use fs::Fs; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - Action, AnimationExt, AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, Entity, - EventEmitter, FocusHandle, Focusable, Global, IsZero, KeyContext, KeyDownEvent, Keystroke, - ModifiersChangedEvent, MouseButton, Point, ScrollStrategy, ScrollWheelEvent, StyledText, - Subscription, WeakEntity, actions, anchored, deferred, div, + Action, Animation, AnimationExt, AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, + Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, IsZero, KeyContext, + KeyDownEvent, Keystroke, ModifiersChangedEvent, MouseButton, Point, ScrollStrategy, + ScrollWheelEvent, StyledText, Subscription, WeakEntity, actions, anchored, deferred, div, }; use language::{Language, LanguageConfig, ToOffset as _}; +use notifications::status_toast::{StatusToast, ToastIcon}; use settings::{BaseKeymap, KeybindSource, KeymapFile, SettingsAssets}; use util::ResultExt; use ui::{ - ActiveTheme as _, App, Banner, BorrowAppContext, ContextMenu, ParentElement as _, Render, - SharedString, Styled as _, Tooltip, Window, prelude::*, + ActiveTheme as _, App, Banner, BorrowAppContext, ContextMenu, Modal, ModalFooter, ModalHeader, + ParentElement as _, Render, Section, SharedString, Styled as _, Tooltip, Window, prelude::*, }; +use ui_input::SingleLineInput; use workspace::{ Item, ModalView, SerializableItem, Workspace, notifications::NotifyTaskExt as _, register_serializable_item, @@ -637,7 +639,8 @@ impl KeymapEditor { "Delete", Box::new(DeleteBinding), ) - .action("Copy action", Box::new(CopyAction)) + .separator() + .action("Copy Action", Box::new(CopyAction)) .action_disabled_when( selected_binding_has_no_context, "Copy Context", @@ -845,9 +848,15 @@ impl KeymapEditor { self.search_mode = self.search_mode.invert(); self.update_matches(cx); + // Update the keystroke editor to turn the `search` bool on + self.keystroke_editor.update(cx, |keystroke_editor, cx| { + keystroke_editor.set_search_mode(self.search_mode == SearchMode::KeyStroke); + cx.notify(); + }); + match self.search_mode { SearchMode::KeyStroke => { - window.focus(&self.keystroke_editor.focus_handle(cx)); + window.focus(&self.keystroke_editor.read(cx).recording_focus_handle(cx)); } SearchMode::Normal => {} } @@ -964,41 +973,60 @@ impl Render for KeymapEditor { .gap_1() .bg(theme.colors().editor_background) .child( - h_flex() + v_flex() .p_2() - .gap_1() - .key_context({ - let mut context = KeyContext::new_with_defaults(); - context.add("BufferSearchBar"); - context - }) + .gap_2() .child( - div() - .size_full() - .h_8() - .pl_2() - .pr_1() - .py_1() - .border_1() - .border_color(theme.colors().border) - .rounded_lg() - .child(self.filter_editor.clone()), - ) - .child( - // TODO: Ask Mikyala if there's a way to get have items be aligned by horizontally - // without embedding a h_flex in another h_flex h_flex() + .gap_2() + .child( + div() + .key_context({ + let mut context = KeyContext::new_with_defaults(); + context.add("BufferSearchBar"); + context + }) + .size_full() + .h_8() + .pl_2() + .pr_1() + .py_1() + .border_1() + .border_color(theme.colors().border) + .rounded_lg() + .child(self.filter_editor.clone()), + ) + .child( + IconButton::new( + "KeymapEditorToggleFiltersIcon", + IconName::Keyboard, + ) + .shape(ui::IconButtonShape::Square) + .tooltip(|window, cx| { + Tooltip::for_action( + "Search by Keystroke", + &ToggleKeystrokeSearch, + window, + cx, + ) + }) + .toggle_state(matches!(self.search_mode, SearchMode::KeyStroke)) + .on_click(|_, window, cx| { + window.dispatch_action(ToggleKeystrokeSearch.boxed_clone(), cx); + }), + ) .when(self.keybinding_conflict_state.any_conflicts(), |this| { this.child( IconButton::new("KeymapEditorConflictIcon", IconName::Warning) + .shape(ui::IconButtonShape::Square) .tooltip({ let filter_state = self.filter_state; move |window, cx| { Tooltip::for_action( match filter_state { - FilterState::All => "Show conflicts", - FilterState::Conflicts => "Hide conflicts", + FilterState::All => "Show Conflicts", + FilterState::Conflicts => "Hide Conflicts", }, &ToggleConflictFilter, window, @@ -1006,7 +1034,7 @@ impl Render for KeymapEditor { ) } }) - .selected_icon_color(Color::Error) + .selected_icon_color(Color::Warning) .toggle_state(matches!( self.filter_state, FilterState::Conflicts @@ -1018,36 +1046,22 @@ impl Render for KeymapEditor { ); }), ) - }) - .child( - IconButton::new("KeymapEditorToggleFiltersIcon", IconName::Filter) - .tooltip(|window, cx| { - Tooltip::for_action( - "Toggle Keystroke Search", - &ToggleKeystrokeSearch, - window, - cx, - ) - }) - .toggle_state(matches!(self.search_mode, SearchMode::KeyStroke)) - .on_click(|_, window, cx| { - window.dispatch_action( - ToggleKeystrokeSearch.boxed_clone(), - cx, - ); - }), - ), - ), + }), + ) + .when(matches!(self.search_mode, SearchMode::KeyStroke), |this| { + this.child( + div() + .map(|this| { + if self.keybinding_conflict_state.any_conflicts() { + this.pr(rems_from_px(54.)) + } else { + this.pr_7() + } + }) + .child(self.keystroke_editor.clone()), + ) + }), ) - .when(matches!(self.search_mode, SearchMode::KeyStroke), |this| { - this.child( - div() - .child(self.keystroke_editor.clone()) - .border_1() - .border_color(theme.colors().border) - .rounded_lg(), - ) - }) .child( Table::new() .interactable(&self.table_interaction_state) @@ -1063,20 +1077,17 @@ impl Render for KeymapEditor { .filter_map(|index| { let candidate_id = this.matches.get(index)?.candidate_id; let binding = &this.keybindings[candidate_id]; + let action_name = binding.action_name.clone(); let action = div() - .child(binding.action_name.clone()) .id(("keymap action", index)) + .child(command_palette::humanize_action_name(&action_name)) .when(!context_menu_deployed, |this| { this.tooltip({ let action_name = binding.action_name.clone(); let action_docs = binding.action_docs; move |_, cx| { - let action_tooltip = Tooltip::new( - command_palette::humanize_action_name( - &action_name, - ), - ); + let action_tooltip = Tooltip::new(&action_name); let action_tooltip = match action_docs { Some(docs) => action_tooltip.meta(docs), None => action_tooltip, @@ -1285,11 +1296,12 @@ struct KeybindingEditorModal { editing_keybind: ProcessedKeybinding, editing_keybind_idx: usize, keybind_editor: Entity, - context_editor: Entity, + context_editor: Entity, input_editor: Option>, fs: Arc, error: Option, keymap_editor: Entity, + workspace: WeakEntity, } impl ModalView for KeybindingEditorModal {} @@ -1316,25 +1328,28 @@ impl KeybindingEditorModal { let keybind_editor = cx .new(|cx| KeystrokeInput::new(editing_keybind.keystrokes().map(Vec::from), window, cx)); - let context_editor = cx.new(|cx| { - let mut editor = Editor::single_line(window, cx); + let context_editor: Entity = cx.new(|cx| { + let input = SingleLineInput::new(window, cx, "Keybinding Context") + .label("Edit Context") + .label_size(LabelSize::Default); if let Some(context) = editing_keybind .context .as_ref() .and_then(KeybindContextString::local) { - editor.set_text(context.clone(), window, cx); - } else { - editor.set_placeholder_text("Keybinding context", cx); + input.editor().update(cx, |editor, cx| { + editor.set_text(context.clone(), window, cx); + }); } - cx.spawn(async |editor, cx| { + let editor_entity = input.editor().clone(); + cx.spawn(async move |_input_handle, cx| { let contexts = cx .background_spawn(async { collect_contexts_from_assets() }) .await; - editor + editor_entity .update(cx, |editor, _cx| { editor.set_completion_provider(Some(std::rc::Rc::new( KeyContextCompletionProvider { contexts }, @@ -1344,17 +1359,19 @@ impl KeybindingEditorModal { }) .detach_and_log_err(cx); - editor + input }); let input_editor = editing_keybind.action_schema.clone().map(|_schema| { cx.new(|cx| { let mut editor = Editor::auto_height_unbounded(1, window, cx); + let workspace = workspace.clone(); + if let Some(input) = editing_keybind.action_input.clone() { editor.set_text(input.text, window, cx); } else { // TODO: default value from schema? - editor.set_placeholder_text("Action input", cx); + editor.set_placeholder_text("Action Input", cx); } cx.spawn(async |editor, cx| { let json_language = load_json_language(workspace, cx).await; @@ -1383,6 +1400,7 @@ impl KeybindingEditorModal { input_editor, error: None, keymap_editor, + workspace, } } @@ -1431,7 +1449,7 @@ impl KeybindingEditorModal { let tab_size = cx.global::().json_tab_size(); let new_context = self .context_editor - .read_with(cx, |editor, cx| editor.text(cx)); + .read_with(cx, |input, cx| input.editor().read(cx).text(cx)); let new_context = new_context.is_empty().not().then_some(new_context); let new_context_err = new_context.as_deref().and_then(|context| { gpui::KeyBindingContextPredicate::parse(context) @@ -1503,6 +1521,25 @@ impl KeybindingEditorModal { let create = self.creating; + let status_toast = StatusToast::new( + format!( + "Saved edits to the {} action.", + command_palette::humanize_action_name(&self.editing_keybind.action_name) + ), + cx, + move |this, _cx| { + this.icon(ToastIcon::new(IconName::Check).color(Color::Success)) + .dismiss_button(true) + // .action("Undo", f) todo: wire the undo functionality + }, + ); + + self.workspace + .update(cx, |workspace, cx| { + workspace.toggle_status_toast(status_toast, cx); + }) + .log_err(); + cx.spawn(async move |this, cx| { if let Err(err) = save_keybinding_update( create, @@ -1533,90 +1570,96 @@ impl KeybindingEditorModal { impl Render for KeybindingEditorModal { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let theme = cx.theme().colors(); - let input_base = || { - div() - .w_full() - .py_2() - .px_3() - .min_h_8() - .rounded_md() - .bg(theme.editor_background) - .border_1() - .border_color(theme.border_variant) - }; + let action_name = + command_palette::humanize_action_name(&self.editing_keybind.action_name).to_string(); - v_flex() - .w(rems(34.)) - .elevation_3(cx) - .child( - v_flex() - .p_3() - .child(Label::new("Edit Keystroke")) - .child( - Label::new("Input the desired keystroke for the selected action.") - .color(Color::Muted) - .mb_2(), - ) - .child(self.keybind_editor.clone()), - ) - .when_some(self.input_editor.clone(), |this, editor| { - this.child( - v_flex() - .p_3() - .pt_0() - .child(Label::new("Edit Input")) - .child( - Label::new("Input the desired input to the binding.") - .color(Color::Muted) - .mb_2(), - ) - .child(input_base().child(editor)), + v_flex().w(rems(34.)).elevation_3(cx).child( + Modal::new("keybinding_editor_modal", None) + .header( + ModalHeader::new().child( + v_flex() + .pb_1p5() + .mb_1() + .gap_0p5() + .border_b_1() + .border_color(theme.border_variant) + .child(Label::new(action_name)) + .when_some(self.editing_keybind.action_docs, |this, docs| { + this.child( + Label::new(docs).size(LabelSize::Small).color(Color::Muted), + ) + }), + ), ) - }) - .child( - v_flex() - .p_3() - .pt_0() - .child(Label::new("Edit Context")) - .child( - Label::new("Input the desired context for the binding.") - .color(Color::Muted) - .mb_2(), - ) - .child(input_base().child(self.context_editor.clone())), - ) - .when_some(self.error.as_ref(), |this, error| { - this.child( - div().p_2().child( - Banner::new() - .map(|banner| match error { - InputError::Error(_) => banner.severity(ui::Severity::Error), - InputError::Warning(_) => banner.severity(ui::Severity::Warning), + .section( + Section::new().child( + v_flex() + .gap_2() + .child( + v_flex() + .child(Label::new("Edit Keystroke")) + .gap_1() + .child(self.keybind_editor.clone()), + ) + .when_some(self.input_editor.clone(), |this, editor| { + this.child( + v_flex() + .mt_1p5() + .gap_1() + .child(Label::new("Edit Arguments")) + .child( + div() + .w_full() + .py_1() + .px_1p5() + .rounded_lg() + .bg(theme.editor_background) + .border_1() + .border_color(theme.border_variant) + .child(editor), + ), + ) }) - // For some reason, the div overflows its container to the - // right. The padding accounts for that. - .child(div().size_full().pr_2().child(Label::new(error.content()))), + .child(self.context_editor.clone()) + .when_some(self.error.as_ref(), |this, error| { + this.child( + Banner::new() + .map(|banner| match error { + InputError::Error(_) => { + banner.severity(ui::Severity::Error) + } + InputError::Warning(_) => { + banner.severity(ui::Severity::Warning) + } + }) + // For some reason, the div overflows its container to the + //right. The padding accounts for that. + .child( + div() + .size_full() + .pr_2() + .child(Label::new(error.content())), + ), + ) + }), ), ) - }) - .child( - h_flex() - .p_2() - .w_full() - .gap_1() - .justify_end() - .border_t_1() - .border_color(theme.border_variant) - .child( - Button::new("cancel", "Cancel") - .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))), - ) - .child( - Button::new("save-btn", "Save").on_click( - cx.listener(|this, _event, _window, cx| Self::save(this, cx)), - ), + .footer( + ModalFooter::new().end_slot( + h_flex() + .gap_1() + .child( + Button::new("cancel", "Cancel") + .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))), + ) + .child(Button::new("save-btn", "Save").on_click(cx.listener( + |this, _event, _window, cx| { + this.save(cx); + }, + ))), ), - ) + ), + ) } } @@ -1848,6 +1891,7 @@ struct KeystrokeInput { inner_focus_handle: FocusHandle, intercept_subscription: Option, _focus_subscriptions: [Subscription; 2], + search: bool, } impl KeystrokeInput { @@ -1870,6 +1914,7 @@ impl KeystrokeInput { outer_focus_handle, intercept_subscription: None, _focus_subscriptions, + search: false, } } @@ -1987,6 +2032,14 @@ impl KeystrokeInput { )) }) } + + fn recording_focus_handle(&self, _cx: &App) -> FocusHandle { + self.inner_focus_handle.clone() + } + + fn set_search_mode(&mut self, search: bool) { + self.search = search; + } } impl EventEmitter<()> for KeystrokeInput {} @@ -2000,7 +2053,84 @@ impl Focusable for KeystrokeInput { impl Render for KeystrokeInput { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let colors = cx.theme().colors(); - let is_inner_focused = self.inner_focus_handle.is_focused(window); + let is_focused = self.outer_focus_handle.contains_focused(window, cx); + let is_recording = self.inner_focus_handle.is_focused(window); + + let horizontal_padding = rems_from_px(64.); + + let recording_bg_color = colors + .editor_background + .blend(colors.text_accent.opacity(0.1)); + + let recording_indicator = h_flex() + .h_4() + .pr_1() + .gap_0p5() + .border_1() + .border_color(colors.border) + .bg(colors + .editor_background + .blend(colors.text_accent.opacity(0.1))) + .rounded_sm() + .child( + Icon::new(IconName::Circle) + .size(IconSize::Small) + .color(Color::Error) + .with_animation( + "recording-pulse", + Animation::new(std::time::Duration::from_secs(2)) + .repeat() + .with_easing(gpui::pulsating_between(0.4, 0.8)), + { + let color = Color::Error.color(cx); + move |this, delta| this.color(Color::Custom(color.opacity(delta))) + }, + ), + ) + .child( + Label::new("REC") + .size(LabelSize::XSmall) + .weight(FontWeight::SEMIBOLD) + .color(Color::Error), + ); + + let search_indicator = h_flex() + .h_4() + .pr_1() + .gap_0p5() + .border_1() + .border_color(colors.border) + .bg(colors + .editor_background + .blend(colors.text_accent.opacity(0.1))) + .rounded_sm() + .child( + Icon::new(IconName::Circle) + .size(IconSize::Small) + .color(Color::Accent) + .with_animation( + "recording-pulse", + Animation::new(std::time::Duration::from_secs(2)) + .repeat() + .with_easing(gpui::pulsating_between(0.4, 0.8)), + { + let color = Color::Accent.color(cx); + move |this, delta| this.color(Color::Custom(color.opacity(delta))) + }, + ), + ) + .child( + Label::new("SEARCH") + .size(LabelSize::XSmall) + .weight(FontWeight::SEMIBOLD) + .color(Color::Accent), + ); + + let record_icon = if self.search { + IconName::MagnifyingGlass + } else { + IconName::PlayFilled + }; return h_flex() .id("keystroke-input") @@ -2008,18 +2138,23 @@ impl Render for KeystrokeInput { .py_2() .px_3() .gap_2() - .min_h_8() + .min_h_10() .w_full() .flex_1() .justify_between() - .rounded_md() + .rounded_lg() .overflow_hidden() - .bg(colors.editor_background) - .border_2() + .map(|this| { + if is_recording { + this.bg(recording_bg_color) + } else { + this.bg(colors.editor_background) + } + }) + .border_1() .border_color(colors.border_variant) - .focus(|mut s| { - s.border_color = Some(colors.border_focused); - s + .when(is_focused, |parent| { + parent.border_color(colors.border_focused) }) .on_key_down(cx.listener(|this, event: &KeyDownEvent, window, cx| { // TODO: replace with action @@ -2028,19 +2163,29 @@ impl Render for KeystrokeInput { cx.notify(); } })) + .child( + h_flex() + .w(horizontal_padding) + .gap_0p5() + .justify_start() + .flex_none() + .when(is_recording, |this| { + this.map(|this| { + if self.search { + this.child(search_indicator) + } else { + this.child(recording_indicator) + } + }) + }), + ) .child( h_flex() .id("keystroke-input-inner") .track_focus(&self.inner_focus_handle) .on_modifiers_changed(cx.listener(Self::on_modifiers_changed)) .on_key_up(cx.listener(Self::on_key_up)) - .when(self.highlight_on_focus, |this| { - this.focus(|mut style| { - style.border_color = Some(colors.border_focused); - style - }) - }) - .w_full() + .size_full() .min_w_0() .justify_center() .flex_wrap() @@ -2049,40 +2194,52 @@ impl Render for KeystrokeInput { ) .child( h_flex() + .w(horizontal_padding) .gap_0p5() + .justify_end() .flex_none() - .when(is_inner_focused, |this| { - this.child( - Icon::new(IconName::Circle) - .color(Color::Error) - .with_animation( - "recording-pulse", - gpui::Animation::new(std::time::Duration::from_secs(1)) - .repeat() - .with_easing(gpui::pulsating_between(0.8, 1.0)), - { - let color = Color::Error.color(cx); - move |this, delta| { - this.color(Color::Custom(color.opacity(delta))) + .map(|this| { + if is_recording { + this.child( + IconButton::new("stop-record-btn", IconName::StopFilled) + .shape(ui::IconButtonShape::Square) + .map(|this| { + if self.search { + this.tooltip(Tooltip::text("Stop Searching")) + } else { + this.tooltip(Tooltip::text("Stop Recording")) } - }, - ), - ) + }) + .icon_color(Color::Error) + .on_click(cx.listener(|this, _event, window, _cx| { + this.outer_focus_handle.focus(window); + })), + ) + } else { + this.child( + IconButton::new("record-btn", record_icon) + .shape(ui::IconButtonShape::Square) + .map(|this| { + if self.search { + this.tooltip(Tooltip::text("Start Searching")) + } else { + this.tooltip(Tooltip::text("Start Recording")) + } + }) + .when(!is_focused, |this| this.icon_color(Color::Muted)) + .on_click(cx.listener(|this, _event, window, _cx| { + this.inner_focus_handle.focus(window); + })), + ) + } }) .child( - IconButton::new("backspace-btn", IconName::Delete) - .tooltip(Tooltip::text("Delete Keystroke")) - .when(!is_inner_focused, |this| this.icon_color(Color::Muted)) - .on_click(cx.listener(|this, _event, _window, cx| { - this.keystrokes.pop(); - cx.emit(()); - cx.notify(); - })), - ) - .child( - IconButton::new("clear-btn", IconName::Eraser) + IconButton::new("clear-btn", IconName::Delete) + .shape(ui::IconButtonShape::Square) .tooltip(Tooltip::text("Clear Keystrokes")) - .when(!is_inner_focused, |this| this.icon_color(Color::Muted)) + .when(!is_recording || !is_focused, |this| { + this.icon_color(Color::Muted) + }) .on_click(cx.listener(|this, _event, _window, cx| { this.keystrokes.clear(); cx.emit(()); diff --git a/crates/ui_input/src/ui_input.rs b/crates/ui_input/src/ui_input.rs index bd99814cb3..ca2dea36df 100644 --- a/crates/ui_input/src/ui_input.rs +++ b/crates/ui_input/src/ui_input.rs @@ -27,6 +27,8 @@ pub struct SingleLineInput { /// /// Its position is determined by the [`FieldLabelLayout`]. label: Option, + /// The size of the label text. + label_size: LabelSize, /// The placeholder text for the text field. placeholder: SharedString, /// Exposes the underlying [`Entity`] to allow for customizing the editor beyond the provided API. @@ -59,6 +61,7 @@ impl SingleLineInput { Self { label: None, + label_size: LabelSize::Small, placeholder: placeholder_text, editor, start_icon: None, @@ -76,6 +79,11 @@ impl SingleLineInput { self } + pub fn label_size(mut self, size: LabelSize) -> Self { + self.label_size = size; + self + } + pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context) { self.disabled = disabled; self.editor @@ -138,7 +146,7 @@ impl Render for SingleLineInput { .when_some(self.label.clone(), |this, label| { this.child( Label::new(label) - .size(LabelSize::Small) + .size(self.label_size) .color(if self.disabled { Color::Disabled } else { @@ -148,16 +156,17 @@ impl Render for SingleLineInput { }) .child( h_flex() + .min_w_48() + .min_h_8() + .w_full() .px_2() .py_1p5() - .bg(style.background_color) + .flex_grow() .text_color(style.text_color) - .rounded_md() + .rounded_lg() + .bg(style.background_color) .border_1() .border_color(style.border_color) - .min_w_48() - .w_full() - .flex_grow() .when_some(self.start_icon, |this, icon| { this.gap_1() .child(Icon::new(icon).size(IconSize::Small).color(Color::Muted)) @@ -173,16 +182,28 @@ impl Component for SingleLineInput { } fn preview(window: &mut Window, cx: &mut App) -> Option { - let input_1 = - cx.new(|cx| SingleLineInput::new(window, cx, "placeholder").label("Some Label")); + let input_small = + cx.new(|cx| SingleLineInput::new(window, cx, "placeholder").label("Small Label")); + + let input_regular = cx.new(|cx| { + SingleLineInput::new(window, cx, "placeholder") + .label("Regular Label") + .label_size(LabelSize::Default) + }); Some( v_flex() .gap_6() - .children(vec![example_group(vec![single_example( - "Default", - div().child(input_1.clone()).into_any_element(), - )])]) + .children(vec![example_group(vec![ + single_example( + "Small Label (Default)", + div().child(input_small.clone()).into_any_element(), + ), + single_example( + "Regular Label", + div().child(input_regular.clone()).into_any_element(), + ), + ])]) .into_any_element(), ) }