All skills
lassejlv avatar

/build-gpui-apps

@52f4cfd
by Lasselassejlv/skills50 stars
1

Build, scaffold, refactor, debug, review, and validate native Rust desktop applications with GPUI. Recommend GPUI Kit and ask before adopting it; use gpui_kit imports after agreement, or preserve the chosen upstream GPUI stack. Includes the merged GPUI Kit component and design skills, full application/Base/Component/Shell documentation, coding and design guides, state, actions, async, input, accessibility, motion, themes, native integration, packaging, auto updates, testing, and production delivery. Use paper-to-gpui when the primary task is faithfully translating a selected Paper.design frame into an existing view.

Use this Skill: https://skilld.dev/gh/lassejlv/skills/build-gpui-apps

This session only. Nothing lands on disk.

referencesgpui-kitupstreamcomponentdate-picker.md

≈3.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

DatePicker

A flexible date picker component with calendar interface that supports single date selection, date range selection, custom date formatting, disabled dates, and preset ranges.

Import

use gpui_kit::component::{
    date_picker::{DatePicker, DatePickerState, DateRangePreset, DatePickerEvent, DateTime},
    calendar::{Date, Matcher},
    time_field::{HourCycle, TimePrecision},
};

Usage

Basic Date Picker

let date_picker = cx.new(|cx| DatePickerState::new(window, cx));

DatePicker::new(&date_picker)

With Initial Date

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)

Date Range Picker

use chrono::{Local, Days};

// Range mode picker
let range_picker = cx.new(|cx| DatePickerState::range(window, cx));

DatePicker::new(&range_picker)
    .number_of_months(2) // Show 2 months for easier range selection

// With initial range
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)

Date and Time

Set a time_precision to edit the time of day as well. The popup then shows a time field below the calendar and stays open after a date is picked, so the time can be adjusted next. Every edit is reported as it happens. Clicking the selected date again — or double-clicking a date — confirms it and closes the popup, as do Enter, Escape and a click outside.

use chrono::NaiveTime;

let date_time_picker = cx.new(|cx| {
    DatePickerState::new(window, cx)
        .time_precision(TimePrecision::Minute) // or TimePrecision::Second
        .default_time(NaiveTime::from_hms_opt(9, 0, 0).unwrap())
});

DatePicker::new(&date_time_picker)

The time uses a 24-hour clock by default. Use hour_cycle for a 12-hour clock with an AM/PM segment:

DatePickerState::new(window, cx)
    .time_precision(TimePrecision::Minute)
    .hour_cycle(HourCycle::H12) // 09:30 PM

default_time is the time a date gets before the user edits it, 00:00 unless configured. The display format follows the precision and hour cycle (for example %Y/%m/%d %H:%M or %Y/%m/%d %I:%M %p) unless date_format is set.

Read and write the whole value with date_time and set_date_time; date and set_date still address the date part and keep the current time.

use chrono::Local;

date_time_picker.update(cx, |state, cx| {
    state.set_date_time(Local::now().naive_local(), window, cx);
});

if let DateTime::Single(Some(at)) = date_time_picker.read(cx).date_time() {
    println!("Selected {at}");
}

A range picker edits dates only, even with a time_precision. For a range with times, place two pickers side by side and validate the order in the owner:

h_flex()
    .gap_2()
    .child(DatePicker::new(&start_picker))
    .child("–")
    .child(DatePicker::new(&end_picker))

In the time field, Up/Down change the selected segment, Left/Right and Tab/Shift-Tab move between segments, digits type a value and advance to the next segment, a/p set AM or PM, and Backspace resets the segment.

With Custom Date Format

let date_picker = cx.new(|cx| {
    DatePickerState::new(window, cx)
        .date_format("%Y-%m-%d") // ISO format
});

DatePicker::new(&date_picker)

// Other format examples:
// "%m/%d/%Y" -> 12/25/2023
// "%B %d, %Y" -> December 25, 2023
// "%d %b %Y" -> 25 Dec 2023

With Placeholder

DatePicker::new(&date_picker)
    .placeholder("Select a date...")

Cleanable Date Picker

DatePicker::new(&date_picker)
    .cleanable(true) // Show clear button when date is selected

Different Sizes

DatePicker::new(&date_picker).large()
DatePicker::new(&date_picker) // medium (default)
DatePicker::new(&date_picker).small()

