Date, Time and Colour Pickers

The pickers read and write a supplied Signal. External writes update the existing fields; on_change runs only for changed user edits. Install CN_STYLES through cn_bundle() or your theme bundle before using them.

Date Picker

cn::calendar is the inline date-selection component. cn::date_picker wraps that calendar in a popover and shows its selected date in a compact trigger. It uses the same CalendarDay and Signal<Option<CalendarDay>> model.

Calendar selection uses --cn-calendar-selected-bg (default --primary) and --cn-calendar-selected-color (default --text-inverse). Event dots and icons use the selected foreground colour while their day is selected, then return to application colours. These colours follow the protected selection rules.

#![allow(unused)]
fn main() {
use blinc::cn::prelude::*;
use blinc::core::reactive::signal;

let selected = signal(None::<CalendarDay>);
let disabled = signal(false);
let placeholder = signal("Choose a date".to_owned());

let picker = cn::date_picker(selected)
    .placeholder(placeholder)
    .disabled(disabled)
    .week_start(1)
    .format(|day| format!("{:02}/{:02}/{}", day.day, day.month, day.year))
    .on_change(|date| tracing::info!(?date, "Date selected"));
}

The default format is YYYY-MM-DD. Picking a day writes Some(day) and closes the popup. The clear action writes None; .clearable(false) omits it, and .clear_text(...) accepts a string, Signal or Computed. External writes update the trigger and an open calendar without invoking on_change. Choosing the same date closes the popup without emitting another change. Disabling an open picker closes it and rejects edits.

Tab reaches the trigger; Enter/Space opens it. The calendar retains its own month controls and keyboard navigation. Clicking outside dismisses the popup. size(InputSize), week_start, min_year, max_year, today and events configure its presentation through the existing calendar API.

