ui/components/
date_picker.rs1use std::cell::Cell;
4use std::rc::Rc;
5
6use chrono::{Datelike, Local, NaiveDate};
7use gpui::{Bounds, Context, Entity, Pixels, Render, anchored, canvas, deferred, point};
8
9use crate::components::calendar::Calendar;
10use crate::prelude::*;
11
12fn format_date(date: NaiveDate) -> SharedString {
13 format!("{}/{}/{}", date.month(), date.day(), date.year()).into()
14}
15
16pub struct DatePicker {
20 selected: Option<NaiveDate>,
21 open: bool,
22 placeholder: SharedString,
23 calendar: Entity<Calendar>,
24 trigger_bounds: Rc<Cell<Option<Bounds<Pixels>>>>,
25}
26
27impl DatePicker {
28 pub fn new(cx: &mut Context<Self>) -> Self {
29 let calendar = cx.new(|_| Calendar::new());
30 cx.observe(&calendar, |this, cal, cx| {
31 if let Some(date) = cal.read(cx).selection() {
32 this.selected = Some(date);
33 this.open = false;
34 cx.notify();
35 }
36 })
37 .detach();
38
39 Self {
40 selected: None,
41 open: false,
42 placeholder: "Pick a date".into(),
43 calendar,
44 trigger_bounds: Rc::new(Cell::new(None)),
45 }
46 }
47
48 pub fn selected(mut self, date: NaiveDate, cx: &mut Context<Self>) -> Self {
49 self.selected = Some(date);
50 self.calendar.update(cx, |cal, _| {
51 *cal = Calendar::new().selected(date);
52 });
53 self
54 }
55
56 pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
57 self.placeholder = placeholder.into();
58 self
59 }
60
61 pub fn value(&self) -> Option<NaiveDate> {
62 self.selected
63 }
64}
65
66impl Render for DatePicker {
67 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
68 let label = self
69 .selected
70 .map(format_date)
71 .unwrap_or_else(|| self.placeholder.clone());
72 let has_value = self.selected.is_some();
73 let open = self.open;
74
75 let trigger = h_flex()
76 .id("date-picker-trigger")
77 .debug_selector(|| "DATE-PICKER-TRIGGER".into())
82 .w(px(240.))
83 .items_center()
84 .justify_between()
85 .px_3()
86 .py_2()
87 .rounded_md()
88 .bg(semantic::surface(cx))
89 .border_1()
90 .border_color(if open {
91 palette::primary(500)
92 } else {
93 semantic::border(cx)
94 })
95 .cursor_pointer()
96 .on_click(cx.listener(|this, _, _, cx| {
97 this.open = !this.open;
98 cx.notify();
99 }))
100 .child(Label::new(label).color(if has_value {
101 Color::Default
102 } else {
103 Color::Placeholder
104 }))
105 .child(Icon::new(IconName::Clock).size(IconSize::Small))
106 .child({
107 let trigger_bounds = self.trigger_bounds.clone();
108 canvas(
109 move |bounds, _, _| trigger_bounds.set(Some(bounds)),
110 |_, _, _, _| {},
111 )
112 .absolute()
113 .top_0()
114 .left_0()
115 .size_full()
116 });
117
118 v_flex().gap_1().child(trigger).when(open, |this| {
119 let mut anchor = anchored().snap_to_window_with_margin(px(8.));
120 if let Some(bounds) = self.trigger_bounds.get() {
121 anchor = anchor.position(point(
122 bounds.origin.x,
123 bounds.origin.y + bounds.size.height + px(4.),
124 ));
125 }
126
127 let floating = deferred(
128 anchor.child(
129 div()
130 .occlude()
131 .id("date-picker-popover")
132 .debug_selector(|| "DATE-PICKER-POPOVER".into())
137 .child(self.calendar.clone())
138 .on_mouse_down_out(cx.listener(|this, _, _, cx| {
139 this.open = false;
140 cx.notify();
141 })),
142 ),
143 )
144 .with_priority(1);
145
146 this.child(floating)
147 })
148 }
149}
150
151#[derive(IntoElement, RegisterComponent)]
153pub struct DatePickerPreview;
154
155impl RenderOnce for DatePickerPreview {
156 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
157 cx.new(|cx| {
158 let mut picker = DatePicker::new(cx);
159 picker = picker.selected(Local::now().date_naive(), cx);
160 picker
161 })
162 }
163}
164
165impl Component for DatePickerPreview {
166 fn scope() -> ComponentScope {
167 ComponentScope::Input
168 }
169
170 fn description() -> Option<&'static str> {
171 Some("Popover trigger + calendar for choosing a date.")
172 }
173
174 fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
175 DatePickerPreview
176 .render(window, cx)
177 .into_any_element()
178 .into()
179 }
180}