Lesson 6: Time Picker
Using useTimePicker and useDateTime for time input.
Lesson 6 of 8: build a time picker with spinbutton fields, then combine it with the calendar for a full datetime picker.
The useTimePicker hook
useTimePicker handles time input — spinbutton fields, keyboard increment/decrement, and AM/PM.
import { useTimePicker } from '@gentleduck/calendar'
function MyTimePicker() {
const { state, getFieldProps } = useTimePicker({
hourCycle: '12',
showSeconds: true,
})
return (
<div>
<input {...getFieldProps('hour')} />
<span>:</span>
<input {...getFieldProps('minute')} />
<span>:</span>
<input {...getFieldProps('second')} />
<button {...getFieldProps('ampm')}>{state.displayAmPm}</button>
</div>
)
}import { useTimePicker } from '@gentleduck/calendar'
function MyTimePicker() {
const { state, getFieldProps } = useTimePicker({
hourCycle: '12',
showSeconds: true,
})
return (
<div>
<input {...getFieldProps('hour')} />
<span>:</span>
<input {...getFieldProps('minute')} />
<span>:</span>
<input {...getFieldProps('second')} />
<button {...getFieldProps('ampm')}>{state.displayAmPm}</button>
</div>
)
}How it works
Each field is a spinbutton. Users can:
- Type a number to set the value.
- Press ArrowUp / ArrowDown to increment/decrement.
- Press Tab to move between fields.
- On the AM/PM field: A for AM, P for PM.
getFieldProps applies:
role="spinbutton"witharia-valuemin,aria-valuemax,aria-valuenow.- Keyboard handlers for increment, decrement, and digit entry.
- Focus management via
tabIndex.
Hour cycles
| Cycle | Range | AM/PM? |
|---|---|---|
'24' | 0-23 | No |
'12' | 1-12 | Yes |
// 24-hour
useTimePicker({ hourCycle: '24' })
// state.value.hour is 0-23
// 12-hour
useTimePicker({ hourCycle: '12' })
// state.displayHour is 1-12
// state.displayAmPm is 'AM' or 'PM'// 24-hour
useTimePicker({ hourCycle: '24' })
// state.value.hour is 0-23
// 12-hour
useTimePicker({ hourCycle: '12' })
// state.displayHour is 1-12
// state.displayAmPm is 'AM' or 'PM'Combining date and time
useDateTime composes useCalendar and useTimePicker into a single hook:
import { NativeAdapter, useDateTime } from '@gentleduck/calendar'
const adapter = new NativeAdapter()
function MyDateTimePicker() {
const { calendar, timePicker, state } = useDateTime({
adapter,
defaultValue: new Date(),
hourCycle: '12',
})
return (
<div>
{/* Calendar UI using calendar.state, calendar.getDayProps, etc. */}
{/* Time UI using timePicker.state, timePicker.getFieldProps, etc. */}
<p>Combined value: {state.value?.toISOString()}</p>
</div>
)
}import { NativeAdapter, useDateTime } from '@gentleduck/calendar'
const adapter = new NativeAdapter()
function MyDateTimePicker() {
const { calendar, timePicker, state } = useDateTime({
adapter,
defaultValue: new Date(),
hourCycle: '12',
})
return (
<div>
{/* Calendar UI using calendar.state, calendar.getDayProps, etc. */}
{/* Time UI using timePicker.state, timePicker.getFieldProps, etc. */}
<p>Combined value: {state.value?.toISOString()}</p>
</div>
)
}state.value is a single Date that joins the selected calendar date with the current time. Date changes keep the time; time changes keep the date.
Constraints
Set minimum and maximum times:
useTimePicker({
hourCycle: '24',
minTime: { hour: 9, minute: 0 },
maxTime: { hour: 17, minute: 0 },
minuteStep: 15, // 15-minute increments
secondStep: 5, // 5-second increments (when showSeconds is true)
})useTimePicker({
hourCycle: '24',
minTime: { hour: 9, minute: 0 },
maxTime: { hour: 17, minute: 0 },
minuteStep: 15, // 15-minute increments
secondStep: 5, // 5-second increments (when showSeconds is true)
})Next: Lesson 7 - Styling