Skip to main content

gpui_base/
date_picker.rs

1use crate::TestSupportExt as _;
2use std::rc::Rc;
3
4use gpui::{
5    AnyElement, App, Div, ElementId, FocusHandle, InteractiveElement, Interactivity, IntoElement,
6    ParentElement, RenderOnce, Role, StatefulInteractiveElement, StyleRefinement, Styled, Window,
7    div,
8};
9
10use crate::{
11    StyledExt as _,
12    a11y_disabled::A11yDisabledExt as _,
13    actions::{Cancel, Confirm},
14};
15
16type OpenChange = Rc<dyn Fn(bool, &mut Window, &mut App)>;
17
18/// Unstyled controlled date-picker root. Applications own its trigger, calendar,
19/// positioning, and visual presentation; Base owns focus and open/dismiss keyboard behavior.
20#[derive(IntoElement)]
21pub struct DatePicker {
22    base: crate::ObservedElement<gpui::Stateful<Div>>,
23    open: bool,
24    disabled: bool,
25    focus_handle: FocusHandle,
26    style: StyleRefinement,
27    children: Vec<AnyElement>,
28    on_open_change: Option<OpenChange>,
29}
30
31impl DatePicker {
32    pub fn new(id: impl Into<ElementId>, focus_handle: &FocusHandle) -> Self {
33        Self {
34            base: div().id(id).test_support(),
35            open: false,
36            disabled: false,
37            focus_handle: focus_handle.clone(),
38            style: StyleRefinement::default(),
39            children: vec![],
40            on_open_change: None,
41        }
42    }
43    pub fn open(mut self, open: bool) -> Self {
44        self.open = open;
45        self
46    }
47    pub fn disabled(mut self, disabled: bool) -> Self {
48        self.disabled = disabled;
49        self
50    }
51    pub fn on_open_change(
52        mut self,
53        handler: impl Fn(bool, &mut Window, &mut App) + 'static,
54    ) -> Self {
55        self.on_open_change = Some(Rc::new(handler));
56        self
57    }
58}
59impl Styled for DatePicker {
60    fn style(&mut self) -> &mut StyleRefinement {
61        &mut self.style
62    }
63}
64impl ParentElement for DatePicker {
65    fn extend(&mut self, children: impl IntoIterator<Item = AnyElement>) {
66        self.children.extend(children);
67    }
68}
69impl InteractiveElement for DatePicker {
70    fn interactivity(&mut self) -> &mut Interactivity {
71        self.base.interactivity()
72    }
73
74    fn track_focus(mut self, handle: &gpui::FocusHandle) -> Self {
75        self.base = self.base.track_focus(handle);
76        self
77    }
78}
79impl StatefulInteractiveElement for DatePicker {}
80impl RenderOnce for DatePicker {
81    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
82        let open = self.open;
83        let disabled = self.disabled;
84        let handler = self.on_open_change;
85        self.base
86            .role(Role::ComboBox)
87            .aria_expanded(open)
88            .track_focus(&self.focus_handle.tab_stop(!disabled))
89            .on_action({
90                let handler = handler.clone();
91                move |_: &Confirm, window, cx| {
92                    // Enter opens the picker, and closes it again once the
93                    // value shown in the popup is the one the user wants.
94                    if disabled {
95                        cx.propagate();
96                    } else if let Some(handler) = &handler {
97                        handler(!open, window, cx);
98                    }
99                }
100            })
101            .on_action(move |_: &Cancel, window, cx| {
102                if open {
103                    if let Some(handler) = &handler {
104                        handler(false, window, cx);
105                    }
106                } else {
107                    cx.propagate();
108                }
109            })
110            .refine_style(&self.style)
111            .children(self.children)
112            .a11y_disabled(disabled)
113    }
114}