diff --git a/crates/story/src/calendar_story.rs b/crates/story/src/calendar_story.rs index 5281a159..951e9aa7 100644 --- a/crates/story/src/calendar_story.rs +++ b/crates/story/src/calendar_story.rs @@ -1,10 +1,10 @@ -use chrono::Days; +use chrono::{Days, Duration, Utc}; use gpui::{ px, IntoElement, ParentElement as _, Render, Styled as _, View, ViewContext, VisualContext as _, WindowContext, }; use ui::{ - date_picker::{DatePicker, DatePickerEvent}, + date_picker::{DatePicker, DatePickerEvent, DateRangePreset}, v_flex, Sizable as _, }; @@ -37,11 +37,48 @@ impl CalendarStory { } fn new(cx: &mut ViewContext) -> Self { + let presets = vec![ + DateRangePreset::single( + "Yesterday", + (Utc::now() - Duration::days(1)).naive_local().date(), + ), + DateRangePreset::single( + "Last Week", + (Utc::now() - Duration::weeks(1)).naive_local().date(), + ), + DateRangePreset::single( + "Last Month", + (Utc::now() - Duration::days(30)).naive_local().date(), + ), + ]; + let range_presets = vec![ + DateRangePreset::range( + "Last 7 Days", + (Utc::now() - Duration::days(7)).naive_local().date(), + Utc::now().naive_local().date(), + ), + DateRangePreset::range( + "Last 14 Days", + (Utc::now() - Duration::days(14)).naive_local().date(), + Utc::now().naive_local().date(), + ), + DateRangePreset::range( + "Last 30 Days", + (Utc::now() - Duration::days(30)).naive_local().date(), + Utc::now().naive_local().date(), + ), + DateRangePreset::range( + "Last 90 Days", + (Utc::now() - Duration::days(90)).naive_local().date(), + Utc::now().naive_local().date(), + ), + ]; let now = chrono::Local::now().naive_local().date(); let date_picker = cx.new_view(|cx| { let mut picker = DatePicker::new("date_picker_medium", cx) .cleanable() - .width(px(220.)); + .width(px(220.)) + .presets(presets); picker.set_date(now, cx); picker }); @@ -62,7 +99,8 @@ impl CalendarStory { let mut picker = DatePicker::new("date_range_picker", cx) .width(px(300.)) .number_of_months(2) - .cleanable(); + .cleanable() + .presets(range_presets.clone()); picker.set_date((now, now.checked_add_days(Days::new(4)).unwrap()), cx); picker }); @@ -85,6 +123,7 @@ impl CalendarStory { .width(px(300.)) .placeholder("Range mode picker") .cleanable() + .presets(range_presets.clone()) }); cx.subscribe(&default_range_mode_picker, |this, _, ev, _| match ev { diff --git a/crates/ui/src/time/calendar.rs b/crates/ui/src/time/calendar.rs index a8426ee1..41fa6ace 100644 --- a/crates/ui/src/time/calendar.rs +++ b/crates/ui/src/time/calendar.rs @@ -159,6 +159,7 @@ pub struct Calendar { year_page: i32, /// Number of the months view to show. number_of_months: usize, + today: NaiveDate, } impl Calendar { @@ -173,6 +174,7 @@ impl Calendar { years: vec![], year_page: 0, number_of_months: 1, + today, } .year_range((today.year() - 50, today.year() + 50)) } @@ -399,6 +401,7 @@ impl Calendar { let is_in_range = self.date.is_in_range(d); let date = *d; + let is_today = *d == self.today; self.item_button( ix, @@ -408,6 +411,9 @@ impl Calendar { !is_current_month, cx, ) + .when(is_today && !is_active, |this| { + this.border_1().border_color(cx.theme().border) + }) // Add border for today .on_click(cx.listener(move |view, _: &ClickEvent, cx| { if view.date.is_single() { view.set_date(date, cx); diff --git a/crates/ui/src/time/date_picker.rs b/crates/ui/src/time/date_picker.rs index dacb0cb9..5b830ff4 100644 --- a/crates/ui/src/time/date_picker.rs +++ b/crates/ui/src/time/date_picker.rs @@ -1,14 +1,19 @@ +use chrono::NaiveDate; use gpui::{ anchored, deferred, div, prelude::FluentBuilder as _, px, AppContext, ElementId, EventEmitter, FocusHandle, FocusableView, InteractiveElement as _, KeyBinding, Length, MouseButton, - ParentElement as _, Render, SharedString, StatefulInteractiveElement as _, Styled as _, View, + ParentElement as _, Render, SharedString, StatefulInteractiveElement as _, Styled, View, ViewContext, VisualContext as _, }; use rust_i18n::t; use crate::{ - dropdown::Escape, h_flex, input::ClearButton, theme::ActiveTheme as _, Icon, IconName, Sizable, - Size, StyleSized as _, StyledExt as _, + button::{Button, ButtonStyled as _}, + dropdown::Escape, + h_flex, + input::ClearButton, + theme::ActiveTheme, + v_flex, Icon, IconName, Sizable, Size, StyleSized as _, StyledExt as _, }; use super::calendar::{Calendar, CalendarEvent, Date}; @@ -23,6 +28,34 @@ pub enum DatePickerEvent { Change(Date), } +#[derive(Clone)] +pub enum DateRangePresetValue { + Single(NaiveDate), + Range(NaiveDate, NaiveDate), +} + +#[derive(Clone)] +pub struct DateRangePreset { + label: SharedString, + value: DateRangePresetValue, +} + +impl DateRangePreset { + /// Creates a new DateRangePreset with single date. + pub fn single(label: impl Into, single: NaiveDate) -> Self { + DateRangePreset { + label: label.into(), + value: DateRangePresetValue::Single(single), + } + } + /// Creates a new DateRangePreset with a range of dates. + pub fn range(label: impl Into, start: NaiveDate, end: NaiveDate) -> Self { + DateRangePreset { + label: label.into(), + value: DateRangePresetValue::Range(start, end), + } + } +} pub struct DatePicker { id: ElementId, focus_handle: FocusHandle, @@ -35,6 +68,7 @@ pub struct DatePicker { date_format: SharedString, calendar: View, number_of_months: usize, + presets: Option>, } impl DatePicker { @@ -82,6 +116,7 @@ impl DatePicker { cleanable: false, number_of_months: 1, placeholder: None, + presets: None, } } @@ -115,6 +150,12 @@ impl DatePicker { self } + /// Set preset ranges for the date picker. + pub fn presets(mut self, presets: Vec) -> Self { + self.presets = Some(presets); + self + } + /// Get the date of the date picker. pub fn date(&self) -> Date { self.date @@ -158,6 +199,17 @@ impl DatePicker { self.open = !self.open; cx.notify(); } + + fn select_preset(&mut self, preset: &DateRangePreset, cx: &mut ViewContext) { + match preset.value { + DateRangePresetValue::Single(single) => { + self.update_date(Date::Single(Some(single)), true, cx) + } + DateRangePresetValue::Range(start, end) => { + self.update_date(Date::Range(Some(start), Some(end)), true, cx) + } + } + } } impl EventEmitter for DatePicker {} @@ -190,8 +242,9 @@ impl Render for DatePicker { view.set_number_of_months(self.number_of_months, cx); }); - let popover_width = - 285.0 * self.number_of_months as f32 + (self.number_of_months - 1) as f32 * 16.0; + let popover_width = self.presets.as_ref().map_or(0.0, |_| 136.0) + + 285.0 * self.number_of_months as f32 + + (self.number_of_months - 1) as f32 * 16.0; div() .id(self.id.clone()) @@ -266,7 +319,34 @@ impl Render for DatePicker { MouseButton::Left, cx.listener(|view, _, cx| view.escape(&Escape, cx)), ) - .child(self.calendar.clone()), + .child( + h_flex() + .gap_3() + .h_full() + .items_start() + .when_some(self.presets.clone(), |this, presets| { + this.child( + v_flex().my_1().gap_2().justify_end().children( + presets.into_iter().enumerate().map( + |(i, preset)| { + Button::new(("preset", i)) + .small() + .ghost() + .label(preset.label.clone()) + .on_click(cx.listener( + move |this, _, cx| { + this.select_preset( + &preset, cx, + ); + }, + )) + }, + ), + ), + ) + }) + .child(self.calendar.clone()), + ), ), ) .with_priority(2),