Add prefix and suffix to Input. (#12)

<img width="393" alt="image"
src="https://github.com/huacnlee/gpui-component/assets/5518/5df8d269-ea04-4d42-95c5-a3358cdee9b5">
This commit is contained in:
Jason Lee 2024-07-01 23:30:06 +08:00 committed by GitHub
parent cbbd20867f
commit f84e8b82ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 132 additions and 30 deletions

View file

@ -24,10 +24,11 @@ This is an example of build app by using GPUI.
- [ ] ContextMenu to let user copy, cut, paste - [ ] ContextMenu to let user copy, cut, paste
- [ ] Selection by mouse, drag to select text - [ ] Selection by mouse, drag to select text
- [x] Cursor blinking - [x] Cursor blinking
- [ ] Textarea
- [ ] Input icon
- [x] Button - [x] Button
- [x] Button with Icon - [x] Button with Icon
- [ ] IconButton - [x] IconButton
- [ ] ToggleButton
- [x] Label - [x] Label
- [x] Icon - [x] Icon
- [x] Checkbox - [x] Checkbox

View file

@ -8,6 +8,8 @@ use ui::{
h_flex, v_flex, Clickable, Disableable as _, Icon, IconName, Selectable, h_flex, v_flex, Clickable, Disableable as _, Icon, IconName, Selectable,
}; };
use crate::section;
pub struct ButtonStory {} pub struct ButtonStory {}
impl ButtonStory { impl ButtonStory {
@ -22,23 +24,6 @@ impl ButtonStory {
impl Render for ButtonStory { impl Render for ButtonStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn section(title: impl Into<SharedString>, cx: &ViewContext<ButtonStory>) -> Div {
use ui::theme::ActiveTheme;
let theme = cx.theme();
h_flex()
.items_center()
.gap_4()
.p_4()
.w_full()
.rounded_lg()
.border_1()
.border_color(theme.border)
.flex_wrap()
.justify_around()
.child(div().flex_none().w_full().child(title.into()))
}
v_flex() v_flex()
.w_full() .w_full()
.justify_start() .justify_start()

View file

@ -3,13 +3,18 @@ use gpui::{
VisualContext, WindowContext, VisualContext, WindowContext,
}; };
use ui::{input::TextInput, v_flex}; use ui::{input::TextInput, v_flex, Icon, IconName};
use crate::section;
pub struct InputStory { pub struct InputStory {
input1: View<TextInput>, input1: View<TextInput>,
input2: View<TextInput>, input2: View<TextInput>,
mash_input: View<TextInput>, mash_input: View<TextInput>,
disabled_input: View<TextInput>, disabled_input: View<TextInput>,
prefix_input1: View<TextInput>,
suffix_input1: View<TextInput>,
both_input1: View<TextInput>,
} }
impl InputStory { impl InputStory {
@ -31,6 +36,23 @@ impl InputStory {
input input
}); });
let prefix_input1 = cx.new_view(|cx| {
TextInput::new(cx)
.prefix(IconName::Search.view(cx))
.placeholder("Search some thing...")
});
let suffix_input1 = cx.new_view(|cx| {
TextInput::new(cx)
.suffix(IconName::Info.view(cx))
.placeholder("Info here...")
});
let both_input1 = cx.new_view(|cx| {
TextInput::new(cx)
.prefix(IconName::Search.view(cx))
.suffix(IconName::Info.view(cx))
.placeholder("This input have prefix and suffix.")
});
Self { Self {
input1, input1,
input2: cx.new_view(|cx| { input2: cx.new_view(|cx| {
@ -45,6 +67,9 @@ impl InputStory {
input.set_disabled(true, cx); input.set_disabled(true, cx);
input input
}), }),
prefix_input1,
suffix_input1,
both_input1,
} }
} }
@ -55,14 +80,26 @@ impl InputStory {
} }
impl Render for InputStory { impl Render for InputStory {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex() v_flex()
.size_full() .size_full()
.justify_start() .justify_start()
.gap_3() .gap_3()
.child(
section("Normal Input", cx)
.child(self.input1.clone()) .child(self.input1.clone())
.child(self.input2.clone()) .child(self.input2.clone()),
)
.child(
section("Input State", cx)
.child(self.disabled_input.clone()) .child(self.disabled_input.clone())
.child(self.mash_input.clone()) .child(self.mash_input.clone()),
)
.child(
section("Preifx and Suffix", cx)
.child(self.prefix_input1.clone())
.child(self.both_input1.clone())
.child(self.suffix_input1.clone()),
)
} }
} }

View file