ClassPart
.cn-date-picker-triggerTrigger; `data-size="sm
.cn-date-picker-icon, .cn-date-picker-valueCalendar icon and formatted date/placeholder
.cn-date-picker-contentDetached popup; also has .cn-popover-content
.cn-date-picker-footerClear-action row
.cn-date-picker-clearClear button; disabled, :hover, :focus-visible

The calendar keeps its documented .cn-calendar-* part classes. Override --cn-date-picker-bg, --cn-date-picker-border and --cn-date-picker-radius or target the part classes. .class(...) and .id(...) apply to the trigger; the popup lives in the overlay layer, outside the trigger's descendants.

.booking-date { min-width: 220px; }
.cn-date-picker-content { padding: 16px; }
.cn-date-picker-clear { color: var(--primary); }

Time Picker

#![allow(unused)]
fn main() {
use blinc::cn::prelude::*;
use blinc::core::reactive::{computed, signal};

let time = signal(TimeOfDay::new(14, 30, 0).unwrap());
let disabled = signal(false);
let picker = cn::time_picker(time)
    .format(TimeFormat::TwelveHour)
    .seconds(true)
    .minute_step(5)
    .size(InputSize::Medium)
    .disabled(disabled)
    .on_change(|time| tracing::info!(%time, "Time selected"));

let caption = computed(move |g| {
    g.get(time).unwrap_or_default().format(TimeFormat::TwelveHour, true)
});
}

TimeOfDay::new(hour, minute, second) validates a 24-hour time and returns None for out-of-range parts. The value has hour(), minute() and second() accessors, implements Display as HH:MM:SS, and carries no date or time zone. The default picker shows hours and minutes in 24-hour form. Hiding seconds preserves their stored value. Midnight is 12 AM; noon is 12 PM. Clicking the period toggles AM/PM. Tab moves between the fields and the period; Shift+Tab moves back. Enter/Space activates the focused period.

The clock button opens a compact wheel popup over the control, kept within the window edges, with five visible rows for each numeric field. The middle row marks the draft selection and neighbouring values fade out toward the edges. Scroll a column with the mouse wheel or trackpad, drag it vertically, or click a nearby value. In 12-hour mode, separate AM and PM buttons sit above the wheels. They preserve the selected hour while switching periods. Seconds add a third numeric column when enabled.

Save writes the draft to the bound signal and calls on_change once if it changed. Cancel, Escape, clicking outside, or disabling the picker discards the draft. External writes update the open draft without calling on_change. The compact fields continue to commit edits immediately.

Tab reaches each column and the footer actions; Shift+Tab goes back. Up/Down step the focused column, Page Up/Down step five times, and Home/End select its first/last value. minute_step and second_step apply to keyboard stepping and the compact fields’ wheel input. Popup columns are native scroll containers with every valid value and stop at their ends; keyboard stepping wraps. Other fields and hidden seconds are preserved. Leaving a column centres its nearest value. Use .popup(false) for the compact editor without a clock button.

Type up to two digits per field, or use Up/Down and the mouse wheel to step. Stepping wraps within that field without carrying into neighbouring fields. minute_step and second_step affect stepping; typing accepts any valid value. Enter restores the field's two-digit presentation. Invalid or partial text leaves the selected time unchanged.

Time styling

.class(...) and .id(...) apply to the outer .cn-time-picker, which exposes data-size="sm|md|lg", data-format="12|24" and disabled. The control heights are 32, 36 and 40px. Numeric fields share the outer focus ring (data-focus-within); their local highlight identifies the part being edited. Selection highlighting clears when a field loses focus.

ClassPart
.cn-time-picker-partField wrapper
.cn-time-picker-segmentEditable field; :hover, :focus, :disabled
.cn-time-picker-segment--sm, --md, --lgField size classes
.cn-time-picker-hour, .cn-time-picker-minute, .cn-time-picker-secondIndividual fields
.cn-time-picker-separatorColon
.cn-time-picker-periodAM/PM button; :focus-visible, :disabled
.cn-time-picker-clockWheel popup trigger; data-state="open", :focus-visible, :disabled
.cn-time-picker-wheelDetached popup; `data-format="12
.cn-time-picker-wheel-header, .cn-time-picker-wheel-titleHeading
.cn-time-picker-wheelsFive-row numeric viewport
.cn-time-picker-wheel-selectionShared selected-row border and fill
.cn-time-picker-wheel-columnNative scroll, drag and keyboard target; :focus-visible, overflow-fade, scrollbar-width: none
.cn-time-picker-wheel-hour, .cn-time-picker-wheel-minute, .cn-time-picker-wheel-secondIndividual columns
.cn-time-picker-wheel-list, .cn-time-picker-wheel-optionScrollable number list and static value rows
.cn-time-picker-wheel-separatorColon between columns
.cn-time-picker-wheel-period, .cn-time-picker-wheel-period-optionSeparate period control; options expose data-selected, :focus-visible
.cn-time-picker-wheel-am, .cn-time-picker-wheel-pmIndividual period choices
.cn-time-picker-wheel-footer, .cn-time-picker-wheel-cancel, .cn-time-picker-wheel-saveFooter and actions

Override --cn-time-picker-bg, --cn-time-picker-border, --cn-time-picker-radius, --cn-time-picker-field-hover and --cn-time-picker-field-focus, or write rules:

