Éléments
DatePicker
Un date picker définit une date à partir d’un Calendar qui s’ouvre depuis le déclencheur de l’app : une échéance, un anniversaire, avec range les jours d’un voyage, avec timePrecision une date et une heure de la journée. Comme Select, il s’ouvre de la manière attendue sur chaque appareil :
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);- Sur les ordinateurs de bureau et dans les navigateurs de bureau, une popup au niveau du déclencheur ; son ouverture place le clavier sur les jours.
- Sur les téléphones et les tablettes, une feuille en bas de l’écran, au-dessus d’un arrière-plan.
Le picker gère le comportement : la valeur (celle de l’app, ou la sienne), l’état d’ouverture, le calendrier, l’heure, les préréglages, le clavier et ce qu’entendent les lecteurs d’écran. L’apparence revient à l’app : elle dessine le déclencheur (avec la valeur, un placeholder, une icône) et stylise la popup, les parties du calendrier, les préréglages et le champ d’heure.
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 provient de nsh/base, le crate nsh-base en Rust. En Rust, date_picker() en crée un avec un setter par prop (.value(..), .range(..), .time_precision(..)) et un listener par événement (.on_change(..)). Sa valeur est un DateTime (DateTime::Single, DateTime::Range, de chrono dates ou dates et heures) ; onChange donne un PickedDate, la valeur avec la précision d’heure du picker.
#Valeur
La valeur prend les formes des inputs HTML : une date ISO, "2026-10-28", comme celle de <input type="date"> ; avec range [start, end] ; avec timePrecision une date et une heure, "2026-10-28T09:30" ("2026-10-28T09:30:15" aux secondes), comme celle de <input type="datetime-local"> ; null signifie aucune valeur. Avec value, l’app la possède : le picker l’affiche et signale chaque changement avec onChange({ value }) ; l’app définit value pour le suivre. Si elle est omise, le picker garde sa propre valeur, à partir de defaultValue au départ.
- Une seule date : un appui sur un jour définit la valeur et ferme la popup.
- Avec
range, le premier appui commence la plage et le second la termine, comme dans un calendrier ; la plage complète définit la valeur et ferme la popup. - Avec
timePrecision(une seule date uniquement), un champ d’heure s’affiche sous le calendrier et chaque modification de l’heure est signalée. Sur les ordinateurs de bureau, choisir un jour garde la popup ouverte pour l’heure et signale la date avec son heure ; un nouvel appui sur le jour sélectionné ferme la popup. Sur les téléphones et les tablettes, un appui sur un jour le choisit à son heure et ferme la feuille, comme pour une seule date ; la feuille se rouvre pour l’heure. Un jour choisi avant toute heure reçoitdefaultTime(par défaut00:00).hourCycleest la manière dont le champ compte les heures.
cleanable ajoute un bouton d’effacement tant qu’il y a une valeur : un appui l’efface (onChange({ value: null })). Il vient après le déclencheur, pas dedans, de sorte qu’un appui dessus n’ouvre jamais la popup ; l’app le place et le remplit (clearButton), comme pour le bouton d’effacement d’un select.
#Préréglages
Les presets sont des boutons à côté du calendrier qui définissent la valeur : « Aujourd’hui », « 7 derniers jours », « Demain à 9 h ». Chacun est { label, value }, value une date, une plage ou des dates et heures, conservées telles quelles ; un appui la définit et ferme la popup. contentClass dispose la colonne des préréglages et le calendrier avec l’heure (une ligne, flex-row) ; presetsClass stylise la colonne, presetClass chaque bouton.
#Le calendrier
Chaque prop de Calendar sauf sa valeur est transmise à celles du picker : numberOfMonths, firstDayOfWeek, disabledMatcher, yearRange, previousAccessibilityLabel, nextAccessibilityLabel, les parties (headerClass à itemStyle) et renderItem. calendarClass et calendarStyle stylisent le calendrier lui-même. Ses vues et ses touches sont celles d’un calendrier.
La ligne de l’heure (timeClass, timeStyle) contient timeLabel, le texte de l’app pour la désigner (« Heure »), à son début et le champ d’heure (timeFieldClass, timeFieldStyle, timeAccessibilityLabel) à sa fin : une ligne dont les éléments sont centrés et espacés (flex-row items-center justify-between, comme ceux de gpui-component). Les segments du champ sont ceux d’un TimeField (segmentClass, separatorClass, renderSegment).
#Props
| Prop | Rust | Type | Défaut | Description |
|---|---|---|---|---|
value | .value(..) | DatePickerValue | aucun | La valeur, possédée par l’app. |
defaultValue | .default_value(..) | DatePickerValue | null | La valeur au départ, sans value. |
range | .range(..) | boolean | false | Choisir une plage de dates. |
open, defaultOpen | .open(..), .default_open(..) | boolean | false | L’état d’ouverture, celui de l’app ou celui du picker. |
disabled | .disabled(..) | boolean | false | Il reste fermé et ne reçoit ni appuis ni focus clavier. |
accessibilityLabel | .accessibility_label(..) | string | aucun | Comment les lecteurs d’écran le nomment. |
dateFormat | .date_format(..) | string | "%Y/%m/%d" et l’heure | Comment les lecteurs d’écran énoncent la valeur, au format de chrono. |
timePrecision | .time_precision(..) | "minute" | "second" | aucun | Modifier aussi l’heure de la journée, jusqu’à cette précision. |
hourCycle | .hour_cycle(..) | "h23" | "h12" | "h23" | Comment le champ d’heure compte les heures. |
defaultTime | .default_time(..) | string | "00:00" | L’heure qu’une date reçoit avant que l’utilisateur n’en définisse une. |
cleanable | .cleanable(..) | boolean | false | Un bouton d’effacement tant qu’il y a une valeur. |
presets | .presets(..) | DateRangePreset[] | [] | Des boutons dans la popup qui définissent la valeur. |
numberOfMonths, firstDayOfWeek, disabledMatcher, yearRange | comme Calendar | comme Calendar | comme Calendar | Les mois, semaines, dates désactivées et années du calendrier. |
previousAccessibilityLabel, nextAccessibilityLabel | comme Calendar | string | aucun | Ce que disent les lecteurs d’écran pour Précédent et Suivant du calendrier. |
placement, align, offset, margin | .placement(..), … | comme Select | "bottom", "start", 0, 0 | Ordinateurs de bureau : où la popup se place par rapport au déclencheur. |
popupClass, popupStyle | .popup_class(..), .popup_style(..) | string, StyleProp<ViewStyle> | aucun | La popup (ordinateurs de bureau) ou la feuille (écrans tactiles). |
backdropClass, backdropStyle | .backdrop_class(..), .backdrop_style(..) | string, StyleProp<ViewStyle> | aucun | L’arrière-plan derrière la feuille. |
contentClass, contentStyle | .content_class(..), .content_style(..) | string, StyleProp<ViewStyle> | aucun | Le contenu de la popup : les préréglages à côté du calendrier et l’heure. |
presetsClass, presetsStyle | .presets_class(..), .presets_style(..) | string, StyleProp<ViewStyle> | aucun | La colonne des préréglages. |
presetClass, presetStyle | .preset_class(..), .preset_style(..) | string, StyleProp<TextStyle> | aucun | Le bouton de chaque préréglage. |
calendarClass, calendarStyle | .calendar_class(..), .calendar_style(..) | string, StyleProp<ViewStyle> | aucun | Le calendrier. |
headerClass … itemStyle | comme Calendar | comme Calendar | aucun | L’en-tête, le corps, les mois, les lignes et les éléments du calendrier. |
renderItem | .render_item(..) | (item: CalendarEntry) => JSX.Element | aucun | Chaque élément du calendrier, dessiné par l’app d’après ce qu’il est ; ses états sont les classes des éléments, comme celles de Calendar. |
timeClass, timeStyle | .time_class(..), .time_style(..) | string, StyleProp<ViewStyle> | aucun | La ligne de l’heure : son libellé au début, le champ à la fin. |
timeLabel | .time_label(..) | JSX.Element | aucun | Ce que la ligne de l’heure affiche avant le champ. |
timeFieldClass, timeFieldStyle | .time_field_class(..), .time_field_style(..) | string, StyleProp<ViewStyle> | aucun | Le champ d’heure. |
timeAccessibilityLabel | .time_accessibility_label(..) | string | aucun | Comment les lecteurs d’écran nomment le champ d’heure. |
segmentClass, segmentStyle, separatorClass, separatorStyle, renderSegment | comme TimeField | comme TimeField | aucun | Les segments du champ d’heure et :. |
clearButtonClass, clearButtonStyle | .clear_button_class(..), .clear_button_style(..) | string, StyleProp<ViewStyle> | aucun | Le bouton d’effacement (cleanable). |
clearButton | .clear_button(..) | JSX.Element | aucun | Le contenu du bouton d’effacement. |
clearButtonAccessibilityLabel | .clear_button_accessibility_label(..) | string | aucun | Comment les lecteurs d’écran nomment le bouton d’effacement. |
trigger | .trigger(..) | JSX.Element | aucun | Ce qui ouvre la popup lorsqu’on appuie dessus ; la popup est placée par rapport à lui. |
class, style | méthodes de style | string, StyleProp<ViewStyle> | aucun | La vue autour du déclencheur. |
#Événements
| Événement | Rust | Description |
|---|---|---|
onChange | .on_change(|value| ..) | La valeur a changé (un jour, la fin d’une plage, un préréglage, l’heure, le bouton d’effacement) : { value }. Définissez value pour le suivre. |
onOpenChange | .on_open_change(|e| ..) | La popup s’est ouverte ou fermée, avec la raison ({ open, reason }). Définissez open pour le suivre. |
onOpen, onClose | .on_open(..), .on_close(..) | La popup est affichée, ou a disparu. |
onCancel | .on_cancel(..) | L’utilisateur a fermé la popup sans faire de choix (Échap ou retour, un appui à l’extérieur ou sur l’arrière-plan). |
#Parties
| Partie | Où | Stylisée avec |
|---|---|---|
| Le déclencheur | la vue du picker | l’élément propre à l’app (trigger) |
| Le bouton d’effacement | la vue du picker, après le déclencheur (cleanable, tant qu’il y a une valeur) | clearButtonClass, clearButtonStyle ; son contenu clearButton |
| La popup ou la feuille | au-dessus de tout, au niveau du déclencheur (ordinateurs de bureau) ou en bas (écrans tactiles) | popupClass, popupStyle |
| L’arrière-plan | derrière la feuille (écrans tactiles) | backdropClass, backdropStyle |
| Le contenu | la popup | contentClass, contentStyle |
| Les préréglages | le contenu, en premier (presets) | presetsClass, presetsStyle ; chacun presetClass, presetStyle |
| Le calendrier | le contenu | calendarClass, calendarStyle, et les parties de Calendar |
| La ligne de l’heure | sous le calendrier (timePrecision) | timeClass, timeStyle ; son libellé timeLabel |
| Le champ d’heure | la ligne de l’heure | timeFieldClass, timeFieldStyle, et les parties de TimeField |
#Clavier
| Touche | Action |
|---|---|
| Entrée (sur le déclencheur) | Ouvre la popup, ou la ferme. |
| Suppr, Retour arrière (sur le déclencheur) | Efface la valeur tant que la popup est fermée. |
| Échap | Ferme la popup sans faire de choix. |
Dans la popup, les touches du calendrier permettent de se déplacer entre les jours et d’en choisir un (voir Clavier) et celles du champ d’heure modifient l’heure (voir Clavier).
#Accessibilité
- Le déclencheur est une combobox pour les lecteurs d’écran : son libellé est
accessibilityLabel, sa valeur la date telle quedateFormatl’écrit (par défaut2026/10/28, avec l’heure àtimePrecision), et il indique si la popup est développée.disabledl’atténue. - Les jours du calendrier sont des boutons nommés par leurs dates ISO (voir Accessibilité) ; les préréglages sont des boutons nommés par leurs libellés ; le champ d’heure est nommé par
timeAccessibilityLabel. - Le bouton d’effacement est un bouton nommé par
clearButtonAccessibilityLabel.
Voir Accessibilité pour les libellés, les rôles et les annonces en général.