React state management hooks for date picker components with internationalization and accessibility support.
—
State management for time field components that allow users to enter and edit time values. Each part of a time value (hour, minute, second, AM/PM) is displayed in individually editable segments, providing precise keyboard-based time input.
Creates a state object for managing time field component state. Extends date field functionality specifically for time-only input scenarios.
/**
* Provides state management for a time field component.
* A time field allows users to enter and edit time values using a keyboard.
* Each part of a time value is displayed in an individually editable segment.
* @param props - Configuration options including locale
* @returns TimeFieldState object extending DateFieldState with time-specific functionality
*/
function useTimeFieldState<T extends TimeValue = TimeValue>(
props: TimeFieldStateOptions<T>
): TimeFieldState;
interface TimeFieldStateOptions<T extends TimeValue = TimeValue> extends TimePickerProps<T> {
/** The locale to display and edit the value according to. */
locale: string;
}
interface TimeFieldState extends DateFieldState {
/** The current time value as a Time object. */
timeValue: Time;
}Usage Examples:
import { useTimeFieldState } from "@react-stately/datepicker";
import { Time } from "@internationalized/date";
// Basic time field
function BasicTimeField() {
const state = useTimeFieldState({
locale: 'en-US',
defaultValue: new Time(14, 30), // 2:30 PM
onChange: (time) => console.log("Selected time:", time?.toString())
});
return (
<div>
{state.segments.map((segment, index) => (
<span
key={index}
style={{
padding: '2px 4px',
margin: '0 1px',
backgroundColor: segment.isPlaceholder ? '#f0f0f0' : 'white',
border: segment.isEditable ? '1px solid #ccc' : 'none',
borderRadius: '2px'
}}
>
{segment.text}
</span>
))}
<div>
Current time: {state.timeValue.toString()}
({state.timeValue.hour}:{state.timeValue.minute.toString().padStart(2, '0')})
</div>
</div>
);
}
// 12-hour time field with AM/PM
function TwelveHourTimeField() {
const state = useTimeFieldState({
locale: 'en-US',
granularity: 'minute',
hourCycle: 12,
onChange: (time) => {
if (time) {
const hours = time.hour;
const minutes = time.minute;
const period = hours >= 12 ? 'PM' : 'AM';
const displayHour = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
console.log(`${displayHour}:${minutes.toString().padStart(2, '0')} ${period}`);
}
}
});
return (
<div>
<div style={{ display: 'flex', alignItems: 'center' }}>
{state.segments.map((segment, index) => (
<input
key={index}
value={segment.text}
placeholder={segment.placeholder}
readOnly={!segment.isEditable}
onChange={(e) => {
if (segment.type === 'dayPeriod') {
// Handle AM/PM toggle
const currentHour = state.timeValue.hour;
const newHour = e.target.value.toUpperCase() === 'PM' && currentHour < 12
? currentHour + 12
: e.target.value.toUpperCase() === 'AM' && currentHour >= 12
? currentHour - 12
: currentHour;
state.setSegment('hour', newHour);
} else {
const num = parseInt(e.target.value);
if (!isNaN(num)) {
state.setSegment(segment.type, num);
}
}
}}
style={{
width: `${Math.max(segment.text.length, 2)}ch`,
border: segment.isEditable ? '1px solid #ccc' : 'none',
textAlign: 'center',
backgroundColor: segment.isPlaceholder ? '#f8f8f8' : 'white'
}}
/>
))}
</div>
<div style={{ marginTop: '8px' }}>
<button onClick={() => state.increment('hour')}>Hour +</button>
<button onClick={() => state.decrement('hour')}>Hour -</button>
<button onClick={() => state.increment('minute')}>Min +</button>
<button onClick={() => state.decrement('minute')}>Min -</button>
</div>
</div>
);
}
// Time field with seconds and validation
function PreciseTimeField() {
const state = useTimeFieldState({
locale: 'en-US',
granularity: 'second',
minValue: new Time(9, 0, 0), // 9:00:00 AM
maxValue: new Time(17, 0, 0), // 5:00:00 PM
isRequired: true,
validate: (time) => {
if (!time) return "Time is required";
// Custom validation: no times ending in 13 seconds
if (time.second === 13) {
return "Unlucky second not allowed";
}
return null;
},
onChange: (time) => {
if (time) {
console.log(`${time.hour}:${time.minute}:${time.second}`);
}
}
});
return (
<div>
<div style={{
padding: '8px',
border: state.isInvalid ? '2px solid red' : '1px solid #ccc',
borderRadius: '4px'
}}>
{state.segments.map((segment, index) => (
<span
key={index}
tabIndex={segment.isEditable ? 0 : -1}
onKeyDown={(e) => {
if (segment.isEditable) {
switch (e.key) {
case 'ArrowUp':
e.preventDefault();
state.increment(segment.type);
break;
case 'ArrowDown':
e.preventDefault();
state.decrement(segment.type);
break;
case 'PageUp':
e.preventDefault();
state.incrementPage(segment.type);
break;
case 'PageDown':
e.preventDefault();
state.decrementPage(segment.type);
break;
}
}
}}
style={{
padding: '2px 4px',
margin: '0 1px',
backgroundColor: segment.isPlaceholder ? '#f0f0f0' : 'white',
border: segment.isEditable ? '1px solid #ddd' : 'none',
borderRadius: '2px',
outline: 'none',
cursor: segment.isEditable ? 'text' : 'default'
}}
>
{segment.text}
</span>
))}
</div>
{state.isInvalid && (
<div style={{ color: 'red', fontSize: '12px', marginTop: '4px' }}>
Please enter a valid time between 9:00:00 AM and 5:00:00 PM
</div>
)}
<div style={{ marginTop: '8px' }}>
<button onClick={() => state.setValue(new Time(12, 0, 0))}>
Set to Noon
</button>
<button onClick={() => state.confirmPlaceholder()}>
Confirm Current
</button>
<button onClick={() => state.setValue(null)}>
Clear
</button>
</div>
</div>
);
}The time field state manages time values specifically, converting between different time representations.
interface TimeFieldState extends DateFieldState {
/** The current time value as a Time object from @internationalized/date */
timeValue: Time;
}The timeValue property provides direct access to the time portion regardless of whether the underlying value includes date information.
Unlike date fields, time fields have specific behavior for time-only scenarios:
Time fields can work with different value types:
interface TimeFieldStateOptions<T extends TimeValue> {
/** Current time value - can be Time, CalendarDateTime, or ZonedDateTime */
value?: T | null;
/** Default time value */
defaultValue?: T | null;
/** Placeholder value that affects formatting */
placeholderValue?: Time;
}Value Type Handling:
// Time-only value
const timeOnlyState = useTimeFieldState({
locale: 'en-US',
value: new Time(14, 30, 0), // 2:30:00 PM
});
// DateTime value (time portion will be extracted)
const dateTimeState = useTimeFieldState({
locale: 'en-US',
value: new CalendarDateTime(2023, 6, 15, 14, 30, 0),
});
// Zoned DateTime value (time portion with timezone)
const zonedState = useTimeFieldState({
locale: 'en-US',
value: toZoned(new CalendarDateTime(2023, 6, 15, 14, 30), 'America/New_York'),
});Control which time segments are displayed and editable:
interface TimeFieldStateOptions<T> {
/** The smallest time unit to display @default 'minute' */
granularity?: 'hour' | 'minute' | 'second';
/** Whether to display in 12 or 24 hour format */
hourCycle?: 12 | 24;
}Granularity Examples:
granularity: 'hour' - Shows only hour and AM/PMgranularity: 'minute' - Shows hour, minute, and AM/PMgranularity: 'second' - Shows hour, minute, second, and AM/PMTime fields support validation with time-specific constraints:
interface TimeFieldStateOptions<T> {
/** Minimum allowed time */
minValue?: TimeValue;
/** Maximum allowed time */
maxValue?: TimeValue;
/** Custom validation function */
validate?: (value: MappedTimeValue<T> | null) => ValidationError | true | null | undefined;
/** Whether the time field is required */
isRequired?: boolean;
}Common Validation Scenarios:
// Business hours validation
const businessHoursState = useTimeFieldState({
locale: 'en-US',
minValue: new Time(9, 0), // 9:00 AM
maxValue: new Time(17, 0), // 5:00 PM
validate: (time) => {
if (time && (time.minute % 15 !== 0)) {
return "Please select times in 15-minute intervals";
}
return null;
}
});
// No midnight allowed
const noMidnightState = useTimeFieldState({
locale: 'en-US',
validate: (time) => {
if (time && time.hour === 0 && time.minute === 0) {
return "Midnight is not allowed";
}
return null;
}
});TimeFieldState extends DateFieldState, providing all segment manipulation capabilities:
// All DateFieldState methods are available:
state.increment('hour');
state.decrement('minute');
state.incrementPage('hour'); // +2 hours
state.decrementPage('minute'); // -15 minutes
state.setSegment('hour', 14);
state.clearSegment('minute');
state.confirmPlaceholder();Install with Tessl CLI
npx tessl i tessl/npm-react-stately--datepicker