input: Improve clear button position in Input. (#782)

| Before | After |
| -- | -- |
| <img width="529" alt="image"
src="https://github.com/user-attachments/assets/02fdba71-7e83-43b9-ba4c-476eb21dcf28"
/> | <img width="530" alt="image"
src="https://github.com/user-attachments/assets/ad7b6378-c9ff-4ea4-943a-87af53a2ac79"
/> |
This commit is contained in:
Jason Lee 2025-04-11 19:13:17 +08:00 committed by GitHub
parent d04866eb77
commit 73be54a0f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 63 additions and 31 deletions

View file

@ -11,7 +11,7 @@ use gpui_component::{
checkbox::Checkbox, checkbox::Checkbox,
h_flex, h_flex,
input::{InputEvent, NumberInput, NumberInputEvent, OtpInput, StepAction, TextInput}, input::{InputEvent, NumberInput, NumberInputEvent, OtpInput, StepAction, TextInput},
v_flex, FocusableCycle, IconName, Sizable, v_flex, FocusableCycle, Icon, IconName, Sizable,
}; };
actions!(input_story, [Tab, TabPrev]); actions!(input_story, [Tab, TabPrev]);
@ -139,21 +139,33 @@ impl InputStory {
let prefix_input1 = cx.new(|cx| { let prefix_input1 = cx.new(|cx| {
TextInput::new(window, cx) TextInput::new(window, cx)
.prefix(|_, _| div().child(IconName::Search).ml_3()) .prefix(|_, _| div().child(Icon::new(IconName::Search).small()).ml_3())
.placeholder("Search some thing...") .placeholder("Search some thing...")
.cleanable() .cleanable()
}); });
let suffix_input1 = cx.new(|cx| { let suffix_input1 = cx.new(|cx| {
TextInput::new(window, cx) TextInput::new(window, cx)
.suffix(|_, _| div().child(IconName::Info).mr_3()) .suffix(|_, _| {
Button::new("info")
.ghost()
.icon(IconName::Info)
.xsmall()
.mr_3()
})
.placeholder("This input only support [a-zA-Z0-9] characters.") .placeholder("This input only support [a-zA-Z0-9] characters.")
.pattern(regex::Regex::new(r"^[a-zA-Z0-9]*$").unwrap()) .pattern(regex::Regex::new(r"^[a-zA-Z0-9]*$").unwrap())
.cleanable() .cleanable()
}); });
let both_input1 = cx.new(|cx| { let both_input1 = cx.new(|cx| {
TextInput::new(window, cx) TextInput::new(window, cx)
.prefix(|_, _| div().child(IconName::Search).ml_3()) .prefix(|_, _| div().child(Icon::new(IconName::Search).small()).ml_3())
.suffix(|_, _| div().child(IconName::Info).mr_3()) .suffix(|_, _| {
Button::new("info")
.ghost()
.icon(IconName::Info)
.xsmall()
.mr_3()
})
.cleanable() .cleanable()
.placeholder("This input have prefix and suffix.") .placeholder("This input have prefix and suffix.")
}); });

View file

@ -149,3 +149,9 @@ Modal:
zh-CN: 取消 zh-CN: 取消
zh-HK: 取消 zh-HK: 取消
it: Annulla it: Annulla
List:
search_placeholder:
en: Search...
zh-CN: 搜索...
zh-HK: 搜索...
it: Ricerca...

View file

@ -355,6 +355,7 @@ impl RenderOnce for Button {
self.base self.base
.id(self.id) .id(self.id)
.cursor_default()
.flex() .flex()
.items_center() .items_center()
.justify_center() .justify_center()

View file

@ -33,7 +33,7 @@ use crate::history::History;
use crate::indicator::Indicator; use crate::indicator::Indicator;
use crate::input::clear_button; use crate::input::clear_button;
use crate::scroll::{Scrollbar, ScrollbarAxis, ScrollbarState}; use crate::scroll::{Scrollbar, ScrollbarAxis, ScrollbarState};
use crate::StyledExt; use crate::{h_flex, StyledExt};
use crate::{ActiveTheme, Root}; use crate::{ActiveTheme, Root};
use crate::{IconName, Size}; use crate::{IconName, Size};
use crate::{Sizable, StyleSized}; use crate::{Sizable, StyleSized};
@ -1756,6 +1756,13 @@ impl Render for TextInput {
let prefix = self.prefix.as_ref().map(|build| build(window, cx)); let prefix = self.prefix.as_ref().map(|build| build(window, cx));
let suffix = self.suffix.as_ref().map(|build| build(window, cx)); let suffix = self.suffix.as_ref().map(|build| build(window, cx));
let show_clear_button =
self.cleanable && !self.loading && !self.text.is_empty() && self.is_single_line();
let bg = if self.disabled {
cx.theme().muted
} else {
cx.theme().background
};
div() div()
.flex() .flex()
@ -1815,40 +1822,45 @@ impl Render for TextInput {
.when_some(self.height, |this, height| this.h(height)) .when_some(self.height, |this, height| this.h(height))
}) })
.when(self.appearance, |this| { .when(self.appearance, |this| {
this.bg(if self.disabled { this.bg(bg)
cx.theme().muted .border_color(cx.theme().input)
} else { .border_1()
cx.theme().background .rounded(cx.theme().radius)
}) .when(cx.theme().shadow, |this| this.shadow_sm())
.border_color(cx.theme().input) .when(focused, |this| this.focused_border(cx))
.border_1()
.rounded(cx.theme().radius)
.when(cx.theme().shadow, |this| this.shadow_sm())
.when(focused, |this| this.focused_border(cx))
}) })
.when(prefix.is_none(), |this| this.input_pl(self.size)) .when(prefix.is_none(), |this| this.input_pl(self.size))
.when(suffix.is_none(), |this| this.input_pr(self.size)) .input_pr(self.size)
.children(prefix)
.gap(gap_x)
.items_center() .items_center()
.gap(gap_x)
.children(prefix)
.child( .child(
div() div()
.id("TextElement") .id("text-element")
.flex_1() .flex_1()
.when(self.is_multi_line(), |this| this.h_full()) .when(self.is_multi_line(), |this| this.h_full())
.flex_grow() .flex_grow()
.overflow_x_hidden() .overflow_x_hidden()
.child(TextElement::new(cx.entity().clone())), .child(TextElement::new(cx.entity().clone())),
) )
.when(self.loading, |this| { .child(
this.child(Indicator::new().color(cx.theme().muted_foreground)) h_flex()
}) .id("suffix")
.children(self.render_toggle_mask_button(window, cx)) .absolute()
.when( .gap(gap_x)
self.cleanable && !self.loading && !self.text.is_empty() && self.is_single_line(), .bg(bg)
|this| this.child(clear_button(cx).on_click(cx.listener(Self::clean))), .items_center()
.when(suffix.is_none(), |this| this.pr_1())
.right_0()
.when(self.loading, |this| {
this.child(Indicator::new().color(cx.theme().muted_foreground))
})
.children(self.render_toggle_mask_button(window, cx))
.when(show_clear_button, |this| {
this.child(clear_button(cx).on_click(cx.listener(Self::clean)))
})
.children(suffix),
) )
.children(suffix)
.when(self.is_multi_line(), |this| { .when(self.is_multi_line(), |this| {
let entity_id = cx.entity().entity_id(); let entity_id = cx.entity().entity_id();
if self.last_layout.is_some() { if self.last_layout.is_some() {

View file

@ -164,8 +164,8 @@ impl Render for NumberInput {
// Sync size to input at first. // Sync size to input at first.
self.sync_size_to_input_if_needed(window, cx); self.sync_size_to_input_if_needed(window, cx);
let btn_size = match self.size { let btn_size = match self.size {
Size::XSmall | Size::Small => Size::XSmall, Size::XSmall | Size::Small => Size::Size(px(16.)),
_ => Size::Small, _ => Size::XSmall,
}; };
h_flex() h_flex()

View file

@ -15,6 +15,7 @@ use gpui::{
Window, Window,
}; };
use gpui::{px, App, Context, EventEmitter, MouseDownEvent, ScrollStrategy, Subscription}; use gpui::{px, App, Context, EventEmitter, MouseDownEvent, ScrollStrategy, Subscription};
use rust_i18n::t;
use smol::Timer; use smol::Timer;
use super::loading::Loading; use super::loading::Loading;
@ -173,7 +174,7 @@ where
TextInput::new(window, cx) TextInput::new(window, cx)
.appearance(false) .appearance(false)
.prefix(|_, cx| Icon::new(IconName::Search).text_color(cx.theme().muted_foreground)) .prefix(|_, cx| Icon::new(IconName::Search).text_color(cx.theme().muted_foreground))
.placeholder("Search...") .placeholder(t!("List.search_placeholder"))
.cleanable() .cleanable()
}); });