Elemente
DatePicker
Ein Date Picker legt ein Datum über einen Calendar fest, der sich vom Trigger der App aus öffnet: ein Fälligkeitsdatum, ein Geburtstag, mit range die Tage einer Reise, mit timePrecision ein Datum samt Uhrzeit. Wie Select öffnet er sich so, wie es jedes Gerät erwartet:
import { type JSX, mount, signal, Text, View, windowSize } from "nsh";
import { type CalendarEntry, DatePicker, type DatePickerValue, type DateRangePreset, type HourCycle, type OpenChangeReason, type TimeFieldSegmentEntry } from "nsh/base";
import { button, desktop, Icon, Log, Panel, Segmented, size, Split, ui, wide } from "./palette";
// Three date pickers in a card: a due date (weekends disabled, presets
// beside the calendar, a clear button), a stay picked as a range over two
// months in a wide window, and a meeting's date and time with the hours
// on the clock the inspector picks. The trigger is the shared button look
// with the value and a chevron that turns while the calendar is open; the
// calendar is a popup at the trigger, a sheet on a touch screen. Every
// item is drawn by `renderItem` (month names, weekday initials, today on a
// quiet fill, the selected days filled, a range's band) and every segment
// of the time by `renderSegment`; the time's label starts its row and the
// field, a well as the number inputs', ends it.
const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
const WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
/** A day's ISO date (`"2026-10-28"`), in local time as the calendar's days are. */
const iso = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
const addDays = (d: Date, days: number) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + days);
/** An ISO date's month and day: `Oct 28`. */
const monthDay = (value: string) => `${MONTHS[Number(value.slice(5, 7)) - 1].slice(0, 3)} ${Number(value.slice(8, 10))}`;
/** A time `"HH:MM"` on the picked clock: `14:30`, or `2:30 PM`. */
function clock(time: string, cycle: HourCycle) {
if (cycle === "h23") return time;
const hour = Number(time.slice(0, 2));
return `${hour % 12 === 0 ? 12 : hour % 12}:${time.slice(3, 5)} ${hour < 12 ? "AM" : "PM"}`;
}
/** The text of a control: 13 with a pointer, the touch label size otherwise. */
const controlText = desktop ? 13 : size.label;
/** A day's cell: seven across the popup with a pointer, across a phone's sheet otherwise. */
const cell = desktop ? 32 : 42;
/** The gap between the months of a calendar of two. */
const monthGap = 16;
/**
* On a touch screen the calendar spans the sheet (the window less the sheet's padding), its columns sharing it: the
* header's titles split what Previous and Next leave, so the month and the year meet in the middle, and each month's
* title of two is over its month.
*/
const toggleWidth = () => Math.floor((windowSize().width - 2 * 10) / 2 - cell);
/** A popup at the trigger with a pointer, a sheet at the bottom on a touch screen. */
const surface = () =>
desktop
? { ...ui().card, borderRadius: 12, padding: 8 }
: { ...ui().card, borderTopLeftRadius: 20, borderTopRightRadius: 20, paddingHorizontal: 10, paddingTop: 14, paddingBottom: 30 };
const backdrop = { backgroundColor: "rgba(0,0,0,0.45)" };
/** The popup's layout: presets in a column beside the calendar with a pointer, a row of chips above it on a phone. */
const content = desktop ? { flexDirection: "row" as const, gap: 10 } : { gap: 12 };
const presets = () =>
desktop
? { gap: 2, minWidth: 104, paddingRight: 10, borderRightWidth: 1, borderColor: ui().border }
: { flexDirection: "row" as const, flexWrap: "wrap" as const, gap: 6 };
const preset = () =>
desktop
? { height: 28, paddingHorizontal: 8, borderRadius: 6, justifyContent: "center" as const, fontSize: 13, color: ui().text }
: { height: 34, paddingHorizontal: 12, borderRadius: 17, borderWidth: 1, borderColor: ui().border, justifyContent: "center" as const, fontSize: 14, color: ui().text };
/** The calendar: in the popup with a pointer; across the sheet on a touch screen, as the time's row under it. */
const calendar = desktop ? { padding: 2 } : {};
/** The time's row under a hairline, its label at the start and the field at the end, on the calendar's edges. */
const time = () => ({
gap: 10,
marginTop: 8,
paddingTop: 8,
borderTopWidth: 1,
borderColor: ui().border,
...(desktop ? { paddingHorizontal: 2 } : {}),
});
/** The time's field: a recessed well as the number inputs', with their hover and the keyboard's ring while it edits. */
const timeField = [
desktop ? "h-8 rounded-lg px-2" : "h-11 rounded-xl px-3",
"items-center border border-input bg-field",
"enabled:hover:border-faint focus-within:outline-2 focus-within:outline-info",
].join(" ");
/** The trigger: the shared button's look, the value at the start and a chevron in a lane as wide as it. */
function Trigger(props: { title?: string; placeholder: string; open: boolean; clearable?: boolean }) {
const look = button("secondary", {
style: () => ({
flexDirection: "row",
justifyContent: "space-between",
gap: 8,
...(desktop ? { width: 260 } : {}),
...(props.open ? { borderColor: ui().faint } : {}),
}),
});
return (
<View {...look}>
<Text style={{ numberOfLines: 1, flex: 1, fontSize: controlText, color: props.title ? ui().text : ui().muted }}>
{props.title ?? props.placeholder}
</Text>
{/* With a clear button over the end (`cleanable`), an empty lane as wide as the button in place of the chevron. */}
<View
class="transition-transform duration-150 motion-reduce:transition-none"
style={{
width: props.clearable ? 20 : 10,
height: 20,
alignItems: "center",
justifyContent: "center",
opacity: props.clearable ? 0 : 1,
transform: [{ rotate: props.open ? "270deg" : "90deg" }],
}}
>
<Text style={{ fontSize: 18, color: ui().muted }}>›</Text>
</View>
</View>
);
}
/**
* The calendar's items (`itemClass`): pressed at 70%; `group`, so the
* drawing inside follows the item's states.
*/
const itemClass = "group active:opacity-70";
/**
* An item of the calendar, drawn once from what it is, its states the
* item's classes: Previous and Next as arrows (faint while they take no
* press), the month and year toggles (or each month's title, with `months`
* over one) by name, the open view's toggle in the accent, the weekdays'
* initials, and the days: today on the theme's quiet `accent` fill
* (gpui-component's), the selected date and a range's ends filled, the days
* of a complete range on a band from end to end (`data-range-*`), the other
* months' days and the disabled ones faint, the keyboard's day ringed.
*/
function Item(props: { item: CalendarEntry; months?: number }) {
const kind = props.item.kind;
if (kind === "previous" || kind === "next") {
return (
<View class="group-disabled:opacity-30" style={{ width: cell, height: cell, alignItems: "center", justifyContent: "center" }}>
<Text style={{ fontSize: 20, color: ui().muted }}>{kind === "previous" ? "‹" : "›"}</Text>
</View>
);
}
if (kind === "monthToggle" || kind === "yearToggle") {
// A month's title in a calendar of several takes no press: the month and its year, over the month.
const title = kind === "monthToggle" && (props.months ?? 1) > 1;
return (
<Text
class="text-foreground group-selected:text-brand"
style={{
width: !desktop ? toggleWidth() : title ? 6 * cell + monthGap / 2 : kind === "monthToggle" ? 3 * cell : 2 * cell,
paddingHorizontal: 3,
textAlign: title ? "center" : kind === "monthToggle" ? "right" : "left",
fontSize: size.label,
fontWeight: "600",
}}
>
{title ? `${MONTHS[props.item.value - 1]} ${props.item.year}` : kind === "monthToggle" ? MONTHS[props.item.value - 1] : String(props.item.value)}
</Text>
);
}
if (kind === "weekday") {
return <Text style={{ minWidth: cell, paddingVertical: 4, textAlign: "center", fontSize: 11, color: ui().faint }}>{WEEKDAYS[props.item.value]}</Text>;
}
if (kind === "month" || kind === "year") {
// The month view's months (rows of 3) and the year view's years (rows of 5), as wide as a month; the shown one
// filled.
return (
<View
class="rounded-lg group-selected:bg-brand-fill"
style={{ minWidth: (7 * cell) / (kind === "month" ? 3 : 5), height: cell + 6, alignItems: "center", justifyContent: "center" }}
>
<Text class="text-foreground group-selected:text-on-brand" style={{ fontSize: controlText }}>
{kind === "month" ? MONTHS[props.item.value - 1].slice(0, 3) : String(props.item.value)}
</Text>
</View>
);
}
// A day, as wide as its column (the calendar's columns share its width). The band spans a range's middle days and
// reaches from its ends' middles; a range of one day has none. The circle fills for the selected date and a
// range's ends (not its middle days), else for today.
const own = props.item.outside ? "text-faint" : "text-foreground";
return (
<View style={{ minWidth: cell, height: cell, alignItems: "center", justifyContent: "center" }}>
<View
class={
"absolute inset-y-0.5 left-0 right-0 hidden bg-brand-tint group-data-range-middle:flex group-data-range-start:flex " +
"group-data-range-start:left-1/2 group-data-range-end:flex group-data-range-end:right-1/2 group-data-range-start:group-data-range-end:hidden"
}
/>
<View
class={
`${props.item.today ? "bg-accent" : ""} border-[1.5px] border-transparent group-focus:border-info ` +
"group-selected:bg-brand-fill group-data-range-middle:bg-transparent"
}
style={{ width: cell - 4, height: cell - 4, borderRadius: (cell - 4) / 2, alignItems: "center", justifyContent: "center" }}
>
<Text
class={
`${own} ${props.item.today ? "font-semibold" : "font-normal"} group-selected:font-semibold group-selected:text-on-brand ` +
`group-data-range-middle:${own} group-disabled:text-faint group-disabled:opacity-50`
}
style={{ fontSize: controlText, fontVariant: ["tabular-nums"] }}
>
{String(props.item.value)}
</Text>
</View>
</View>
);
}
/**
* A segment of the time: filled while it has the keys (`focus:` on the
* segment, `segmentClass`), its digits or AM / PM in the color on the fill
* (`group-focus:`), so moving between the segments draws nothing.
*/
const segmentClass = "group rounded active:opacity-70 focus:bg-brand-fill";
function Segment(props: { segment: TimeFieldSegmentEntry }) {
return (
<Text class="text-foreground group-focus:text-on-brand" style={{ paddingHorizontal: 3, fontSize: controlText, fontVariant: ["tabular-nums"] }}>
{props.segment.label}
</Text>
);
}
/** A label over its control, a quieter line under the label. */
function Field(props: { label: string; detail: string; children: JSX.Element }) {
return (
<View style={{ gap: 8 }}>
<View style={{ gap: 2 }}>
<Text style={{ fontSize: size.label, fontWeight: "500", color: ui().text }}>{props.label}</Text>
<Text style={{ fontSize: size.detail, color: ui().muted }}>{props.detail}</Text>
</View>
{props.children}
</View>
);
}
function App() {
const due = signal<string | null>(null);
const stay = signal<DatePickerValue>(null);
const meeting = signal<string | null>(null);
const cycle = signal<HourCycle>("h12");
const dueOpen = signal(false);
const stayOpen = signal(false);
const meetingOpen = signal(false);
const events = signal<string[]>([]);
const log = (line: string) => events.set([line, ...events()].slice(0, 8));
const opened = (open: (next: boolean) => void) => ({ open: next }: { open: boolean; reason: OpenChangeReason }) => open(next);
// The presets from today.
const today = new Date();
const monday = addDays(today, ((8 - today.getDay()) % 7) || 7);
const dues: DateRangePreset[] = [
{ label: "Today", value: iso(today) },
{ label: "Tomorrow", value: iso(addDays(today, 1)) },
{ label: "Next Monday", value: iso(monday) },
];
const stays: DateRangePreset[] = [
{ label: "This week", value: [iso(today), iso(addDays(today, (7 - today.getDay()) % 7))] },
{ label: "Next 7 nights", value: [iso(today), iso(addDays(today, 7))] },
];
const stayTitle = () => {
const r = stay();
return Array.isArray(r) && r[0] && r[1] ? `${monthDay(r[0])} – ${monthDay(r[1])}, ${r[1].slice(0, 4)}` : undefined;
};
const meetingTitle = () => {
const v = meeting();
return v ? `${monthDay(v)}, ${v.slice(0, 4)} · ${clock(v.slice(11, 16), cycle())}` : undefined;
};
return (
<View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>date-picker</Text>
<Text style={{ color: ui().muted }}>
{desktop ? "Arrows move between days, Return picks; the trigger takes Delete to clear" : "Tap a day; the meeting's time takes digits"}
</Text>
<Split
gap={14}
inspector={
<>
<Panel title="value">
<Text style={{ fontSize: 13, color: ui().text }}>{`due ${JSON.stringify(due())}`}</Text>
<Text style={{ fontSize: 13, color: ui().text }}>{`stay ${JSON.stringify(stay())}`}</Text>
<Text style={{ fontSize: 13, color: ui().text }}>{`meeting ${JSON.stringify(meeting())}`}</Text>
</Panel>
<Segmented label="hourCycle" options={["h12", "h23"] as const} value={cycle()} onChange={(v) => cycle.set(v)} />
<Log title="onChange" lines={events()} minHeight={120} />
</>
}
>
<View style={{ ...ui().card, borderRadius: 16, padding: 16, gap: 20 }}>
<Field label="Due" detail="Weekdays only, presets beside the calendar, a clear button">
<DatePicker
value={due()}
onChange={({ value }) => {
due.set(typeof value === "string" ? value : null);
log(`Due → ${JSON.stringify(value)}`);
}}
onOpenChange={opened((next) => dueOpen.set(next))}
accessibilityLabel="Due"
disabledMatcher={[0, 6]}
presets={dues}
offset={4}
margin={8}
popupStyle={surface()}
backdropStyle={backdrop}
contentStyle={content}
presetsStyle={presets()}
presetStyle={preset()}
presetClass="active:opacity-70"
calendarStyle={calendar}
headerStyle={{ marginBottom: 4 }}
itemClass={itemClass}
renderItem={(item) => <Item item={item} />}
style={desktop ? { width: 260 } : undefined}
cleanable
clearButtonAccessibilityLabel="Clear the due date"
clearButtonClass="absolute items-center justify-center rounded-full enabled:hover:opacity-70"
clearButtonStyle={{ top: (size.control - 20) / 2, right: 10, width: 20, height: 20 }}
clearButton={<Icon name="close" size={14} tint={ui().muted} />}
trigger={<Trigger title={due() ? `${monthDay(due()!)}, ${due()!.slice(0, 4)}` : undefined} placeholder="No due date" open={dueOpen()} clearable={due() !== null} />}
/>
</Field>
<Field label="Stay" detail={wide() ? "A range over two months: the first press starts it" : "A range: the first press starts it, the second ends it"}>
<DatePicker
range
value={stay()}
onChange={({ value }) => {
stay.set(value);
log(`Stay → ${JSON.stringify(value)}`);
}}
onOpenChange={opened((next) => stayOpen.set(next))}
accessibilityLabel="Stay"
numberOfMonths={wide() ? 2 : 1}
firstDayOfWeek={1}
presets={stays}
offset={4}
margin={8}
popupStyle={surface()}
backdropStyle={backdrop}
contentStyle={content}
presetsStyle={presets()}
presetStyle={preset()}
presetClass="active:opacity-70"
calendarStyle={calendar}
headerStyle={{ marginBottom: 4 }}
bodyStyle={{ gap: monthGap }}
itemClass={itemClass}
renderItem={(item) => <Item item={item} months={wide() ? 2 : 1} />}
trigger={<Trigger title={stayTitle()} placeholder="Check-in – check-out" open={stayOpen()} />}
/>
</Field>
<Field label="Meeting" detail={desktop ? "A day, then its time; the day again closes the calendar" : "A tap picks the day; open it again for the time"}>
<DatePicker
value={meeting()}
onChange={({ value }) => {
meeting.set(typeof value === "string" ? value : null);
log(`Meeting → ${JSON.stringify(value)}`);
}}
onOpenChange={opened((next) => meetingOpen.set(next))}
accessibilityLabel="Meeting"
timePrecision="minute"
hourCycle={cycle()}
defaultTime="09:00"
offset={4}
margin={8}
popupStyle={surface()}
backdropStyle={backdrop}
calendarStyle={calendar}
headerStyle={{ marginBottom: 4 }}
itemClass={itemClass}
renderItem={(item) => <Item item={item} />}
timeStyle={time()}
timeLabel={<Text style={{ fontSize: controlText, color: ui().muted }}>Time</Text>}
timeAccessibilityLabel="Meeting time"
timeFieldClass={timeField}
segmentClass={segmentClass}
separatorStyle={{ fontSize: controlText, color: ui().faint }}
renderSegment={(segment) => <Segment segment={segment} />}
trigger={<Trigger title={meetingTitle()} placeholder="Not scheduled" open={meetingOpen()} />}
/>
</Field>
</View>
</Split>
</View>
);
}
mount(App);- Auf Desktops und in Desktop-Browsern als Popup am Trigger; beim Öffnen liegt der Tastaturfokus auf den Tagen.
- Auf Smartphones und Tablets als Sheet am unteren Bildschirmrand über einem Backdrop.
Der Picker besitzt das Verhalten: den Wert (den der App oder seinen eigenen), den Öffnungszustand, den Kalender, die Uhrzeit, die Presets, die Tastatur und das, was Screenreader vorlesen. Das Aussehen liegt bei der App: Sie zeichnet den Trigger (mit dem Wert, einem Platzhalter, einem Icon) und gestaltet das Popup, die Teile des Kalenders, die Presets und das Zeitfeld.
use chrono::NaiveDate;
use nsh_base::{DateRangePreset, DateTime, PickedDate, date_picker, theme};
use nsh_core::prelude::*;
fn trip_dates() -> impl IntoElement {
let trip = signal(DateTime::Range(None, None));
let title = move || {
trip.get()
.format("%b %-d")
.map(|s| s.to_string())
.unwrap_or_else(|| "Pick dates".to_string())
};
let day = |m, d| NaiveDate::from_ymd_opt(2026, m, d).unwrap();
date_picker()
.range(true)
.number_of_months(2)
.value(move || trip.get())
.on_change(move |next: &PickedDate| trip.set(next.value))
.presets(vec![
DateRangePreset::range("This week", day(10, 25), day(10, 31)),
DateRangePreset::range("Next week", day(11, 1), day(11, 7)),
])
.accessibility_label("Trip")
.offset(px(4.0))
.popup_style(|s| {
s.p_3()
.rounded_lg()
.border_1()
.border_color(move || theme().border)
.bg(move || theme().popover)
.shadow_md()
})
.trigger(
view()
.h_9()
.w_56()
.px_3()
.justify_center()
.rounded_md()
.border_1()
.border_color(move || theme().input)
.child(text(title)),
)
}import { mount, signal, Text, View } from "nsh";
import { DatePicker, type DatePickerValue } from "nsh/base";
function App() {
const due = signal<DatePickerValue>(null);
return (
<DatePicker
value={due()}
onChange={({ value }) => due.set(value)}
timePrecision="minute"
cleanable
clearButton={<Text>×</Text>}
clearButtonAccessibilityLabel="Clear the due date"
presets={[{ label: "Tomorrow 9:00", value: "2026-10-29T09:00" }]}
accessibilityLabel="Due"
offset={4}
popupClass="rounded-lg border border-border bg-popover p-3 text-popover-foreground shadow-md"
backdropClass="bg-black/40"
contentClass="flex-row gap-3"
presetClass="rounded-md px-2 py-1.5 text-sm hover:bg-accent"
headerClass="flex-row items-center justify-between"
rowClass="flex-row justify-between"
itemClass="h-8 min-w-8 items-center justify-center rounded-md text-sm focus:ring-2 focus:ring-ring selected:bg-primary selected:text-primary-foreground disabled:opacity-50"
timeClass="mt-2 gap-3"
timeLabel={<Text class="text-sm">Time</Text>}
timeAccessibilityLabel="Due time"
timeFieldClass="flex-row rounded-md border border-input px-2 py-1"
segmentClass="text-sm tabular-nums focus:bg-accent"
trigger={
<View class="h-9 w-56 justify-center rounded-md border border-input px-3 hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring">
<Text>{typeof due() === "string" ? (due() as string).replace("T", " ") : "Pick a date"}</Text>
</View>
}
/>
);
}
mount(App);DatePicker stammt aus nsh/base, dem nsh-base-Crate in Rust. In Rust erzeugt date_picker() einen Picker mit einem Setter pro Prop (.value(..), .range(..), .time_precision(..)) und einem Listener pro Event (.on_change(..)). Sein Wert ist ein DateTime (DateTime::Single, DateTime::Range, aus chrono Datumsangaben oder Datums- und Zeitangaben); onChange liefert einen PickedDate, also den Wert mit der Zeitgenauigkeit des Pickers.
#Wert
Der Wert hat die Formen der HTML-Inputs: ein ISO-Datum, "2026-10-28", wie das von <input type="date">; mit range [start, end]; mit timePrecision Datum und Uhrzeit, "2026-10-28T09:30" ("2026-10-28T09:30:15" bei Sekunden), wie das von <input type="datetime-local">; null bedeutet keinen Wert. Mit value gehört der Wert der App: Der Picker zeigt ihn an und meldet jede Änderung mit onChange({ value }); die App setzt value, um ihr zu folgen. Wird er weggelassen, behält der Picker seinen eigenen Wert, zunächst aus defaultValue.
- Einzelnes Datum: Ein Druck auf einen Tag setzt den Wert und schließt das Popup.
- Mit
rangebeginnt der erste Druck den Bereich und der zweite beendet ihn, wie bei einem Calendar; der vollständige Bereich setzt den Wert und schließt das Popup. - Mit
timePrecision(nur bei einem einzelnen Datum) erscheint unter dem Kalender ein Zeitfeld, und jede Änderung der Uhrzeit wird gemeldet. Auf Desktops lässt die Auswahl eines Tages das Popup für die Uhrzeit geöffnet und meldet das Datum mit seiner Uhrzeit; ein erneuter Druck auf den ausgewählten Tag schließt das Popup. Auf Smartphones und Tablets wählt ein Tipp auf einen Tag diesen mit seiner Uhrzeit aus und schließt das Sheet, wie bei einem einzelnen Datum; für die Uhrzeit öffnet sich das Sheet erneut. Ein Tag, der vor jeder Uhrzeit gewählt wird, erhältdefaultTime(Standard00:00).hourCyclelegt fest, wie das Feld die Stunden zählt.
cleanable fügt eine Clear-Schaltfläche hinzu, solange ein Wert vorhanden ist: Ein Druck darauf löscht ihn (onChange({ value: null })). Sie steht nach dem Trigger, nicht darin, sodass ein Druck darauf nie das Popup öffnet; die App platziert und befüllt sie (clearButton), wie bei der Clear-Schaltfläche eines Select.
#Presets
presets sind Schaltflächen neben dem Kalender, die den Wert setzen: „Heute“, „Letzte 7 Tage“, „Morgen um 9“. Jedes ist { label, value }, value ein Datum, einen Bereich oder Datums- und Zeitangaben, unverändert übernommen; ein Druck setzt es und schließt das Popup. contentClass ordnet die Spalte der Presets und den Kalender mit der Uhrzeit an (eine Zeile, flex-row); presetsClass gestaltet die Spalte, presetClass jede Schaltfläche.
#Der Kalender
Jede Prop von Calendar außer dem Wert wird an die des Pickers weitergereicht: numberOfMonths, firstDayOfWeek, disabledMatcher, yearRange, previousAccessibilityLabel, nextAccessibilityLabel, die Parts (headerClass bis itemStyle) und renderItem. calendarClass und calendarStyle gestalten den Kalender selbst. Seine Ansichten und Tasten sind die eines Calendar.
Die Zeitzeile (timeClass, timeStyle) enthält an ihrem Anfang timeLabel, die Beschriftung der App dafür („Uhrzeit“), und an ihrem Ende das Zeitfeld (timeFieldClass, timeFieldStyle, timeAccessibilityLabel): eine Zeile, deren Elemente zentriert und auseinandergezogen sind (flex-row items-center justify-between, wie bei gpui-component). Die Segmente des Feldes entsprechen denen eines TimeField (segmentClass, separatorClass, renderSegment).
#Props
| Prop | Rust | Typ | Standard | Beschreibung |
|---|---|---|---|---|
value |
.value(..) |
DatePickerValue |
keiner | Der Wert, der der App gehört. |
defaultValue |
.default_value(..) |
DatePickerValue |
null |
Der Anfangswert, ohne value. |
range |
.range(..) |
boolean |
false |
Einen Datumsbereich auswählen. |
open, defaultOpen |
.open(..), .default_open(..) |
boolean |
false |
Der Öffnungszustand, von der App oder vom Picker selbst verwaltet. |
disabled |
.disabled(..) |
boolean |
false |
Er bleibt geschlossen und nimmt weder Drücke noch Tastaturfokus an. |
accessibilityLabel |
.accessibility_label(..) |
string |
keiner | Wie Screenreader ihn nennen. |
dateFormat |
.date_format(..) |
string |
"%Y/%m/%d" und die Uhrzeit |
Wie Screenreader den Wert vorlesen, im Format von chrono. |
timePrecision |
.time_precision(..) |
"minute" | "second" |
keiner | Auch die Uhrzeit bearbeiten, bis hinunter zu dieser Genauigkeit. |
hourCycle |
.hour_cycle(..) |
"h23" | "h12" |
"h23" |
Wie das Zeitfeld die Stunden zählt. |
defaultTime |
.default_time(..) |
string |
"00:00" |
Die Uhrzeit, die ein Datum erhält, bevor der Benutzer eine festlegt. |
cleanable |
.cleanable(..) |
boolean |
false |
Eine Clear-Schaltfläche, solange ein Wert vorhanden ist. |
presets |
.presets(..) |
DateRangePreset[] |
[] |
Schaltflächen im Popup, die den Wert setzen. |
numberOfMonths, firstDayOfWeek, disabledMatcher, yearRange |
wie bei Calendar |
wie bei Calendar |
wie bei Calendar |
Die Monate, Wochen, deaktivierten Daten und Jahre des Kalenders. |
previousAccessibilityLabel, nextAccessibilityLabel |
wie bei Calendar |
string |
keiner | Was Screenreader für „Zurück“ und „Weiter“ des Kalenders ansagen. |
placement, align, offset, margin |
.placement(..), … |
wie bei Select |
"bottom", "start", 0, 0 |
Desktops: Wo das Popup relativ zum Trigger erscheint. |
popupClass, popupStyle |
.popup_class(..), .popup_style(..) |
string, StyleProp<ViewStyle> |
keiner | Das Popup (Desktops) oder das Sheet (Touchscreens). |
backdropClass, backdropStyle |
.backdrop_class(..), .backdrop_style(..) |
string, StyleProp<ViewStyle> |
keiner | Das Backdrop hinter dem Sheet. |
contentClass, contentStyle |
.content_class(..), .content_style(..) |
string, StyleProp<ViewStyle> |
keiner | Der Inhalt des Popups: die Presets neben dem Kalender und die Uhrzeit. |
presetsClass, presetsStyle |
.presets_class(..), .presets_style(..) |
string, StyleProp<ViewStyle> |
keiner | Die Spalte der Presets. |
presetClass, presetStyle |
.preset_class(..), .preset_style(..) |
string, StyleProp<TextStyle> |
keiner | Die Schaltfläche jedes Presets. |
calendarClass, calendarStyle |
.calendar_class(..), .calendar_style(..) |
string, StyleProp<ViewStyle> |
keiner | Der Kalender. |
headerClass … itemStyle |
wie bei Calendar |
wie bei Calendar |
keiner | Header, Body, Monate, Zeilen und Elemente des Kalenders. |
renderItem |
.render_item(..) |
(item: CalendarEntry) => JSX.Element |
keiner | Jedes Element des Kalenders, von der App anhand dessen gezeichnet, was es ist; seine Zustände sind die Klassen der Elemente, wie bei Calendar. |
timeClass, timeStyle |
.time_class(..), .time_style(..) |
string, StyleProp<ViewStyle> |
keiner | Die Zeile der Uhrzeit: ihre Beschriftung am Anfang, das Feld am Ende. |
timeLabel |
.time_label(..) |
JSX.Element |
keiner | Was die Zeile der Uhrzeit vor dem Feld zeigt. |
timeFieldClass, timeFieldStyle |
.time_field_class(..), .time_field_style(..) |
string, StyleProp<ViewStyle> |
keiner | Das Zeitfeld. |
timeAccessibilityLabel |
.time_accessibility_label(..) |
string |
keiner | Wie Screenreader das Zeitfeld nennen. |
segmentClass, segmentStyle, separatorClass, separatorStyle, renderSegment |
wie bei TimeField |
wie bei TimeField |
keiner | Die Segmente des Zeitfeldes und :. |
clearButtonClass, clearButtonStyle |
.clear_button_class(..), .clear_button_style(..) |
string, StyleProp<ViewStyle> |
keiner | Die Clear-Schaltfläche (cleanable). |
clearButton |
.clear_button(..) |
JSX.Element |
keiner | Der Inhalt der Clear-Schaltfläche. |
clearButtonAccessibilityLabel |
.clear_button_accessibility_label(..) |
string |
keiner | Wie Screenreader die Clear-Schaltfläche nennen. |
trigger |
.trigger(..) |
JSX.Element |
keiner | Was beim Drücken das Popup öffnet; das Popup wird relativ dazu platziert. |
class, style |
Style-Methoden | string, StyleProp<ViewStyle> |
keiner | Die View um den Trigger. |
#Events
| Event | Rust | Beschreibung |
|---|---|---|
onChange |
.on_change(|value| ..) |
Der Wert hat sich geändert (ein Tag, das Ende eines Bereichs, ein Preset, die Uhrzeit, die Clear-Schaltfläche): { value }. Setze value, um ihm zu folgen. |
onOpenChange |
.on_open_change(|e| ..) |
Das Popup wurde geöffnet oder geschlossen, mit dem Grund ({ open, reason }). Setze open, um ihm zu folgen. |
onOpen, onClose |
.on_open(..), .on_close(..) |
Das Popup wird angezeigt bzw. ist verschwunden. |
onCancel |
.on_cancel(..) |
Der Benutzer hat das Popup ohne Auswahl geschlossen (Escape oder Zurück, ein Druck außerhalb oder auf das Backdrop). |
#Parts
| Part | Wo | Gestaltet mit |
|---|---|---|
| Der Trigger | die View des Pickers | dem eigenen Element der App (trigger) |
| Die Clear-Schaltfläche | die View des Pickers, nach dem Trigger (cleanable, solange ein Wert vorhanden ist) |
clearButtonClass, clearButtonStyle; ihr Inhalt clearButton |
| Das Popup oder Sheet | über allem, am Trigger (Desktops) oder am unteren Rand (Touchscreens) | popupClass, popupStyle |
| Das Backdrop | hinter dem Sheet (Touchscreens) | backdropClass, backdropStyle |
| Der Inhalt | das Popup | contentClass, contentStyle |
| Die Presets | der Inhalt, zuerst (presets) |
presetsClass, presetsStyle; jedes presetClass, presetStyle |
| Der Kalender | der Inhalt | calendarClass, calendarStyle und den Parts von Calendar |
| Die Zeile der Uhrzeit | unter dem Kalender (timePrecision) |
timeClass, timeStyle; ihre Beschriftung timeLabel |
| Das Zeitfeld | die Zeile der Uhrzeit | timeFieldClass, timeFieldStyle und den Parts von TimeField |
#Tastatur
| Taste | Aktion |
|---|---|
| Return (auf dem Trigger) | Öffnet das Popup oder schließt es. |
| Delete, Backspace (auf dem Trigger) | Löscht den Wert, solange das Popup geschlossen ist. |
| Escape | Schließt das Popup ohne Auswahl. |
Im Popup bewegen die Tasten des Kalenders zwischen den Tagen und wählen einen aus (siehe Tastatur), und die Tasten des Zeitfeldes bearbeiten die Uhrzeit (siehe Tastatur).
#Barrierefreiheit
- Der Trigger ist für Screenreader eine Combobox: Ihr Label ist
accessibilityLabel, ihr Wert das Datum, wiedateFormates schreibt (standardmäßig2026/10/28, mit der Uhrzeit beitimePrecision), und sie gibt an, ob das Popup erweitert ist.disabledblendet sie ab. - Die Tage des Kalenders sind Schaltflächen, die nach ihren ISO-Daten benannt sind (siehe Barrierefreiheit); die Presets sind Schaltflächen, die nach ihren Beschriftungen benannt sind; das Zeitfeld wird durch
timeAccessibilityLabelbenannt. - Die Clear-Schaltfläche ist eine Schaltfläche, die durch
clearButtonAccessibilityLabelbenannt wird.
Siehe Barrierefreiheit für Labels, Rollen und Ansagen im Allgemeinen.