Elementos
DatePicker
Un date picker establece una fecha desde un Calendar que se abre desde el disparador de la app: una fecha de vencimiento, un cumpleaños,
con range los días de un viaje, con timePrecision una fecha y una hora del día. Como Select, se abre como
espera cada dispositivo:
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);- En los escritorios y en los navegadores de escritorio, un popup junto al disparador; al abrirlo, el teclado queda sobre los días.
- En teléfonos y tabletas, una hoja en la parte inferior de la pantalla sobre un fondo (backdrop).
El selector se encarga del comportamiento: el valor (el de la app o el suyo propio), el estado de apertura, el calendario, la hora, los preajustes, el teclado y lo que oyen los lectores de pantalla. El aspecto es de la app: dibuja el disparador (con el valor, un marcador de posición, un icono) y da estilo al popup, a las partes del calendario, a los preajustes y al campo de hora.
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 viene de nsh/base, el crate nsh-base de Rust. En Rust, date_picker()
crea uno con un setter por prop (.value(..), .range(..), .time_precision(..)) y un listener por evento
(.on_change(..)). Su valor es un DateTime (DateTime::Single, DateTime::Range, a partir de fechas chrono o de fechas y
horas); onChange devuelve un PickedDate, el valor con la precisión de hora del selector.
#Valor
El valor tiene las formas de los inputs de HTML: una fecha ISO, "2026-10-28", como la de <input type="date">; con range
[start, end]; con timePrecision una fecha y hora, "2026-10-28T09:30" ("2026-10-28T09:30:15" con segundos), como la de
<input type="datetime-local">; null es ninguno. Con value, el valor es de la app: el selector lo muestra e informa de cada
cambio con onChange({ value }); la app establece value para seguirlo. Si se omite, el selector conserva su propio valor, que al principio viene de
defaultValue.
- Una sola fecha: pulsar un día establece el valor y cierra el popup.
- Con
range, la primera pulsación inicia el rango y la segunda lo termina, como en un calendario; el rango completo establece el valor y cierra el popup. - Con
timePrecision(solo para una fecha), debajo del calendario se muestra un campo de hora y se informa de cada edición de la hora. En los escritorios, elegir un día mantiene el popup abierto para la hora e informa de la fecha con su hora; volver a pulsar el día seleccionado cierra el popup. En teléfonos y tabletas, tocar un día lo elige con su hora y cierra la hoja, como hace una sola fecha; la hoja se vuelve a abrir para la hora. Un día elegido antes de fijar una hora recibedefaultTime(por defecto00:00).hourCyclees cómo cuenta las horas el campo.
cleanable añade un botón de borrado mientras haya un valor: al pulsarlo se borra (onChange({ value: null })). Va
después del disparador, no dentro de él, de modo que pulsarlo nunca abre el popup; la app lo coloca y lo rellena
(clearButton), como el botón de borrado de Select.
#Preajustes
Los presets son botones junto al calendario que establecen el valor: “Hoy”, “Últimos 7 días”, “Mañana a las 9”. Cada uno es
{ label, value }, value una fecha, un rango o fechas y horas, conservado tal como se da; al pulsarlo se establece y se cierra el popup.
contentClass organiza la columna de preajustes y el calendario con la hora (una fila, flex-row); presetsClass da estilo a
la columna y presetClass a cada botón.
#El calendario
Todas las props de Calendar salvo su valor se pasan a las del selector: numberOfMonths, firstDayOfWeek,
disabledMatcher, yearRange, previousAccessibilityLabel, nextAccessibilityLabel, las partes (de headerClass a
itemStyle) y renderItem. calendarClass y calendarStyle dan estilo al propio calendario. Sus vistas y teclas son las de un
calendario.
La fila de la hora (timeClass, timeStyle) contiene timeLabel, las palabras de la app para ella (“Hora”), al principio, y el campo
de hora (timeFieldClass, timeFieldStyle, timeAccessibilityLabel) al final: una fila con sus elementos centrados y
separados (flex-row items-center justify-between, como el de gpui-component). Los segmentos del campo son los de un
TimeField (segmentClass, separatorClass, renderSegment).
#Props
| Prop | Rust | Tipo | Por defecto | Descripción |
|---|---|---|---|---|
value |
.value(..) |
DatePickerValue |
ninguno | El valor, que pertenece a la app. |
defaultValue |
.default_value(..) |
DatePickerValue |
null |
El valor inicial, sin value. |
range |
.range(..) |
boolean |
false |
Elegir un rango de fechas. |
open, defaultOpen |
.open(..), .default_open(..) |
boolean |
false |
El estado de apertura, el de la app o el propio del selector. |
disabled |
.disabled(..) |
boolean |
false |
Permanece cerrado y no admite pulsaciones ni foco de teclado. |
accessibilityLabel |
.accessibility_label(..) |
string |
ninguno | Cómo lo llaman los lectores de pantalla. |
dateFormat |
.date_format(..) |
string |
"%Y/%m/%d" y la hora |
Cómo oyen el valor los lectores de pantalla, en el formato de chrono. |
timePrecision |
.time_precision(..) |
"minute" | "second" |
ninguno | Edita también la hora del día, hasta esta precisión. |
hourCycle |
.hour_cycle(..) |
"h23" | "h12" |
"h23" |
Cómo cuenta las horas el campo de hora. |
defaultTime |
.default_time(..) |
string |
"00:00" |
La hora que recibe una fecha antes de que el usuario fije una. |
cleanable |
.cleanable(..) |
boolean |
false |
Un botón de borrado mientras haya un valor. |
presets |
.presets(..) |
DateRangePreset[] |
[] |
Botones del popup que establecen el valor. |
numberOfMonths, firstDayOfWeek, disabledMatcher, yearRange |
como en Calendar |
como en Calendar |
como en Calendar |
Los meses, semanas, fechas deshabilitadas y años del calendario. |
previousAccessibilityLabel, nextAccessibilityLabel |
como en Calendar |
string |
ninguno | Lo que dicen los lectores de pantalla para Anterior y Siguiente del calendario. |
placement, align, offset, margin |
.placement(..), … |
como en Select |
"bottom", "start", 0, 0 |
Escritorios: dónde se coloca el popup respecto al disparador. |
popupClass, popupStyle |
.popup_class(..), .popup_style(..) |
string, StyleProp<ViewStyle> |
ninguno | El popup (escritorios) o la hoja (pantallas táctiles). |
backdropClass, backdropStyle |
.backdrop_class(..), .backdrop_style(..) |
string, StyleProp<ViewStyle> |
ninguno | El fondo (backdrop) detrás de la hoja. |
contentClass, contentStyle |
.content_class(..), .content_style(..) |
string, StyleProp<ViewStyle> |
ninguno | El contenido del popup: los preajustes junto al calendario y la hora. |
presetsClass, presetsStyle |
.presets_class(..), .presets_style(..) |
string, StyleProp<ViewStyle> |
ninguno | La columna de preajustes. |
presetClass, presetStyle |
.preset_class(..), .preset_style(..) |
string, StyleProp<TextStyle> |
ninguno | El botón de cada preajuste. |
calendarClass, calendarStyle |
.calendar_class(..), .calendar_style(..) |
string, StyleProp<ViewStyle> |
ninguno | El calendario. |
headerClass … itemStyle |
como en Calendar |
como en Calendar |
ninguno | El encabezado, el cuerpo, los meses, las filas y los elementos del calendario. |
renderItem |
.render_item(..) |
(item: CalendarEntry) => JSX.Element |
ninguno | Cada elemento del calendario, dibujado por la app según lo que es; sus estados son las clases de los elementos, como en Calendar. |
timeClass, timeStyle |
.time_class(..), .time_style(..) |
string, StyleProp<ViewStyle> |
ninguno | La fila de la hora: su etiqueta al principio, el campo al final. |
timeLabel |
.time_label(..) |
JSX.Element |
ninguno | Lo que muestra la fila de la hora antes del campo. |
timeFieldClass, timeFieldStyle |
.time_field_class(..), .time_field_style(..) |
string, StyleProp<ViewStyle> |
ninguno | El campo de hora. |
timeAccessibilityLabel |
.time_accessibility_label(..) |
string |
ninguno | Cómo llaman los lectores de pantalla al campo de hora. |
segmentClass, segmentStyle, separatorClass, separatorStyle, renderSegment |
como en TimeField |
como en TimeField |
ninguno | Los segmentos del campo de hora y :. |
clearButtonClass, clearButtonStyle |
.clear_button_class(..), .clear_button_style(..) |
string, StyleProp<ViewStyle> |
ninguno | El botón de borrado (cleanable). |
clearButton |
.clear_button(..) |
JSX.Element |
ninguno | El contenido del botón de borrado. |
clearButtonAccessibilityLabel |
.clear_button_accessibility_label(..) |
string |
ninguno | Cómo llaman los lectores de pantalla al botón de borrado. |
trigger |
.trigger(..) |
JSX.Element |
ninguno | Lo que abre el popup al pulsarlo; el popup se coloca respecto a él. |
class, style |
métodos de estilo | string, StyleProp<ViewStyle> |
ninguno | La vista que rodea al disparador. |
#Eventos
| Evento | Rust | Descripción |
|---|---|---|
onChange |
.on_change(|value| ..) |
El valor cambió (un día, el final de un rango, un preajuste, la hora, el botón de borrado): { value }. Establece value para seguirlo. |
onOpenChange |
.on_open_change(|e| ..) |
El popup se abrió o se cerró, con el motivo ({ open, reason }). Establece open para seguirlo. |
onOpen, onClose |
.on_open(..), .on_close(..) |
El popup se muestra, o ya no está. |
onCancel |
.on_cancel(..) |
El usuario cerró el popup sin elegir (Escape o atrás, una pulsación fuera o en el fondo). |
#Partes
| Parte | Dónde | Estilizado con |
|---|---|---|
| El disparador | la vista del selector | el elemento propio de la app (trigger) |
| El botón de borrado | la vista del selector, después del disparador (cleanable, mientras haya un valor) |
clearButtonClass, clearButtonStyle; su contenido clearButton |
| El popup o la hoja | sobre todo lo demás, junto al disparador (escritorios) o en la parte inferior (pantallas táctiles) | popupClass, popupStyle |
| El fondo (backdrop) | detrás de la hoja (pantallas táctiles) | backdropClass, backdropStyle |
| El contenido | el popup | contentClass, contentStyle |
| Los preajustes | el contenido, primero (presets) |
presetsClass, presetsStyle; cada uno presetClass, presetStyle |
| El calendario | el contenido | calendarClass, calendarStyle, y las partes de Calendar |
| La fila de la hora | debajo del calendario (timePrecision) |
timeClass, timeStyle; su etiqueta timeLabel |
| El campo de hora | la fila de la hora | timeFieldClass, timeFieldStyle, y las partes de TimeField |
#Teclado
| Tecla | Acción |
|---|---|
| Return (en el disparador) | Abre el popup, o lo cierra. |
| Delete, Backspace (en el disparador) | Borra el valor mientras el popup está cerrado. |
| Escape | Cierra el popup sin elegir. |
En el popup, las teclas del calendario se mueven entre los días y eligen uno (véase Teclado) y las del campo de hora editan la hora (véase Teclado).
#Accesibilidad
- Para los lectores de pantalla, el disparador es un combobox: su etiqueta es
accessibilityLabel, su valor es la fecha tal como la escribedateFormat(por defecto2026/10/28, con la hora entimePrecision), e indica si el popup está expandido.disabledlo atenúa. - Los días del calendario son botones nombrados por sus fechas ISO (véase Accesibilidad); los
preajustes son botones nombrados por sus etiquetas; el campo de hora se nombra con
timeAccessibilityLabel. - El botón de borrado es un botón nombrado con
clearButtonAccessibilityLabel.
Consulta Accesibilidad para ver etiquetas, roles y anuncios en general.