
gpui-kit DatePicker 组件完全指南构建跨平台日期选择与日期范围选择界面【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kit导读DatePicker 是 gpui-kit 中基于日历交互界面的日期选择组件支持单日期选择、日期范围选择、自定义日期格式化、日期禁用规则以及预设范围等完整能力。本指南以 website/component/date-picker.md 为骨架结合 crates/component/src/time/date_picker.rs、crates/component/src/time/calendar.rs、crates/base/src/date_picker.rs 与 crates/base/src/calendar.rs 的源码实现系统讲解从基础使用到业务场景事件预约、酒店预订、财务周期选择的完整实战方案。读完本文你将能熟练驾驭 DatePicker 的全部配置项、禁用规则匹配器与事件订阅机制并理解其背后的组件分层原理。组件架构概览从源码结构看gpui-kit 的 DatePicker 遵循基础行为与样式分离的分层设计DatePickerState状态实体持有日期值、日历实体、日期格式、月份数、禁用匹配器等状态负责状态更新、事件发射与键盘行为见 crates/component/src/time/date_picker.rs。DatePicker样式化元素接收EntityDatePickerState负责输入框外观、清空按钮、日历弹层与预设按钮的渲染实现Sizable、Styled、Disableable、FocusableExt等 trait。CalendarState/Calendar日历内核真正的月视图/年视图网格、导航与日期激活逻辑由 base 层提供无样式结构component 层做主题化装饰见 crates/base/src/calendar.rs 与 crates/component/src/time/calendar.rs。Matcher禁用日期规则抽象支持星期、区间、间隔、自定义闭包四种形式见 crates/base/src/calendar.rs。日期值使用Date枚举表示即Date::Single(OptionNaiveDate)与Date::Range(OptionNaiveDate, OptionNaiveDate)并实现了FromNaiveDate与From(NaiveDate, NaiveDate)转换见 crates/base/src/calendar.rs因此set_date可以直接接收日期或日期元组。引入与基础用法导入模块use gpui_kit::component::{ date_picker::{DatePicker, DatePickerState, DateRangePreset, DatePickerEvent}, calendar::{Date, Matcher}, };date_picker与calendar两个模块均从 crates/component/src/lib.rs 统一导出并在组件初始化时通过date_picker::init(cx)注册enter、escape、delete、backspace等键盘快捷键见 crates/component/src/time/date_picker.rs。基础日期选择器let date_picker cx.new(|cx| DatePickerState::new(window, cx)); DatePicker::new(date_picker)DatePickerState::new内部调用new_with_range(false, ...)即单日期模式DatePickerState::range则对应new_with_range(true, ...)的范围模式见 crates/component/src/time/date_picker.rs。带初始日期use chrono::Local; let date_picker cx.new(|cx| { let mut picker DatePickerState::new(window, cx); picker.set_date(Local::now().naive_local().date(), window, cx); picker }); DatePicker::new(date_picker)set_date内部通过update_date(date, false, ...)同步更新内部日历状态并保持弹层关闭emit 参数为false不会触发Change事件见 crates/component/src/time/date_picker.rs。story 示例中同样以当前日期初始化选择器并禁用周末crates/story/src/stories/date_picker_story.rs。日期范围选择器use chrono::{Local, Days}; // 范围模式选择器 let range_picker cx.new(|cx| DatePickerState::range(window, cx)); DatePicker::new(range_picker) .number_of_months(2) // 展示 2 个月便于范围选择 // 带初始范围 let range_picker cx.new(|cx| { let now Local::now().naive_local().date(); let mut picker DatePickerState::new(window, cx); picker.set_date( (now, now.checked_add_days(Days::new(7)).unwrap()), window, cx, ); picker }); DatePicker::new(range_picker) .number_of_months(2)范围模式下的选择逻辑位于 base 层CalendarState::select_date第一次点击记录起点Range(Some(value), None)第二次点击若日期晚于或等于起点则补全终点否则重置为新起点见 crates/base/src/calendar.rs。核心配置项自定义日期格式let date_picker cx.new(|cx| { DatePickerState::new(window, cx) .date_format(%Y-%m-%d) // ISO 格式 }); DatePicker::new(date_picker) // 其他格式示例 // %m/%d/%Y - 12/25/2023 // %B %d, %Y - December 25, 2023 // %d %b %Y - 25 Dec 2023默认格式为%Y/%m/%d如2026/09/15格式串直接透传给 chrono 的NaiveDate::format范围模式下会格式化为{start} - {end}形式见 crates/base/src/calendar.rs。kit 测试断言选中日期后输入框 value 为2026/09/15crates/kit/tests/date_picker.rs。占位符DatePicker::new(date_picker) .placeholder(Select a date...)未设置时使用 i18n 默认文案t!(DatePicker.placeholder)见 crates/component/src/time/date_picker.rs。当未选中日期时占位符以muted_foreground颜色显示。可清空CleanableDatePicker::new(date_picker) .cleanable(true) // 选中日期后显示清空按钮当cleanable为true且状态中日期非空时输入框右侧显示清空按钮clear_button点击后调用clean将日期重置为Single(None)或Range(None, None)并发射Change事件见 crates/component/src/time/date_picker.rs。kit 测试验证了选中后出现 clean 按钮 → 点击清空 → 按钮消失、value 为空的完整链路crates/kit/tests/date_picker.rs。不同尺寸DatePicker::new(date_picker).large() DatePicker::new(date_picker) // medium默认 DatePicker::new(date_picker).small()尺寸通过Sizabletrait 实现并同时作用于输入框与日历弹层宽度Small 为224px × 月份数、Medium 为280px × 月份数、Large 为340px × 月份数日历面板实际宽度见 crates/component/src/time/date_picker.rs。禁用状态DatePicker::new(date_picker).disabled(true)禁用后组件不可点击、不可打开弹层输入框半透明opacity(0.5)且不再显示清空按钮与日历图标同时 tab 焦点被移除tab_stop(!disabled)。kit 测试disabled_date_picker_does_not_open验证了禁用组件点击后弹层不展开crates/kit/tests/date_picker.rs。自定义外观// 关闭默认样式 DatePicker::new(date_picker).appearance(false) // 放入自定义容器 div() .border_b_2() .px_6() .py_3() .border_color(cx.theme().border) .bg(cx.theme().secondary) .child(DatePicker::new(date_picker).appearance(false))appearance(false)去除输入框的背景、边框、圆角与聚焦环只保留内容与交互适合嵌入工具栏、搜索框等自定义容器见 crates/component/src/time/date_picker.rs。日期限制禁用规则禁用规则统一通过disabled_matcher传入其参数实现了IntoMatcher。Matcher的四种形态在 base 层定义crates/base/src/calendar.rs并在点击时由CalendarState::select_date校验——命中的日期直接拒绝选择见 crates/base/src/calendar.rs。禁用周末use gpui_kit::component::calendar; let date_picker cx.new(|cx| { DatePickerState::new(window, cx) .disabled_matcher(vec![0, 6]) // Sunday0, Saturday6 }); DatePicker::new(date_picker)Vecu32会转换为Matcher::DayOfWeek匹配规则为date.weekday().num_days_from_sunday()是否落在列表中见 crates/base/src/calendar.rs 与 crates/base/src/calendar.rs。禁用日期范围use chrono::{Local, Days}; let now Local::now().naive_local().date(); let date_picker cx.new(|cx| { DatePickerState::new(window, cx) .disabled_matcher(calendar::Matcher::range( Some(now), now.checked_add_days(Days::new(7)), )) }); DatePicker::new(date_picker)Matcher::range(from, to)表示闭区间date from date to的日期被禁用from/to 均可为None表示单侧开放见 crates/base/src/calendar.rs。禁用日期间隔let date_picker cx.new(|cx| { DatePickerState::new(window, cx) .disabled_matcher(calendar::Matcher::interval( Some(now), now.checked_add_days(Days::new(5)) )) }); DatePicker::new(date_picker)Matcher::interval(before, after)与 range 相反表示开区间之外的日期被禁用date before || date after即只允许[before, after]区间内的日期可选语义适合仅允许某段时间的场景见 crates/base/src/calendar.rs。两者语义对比Matcher禁用逻辑典型场景DayOfWeek(vec![0, 6])命中指定星期禁用周末Range(from, to)区间内的日期禁用禁用未来 7 天Interval(before, after)区间外的日期禁用只允许本周可选Custom(fn)闭包返回 true 即禁用任意业务规则自定义禁用日期// 禁用每月前 5 天 let date_picker cx.new(|cx| { DatePickerState::new(window, cx) .disabled_matcher(calendar::Matcher::custom(|date| { date.day0() 5 })) }); DatePicker::new(date_picker) // 禁用所有周一 let date_picker cx.new(|cx| { DatePickerState::new(window, cx) .disabled_matcher(calendar::Matcher::custom(|date| { date.weekday() chrono::Weekday::Mon })) });Matcher::custom接受Fn(NaiveDate) - bool Send Sync static闭包返回true表示禁用。任何基于日期字段的规则如day0()、weekday()、与某个日期比较都可以实现例如 story 中禁用过去日期的写法*date Local::now().naive_local().date()crates/story/src/stories/date_picker_story.rs。自定义年份范围默认情况下年视图展示当前年份前后各 50 年即(当前年 - 50, 当前年 50)见 crates/base/src/calendar.rs。通过set_year_range可以配置不同范围——例如生日选择器回溯到 1900 年use chrono::Datelike; // 生日选择器允许 1900 到当前年份含的年份 let birthday_picker cx.new(|cx| { let current_year chrono::Local::now().year(); let mut picker DatePickerState::new(window, cx) .date_format(%Y-%m-%d); picker.set_year_range((1900, current_year 1), window, cx); picker }); DatePicker::new(birthday_picker) .cleanable(true) .placeholder(Select birthday)range参数使用半开区间(start, end)end不包含。因此要包含current_year需传入(1900, current_year 1)。内部实现将区间按每页 20 年切块chunks(20)并定位到当前年份所在页见 crates/base/src/calendar.rs。set_year_range同时适用于单日期与范围模式。story 中的生日选择器以(1927, now.year() 1)为例crates/story/src/stories/date_picker_story.rs。预设范围Presets预设通过DateRangePreset构造分为单日期与范围两种。DateRangePreset::single(label, date)与DateRangePreset::range(label, start, end)见 crates/component/src/time/date_picker.rs。预设按钮渲染在日历左侧纵向排列v_flex().gap_2().justify_end()点击后调用select_preset直接更新日期并发射Change事件见 crates/component/src/time/date_picker.rs。单日期预设use chrono::{Utc, Duration}; 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(), ), ]; DatePicker::new(date_picker) .presets(presets)日期范围预设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 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(), ), ]; DatePicker::new(date_picker) .number_of_months(2) .presets(range_presets)kit 测试演示了预设的完整交互点击预设按钮后弹层关闭、输入框值更新为对应日期2026/09/15并断言预设按钮位于选择器输入框下方crates/kit/tests/date_picker.rs。处理日期选择事件let date_picker cx.new(|cx| DatePickerState::new(window, cx)); cx.subscribe(date_picker, |view, _, event, _| { match event { DatePickerEvent::Change(date) { match date { Date::Single(Some(selected_date)) { println!(Single date selected: {}, selected_date); } Date::Range(Some(start), Some(end)) { println!(Date range selected: {} to {}, start, end); } Date::Range(Some(start), None) { println!(Range start selected: {}, start); } _ { println!(Date cleared); } } } } });DatePickerEvent::Change携带完整的Date值覆盖三种情况单日期完成、范围起点已选但未完成Range(Some(start), None)、范围完成与清空。范围模式下第一次点击只发射起点第二次点击补全终点后发射完整范围。注意set_date编程式设置不会触发事件只有用户交互日历点击、预设点击、清空才会通过update_date(date, true, ...)发射Change见 crates/component/src/time/date_picker.rs。多月份并排显示// 并排显示 2 个月适合日期范围选择 DatePicker::new(date_picker) .number_of_months(2) // 显示 3 个月 DatePicker::new(date_picker) .number_of_months(3)number_of_months同时存在于DatePickerState默认 1与DatePicker元素上元素层的设置会传入内部Calendar。多月份模式下头部为每个月份渲染独立的年月标签text_sm().font_medium()各月按顺序向右排开见 crates/base/src/calendar.rs。高级实战示例仅工作日可选use chrono::Weekday; let business_days_picker cx.new(|cx| { DatePickerState::new(window, cx) .disabled_matcher(calendar::Matcher::custom(|date| { matches!(date.weekday(), Weekday::Sat | Weekday::Sun) })) }); DatePicker::new(business_days_picker) .placeholder(Select business day)限制最大范围时长use chrono::Days; let max_30_days_picker cx.new(|cx| DatePickerState::range(window, cx)); cx.subscribe(max_30_days_picker, |view, picker, event, _| { match event { DatePickerEvent::Change(Date::Range(Some(start), Some(end))) { let duration end.signed_duration_since(*start).num_days(); if duration 30 { // 超过 30 天则重置为只保留起点 picker.update(cx, |state, cx| { state.set_date(Date::Range(Some(*start), None), window, cx); }); } } _ {} } }); DatePicker::new(max_30_days_picker) .number_of_months(2) .placeholder(Select up to 30 days)此示例利用Change事件对用户选择进行二次校验当范围跨度超过 30 天时通过set_date将状态回退为仅含起点的未完成范围用户可重新选择终点。季度预设use chrono::{NaiveDate, Datelike}; fn quarter_start(year: i32, quarter: u32) - NaiveDate { let month (quarter - 1) * 3 1; NaiveDate::from_ymd_opt(year, month, 1).unwrap() } fn quarter_end(year: i32, quarter: u32) - NaiveDate { let month quarter * 3; let start NaiveDate::from_ymd_opt(year, month, 1).unwrap(); NaiveDate::from_ymd_opt(year, month, start.days_in_month()).unwrap() } let year Local::now().year(); let quarterly_presets vec![ DateRangePreset::range(Q1, quarter_start(year, 1), quarter_end(year, 1)), DateRangePreset::range(Q2, quarter_start(year, 2), quarter_end(year, 2)), DateRangePreset::range(Q3, quarter_start(year, 3), quarter_end(year, 3)), DateRangePreset::range(Q4, quarter_start(year, 4), quarter_end(year, 4)), ]; DatePicker::new(date_picker) .presets(quarterly_presets)事件日期选择器禁用过去日期let event_date cx.new(|cx| { let mut picker DatePickerState::new(window, cx) .date_format(%B %d, %Y) .disabled_matcher(calendar::Matcher::custom(|date| { // 禁用过去日期 *date Local::now().naive_local().date() })); picker }); DatePicker::new(event_date) .placeholder(Choose event date) .cleanable(true)预订系统日期范围let booking_range cx.new(|cx| DatePickerState::range(window, cx)); let booking_presets vec![ DateRangePreset::range(This Weekend, /* weekend dates */), DateRangePreset::range(Next Week, /* next week dates */), DateRangePreset::range(This Month, /* this month dates */), ]; DatePicker::new(booking_range) .number_of_months(2) .presets(booking_presets) .placeholder(Select check-in and check-out dates)财务周期选择器let financial_period cx.new(|cx| { DatePickerState::range(window, cx) .date_format(%Y-%m-%d) }); DatePicker::new(financial_period) .number_of_months(3) .presets(quarterly_presets) .placeholder(Select reporting period)键盘交互与无障碍支持DatePicker 内置完整的键盘支持快捷键通过KeyBinding绑定到DatePicker上下文见 crates/component/src/time/date_picker.rs按键行为Enter确认并打开/关闭弹层ConfirmEscape关闭弹层并归还焦点CancelDelete/Backspace清空当前选中日期弹层关闭时焦点会归还给输入框focus_back_if_need。无障碍方面base 层根元素声明了Role::ComboBox与aria_expanded状态见 crates/base/src/date_picker.rs选中日期通过aria_value暴露给辅助技术。测试验证与更多参考gpui-kit 为 DatePicker 提供了完整的自动化测试覆盖以下关键路径crates/kit/tests/date_picker.rs点击打开/关闭弹层expanded状态断言点击预设并验证输入框 value2026/09/15月份前后翻页calendar-prev/calendar-next元素定位日期选择后的 value 断言与清空按钮可见性Escape关闭弹层禁用状态不可打开弹层此外crates/story/src/stories/date_picker_story.rs 提供了含单日期、范围、大中小尺寸、自定义格式、禁用规则、生日年份范围、无外观模式等完整 story 示例可直接运行 story 应用见 crates/story/src/main.rs交互体验各配置项的实际效果。【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考