diff --git a/crates/story/src/date_picker_story.rs b/crates/story/src/date_picker_story.rs index 80a95467..58b7b31c 100644 --- a/crates/story/src/date_picker_story.rs +++ b/crates/story/src/date_picker_story.rs @@ -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, date_range_picker: Entity, default_range_mode_picker: Entity, + without_appearance_picker: Entity, _subscriptions: Vec, } @@ -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) -> impl IntoElement { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> 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(), + ), + ), + ) } } diff --git a/crates/story/src/dropdown_story.rs b/crates/story/src/dropdown_story.rs index 3bbf0deb..7046b239 100644 --- a/crates/story/src/dropdown_story.rs +++ b/crates/story/src/dropdown_story.rs @@ -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>>, simple_dropdown3: Entity>>, disabled_dropdown: Entity>>, + appearance_dropdown: Entity>>, + input_state: Entity, } 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::::new(), None, window, cx)), disabled_dropdown: cx .new(|cx| DropdownState::new(Vec::::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() diff --git a/crates/story/src/form_story.rs b/crates/story/src/form_story.rs index 267af4db..0b45f8a4 100644 --- a/crates/story/src/form_story.rs +++ b/crates/story/src/form_story.rs @@ -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>>, name_input: Entity, email_input: Entity, bio_input: Entity, @@ -50,6 +52,20 @@ impl FormStory { } fn new(window: &mut Window, cx: &mut Context) -> 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() diff --git a/crates/story/src/input_story.rs b/crates/story/src/input_story.rs index 57b20b4e..18d3491e 100644 --- a/crates/story/src/input_story.rs +++ b/crates/story/src/input_story.rs @@ -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)), + ), + ) } } diff --git a/crates/story/src/number_input_story.rs b/crates/story/src/number_input_story.rs index 3b48949e..72f6cc70 100644 --- a/crates/story/src/number_input_story.rs +++ b/crates/story/src/number_input_story.rs @@ -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, number_input3_value: f64, + number_input4: Entity, + number_input4_value: f64, _subscriptions: Vec, } @@ -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)), + ), + ) } } diff --git a/crates/ui/src/dropdown.rs b/crates/ui/src/dropdown.rs index f93d0f5d..28350b57 100644 --- a/crates/ui/src/dropdown.rs +++ b/crates/ui/src/dropdown.rs @@ -264,6 +264,7 @@ pub struct Dropdown { empty: Option, menu_width: Length, disabled: bool, + appearance: bool, } pub struct SearchableVec { @@ -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)) }) diff --git a/crates/ui/src/input/number_input.rs b/crates/ui/src/input/number_input.rs index 46cacf3c..a30d21d4 100644 --- a/crates/ui/src/input/number_input.rs +++ b/crates/ui/src/input/number_input.rs @@ -29,6 +29,7 @@ pub struct NumberInput { size: Size, prefix: Option, suffix: Option, + 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") diff --git a/crates/ui/src/input/text_input.rs b/crates/ui/src/input/text_input.rs index fadb7874..3d99b427 100644 --- a/crates/ui/src/input/text_input.rs +++ b/crates/ui/src/input/text_input.rs @@ -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 diff --git a/crates/ui/src/time/date_picker.rs b/crates/ui/src/time/date_picker.rs index bd5e3ab6..7db0f38b 100644 --- a/crates/ui/src/time/date_picker.rs +++ b/crates/ui/src/time/date_picker.rs @@ -238,6 +238,7 @@ pub struct DatePicker { size: Size, number_of_months: usize, presets: Option>, + 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(