TimeField
A text field for a time of day on the presentation's clock, with an optional grid and limits, that hands your application a 24-hour time.
Use Time for a time of day without a date: an opening hour, a reminder, a slot. The reader
types the time on the clock of the presentation you pass, 24-hour or 12-hour. Your application
receives HH:mm (or HH:mm:ss) on the 24-hour clock, or ''.
Value: 09:30 · state: valid
Value: 14:05 · state: valid
// apps/docs/examples/ui/time-field.tsx'use client';
import {useState} from 'react';
import { composePresentationContext, toIanaTimeZone, toLocalTimeString,} from '@workspace/active-format';import { TimeField, type TimeFieldState,} from '@workspace/active-ui/components/composed/time-field';import { Field, FieldDescription, FieldError, FieldLabel,} from '@workspace/active-ui/components/primitives/field';
const ZONE = toIanaTimeZone('Europe/Amsterdam');const OPENS = toLocalTimeString('08:00');const CLOSES = toLocalTimeString('18:00');const H24 = composePresentationContext('en', {timeFormat: 'H24'}, ZONE);const H12 = composePresentationContext('en', {timeFormat: 'H12'}, ZONE);
const REASONS: Record<'format' | 'range' | 'step', string> = { format: 'Write a time such as 9:30.', range: 'Choose a time between 08:00 and 18:00.', step: 'Choose a quarter of an hour.',};
function Describe({ id, value, state,}: { id: string; value: string; state: TimeFieldState;}) { return state.status === 'invalid' ? ( <FieldError id={id}>{REASONS[state.rejection]}</FieldError> ) : ( <FieldDescription id={id}> Value: <code>{value || "''"}</code> · state: {state.status} </FieldDescription> );}
/** * A time of day, typed: on a 24-hour clock with a quarter-hour grid and * office hours as limits, and on a 12-hour clock. The value is always * `HH:mm` on the 24-hour clock; a time off the grid or outside the limits is * invalid and never rounded. */export default function TimeFieldExample() { const [meeting, setMeeting] = useState('09:30'); const [meetingState, setMeetingState] = useState<TimeFieldState>({ status: 'valid', }); const [reminder, setReminder] = useState('14:05'); const [reminderState, setReminderState] = useState<TimeFieldState>({ status: 'valid', }); return ( <div className="grid w-full max-w-xs gap-6"> <Field data-invalid={meetingState.status === 'invalid' || undefined}> <FieldLabel htmlFor="time-meeting">Meeting (24-hour)</FieldLabel> <TimeField id="time-meeting" value={meeting} onValueChange={setMeeting} onStateChange={setMeetingState} locale={H24} step={{unit: 'minute', value: 15}} min={OPENS} max={CLOSES} aria-describedby="time-meeting-state" /> <Describe id="time-meeting-state" value={meeting} state={meetingState} /> </Field> <Field data-invalid={reminderState.status === 'invalid' || undefined}> <FieldLabel htmlFor="time-reminder">Reminder (12-hour)</FieldLabel> <TimeField id="time-reminder" value={reminder} onValueChange={setReminder} onStateChange={setReminderState} locale={H12} aria-describedby="time-reminder-state" /> <Describe id="time-reminder-state" value={reminder} state={reminderState} /> </Field> </div> );}What the composition adds#
Time is an Input plus the time reading of
active-format. It:
reads
13:05,13.05and, on a 12-hour clock,1:05 PM, and shows a valid time in the presentation's notation;applies a grid (
step, counted from midnight) and limits (min,max): a time off the grid or outside the limits is invalid and never rounded;keeps empty, incomplete, invalid and valid apart, and says why a time is invalid:
format,rangeorstep.
Usage#
import {TimeField} from '@workspace/active-ui/components/composed/time-field';
<TimeField id="opens" value={value} // 'HH:mm' or '' onValueChange={setValue} onStateChange={setState} locale={presentation} step={{unit: 'minute', value: 15}}/>;min and max are Local values: make them with to from
active-format.
States and validation#
on reports {status: 'empty' | 'incomplete' | 'valid'} or
{status: 'invalid', rejection: 'format' | 'range' | 'step'} on every keystroke. Turn the
rejection into your own message. A valid time is shown in its canonical form when the field loses
focus; unusable text stays visible.
When locale, precision, step, min or max change while someone is typing, the half-typed
text is dropped and the last value is shown in the new form. A step in seconds on a minute field,
or min after max, throws while rendering: it is a programming error.
What your application provides#
The presentation, the grid and the limits, and the form binding.
A visible label and the messages for each rejection.
Accessibility and keyboard#
The field is an ordinary text input; there is no picker. All other input props, such as id,
name, disabled and aria-, go to the input.
API#
Import from @workspace/
Props
- Type
DatePresentation
The presentation: the clock (time H24/H12, else the language's own), the separator and the day-period words.
- Type
string
The chosen time of day as a Local (HH:mm, or HH:mm:ss at second precision, 24-hour), or ''. A clock reading: no day, no zone.
- Type
LocalTime String
The latest allowed time, inclusive.
- Type
LocalTime String
The earliest allowed time, inclusive.
- Type
string
Text in the empty field. Defaults to the input pattern (HH:mm, h:mm a): a notation, not prose.
- Type
TimeInput Precision - Default
'minute'
minute (default) or second.
- Type
TimeInput Step
The allowed grid, counted from midnight ({unit: 'minute', value: 15}). A time off the grid is invalid (step) and is never rounded to it.
Events
Called by the component when something happens.
- Type
(value: string) => void
Receives the new time, or ''. As with Date, '' means only "cleared": half-typed or invalid text never travels out as a value, so a typo cannot erase a stored time.
- Type
(state: TimeField State) => void
Receives the state of the typed text on every keystroke.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
Types
The state of the typed time text, as the field reports it.
type TimeFieldState =
| {status: 'empty' | 'incomplete' | 'valid'}
| {status: 'invalid'; rejection: 'format' | 'range' | 'step'};The props of Time, each described in its prop list on this page.
interface TimeFieldProps extends Omit<
React.ComponentProps<'input'>,
'value' | 'defaultValue' | 'onChange' | 'type' | 'min' | 'max' | 'step'
> {
value: string;
onValueChange: (value: string) => void;
onStateChange?: (state: TimeFieldState) => void;
locale: DatePresentation;
precision?: TimeInputPrecision;
step?: TimeInputStep;
min?: LocalTimeString;
max?: LocalTimeString;
placeholder?: string;
}