0
# Time Field State
1
2
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.
3
4
## Capabilities
5
6
### useTimeFieldState Hook
7
8
Creates a state object for managing time field component state. Extends date field functionality specifically for time-only input scenarios.
9
10
```typescript { .api }
11
/**
12
* Provides state management for a time field component.
13
* A time field allows users to enter and edit time values using a keyboard.
14
* Each part of a time value is displayed in an individually editable segment.
15
* @param props - Configuration options including locale
16
* @returns TimeFieldState object extending DateFieldState with time-specific functionality
17
*/
18
function useTimeFieldState<T extends TimeValue = TimeValue>(
19
props: TimeFieldStateOptions<T>
20
): TimeFieldState;
21
22
interface TimeFieldStateOptions<T extends TimeValue = TimeValue> extends TimePickerProps<T> {
23
/** The locale to display and edit the value according to. */
24
locale: string;
25
}
26
27
interface TimeFieldState extends DateFieldState {
28
/** The current time value as a Time object. */
29
timeValue: Time;
30
}
31
```
32
33
**Usage Examples:**
34
35
```typescript
36
import { useTimeFieldState } from "@react-stately/datepicker";
37
import { Time } from "@internationalized/date";
38
39
// Basic time field
40
function BasicTimeField() {
41
const state = useTimeFieldState({
42
locale: 'en-US',
43
defaultValue: new Time(14, 30), // 2:30 PM
44
onChange: (time) => console.log("Selected time:", time?.toString())
45
});
46
47
return (
48
<div>
49
{state.segments.map((segment, index) => (
50
<span
51
key={index}
52
style={{
53
padding: '2px 4px',
54
margin: '0 1px',
55
backgroundColor: segment.isPlaceholder ? '#f0f0f0' : 'white',
56
border: segment.isEditable ? '1px solid #ccc' : 'none',
57
borderRadius: '2px'
58
}}
59
>
60
{segment.text}
61
</span>
62
))}
63
<div>
64
Current time: {state.timeValue.toString()}
65
({state.timeValue.hour}:{state.timeValue.minute.toString().padStart(2, '0')})
66
</div>
67
</div>
68
);
69
}
70
71
// 12-hour time field with AM/PM
72
function TwelveHourTimeField() {
73
const state = useTimeFieldState({
74
locale: 'en-US',
75
granularity: 'minute',
76
hourCycle: 12,
77
onChange: (time) => {
78
if (time) {
79
const hours = time.hour;
80
const minutes = time.minute;
81
const period = hours >= 12 ? 'PM' : 'AM';
82
const displayHour = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
83
console.log(`${displayHour}:${minutes.toString().padStart(2, '0')} ${period}`);
84
}
85
}
86
});
87
88
return (
89
<div>
90
<div style={{ display: 'flex', alignItems: 'center' }}>
91
{state.segments.map((segment, index) => (
92
<input
93
key={index}
94
value={segment.text}
95
placeholder={segment.placeholder}
96
readOnly={!segment.isEditable}
97
onChange={(e) => {
98
if (segment.type === 'dayPeriod') {
99
// Handle AM/PM toggle
100
const currentHour = state.timeValue.hour;
101
const newHour = e.target.value.toUpperCase() === 'PM' && currentHour < 12
102
? currentHour + 12
103
: e.target.value.toUpperCase() === 'AM' && currentHour >= 12
104
? currentHour - 12
105
: currentHour;
106
state.setSegment('hour', newHour);
107
} else {
108
const num = parseInt(e.target.value);
109
if (!isNaN(num)) {
110
state.setSegment(segment.type, num);
111
}
112
}
113
}}
114
style={{
115
width: `${Math.max(segment.text.length, 2)}ch`,
116
border: segment.isEditable ? '1px solid #ccc' : 'none',
117
textAlign: 'center',
118
backgroundColor: segment.isPlaceholder ? '#f8f8f8' : 'white'
119
}}
120
/>
121
))}
122
</div>
123
124
<div style={{ marginTop: '8px' }}>
125
<button onClick={() => state.increment('hour')}>Hour +</button>
126
<button onClick={() => state.decrement('hour')}>Hour -</button>
127
<button onClick={() => state.increment('minute')}>Min +</button>
128
<button onClick={() => state.decrement('minute')}>Min -</button>
129
</div>
130
</div>
131
);
132
}
133
134
// Time field with seconds and validation
135
function PreciseTimeField() {
136
const state = useTimeFieldState({
137
locale: 'en-US',
138
granularity: 'second',
139
minValue: new Time(9, 0, 0), // 9:00:00 AM
140
maxValue: new Time(17, 0, 0), // 5:00:00 PM
141
isRequired: true,
142
validate: (time) => {
143
if (!time) return "Time is required";
144
145
// Custom validation: no times ending in 13 seconds
146
if (time.second === 13) {
147
return "Unlucky second not allowed";
148
}
149
150
return null;
151
},
152
onChange: (time) => {
153
if (time) {
154
console.log(`${time.hour}:${time.minute}:${time.second}`);
155
}
156
}
157
});
158
159
return (
160
<div>
161
<div style={{
162
padding: '8px',
163
border: state.isInvalid ? '2px solid red' : '1px solid #ccc',
164
borderRadius: '4px'
165
}}>
166
{state.segments.map((segment, index) => (
167
<span
168
key={index}
169
tabIndex={segment.isEditable ? 0 : -1}
170
onKeyDown={(e) => {
171
if (segment.isEditable) {
172
switch (e.key) {
173
case 'ArrowUp':
174
e.preventDefault();
175
state.increment(segment.type);
176
break;
177
case 'ArrowDown':
178
e.preventDefault();
179
state.decrement(segment.type);
180
break;
181
case 'PageUp':
182
e.preventDefault();
183
state.incrementPage(segment.type);
184
break;
185
case 'PageDown':
186
e.preventDefault();
187
state.decrementPage(segment.type);
188
break;
189
}
190
}
191
}}
192
style={{
193
padding: '2px 4px',
194
margin: '0 1px',
195
backgroundColor: segment.isPlaceholder ? '#f0f0f0' : 'white',
196
border: segment.isEditable ? '1px solid #ddd' : 'none',
197
borderRadius: '2px',
198
outline: 'none',
199
cursor: segment.isEditable ? 'text' : 'default'
200
}}
201
>
202
{segment.text}
203
</span>
204
))}
205
</div>
206
207
{state.isInvalid && (
208
<div style={{ color: 'red', fontSize: '12px', marginTop: '4px' }}>
209
Please enter a valid time between 9:00:00 AM and 5:00:00 PM
210
</div>
211
)}
212
213
<div style={{ marginTop: '8px' }}>
214
<button onClick={() => state.setValue(new Time(12, 0, 0))}>
215
Set to Noon
216
</button>
217
<button onClick={() => state.confirmPlaceholder()}>
218
Confirm Current
219
</button>
220
<button onClick={() => state.setValue(null)}>
221
Clear
222
</button>
223
</div>
224
</div>
225
);
226
}
227
```
228
229
### Time Value Management
230
231
The time field state manages time values specifically, converting between different time representations.
232
233
```typescript { .api }
234
interface TimeFieldState extends DateFieldState {
235
/** The current time value as a Time object from @internationalized/date */
236
timeValue: Time;
237
}
238
```
239
240
The `timeValue` property provides direct access to the time portion regardless of whether the underlying value includes date information.
241
242
### Time-specific Features
243
244
Unlike date fields, time fields have specific behavior for time-only scenarios:
245
246
- **Hour Cycle Support**: Automatic 12/24 hour format handling
247
- **AM/PM Management**: Intelligent day period segment handling
248
- **Time Zone Handling**: Supports time-only values and datetime values with time zones
249
- **Placeholder Time**: Uses sensible defaults (midnight) when no time is specified
250
251
### Integration with Date Values
252
253
Time fields can work with different value types:
254
255
```typescript { .api }
256
interface TimeFieldStateOptions<T extends TimeValue> {
257
/** Current time value - can be Time, CalendarDateTime, or ZonedDateTime */
258
value?: T | null;
259
/** Default time value */
260
defaultValue?: T | null;
261
/** Placeholder value that affects formatting */
262
placeholderValue?: Time;
263
}
264
```
265
266
**Value Type Handling:**
267
268
```typescript
269
// Time-only value
270
const timeOnlyState = useTimeFieldState({
271
locale: 'en-US',
272
value: new Time(14, 30, 0), // 2:30:00 PM
273
});
274
275
// DateTime value (time portion will be extracted)
276
const dateTimeState = useTimeFieldState({
277
locale: 'en-US',
278
value: new CalendarDateTime(2023, 6, 15, 14, 30, 0),
279
});
280
281
// Zoned DateTime value (time portion with timezone)
282
const zonedState = useTimeFieldState({
283
locale: 'en-US',
284
value: toZoned(new CalendarDateTime(2023, 6, 15, 14, 30), 'America/New_York'),
285
});
286
```
287
288
### Granularity Control
289
290
Control which time segments are displayed and editable:
291
292
```typescript { .api }
293
interface TimeFieldStateOptions<T> {
294
/** The smallest time unit to display @default 'minute' */
295
granularity?: 'hour' | 'minute' | 'second';
296
/** Whether to display in 12 or 24 hour format */
297
hourCycle?: 12 | 24;
298
}
299
```
300
301
**Granularity Examples:**
302
- `granularity: 'hour'` - Shows only hour and AM/PM
303
- `granularity: 'minute'` - Shows hour, minute, and AM/PM
304
- `granularity: 'second'` - Shows hour, minute, second, and AM/PM
305
306
### Time Validation
307
308
Time fields support validation with time-specific constraints:
309
310
```typescript { .api }
311
interface TimeFieldStateOptions<T> {
312
/** Minimum allowed time */
313
minValue?: TimeValue;
314
/** Maximum allowed time */
315
maxValue?: TimeValue;
316
/** Custom validation function */
317
validate?: (value: MappedTimeValue<T> | null) => ValidationError | true | null | undefined;
318
/** Whether the time field is required */
319
isRequired?: boolean;
320
}
321
```
322
323
**Common Validation Scenarios:**
324
```typescript
325
// Business hours validation
326
const businessHoursState = useTimeFieldState({
327
locale: 'en-US',
328
minValue: new Time(9, 0), // 9:00 AM
329
maxValue: new Time(17, 0), // 5:00 PM
330
validate: (time) => {
331
if (time && (time.minute % 15 !== 0)) {
332
return "Please select times in 15-minute intervals";
333
}
334
return null;
335
}
336
});
337
338
// No midnight allowed
339
const noMidnightState = useTimeFieldState({
340
locale: 'en-US',
341
validate: (time) => {
342
if (time && time.hour === 0 && time.minute === 0) {
343
return "Midnight is not allowed";
344
}
345
return null;
346
}
347
});
348
```
349
350
### Inherited DateFieldState Functionality
351
352
TimeFieldState extends DateFieldState, providing all segment manipulation capabilities:
353
354
```typescript { .api }
355
// All DateFieldState methods are available:
356
state.increment('hour');
357
state.decrement('minute');
358
state.incrementPage('hour'); // +2 hours
359
state.decrementPage('minute'); // -15 minutes
360
state.setSegment('hour', 14);
361
state.clearSegment('minute');
362
state.confirmPlaceholder();
363
```