Disabled State

DatePicker::new(&date_picker).disabled(true)

Custom Appearance

// Without default styling
DatePicker::new(&date_picker).appearance(false)

// Use in custom container
div()
    .border_b_2()
    .px_6()
    .py_3()
    .border_color(cx.theme().border)
    .bg(cx.theme().secondary)
    .child(DatePicker::new(&date_picker).appearance(false))

Date Restrictions

Disabled Weekends

use gpui_kit::component::calendar;

let date_picker = cx.new(|cx| {
    DatePickerState::new(window, cx)
        .disabled_matcher(vec![0, 6]) // Sunday=0, Saturday=6
});

DatePicker::new(&date_picker)

Disabled Date Range

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)

Disabled Date Interval

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)

Custom Disabled Dates

// Disable first 5 days of each month
let date_picker = cx.new(|cx| {
    DatePickerState::new(window, cx)
        .disabled_matcher(calendar::Matcher::custom(|date| {
            date.day0() < 5
        }))
});

DatePicker::new(&date_picker)

// Disable all Mondays
let date_picker = cx.new(|cx| {
    DatePickerState::new(window, cx)
        .disabled_matcher(calendar::Matcher::custom(|date| {
            date.weekday() == chrono::Weekday::Mon
        }))
});

Custom Year Range

By default, the date picker shows 50 years before and after the current year in year selection mode. Use set_year_range to configure a different range — for example, a birthday picker that goes back to 1900.

The range argument uses a half-open interval (start, end) where end is exclusive. Pass (1900, current_year + 1) to include current_year.

use chrono::Datelike;

// Birthday picker: allow years from 1900 to the current year (inclusive)
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")

set_year_range works for both single-date and range-mode pickers.

Preset Ranges

Single Date Presets

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)

Date Range 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)

Handle Date Selection Events

let date_picker = cx.new(|cx| DatePickerState::new(window, cx));

cx.subscribe(&date_picker, |view, _, event, _| {
    match event {
        DatePickerEvent::Change(value) => {
            match value.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");
                }
            }
        }
    }
});

Multiple Months Display

// Show 2 months side by side (useful for date ranges)
DatePicker::new(&date_picker)
    .number_of_months(2)

// Show 3 months
DatePicker::new(&date_picker)
    .number_of_months(3)

Advanced Examples

Business Days Only

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")

Date Range with Max Duration

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(value) => {
            let Date::Range(Some(start), Some(end)) = value.date() else {
                return;
            };
            let duration = end.signed_duration_since(start).num_days();
            if duration > 30 {
                // Reset to start date only if range exceeds 30 days
                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")

Quarter Presets

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)

Examples

Event Date Picker

let event_date = cx.new(|cx| {
    let mut picker = DatePickerState::new(window, cx)
        .date_format("%B %d, %Y")
        .disabled_matcher(calendar::Matcher::custom(|date| {
            // Disable past dates
            *date < Local::now().naive_local().date()
        }));
    picker
});

DatePicker::new(&event_date)
    .placeholder("Choose event date")
    .cleanable(true)

Booking System Date Range

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")

Financial Period Selector

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")

Documentation license: original prose and illustrations for which GPUI Kit holds licensing rights are also offered under CC BY 4.0. When copying or adapting, credit GPUI Kit, link the source (https://gpui-kit.com/component/date-picker) and https://creativecommons.org/licenses/by/4.0/, and indicate changes. Code examples and software source use Apache-2.0; third-party material retains its terms; existing Apache-2.0 permissions remain.

Bundled from GPUI Kit. Documentation prose: CC BY 4.0; code examples: Apache-2.0. Changes: documentation links localized, asset URLs made absolute, and this attribution added.

Source: SKILL.md on GitHub

No alerts3d3 checks · Risk SAFE
  • Gen Agent Trust Hub3d

    The skill provides comprehensive documentation and reference material for building native desktop applications using Rust and GPUI. No security risks, prompt injections, or malicious behaviors were identified. External dependencies originate from reputable organizations in the Rust ecosystem.

  • Socket3d

    No alerts

  • Snyk3d

    Risk: LOW · No issues

Signed by skilld at 52f4cfd. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 4 days ago

README badge

README badge for lassejlv/skills/build-gpui-apps