chrore: Refactor the disabled API for NumberInput, DatePicker, OtpInput, Calandar. (#1102)

- Added `disabled` method to OtpInput.
- Added `disabled` method to DatePicker.
- Added `disabled_matcher` method to CalendarState.

## Break Changes

- The `InputState` has been removed `disabled` and `set_disabled`
method, the disabled state should assign from TextInput element.

```diff
- let state = InputState::new("input1").disabled(true)
+ let state = InputState::new("input1");
+ TextInput::new(&state).disabled(true)
```

- Renamed `set_disabled` method to `set_disabled_matcher` from
`CalendarState`.
- Removed `set_disabled` method from `DatePickerState`, use
`disabled_matcher` method in `DatePicker` element.

---------

Co-authored-by: Jason Lee <huacnlee@gmail.com>
This commit is contained in:
obito 2025-07-29 17:31:52 +08:00 committed by GitHub
parent c07bc4aaad
commit 7487662343
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 167 additions and 82 deletions

View file

@ -13,6 +13,7 @@ pub struct CalendarStory {
focus_handle: FocusHandle, focus_handle: FocusHandle,
calendar: Entity<CalendarState>, calendar: Entity<CalendarState>,
calendar_wide: Entity<CalendarState>, calendar_wide: Entity<CalendarState>,
calendar_with_disabled_matcher: Entity<CalendarState>,
} }
impl super::Story for CalendarStory { impl super::Story for CalendarStory {
@ -37,10 +38,13 @@ impl CalendarStory {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let calendar = cx.new(|cx| CalendarState::new(window, cx)); let calendar = cx.new(|cx| CalendarState::new(window, cx));
let calendar_wide = cx.new(|cx| CalendarState::new(window, cx)); let calendar_wide = cx.new(|cx| CalendarState::new(window, cx));
let calendar_with_disabled_matcher =
cx.new(|cx| CalendarState::new(window, cx).disabled_matcher(vec![0, 3, 6]));
Self { Self {
calendar, calendar,
calendar_wide, calendar_wide,
calendar_with_disabled_matcher,
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
} }
} }
@ -66,5 +70,10 @@ impl Render for CalendarStory {
.max_w_md() .max_w_md()
.child(Calendar::new(&self.calendar_wide).number_of_months(3)), .child(Calendar::new(&self.calendar_wide).number_of_months(3)),
) )
.child(
section("With Disabled matcher (Sundays, Wednesdays, Saturdays)")
.max_w_md()
.child(Calendar::new(&self.calendar_with_disabled_matcher)),
)
} }
} }

View file