@ -19,7 +19,7 @@ pub use switch_story::SwitchStory;
pub use tooltip_story::TooltipStory; pub use tooltip_story::TooltipStory;
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, EventEmitter, div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, EventEmitter,
FocusableView, IntoElement, ParentElement, Render, SharedString, Styled as _, Task, View, FocusableView, IntoElement, ParentElement, Render, SharedString, Styled as _, Task, View,
ViewContext, VisualContext, WindowContext, ViewContext, VisualContext, WindowContext,
}; };
@ -30,7 +30,7 @@ use workspace::{
}; };
use anyhow::Result; use anyhow::Result;
use ui::{divider::Divider, label::Label, v_flex}; use ui::{divider::Divider, h_flex, label::Label, v_flex};
pub fn story_case( pub fn story_case(
name: &'static str, name: &'static str,
@ -40,6 +40,23 @@ pub fn story_case(
StoryContainer::new(name, description, cx) StoryContainer::new(name, description, cx)
} }
pub fn section(title: impl Into<SharedString>, cx: &WindowContext) -> Div {
use ui::theme::ActiveTheme;
let theme = cx.theme();
h_flex()
.items_center()
.gap_4()
.p_4()
.w_full()
.rounded_lg()
.border_1()
.border_color(theme.border)
.flex_wrap()
.justify_around()
.child(div().flex_none().w_full().child(title.into()))
}
pub struct StoryContainer { pub struct StoryContainer {
focus_handle: gpui::FocusHandle, focus_handle: gpui::FocusHandle,
name: SharedString, name: SharedString,

View file

@ -1,7 +1,9 @@
use std::ops::Deref;
use crate::{button::ButtonSize, theme::ActiveTheme}; use crate::{button::ButtonSize, theme::ActiveTheme};
use gpui::{ use gpui::{
prelude::FluentBuilder as _, svg, AnyElement, Hsla, IntoElement, RenderOnce, SharedString, prelude::FluentBuilder as _, svg, AnyElement, AnyView, Hsla, IntoElement, Render, RenderOnce,
StyleRefinement, Styled, Svg, WindowContext, SharedString, StyleRefinement, Styled, Svg, View, VisualContext, WindowContext,
}; };
#[derive(IntoElement)] #[derive(IntoElement)]
@ -44,6 +46,11 @@ impl IconName {
} }
.into() .into()
} }
/// Return the icon as a View<Icon>
pub fn view(self, cx: &mut WindowContext) -> View<Icon> {
Icon::new(self).view(cx)
}
} }
impl From<IconName> for Icon { impl From<IconName> for Icon {
@ -52,6 +59,12 @@ impl From<IconName> for Icon {
} }
} }
impl From<IconName> for AnyElement {
fn from(val: IconName) -> Self {
Icon::new(val).into_any_element()
}
}
impl RenderOnce for IconName { impl RenderOnce for IconName {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement { fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
Icon::new(self) Icon::new(self)
@ -89,6 +102,11 @@ impl Icon {
self self
} }
/// Create a new view for the icon
pub fn view(self, cx: &mut WindowContext) -> View<Icon> {
cx.new_view(|_| self)
}
} }
impl Styled for Icon { impl Styled for Icon {
@ -122,3 +140,20 @@ impl From<Icon> for AnyElement {
val.into_any_element() val.into_any_element()
} }
} }
impl Render for Icon {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
let text_color = self.text_color.unwrap_or_else(|| cx.theme().foreground);
svg()
.flex_none()
.size_4()
.text_color(text_color)
.map(|this| match self.size {
ButtonSize::XSmall => this.size_3(),
ButtonSize::Small => this.size_3p5(),
ButtonSize::Medium => this.size_4(),
})
.path(self.path.clone())
}
}

View file

@ -73,6 +73,8 @@ pub fn init(cx: &mut AppContext) {
pub struct TextInput { pub struct TextInput {
focus_handle: FocusHandle, focus_handle: FocusHandle,
text: SharedString, text: SharedString,
prefix: Option<AnyView>,
suffix: Option<AnyView>,
placeholder: SharedString, placeholder: SharedString,
blink_cursor: Model<BlinkCursor>, blink_cursor: Model<BlinkCursor>,
selected_range: Range<usize>, selected_range: Range<usize>,
@ -102,6 +104,8 @@ impl TextInput {
disabled: false, disabled: false,
masked: false, masked: false,
appearance: true, appearance: true,
prefix: None,
suffix: None,
}; };
// Observe the blink cursor to repaint the view when it changes. // Observe the blink cursor to repaint the view when it changes.
@ -158,6 +162,24 @@ impl TextInput {
self self
} }
/// Set the prefix element of the input field, for example a search Icon.
pub fn prefix(mut self, prefix: impl Into<AnyView>) -> Self {
self.prefix = Some(prefix.into());
self
}
/// Set the placeholder text of the input field.
pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
self.placeholder = placeholder.into();
self
}
/// Set the suffix element of the input field, for example a clear button.
pub fn suffix(mut self, suffix: impl Into<AnyView>) -> Self {
self.suffix = Some(suffix.into());
self
}
/// Return the text of the input field. /// Return the text of the input field.
pub fn text(&self) -> SharedString { pub fn text(&self) -> SharedString {
self.text.clone() self.text.clone()
@ -609,6 +631,7 @@ impl Element for TextElement {
impl Render for TextInput { impl Render for TextInput {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let focused = self.focus_handle.is_focused(cx); let focused = self.focus_handle.is_focused(cx);
div() div()
.flex() .flex()
.key_context("TextInput") .key_context("TextInput")
@ -654,8 +677,12 @@ impl Render for TextInput {
cx.theme().background cx.theme().background
}) })
}) })
.child(div().w_full().child(TextElement { .when_some(self.prefix.clone(), |this, prefix| this.child(prefix))
.gap_1()
.items_center()
.child(div().flex_grow().overflow_x_hidden().child(TextElement {
input: cx.view().clone(), input: cx.view().clone(),
})) }))
.when_some(self.suffix.clone(), |this, suffix| this.child(suffix))
} }
} }