.appointment .cn-time-picker-segment { width: 48px; }
.appointment[data-focus-within] { outline-color: #6366f1; }

The popup lives in the overlay layer, outside the trigger's descendants. Style its classes directly. Define size/colour variables in :root before loading CN_STYLES. --cn-time-picker-wheel-row-height (32px) scales all five rows together, and --cn-time-picker-wheel-font-size (22px) sets the digits. --cn-time-picker-wheel-border and --cn-time-picker-wheel-selection-bg style the selected row. Layout and typography are CSS rules. Columns use Blinc’s overflow-fade for their edge fade; --cn-time-picker-wheel-fade defaults to 64px. The default popup is 232px wide (288px with seconds), with 32px rows and small footer actions.

:root { --cn-time-picker-wheel-row-height: 36px; }
.cn-time-picker-wheel-selection { border-color: var(--primary); }
.cn-time-picker-wheel-period-option[data-selected] { color: var(--primary); }

Colour Picker

#![allow(unused)]
fn main() {
use blinc::cn::prelude::*;
use blinc::core::{Color, reactive::signal};

let color = signal(Color::from_hex(0x6366f1));
let disabled = signal(false);
let favorites = signal(vec![Color::RED, Color::GREEN, Color::BLUE]);

let picker = cn::color_picker(color)
    .alpha(true)
    .swatches(favorites)
    .disabled(disabled)
    .on_change(|color| tracing::info!(hex = %color.to_hex_string(true), "Colour selected"));

// The same editor embedded directly into a settings panel.
let inline = cn::color_picker(color).alpha(true).inline();
}

The default trigger shows a swatch and hex value. It opens a popover with a saturation/brightness field, hue strip and hex editor. .inline() shows the panel directly. .alpha(true) adds an opacity strip. With alpha hidden, editing RGB or selecting a swatch preserves the bound opacity.

Drag the selection ring or press anywhere on a field to select a value. Arrow keys adjust saturation/brightness by 1%, hue by 1 degree, or opacity by 1%; Shift increases the step to 10. Home/End select the endpoints of a strip, or the saturation endpoints in the two-dimensional field. The hue and saturation selection survives moving through grey or black. Tab moves between the coordinate fields, hex editor and presets. Arrow keys act only on the current field; navigating inside the hex editor leaves the selected colour unchanged. Enter/Space opens the focused swatch trigger or selects a focused preset.

Hex entry is a draft until Enter. It accepts #RGB, #RGBA, #RRGGBB and #RRGGBBAA, with an optional #. Invalid input keeps the selected colour and shows an error border. RGB-only input preserves opacity; an alpha-bearing form changes opacity when .alpha(true) is enabled. An external colour write replaces the draft with the new canonical hex value.

swatches takes a Vec<Color>, Signal or Computed. It supplies selectable presets; the application owns persistence. No palette is imposed by default. Disabled pickers reject pointer, keyboard and hex edits. Disabling an open picker closes its popover. Clicking outside dismisses the panel.

Colour styling

The selected colour and coordinate gradients visualize the value. Widget chrome, focus, disabled appearance and motion follow CSS rules and theme tokens.

ClassPart
.cn-color-picker-triggerSwatch trigger; data-state="open", :focus-visible, :disabled
.cn-color-picker-panelInline or detached popup panel; disabled
.cn-color-picker-swatch, .cn-color-picker-valuePreview swatch and trigger hex label
.cn-color-picker-svSaturation/brightness field
.cn-color-picker-white, .cn-color-picker-blackField gradient layers
.cn-color-picker-hue, .cn-color-picker-alphaHue and opacity strips
.cn-color-picker-hue-segmentHue spectrum sectors, data-sector="0" through "5"
.cn-color-picker-markerSelection rings
.cn-color-picker-labelStrip labels
.cn-color-picker-entry, .cn-color-picker-hex-wrapHex row and field wrapper
.cn-color-picker-hex, .cn-color-picker-hex--invalidHex editor and invalid draft
.cn-color-picker-swatches, .cn-color-picker-presetPalette and swatches; data-state="selected"

The coordinate fields expose disabled, tabindex="0" and data-focus-visible. Presets expose disabled and tabindex="0" too. The fields and hex entry retain their node identities during value updates.

.class(...) and .id(...) apply to the trigger, or to the panel in inline mode. A popup is mounted in the overlay layer, so style it using its own part classes rather than a descendant selector from the trigger.

.cn-color-picker-panel {
    --cn-color-picker-border: var(--border-secondary);
    border-radius: 16px;
    width: 280px;
}
.cn-color-picker-sv { height: 180px; }
.cn-color-picker-marker { border-color: white; }

Variables are --cn-color-picker-bg, --cn-color-picker-panel-bg, --cn-color-picker-border, --cn-color-picker-radius and --cn-color-picker-marker. The checker canvas reads the global --cn-color-picker-checker-light and --cn-color-picker-checker-dark colours. Use --duration-fast and --ease-state to change interaction transitions.

Run the native example with cargo run -p blinc_app_examples --features cn --example cn_pickers_demo. The Colour Picker section in cn_demo also shows linked inline and popover editors, a bound disabled switch and a reactive palette with a save button. Its Time Picker section shows linked 12/24-hour editors and sizes, and its Date Picker section shows default and custom-formatted calendar popovers. The inline calendar is documented in Form Components.