@ -45,18 +45,17 @@ impl DatePickerStory {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let now = chrono::Local::now().naive_local().date(); let now = chrono::Local::now().naive_local().date();
let date_picker = cx.new(|cx| { let date_picker = cx.new(|cx| {
let mut picker = DatePickerState::new(window, cx); let mut picker = DatePickerState::new(window, cx).disabled_matcher(vec![0, 6]);
picker.set_date(now, window, cx); picker.set_date(now, window, cx);
picker.set_disabled(vec![0, 6], window, cx);
picker picker
}); });
let date_picker_large = cx.new(|cx| { let date_picker_large = cx.new(|cx| {
let mut picker = DatePickerState::new(window, cx).date_format("%Y-%m-%d"); let mut picker = DatePickerState::new(window, cx)
picker.set_disabled( .date_format("%Y-%m-%d")
calendar::Matcher::range(Some(now), now.checked_add_days(Days::new(7))), .disabled_matcher(calendar::Matcher::range(
window, Some(now),
cx, now.checked_add_days(Days::new(7)),
); ));
picker.set_date( picker.set_date(
now.checked_sub_days(Days::new(1)).unwrap_or_default(), now.checked_sub_days(Days::new(1)).unwrap_or_default(),
window, window,
@ -65,22 +64,15 @@ impl DatePickerStory {
picker picker
}); });
let date_picker_small = cx.new(|cx| { let date_picker_small = cx.new(|cx| {
let mut picker = DatePickerState::new(window, cx); let mut picker = DatePickerState::new(window, cx).disabled_matcher(
picker.set_disabled(
calendar::Matcher::interval(Some(now), now.checked_add_days(Days::new(5))), calendar::Matcher::interval(Some(now), now.checked_add_days(Days::new(5))),
window,
cx,
); );
picker.set_date(now, window, cx); picker.set_date(now, window, cx);
picker picker
}); });
let data_picker_custom = cx.new(|cx| { let data_picker_custom = cx.new(|cx| {
let mut picker = DatePickerState::new(window, cx); let mut picker = DatePickerState::new(window, cx)
picker.set_disabled( .disabled_matcher(calendar::Matcher::custom(|date| date.day0() < 5));
calendar::Matcher::custom(|date| date.day0() < 5),
window,
cx,
);
picker.set_date(now, window, cx); picker.set_date(now, window, cx);
picker picker
}); });

View file

@ -8,7 +8,7 @@ use crate::{section, Tab, TabPrev};
use gpui_component::{ use gpui_component::{
button::{Button, ButtonVariants}, button::{Button, ButtonVariants},
input::{InputEvent, InputState, MaskPattern, NumberInput, NumberInputEvent, StepAction}, input::{InputEvent, InputState, MaskPattern, NumberInput, NumberInputEvent, StepAction},
v_flex, ActiveTheme, FocusableCycle, IconName, Sizable, v_flex, ActiveTheme, Disableable, FocusableCycle, IconName, Sizable,
}; };
const CONTEXT: &str = "NumberInputStory"; const CONTEXT: &str = "NumberInputStory";
@ -29,6 +29,7 @@ pub struct NumberInputStory {
number_input3_value: f64, number_input3_value: f64,
number_input4: Entity<InputState>, number_input4: Entity<InputState>,
number_input4_value: f64, number_input4_value: f64,
disabled_input: Entity<InputState>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@ -88,6 +89,12 @@ impl NumberInputStory {
}) })
}); });
let disabled_input = cx.new(|cx| {
InputState::new(window, cx)
.default_value("100")
.placeholder("Disabled input")
});
let _subscriptions = vec![ let _subscriptions = vec![
cx.subscribe_in(&number_input1, window, Self::on_input_event), cx.subscribe_in(&number_input1, window, Self::on_input_event),
cx.subscribe_in(&number_input1, window, Self::on_number_input_event), cx.subscribe_in(&number_input1, window, Self::on_number_input_event),
@ -97,6 +104,8 @@ impl NumberInputStory {
cx.subscribe_in(&number_input3, window, Self::on_number_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_input_event),
cx.subscribe_in(&number_input4, window, Self::on_number_input_event), cx.subscribe_in(&number_input4, window, Self::on_number_input_event),
cx.subscribe_in(&disabled_input, window, Self::on_input_event),
cx.subscribe_in(&disabled_input, window, Self::on_number_input_event),
]; ];
Self { Self {
@ -108,6 +117,7 @@ impl NumberInputStory {
number_input3_value: 0.0, number_input3_value: 0.0,
number_input4, number_input4,
number_input4_value: 0.0, number_input4_value: 0.0,
disabled_input,
_subscriptions, _subscriptions,
} }
} }
@ -238,6 +248,11 @@ impl Render for NumberInputStory {
.max_w_md() .max_w_md()
.child(NumberInput::new(&self.number_input1)), .child(NumberInput::new(&self.number_input1)),
) )
.child(
section("Disabled")
.max_w_md()
.child(NumberInput::new(&self.disabled_input).disabled(true)),
)
.child( .child(
section("Small Size with suffix").max_w_md().child( section("Small Size with suffix").max_w_md().child(
NumberInput::new(&self.number_input2) NumberInput::new(&self.number_input2)

View file

@ -7,7 +7,7 @@ use gpui_component::{
checkbox::Checkbox, checkbox::Checkbox,
h_flex, h_flex,
input::{InputEvent, OtpInput, OtpState}, input::{InputEvent, OtpInput, OtpState},
v_flex, FocusableCycle, Sizable, StyledExt, v_flex, Disableable as _, FocusableCycle, Sizable, StyledExt,
}; };
use crate::{section, Tab, TabPrev}; use crate::{section, Tab, TabPrev};
@ -28,6 +28,7 @@ pub struct OtpInputStory {
otp_state_small: Entity<OtpState>, otp_state_small: Entity<OtpState>,
otp_state_large: Entity<OtpState>, otp_state_large: Entity<OtpState>,
otp_state_sized: Entity<OtpState>, otp_state_sized: Entity<OtpState>,
otp_state_disabled: Entity<OtpState>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@ -88,6 +89,11 @@ impl OtpInputStory {
.masked(true) .masked(true)
.default_value("654321") .default_value("654321")
}), }),
otp_state_disabled: cx.new(|cx| {
OtpState::new(6, window, cx)
.masked(true)
.default_value("123456")
}),
_subscriptions, _subscriptions,
} }
} }
@ -102,17 +108,20 @@ impl OtpInputStory {
fn toggle_opt_masked(&mut self, _: &bool, window: &mut Window, cx: &mut Context<Self>) { fn toggle_opt_masked(&mut self, _: &bool, window: &mut Window, cx: &mut Context<Self>) {
self.otp_masked = !self.otp_masked; self.otp_masked = !self.otp_masked;
self.otp_state.update(cx, |input, cx| { self.otp_state.update(cx, |state, cx| {
input.set_masked(self.otp_masked, window, cx) state.set_masked(self.otp_masked, window, cx)
}); });
self.otp_state_small.update(cx, |input, cx| { self.otp_state_small.update(cx, |state, cx| {
input.set_masked(self.otp_masked, window, cx) state.set_masked(self.otp_masked, window, cx)
}); });
self.otp_state_large.update(cx, |input, cx| { self.otp_state_large.update(cx, |state, cx| {
input.set_masked(self.otp_masked, window, cx) state.set_masked(self.otp_masked, window, cx)
}); });
self.otp_state_sized.update(cx, |input, cx| { self.otp_state_sized.update(cx, |state, cx| {
input.set_masked(self.otp_masked, window, cx) state.set_masked(self.otp_masked, window, cx)
});
self.otp_state_disabled.update(cx, |state, cx| {
state.set_masked(self.otp_masked, window, cx)
}); });
} }
} }
@ -162,5 +171,8 @@ impl Render for OtpInputStory {
.with_size(px(55.)), .with_size(px(55.)),
), ),
) )
.child(
section("Disabled").child(OtpInput::new(&self.otp_state_disabled).disabled(true)),
)
} }
} }

