input: Add appearance option to NumberInput, Dropdown, DatePicker to render without style. (#1054)
<img width="1100" height="935" alt="image" src="https://github.com/user-attachments/assets/b213c778-eef0-4d63-8d07-b68da9a078b1" />
This commit is contained in:
parent
867b4ff3a6
commit
6508927e7f
9 changed files with 223 additions and 45 deletions
|
|
@ -1,12 +1,12 @@
|
|||
use chrono::{Datelike, Days, Duration, Utc};
|
||||
use gpui::{
|
||||
px, App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement as _, Render,
|
||||
div, px, App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement as _, Render,
|
||||
Styled as _, Subscription, Window,
|
||||
};
|
||||
use gpui_component::{
|
||||
calendar,
|
||||
date_picker::{DatePicker, DatePickerEvent, DatePickerState, DateRangePreset},
|
||||
v_flex, Sizable as _,
|
||||
v_flex, ActiveTheme as _, Sizable as _,
|
||||
};
|
||||
|
||||
use crate::section;
|
||||
|
|
@ -19,6 +19,7 @@ pub struct DatePickerStory {
|
|||
date_picker_value: Option<String>,
|
||||
date_range_picker: Entity<DatePickerState>,
|
||||
default_range_mode_picker: Entity<DatePickerState>,
|
||||
without_appearance_picker: Entity<DatePickerState>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +96,8 @@ impl DatePickerStory {
|
|||
|
||||
let default_range_mode_picker = cx.new(|cx| DatePickerState::range(window, cx));
|
||||
|
||||
let without_appearance_picker = cx.new(|cx| DatePickerState::new(window, cx));
|
||||
|
||||
let _subscriptions = vec![
|
||||
cx.subscribe(&date_picker, |this, _, ev, _| match ev {
|
||||
DatePickerEvent::Change(date) => {
|
||||
|
|
@ -120,6 +123,7 @@ impl DatePickerStory {
|
|||
data_picker_custom,
|
||||
date_range_picker,
|
||||
default_range_mode_picker,
|
||||
without_appearance_picker,
|
||||
date_picker_value: None,
|
||||
_subscriptions,
|
||||
}
|
||||
|
|
@ -133,7 +137,7 @@ impl Focusable for DatePickerStory {
|
|||
}
|
||||
|
||||
impl Render for DatePickerStory {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let presets = vec![
|
||||
DateRangePreset::single(
|
||||
"Yesterday",
|
||||
|
|
@ -216,5 +220,15 @@ impl Render for DatePickerStory {
|
|||
format!("Date picker value: {:?}", self.date_picker_value).into_element(),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section("Without Appearance").max_w_128().child(
|
||||
div().w_full().bg(cx.theme().secondary).child(
|
||||
DatePicker::new(&self.without_appearance_picker)
|
||||
.appearance(false)
|
||||
.placeholder("Without appearance")
|
||||
.cleanable(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
use gpui::{
|
||||
px, App, AppContext, Context, Entity, Focusable, InteractiveElement, IntoElement, KeyBinding,
|
||||
ParentElement, Render, SharedString, Styled, Window,
|
||||
};
|
||||
|
||||
use gpui_component::{
|
||||
checkbox::Checkbox,
|
||||
dropdown::{Dropdown, DropdownEvent, DropdownItem, DropdownState, SearchableVec},
|
||||
h_flex, v_flex, ActiveTheme, FocusableCycle, IconName, Sizable,
|
||||
};
|
||||
use gpui::*;
|
||||
use gpui_component::{button::*, checkbox::*, divider::*, dropdown::*, input::*, *};
|
||||
|
||||
use crate::section;
|
||||
use crate::{Tab, TabPrev};
|
||||
|
|
@ -58,6 +50,8 @@ pub struct DropdownStory {
|
|||
simple_dropdown2: Entity<DropdownState<SearchableVec<SharedString>>>,
|
||||
simple_dropdown3: Entity<DropdownState<Vec<SharedString>>>,
|
||||
disabled_dropdown: Entity<DropdownState<Vec<SharedString>>>,
|
||||
appearance_dropdown: Entity<DropdownState<Vec<SharedString>>>,
|
||||
input_state: Entity<InputState>,
|
||||
}
|
||||
|
||||
impl super::Story for DropdownStory {
|
||||
|
|
@ -97,6 +91,21 @@ impl DropdownStory {
|
|||
];
|
||||
|
||||
let country_dropdown = cx.new(|cx| DropdownState::new(countries, Some(6), window, cx));
|
||||
let appearance_dropdown = cx.new(|cx| {
|
||||
DropdownState::new(
|
||||
vec![
|
||||
"CN".into(),
|
||||
"US".into(),
|
||||
"HK".into(),
|
||||
"JP".into(),
|
||||
"KR".into(),
|
||||
],
|
||||
Some(0),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let input_state = cx.new(|cx| InputState::new(window, cx).placeholder("Your phone number"));
|
||||
|
||||
let fruits = SearchableVec::new(vec![
|
||||
"Apple".into(),
|
||||
|
|
@ -156,6 +165,8 @@ impl DropdownStory {
|
|||
.new(|cx| DropdownState::new(Vec::<SharedString>::new(), None, window, cx)),
|
||||
disabled_dropdown: cx
|
||||
.new(|cx| DropdownState::new(Vec::<SharedString>::new(), None, window, cx)),
|
||||
appearance_dropdown,
|
||||
input_state,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -276,6 +287,41 @@ impl Render for DropdownStory {
|
|||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section("Appearance false with TextInput")
|
||||
.max_w_128()
|
||||
.child(
|
||||
h_flex()
|
||||
.border_1()
|
||||
.border_color(cx.theme().input)
|
||||
.rounded_lg()
|
||||
.text_color(cx.theme().secondary_foreground)
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(
|
||||
div().w(px(140.)).child(
|
||||
Dropdown::new(&self.appearance_dropdown)
|
||||
.appearance(false)
|
||||
.py_2()
|
||||
.pl_3(),
|
||||
),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
div().flex_1().child(
|
||||
TextInput::new(&self.input_state)
|
||||
.appearance(false)
|
||||
.pr_3()
|
||||
.py_2(),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.p_2()
|
||||
.child(Button::new("send").small().ghost().label("Send")),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section("Selected Values").max_w_lg().child(
|
||||
v_flex()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, App, AppContext, Axis, Context, Entity, Focusable,
|
||||
div, prelude::FluentBuilder as _, px, App, AppContext, Axis, Context, Entity, Focusable,
|
||||
InteractiveElement, IntoElement, ParentElement as _, Render, Styled, Window,
|
||||
};
|
||||
use gpui_component::{
|
||||
|
|
@ -8,14 +8,16 @@ use gpui_component::{
|
|||
color_picker::{ColorPicker, ColorPickerState},
|
||||
date_picker::{DatePicker, DatePickerState},
|
||||
divider::Divider,
|
||||
dropdown::{Dropdown, DropdownState},
|
||||
form::{form_field, v_form},
|
||||
h_flex,
|
||||
input::{InputState, TextInput},
|
||||
switch::Switch,
|
||||
v_flex, AxisExt, FocusableCycle, Selectable, Sizable, Size,
|
||||
v_flex, ActiveTheme, AxisExt, FocusableCycle, Selectable, Sizable, Size,
|
||||
};
|
||||
|
||||
pub struct FormStory {
|
||||
name_prefix_state: Entity<DropdownState<Vec<String>>>,
|
||||
name_input: Entity<InputState>,
|
||||
email_input: Entity<InputState>,
|
||||
bio_input: Entity<InputState>,
|
||||
|
|
@ -50,6 +52,20 @@ impl FormStory {
|
|||
}
|
||||
|
||||
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let name_prefix_state = cx.new(|cx| {
|
||||
DropdownState::new(
|
||||
vec![
|
||||
"Mr.".to_string(),
|
||||
"Mrs.".to_string(),
|
||||
"Ms.".to_string(),
|
||||
"Dr.".to_string(),
|
||||
],
|
||||
Some(0),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let name_input = cx.new(|cx| InputState::new(window, cx).default_value("Jason Lee"));
|
||||
let color_state = cx.new(|cx| ColorPickerState::new(window, cx));
|
||||
|
||||
|
|
@ -64,6 +80,7 @@ impl FormStory {
|
|||
let date = cx.new(|cx| DatePickerState::new(window, cx));
|
||||
|
||||
Self {
|
||||
name_prefix_state,
|
||||
name_input,
|
||||
email_input,
|
||||
bio_input,
|
||||
|
|
@ -158,9 +175,23 @@ impl Render for FormStory {
|
|||
.layout(self.layout)
|
||||
.with_size(self.size)
|
||||
.child(
|
||||
form_field()
|
||||
.label_fn(|_, _| "Name")
|
||||
.child(TextInput::new(&self.name_input)),
|
||||
form_field().label_fn(|_, _| "Name").child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.rounded(cx.theme().radius)
|
||||
.child(
|
||||
div().w(px(90.)).child(
|
||||
Dropdown::new(&self.name_prefix_state)
|
||||
.pr_0()
|
||||
.appearance(false),
|
||||
),
|
||||
)
|
||||
.child(div().flex_1().child(
|
||||
TextInput::new(&self.name_input).pl_0().appearance(false),
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
form_field()
|
||||
|
|
|
|||
|
|
@ -4,11 +4,7 @@ use gpui::{
|
|||
};
|
||||
|
||||
use crate::{section, Tab, TabPrev};
|
||||
use gpui_component::{
|
||||
button::{Button, ButtonVariants as _},
|
||||
input::{InputEvent, InputState, MaskPattern, TextInput},
|
||||
v_flex, ContextModal, FocusableCycle, Icon, IconName, Sizable,
|
||||
};
|
||||
use gpui_component::{button::*, input::*, *};
|
||||
|
||||
const CONTEXT: &str = "InputStory";
|
||||
|
||||
|
|
@ -271,5 +267,26 @@ impl Render for InputStory {
|
|||
window.focused_input(cx).map(|input| input.read(cx).value())
|
||||
))),
|
||||
)
|
||||
.child(
|
||||
section("Appearance false").max_w_md().child(
|
||||
div()
|
||||
.border_1()
|
||||
.px_6()
|
||||
.py_5()
|
||||
.rounded_lg()
|
||||
.bg(if cx.theme().is_dark() {
|
||||
yellow_950()
|
||||
} else {
|
||||
yellow_50()
|
||||
})
|
||||
.text_color(if cx.theme().is_dark() {
|
||||
yellow_100()
|
||||
} else {
|
||||
yellow_900()
|
||||
})
|
||||
.w_full()
|
||||
.child(TextInput::new(&self.input1).appearance(false)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use gpui::{
|
||||
App, AppContext as _, Context, Entity, FocusHandle, Focusable, InteractiveElement, IntoElement,
|
||||
KeyBinding, ParentElement as _, Render, Styled, Subscription, Window,
|
||||
div, App, AppContext as _, Context, Entity, FocusHandle, Focusable, InteractiveElement,
|
||||
IntoElement, KeyBinding, ParentElement as _, Render, Styled, Subscription, Window,
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ use crate::{section, Tab, TabPrev};
|
|||
use gpui_component::{
|
||||
button::{Button, ButtonVariants},
|
||||
input::{InputEvent, InputState, MaskPattern, NumberInput, NumberInputEvent, StepAction},
|
||||
v_flex, FocusableCycle, IconName, Sizable,
|
||||
v_flex, ActiveTheme, FocusableCycle, IconName, Sizable,
|
||||
};
|
||||
|
||||
const CONTEXT: &str = "NumberInputStory";
|
||||
|
|
@ -27,6 +27,8 @@ pub struct NumberInputStory {
|
|||
number_input2_value: u64,
|
||||
number_input3: Entity<InputState>,
|
||||
number_input3_value: f64,
|
||||
number_input4: Entity<InputState>,
|
||||
number_input4_value: f64,
|
||||
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
|
@ -77,6 +79,15 @@ impl NumberInputStory {
|
|||
})
|
||||
});
|
||||
|
||||
let number_input4 = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.placeholder("Number Input without appearance")
|
||||
.mask_pattern(MaskPattern::Number {
|
||||
separator: Some(','),
|
||||
fraction: Some(2),
|
||||
})
|
||||
});
|
||||
|
||||
let _subscriptions = vec![
|
||||
cx.subscribe_in(&number_input1, window, Self::on_input_event),
|
||||
cx.subscribe_in(&number_input1, window, Self::on_number_input_event),
|
||||
|
|
@ -84,6 +95,8 @@ impl NumberInputStory {
|
|||
cx.subscribe_in(&number_input2, window, Self::on_number_input_event),
|
||||
cx.subscribe_in(&number_input3, window, Self::on_input_event),
|
||||
cx.subscribe_in(&number_input3, window, Self::on_number_input_event),
|
||||
cx.subscribe_in(&number_input4, window, Self::on_input_event),
|
||||
cx.subscribe_in(&number_input4, window, Self::on_number_input_event),
|
||||
];
|
||||
|
||||
Self {
|
||||
|
|
@ -93,6 +106,8 @@ impl NumberInputStory {
|
|||
number_input2_value: 0,
|
||||
number_input3,
|
||||
number_input3_value: 0.0,
|
||||
number_input4,
|
||||
number_input4_value: 0.0,
|
||||
_subscriptions,
|
||||
}
|
||||
}
|
||||
|
|
@ -162,6 +177,11 @@ impl NumberInputStory {
|
|||
this.update(cx, |input, cx| {
|
||||
input.set_value(self.number_input3_value.to_string(), window, cx);
|
||||
});
|
||||
} else if this == &self.number_input4 {
|
||||
self.number_input4_value = self.number_input4_value - 1.0;
|
||||
this.update(cx, |input, cx| {
|
||||
input.set_value(self.number_input4_value.to_string(), window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
StepAction::Increment => {
|
||||
|
|
@ -180,6 +200,11 @@ impl NumberInputStory {
|
|||
this.update(cx, |input, cx| {
|
||||
input.set_value(self.number_input3_value.to_string(), window, cx);
|
||||
});
|
||||
} else if this == &self.number_input4 {
|
||||
self.number_input4_value = self.number_input4_value + 1.0;
|
||||
this.update(cx, |input, cx| {
|
||||
input.set_value(self.number_input4_value.to_string(), window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -225,5 +250,14 @@ impl Render for NumberInputStory {
|
|||
.max_w_md()
|
||||
.child(NumberInput::new(&self.number_input3)),
|
||||
)
|
||||
.child(
|
||||
section("Without appearance").max_w_md().child(
|
||||
div()
|
||||
.w_full()
|
||||
.bg(cx.theme().secondary)
|
||||
.rounded_md()
|
||||
.child(NumberInput::new(&self.number_input4).appearance(false)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ pub struct Dropdown<D: DropdownDelegate + 'static> {
|
|||
empty: Option<AnyElement>,
|
||||
menu_width: Length,
|
||||
disabled: bool,
|
||||
appearance: bool,
|
||||
}
|
||||
|
||||
pub struct SearchableVec<T> {
|
||||
|
|
@ -533,6 +534,7 @@ where
|
|||
empty: None,
|
||||
menu_width: Length::Auto,
|
||||
disabled: false,
|
||||
appearance: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -581,6 +583,12 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Set the appearance of the dropdown, if false the dropdown input will no border, background.
|
||||
pub fn appearance(mut self, appearance: bool) -> Self {
|
||||
self.appearance = appearance;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the title element for the dropdown input.
|
||||
fn display_title(&self, _: &Window, cx: &App) -> impl IntoElement {
|
||||
let default_title = div()
|
||||
|
|
@ -714,17 +722,25 @@ where
|
|||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().input)
|
||||
.rounded(cx.theme().radius)
|
||||
.when(cx.theme().shadow, |this| this.shadow_xs())
|
||||
.map(|this| if self.disabled { this } else { this })
|
||||
.when(self.appearance, |this| {
|
||||
this.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().input)
|
||||
.rounded(cx.theme().radius)
|
||||
.when(cx.theme().shadow, |this| this.shadow_xs())
|
||||
})
|
||||
.map(|this| {
|
||||
if self.disabled {
|
||||
this.shadow_none()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
})
|
||||
.overflow_hidden()
|
||||
.input_text_size(self.size)
|
||||
.when(outline_visible, |this| this.focused_border(cx))
|
||||
.input_size(self.size)
|
||||
.input_text_size(self.size)
|
||||
.refine_style(&self.style)
|
||||
.when(outline_visible, |this| this.focused_border(cx))
|
||||
.when(allow_open, |this| {
|
||||
this.on_click(window.listener_for(&self.state, DropdownState::toggle_menu))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ pub struct NumberInput {
|
|||
size: Size,
|
||||
prefix: Option<AnyElement>,
|
||||
suffix: Option<AnyElement>,
|
||||
appearance: bool,
|
||||
}
|
||||
|
||||
impl NumberInput {
|
||||
|
|
@ -40,6 +41,7 @@ impl NumberInput {
|
|||
placeholder: SharedString::default(),
|
||||
prefix: None,
|
||||
suffix: None,
|
||||
appearance: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +76,12 @@ impl NumberInput {
|
|||
self.suffix = Some(suffix.into_any_element());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the appearance of the number input, if false will no border and background.
|
||||
pub fn appearance(mut self, appearance: bool) -> Self {
|
||||
self.appearance = appearance;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl InputState {
|
||||
|
|
@ -128,10 +136,12 @@ impl RenderOnce for NumberInput {
|
|||
.flex_1()
|
||||
.input_size(self.size)
|
||||
.px(self.size.input_px() / 2.)
|
||||
.bg(cx.theme().background)
|
||||
.border_color(cx.theme().input)
|
||||
.border_1()
|
||||
.rounded(cx.theme().radius)
|
||||
.when(self.appearance, |this| {
|
||||
this.bg(cx.theme().background)
|
||||
.border_color(cx.theme().input)
|
||||
.border_1()
|
||||
.rounded(cx.theme().radius)
|
||||
})
|
||||
.when(focused, |this| this.focused_border(cx))
|
||||
.child(
|
||||
Button::new("minus")
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ impl TextInput {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set the appearance of the input field.
|
||||
/// Set the appearance of the input field, if false the input field will no border, background.
|
||||
pub fn appearance(mut self, appearance: bool) -> Self {
|
||||
self.appearance = appearance;
|
||||
self
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@ pub struct DatePicker {
|
|||
size: Size,
|
||||
number_of_months: usize,
|
||||
presets: Option<Vec<DateRangePreset>>,
|
||||
appearance: bool,
|
||||
}
|
||||
|
||||
impl Sizable for DatePicker {
|
||||
|
|
@ -275,6 +276,7 @@ impl DatePicker {
|
|||
style: StyleRefinement::default(),
|
||||
number_of_months: 2,
|
||||
presets: None,
|
||||
appearance: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -301,6 +303,12 @@ impl DatePicker {
|
|||
self.number_of_months = number_of_months;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set appearance of the date picker, if false, the date picker will be in a minimal style.
|
||||
pub fn appearance(mut self, appearance: bool) -> Self {
|
||||
self.appearance = appearance;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for DatePicker {
|
||||
|
|
@ -337,14 +345,16 @@ impl RenderOnce for DatePicker {
|
|||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().input)
|
||||
.rounded(cx.theme().radius)
|
||||
.when(cx.theme().shadow, |this| this.shadow_xs())
|
||||
.when(self.appearance, |this| {
|
||||
this.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().input)
|
||||
.rounded(cx.theme().radius)
|
||||
.when(cx.theme().shadow, |this| this.shadow_xs())
|
||||
.when(is_focused, |this| this.focused_border(cx))
|
||||
})
|
||||
.overflow_hidden()
|
||||
.input_text_size(self.size)
|
||||
.when(is_focused, |this| this.focused_border(cx))
|
||||
.input_size(self.size)
|
||||
.when(!state.open, |this| {
|
||||
this.on_click(
|
||||
|
|
|
|||
Loading…
Reference in a new issue