View file

@ -6,7 +6,7 @@ use gpui::{
use crate::{ use crate::{
button::{Button, ButtonVariants as _}, button::{Button, ButtonVariants as _},
h_flex, ActiveTheme, IconName, Sizable, Size, StyleSized, StyledExt as _, h_flex, ActiveTheme, Disableable, IconName, Sizable, Size, StyleSized, StyledExt as _,
}; };
use super::{InputState, TextInput}; use super::{InputState, TextInput};
@ -30,6 +30,7 @@ pub struct NumberInput {
prefix: Option<AnyElement>, prefix: Option<AnyElement>,
suffix: Option<AnyElement>, suffix: Option<AnyElement>,
appearance: bool, appearance: bool,
disabled: bool,
} }
impl NumberInput { impl NumberInput {
@ -42,6 +43,7 @@ impl NumberInput {
prefix: None, prefix: None,
suffix: None, suffix: None,
appearance: true, appearance: true,
disabled: false,
} }
} }
@ -84,6 +86,13 @@ impl NumberInput {
} }
} }
impl Disableable for NumberInput {
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl InputState { impl InputState {
fn on_action_increment(&mut self, _: &Increment, window: &mut Window, cx: &mut Context<Self>) { fn on_action_increment(&mut self, _: &Increment, window: &mut Window, cx: &mut Context<Self>) {
self.on_number_input_step(StepAction::Increment, window, cx); self.on_number_input_step(StepAction::Increment, window, cx);
@ -142,6 +151,7 @@ impl RenderOnce for NumberInput {
.border_1() .border_1()
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
}) })
.when(self.disabled, |this| this.bg(cx.theme().muted))
.when(focused, |this| this.focused_border(cx)) .when(focused, |this| this.focused_border(cx))
.child( .child(
Button::new("minus") Button::new("minus")
@ -149,6 +159,7 @@ impl RenderOnce for NumberInput {
.with_size(self.size.smaller()) .with_size(self.size.smaller())
.icon(IconName::Minus) .icon(IconName::Minus)
.compact() .compact()
.disabled(self.disabled)
.on_click({ .on_click({
let state = self.state.clone(); let state = self.state.clone();
move |_, window, cx| { move |_, window, cx| {
@ -159,6 +170,7 @@ impl RenderOnce for NumberInput {
.child( .child(
TextInput::new(&self.state) TextInput::new(&self.state)
.appearance(false) .appearance(false)
.disabled(self.disabled)
.px(px(2.)) .px(px(2.))
.gap_0() .gap_0()
.when_some(self.prefix, |this, prefix| this.prefix(prefix)) .when_some(self.prefix, |this, prefix| this.prefix(prefix))
@ -170,6 +182,7 @@ impl RenderOnce for NumberInput {
.with_size(self.size.smaller()) .with_size(self.size.smaller())
.icon(IconName::Plus) .icon(IconName::Plus)
.compact() .compact()
.disabled(self.disabled)
.on_click({ .on_click({
let state = self.state.clone(); let state = self.state.clone();
move |_, window, cx| { move |_, window, cx| {

View file

@ -6,7 +6,7 @@ use gpui::{
}; };
use super::{blink_cursor::BlinkCursor, InputEvent}; use super::{blink_cursor::BlinkCursor, InputEvent};
use crate::{h_flex, v_flex, ActiveTheme, Icon, IconName, Sizable, Size}; use crate::{h_flex, v_flex, ActiveTheme, Disableable, Icon, IconName, Sizable, Size};
pub struct OtpState { pub struct OtpState {
focus_handle: FocusHandle, focus_handle: FocusHandle,
@ -183,6 +183,7 @@ pub struct OtpInput {
state: Entity<OtpState>, state: Entity<OtpState>,
number_of_groups: usize, number_of_groups: usize,
size: Size, size: Size,
disabled: bool,
} }
impl OtpInput { impl OtpInput {
@ -192,6 +193,7 @@ impl OtpInput {
state: state.clone(), state: state.clone(),
number_of_groups: 2, number_of_groups: 2,
size: Size::Medium, size: Size::Medium,
disabled: false,
} }
} }
@ -201,7 +203,12 @@ impl OtpInput {
self self
} }
} }
impl Disableable for OtpInput {
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl Sizable for OtpInput { impl Sizable for OtpInput {
fn with_size(mut self, size: impl Into<crate::Size>) -> Self { fn with_size(mut self, size: impl Into<crate::Size>) -> Self {
self.size = size.into(); self.size = size.into();
@ -248,6 +255,10 @@ impl RenderOnce for OtpInput {
.border_1() .border_1()
.border_color(cx.theme().input) .border_color(cx.theme().input)
.bg(cx.theme().background) .bg(cx.theme().background)
.when(self.disabled, |this| {
this.bg(cx.theme().muted)
.text_color(cx.theme().muted_foreground)
})
.when(is_input_focused, |this| this.border_color(cx.theme().ring)) .when(is_input_focused, |this| this.border_color(cx.theme().ring))
.when(cx.theme().shadow, |this| this.shadow_xs()) .when(cx.theme().shadow, |this| this.shadow_xs())
.items_center() .items_center()
@ -271,6 +282,9 @@ impl RenderOnce for OtpInput {
this.child( this.child(
Icon::new(IconName::Asterisk) Icon::new(IconName::Asterisk)
.text_color(cx.theme().secondary_foreground) .text_color(cx.theme().secondary_foreground)
.when(self.disabled, |this| {
this.text_color(cx.theme().muted_foreground)
})
.with_size(text_size), .with_size(text_size),
) )
} else { } else {
@ -294,7 +308,9 @@ impl RenderOnce for OtpInput {
v_flex() v_flex()
.id(("otp-input", self.state.entity_id())) .id(("otp-input", self.state.entity_id()))
.track_focus(&self.state.read(cx).focus_handle) .track_focus(&self.state.read(cx).focus_handle)
.on_key_down(window.listener_for(&self.state, OtpState::on_key_down)) .when(!self.disabled, |this| {
this.on_key_down(window.listener_for(&self.state, OtpState::on_key_down))
})
.items_center() .items_center()
.child( .child(
h_flex().items_center().gap_5().children( h_flex().items_center().gap_5().children(

View file

@ -723,24 +723,11 @@ impl InputState {
/// Set with disabled mode. /// Set with disabled mode.
/// ///
/// See also: [`Self::set_disabled`], [`Self::is_disabled`]. /// See also: [`Self::set_disabled`], [`Self::is_disabled`].
pub fn disabled(mut self, disabled: bool) -> Self { pub(crate) fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled; self.disabled = disabled;
self self
} }
/// Set the disabled state of the input field.
///
/// See also: [`Self::disabled`], [`Self::is_disabled`].
pub fn set_disabled(&mut self, disabled: bool, _: &mut Window, cx: &mut Context<Self>) {
self.disabled = disabled;
cx.notify();
}
/// Return is the input field is disabled.
pub fn is_disabled(&self) -> bool {
self.disabled
}
/// Set with password masked state. /// Set with password masked state.
pub fn masked(mut self, masked: bool) -> Self { pub fn masked(mut self, masked: bool) -> Self {
self.masked = masked; self.masked = masked;

View file

@ -1,4 +1,4 @@
use std::borrow::Cow; use std::{borrow::Cow, rc::Rc};
use chrono::{Datelike, Local, NaiveDate}; use chrono::{Datelike, Local, NaiveDate};
use gpui::{ use gpui::{
@ -248,6 +248,7 @@ impl Matcher {
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Calendar { pub struct Calendar {
id: ElementId,
size: Size, size: Size,
state: Entity<CalendarState>, state: Entity<CalendarState>,
style: StyleRefinement, style: StyleRefinement,
@ -267,7 +268,7 @@ pub struct CalendarState {
today: NaiveDate, today: NaiveDate,
/// Number of the months view to show. /// Number of the months view to show.
number_of_months: usize, number_of_months: usize,
disabled: Option<Matcher>, pub(crate) disabled_matcher: Option<Rc<Matcher>>,
} }
impl CalendarState { impl CalendarState {
@ -283,11 +284,29 @@ impl CalendarState {
year_page: 0, year_page: 0,
today, today,
number_of_months: 1, number_of_months: 1,
disabled: None, disabled_matcher: None,
} }
.year_range((today.year() - 50, today.year() + 50)) .year_range((today.year() - 50, today.year() + 50))
} }
/// Set the disabled matcher of the calendar state.
pub fn disabled_matcher(mut self, matcher: impl Into<Matcher>) -> Self {
self.disabled_matcher = Some(Rc::new(matcher.into()));
self
}
/// Set the disabled matcher of the calendar.
///
/// The disabled matcher will be used to disable the days that match the matcher.
pub fn set_disabled_matcher(
&mut self,
disabled: impl Into<Matcher>,
_: &mut Window,
_: &mut Context<Self>,
) {
self.disabled_matcher = Some(Rc::new(disabled.into()));
}
/// Set the date of the calendar. /// Set the date of the calendar.
/// ///
/// When you set a range date, the mode will be automatically set to `Mode::Range`. /// When you set a range date, the mode will be automatically set to `Mode::Range`.
@ -295,9 +314,9 @@ impl CalendarState {
let date = date.into(); let date = date.into();
let invalid = self let invalid = self
.disabled .disabled_matcher
.as_ref() .as_ref()
.map_or(false, |disabled| disabled.date_matched(&date)); .map_or(false, |matcher| matcher.date_matched(&date));
if invalid { if invalid {
return; return;
@ -356,13 +375,6 @@ impl CalendarState {
self self
} }
/// Set the disabled matcher of the calendar.
///
/// The disabled matcher will be used to disable the days that match the matcher.
pub fn set_disabled(&mut self, disabled: Matcher, _: &mut Window, _: &mut Context<Self>) {
self.disabled = Some(disabled);
}
/// Get year and month by offset month. /// Get year and month by offset month.
fn offset_year_month(&self, offset_month: usize) -> (i32, u32) { fn offset_year_month(&self, offset_month: usize) -> (i32, u32) {
let mut month = self.current_month as i32 + offset_month as i32; let mut month = self.current_month as i32 + offset_month as i32;
@ -497,6 +509,7 @@ impl Render for CalendarState {
impl Calendar { impl Calendar {
pub fn new(state: &Entity<CalendarState>) -> Self { pub fn new(state: &Entity<CalendarState>) -> Self {
Self { Self {
id: ("calendar", state.entity_id()).into(),
size: Size::default(), size: Size::default(),
state: state.clone(), state: state.clone(),
style: StyleRefinement::default(), style: StyleRefinement::default(),
@ -527,7 +540,7 @@ impl Calendar {
let date = *d; let date = *d;
let is_today = *d == state.today; let is_today = *d == state.today;
let disabled = state let disabled = state
.disabled .disabled_matcher
.as_ref() .as_ref()
.map_or(false, |disabled| disabled.matched(&date)); .map_or(false, |disabled| disabled.matched(&date));
@ -914,10 +927,12 @@ impl RenderOnce for Calendar {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let view_mode = self.state.read(cx).view_mode; let view_mode = self.state.read(cx).view_mode;
let number_of_months = self.number_of_months; let number_of_months = self.number_of_months;
self.state self.state.update(cx, |state, _| {
.update(cx, |state, _| state.number_of_months = number_of_months); state.number_of_months = number_of_months;
});
v_flex() v_flex()
.id(self.id.clone())
.track_focus(&self.state.read(cx).focus_handle) .track_focus(&self.state.read(cx).focus_handle)
.border_1() .border_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)

View file

@ -1,3 +1,5 @@
use std::rc::Rc;
use chrono::NaiveDate; use chrono::NaiveDate;
use gpui::{ use gpui::{
anchored, deferred, div, prelude::FluentBuilder as _, px, App, AppContext, Context, ElementId, anchored, deferred, div, prelude::FluentBuilder as _, px, App, AppContext, Context, ElementId,
@ -12,7 +14,8 @@ use crate::{
button::{Button, ButtonVariants as _}, button::{Button, ButtonVariants as _},
h_flex, h_flex,
input::clear_button, input::clear_button,
v_flex, ActiveTheme, Icon, IconName, Sizable, Size, StyleSized as _, StyledExt as _, v_flex, ActiveTheme, Disableable, Icon, IconName, Sizable, Size, StyleSized as _,
StyledExt as _,
}; };
use super::calendar::{Calendar, CalendarEvent, CalendarState, Date, Matcher}; use super::calendar::{Calendar, CalendarEvent, CalendarState, Date, Matcher};
@ -64,6 +67,7 @@ pub struct DatePickerState {
calendar: Entity<CalendarState>, calendar: Entity<CalendarState>,
date_format: SharedString, date_format: SharedString,
number_of_months: usize, number_of_months: usize,
disabled_matcher: Option<Rc<Matcher>>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@ -116,6 +120,7 @@ impl DatePickerState {
open: false, open: false,
date_format: "%Y/%m/%d".into(), date_format: "%Y/%m/%d".into(),
number_of_months: 1, number_of_months: 1,
disabled_matcher: None,
_subscriptions, _subscriptions,
} }
} }
@ -154,15 +159,17 @@ impl DatePickerState {
cx.notify(); cx.notify();
} }
/// Set the disabled match for the calendar.
pub fn disabled_matcher(mut self, disabled: impl Into<Matcher>) -> Self {
self.disabled_matcher = Some(Rc::new(disabled.into()));
self
}
/// Set the disabled matcher of the date picker. /// Set the disabled matcher of the date picker.
pub fn set_disabled( fn set_canlendar_disabled_matcher(&mut self, _: &mut Window, cx: &mut Context<Self>) {
&mut self, let matcher = self.disabled_matcher.clone();
disabled: impl Into<Matcher>, self.calendar.update(cx, |state, _| {
window: &mut Window, state.disabled_matcher = matcher;
cx: &mut Context<Self>,
) {
self.calendar.update(cx, |view, cx| {
view.set_disabled(disabled.into(), window, cx);
}); });
} }
@ -239,6 +246,7 @@ pub struct DatePicker {
number_of_months: usize, number_of_months: usize,
presets: Option<Vec<DateRangePreset>>, presets: Option<Vec<DateRangePreset>>,
appearance: bool, appearance: bool,
disabled: bool,
} }
impl Sizable for DatePicker { impl Sizable for DatePicker {
@ -259,6 +267,13 @@ impl Styled for DatePicker {
} }
} }
impl Disableable for DatePicker {
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl Render for DatePickerState { impl Render for DatePickerState {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl gpui::IntoElement { fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl gpui::IntoElement {
Empty Empty
@ -277,6 +292,7 @@ impl DatePicker {
number_of_months: 2, number_of_months: 2,
presets: None, presets: None,
appearance: true, appearance: true,
disabled: false,
} }
} }
@ -313,6 +329,10 @@ impl DatePicker {
impl RenderOnce for DatePicker { impl RenderOnce for DatePicker {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
self.state.update(cx, |state, cx| {
state.set_canlendar_disabled_matcher(window, cx);
});
// This for keep focus border style, when click on the popup. // This for keep focus border style, when click on the popup.
let is_focused = self.focus_handle(cx).contains_focused(window, cx); let is_focused = self.focus_handle(cx).contains_focused(window, cx);
let state = self.state.read(cx); let state = self.state.read(cx);
@ -352,11 +372,15 @@ impl RenderOnce for DatePicker {
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.when(cx.theme().shadow, |this| this.shadow_xs()) .when(cx.theme().shadow, |this| this.shadow_xs())
.when(is_focused, |this| this.focused_border(cx)) .when(is_focused, |this| this.focused_border(cx))
.when(self.disabled, |this| {
this.bg(cx.theme().muted)
.text_color(cx.theme().muted_foreground)
})
}) })
.overflow_hidden() .overflow_hidden()
.input_text_size(self.size) .input_text_size(self.size)
.input_size(self.size) .input_size(self.size)
.when(!state.open, |this| { .when(!state.open && !self.disabled, |this| {
this.on_click( this.on_click(
window.listener_for(&self.state, DatePickerState::toggle_calendar), window.listener_for(&self.state, DatePickerState::toggle_calendar),
) )
@ -368,17 +392,19 @@ impl RenderOnce for DatePicker {
.justify_between() .justify_between()
.gap_1() .gap_1()
.child(div().w_full().overflow_hidden().child(display_title)) .child(div().w_full().overflow_hidden().child(display_title))
.when(show_clean, |this| { .when(!self.disabled, |this| {
this.child(clear_button(cx).on_click( this.when(show_clean, |this| {
window.listener_for(&self.state, DatePickerState::clean), this.child(clear_button(cx).on_click(
)) window.listener_for(&self.state, DatePickerState::clean),
}) ))
.when(!show_clean, |this| { })
this.child( .when(!show_clean, |this| {
Icon::new(IconName::Calendar) this.child(
.xsmall() Icon::new(IconName::Calendar)
.text_color(cx.theme().muted_foreground), .xsmall()
) .text_color(cx.theme().muted_foreground),
)
})
}), }),
), ),
) )