648 lines
18 KiB
Markdown
648 lines
18 KiB
Markdown
# Time Field
|
||
|
||
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.
|
||
|
||
## Import
|
||
|
||
```
|
||
Copyts
|
||
import { TimeField } from "@kobalte/core/time-field";
|
||
// or
|
||
import { Root, Label, ... } from "@kobalte/core/time-field";
|
||
```
|
||
|
||
```
|
||
Copyts
|
||
import { TimeField } from "@kobalte/core/time-field";
|
||
// or
|
||
import { Root, Label, ... } from "@kobalte/core/time-field";
|
||
```
|
||
|
||
## Features
|
||
|
||
- Times can optionally include a time zone. All modifications follow time zone rules such as daylight saving time.
|
||
- Support for locale-specific formatting, number systems, hour cycles, and right-to-left layout.
|
||
- Each time unit is displayed as an individually focusable and editable segment, which allows users an easy way to edit times using the keyboard, in any format and locale.
|
||
- Time segments are editable using an easy to use numeric keypad, and all interactions are accessible using touch-based screen readers.
|
||
- Can be controlled or uncontrolled.
|
||
- Integrates with HTML forms.
|
||
|
||
## Anatomy
|
||
|
||
The time field consists of:
|
||
|
||
- **TimeField**: The root container for the time field.
|
||
- **TimeField.Label**: The label that gives the user information on the time field.
|
||
- **TimeField.Field**: The container for the segments.
|
||
- **TimeField.Segment**: The component that represents a unit of a time.
|
||
- **TimeField.Description**: The description that gives the user more information on the time field.
|
||
- **TimeField.ErrorMessage**: The error message that gives the user information about how to fix a validation error on the time field.
|
||
- **TimeField.HiddenInput**: The native html input that is visually hidden in the time field.
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField>
|
||
<TimeField.Label />
|
||
<TimeField.Field>
|
||
<TimeField.Segment />
|
||
</TimeField.Field>
|
||
<TimeField.Description />
|
||
<TimeField.ErrorMessage />
|
||
<TimeField.HiddenInput />
|
||
</TimeField>
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField>
|
||
<TimeField.Label />
|
||
<TimeField.Field>
|
||
<TimeField.Segment />
|
||
</TimeField.Field>
|
||
<TimeField.Description />
|
||
<TimeField.ErrorMessage />
|
||
<TimeField.HiddenInput />
|
||
</TimeField>
|
||
```
|
||
|
||
## Example
|
||
|
||
Event time
|
||
|
||
––
|
||
|
||
:
|
||
|
||
––
|
||
|
||
AM
|
||
|
||
index.tsxstyle.css
|
||
|
||
```
|
||
Copytsx
|
||
import { TimeField } from "@kobalte/core/time-field";
|
||
import "./style.css";
|
||
|
||
function App() {
|
||
return (
|
||
<TimeField class="time-field">
|
||
<TimeField.Label class="time-field__label">Event time</TimeField.Label>
|
||
<TimeField.Field class="time-field__field">
|
||
{segment => <TimeField.Segment class="time-field__segment" segment={segment()} />}
|
||
</TimeField.Field>
|
||
</TimeField>
|
||
);
|
||
}
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
import { TimeField } from "@kobalte/core/time-field";
|
||
import "./style.css";
|
||
|
||
function App() {
|
||
return (
|
||
<TimeField class="time-field">
|
||
<TimeField.Label class="time-field__label">Event time</TimeField.Label>
|
||
<TimeField.Field class="time-field__field">
|
||
{segment => <TimeField.Segment class="time-field__segment" segment={segment()} />}
|
||
</TimeField.Field>
|
||
</TimeField>
|
||
);
|
||
}
|
||
```
|
||
|
||
## Usage
|
||
|
||
### Default Value
|
||
|
||
A TimeField displays a placeholder by default. An initial, uncontrolled value can be provided to the TimeField using the `defaultValue` prop.
|
||
|
||
Time values are provided using objects in the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/) package. This library handles correct international date and time manipulation across calendars, time zones, and other localization concerns.
|
||
|
||
11
|
||
|
||
:
|
||
|
||
45
|
||
|
||
AM
|
||
|
||
Selected Time: 11:45 AM
|
||
|
||
```
|
||
Copytsx
|
||
import { Time } from "@internationalized/date";
|
||
|
||
<TimeField defaultValue={new Time(11, 45)}>
|
||
<TimeField.Field>
|
||
{(segment) => (
|
||
<TimeField.Segment segment={segment()} />
|
||
)}
|
||
</TimeField.Field>
|
||
</TimeField>
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
import { Time } from "@internationalized/date";
|
||
|
||
<TimeField defaultValue={new Time(11, 45)}>
|
||
<TimeField.Field>
|
||
{(segment) => (
|
||
<TimeField.Segment segment={segment()} />
|
||
)}
|
||
</TimeField.Field>
|
||
</TimeField>
|
||
```
|
||
|
||
### Controlled Value
|
||
|
||
The `value` prop can be used to make the value controlled. The `onChange` event is fired when the time value changes.
|
||
|
||
9
|
||
|
||
:
|
||
|
||
45
|
||
|
||
AM
|
||
|
||
Selected Time: 9:45 AM
|
||
|
||
Selected time: 9:45 AM
|
||
|
||
```
|
||
Copytsx
|
||
import { createSignal } from "solid-js";
|
||
import { createDateFormatter } from "@kobalte/core/i18n";
|
||
import { getLocalTimeZone, Time, toCalendarDateTime, today } from "@internationalized/date";
|
||
|
||
function ControlledValueExample() {
|
||
const [value, setValue] = createSignal(new Time(9, 45));
|
||
|
||
const dateFormatter = createDateFormatter({
|
||
hour12: true,
|
||
timeStyle: "short",
|
||
});
|
||
|
||
return (
|
||
<>
|
||
<TimeField value={value()} onChange={setValue}>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>
|
||
<p>
|
||
Selected time:{" "}
|
||
{value()
|
||
? dateFormatter().format(
|
||
toCalendarDateTime(today(getLocalTimeZone()), value()).toDate(getLocalTimeZone()),
|
||
)
|
||
: "––"}
|
||
</p>
|
||
</>
|
||
);
|
||
}
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
import { createSignal } from "solid-js";
|
||
import { createDateFormatter } from "@kobalte/core/i18n";
|
||
import { getLocalTimeZone, Time, toCalendarDateTime, today } from "@internationalized/date";
|
||
|
||
function ControlledValueExample() {
|
||
const [value, setValue] = createSignal(new Time(9, 45));
|
||
|
||
const dateFormatter = createDateFormatter({
|
||
hour12: true,
|
||
timeStyle: "short",
|
||
});
|
||
|
||
return (
|
||
<>
|
||
<TimeField value={value()} onChange={setValue}>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>
|
||
<p>
|
||
Selected time:{" "}
|
||
{value()
|
||
? dateFormatter().format(
|
||
toCalendarDateTime(today(getLocalTimeZone()), value()).toDate(getLocalTimeZone()),
|
||
)
|
||
: "––"}
|
||
</p>
|
||
</>
|
||
);
|
||
}
|
||
```
|
||
|
||
### Time Zones
|
||
|
||
TimeField is time zone aware when a `ZonedDateTime` object is provided as the value. In this case, the time zone abbreviation is displayed, and time zone concerns such as daylight saving time are taken into account when the value is manipulated.
|
||
|
||
[@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/) includes functions for parsing strings in multiple formats into `ZonedDateTime` objects.
|
||
|
||
12
|
||
|
||
:
|
||
|
||
45
|
||
|
||
AM
|
||
|
||
PST
|
||
|
||
Selected Time: 12:45 AM PST
|
||
|
||
```
|
||
Copytsx
|
||
import { parseZonedDateTime } from "@internationalized/date";
|
||
|
||
<TimeField defaultValue={parseZonedDateTime("2022-11-07T00:45[America/Los_Angeles]")}>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>;
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
import { parseZonedDateTime } from "@internationalized/date";
|
||
|
||
<TimeField defaultValue={parseZonedDateTime("2022-11-07T00:45[America/Los_Angeles]")}>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>;
|
||
```
|
||
|
||
### Granularity
|
||
|
||
The `granularity` prop allows you to control the smallest unit that is displayed by a TimeField. By default, times are displayed with "minute" granularity. More granular time values can be displayed by setting the `granularity` prop to "second".
|
||
|
||
––
|
||
|
||
:
|
||
|
||
––
|
||
|
||
:
|
||
|
||
––
|
||
|
||
AM
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField granularity="second">
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField granularity="second">
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>
|
||
```
|
||
|
||
### Minimum and Maximum Values
|
||
|
||
The `minValue` and `maxValue` props can be used to perform builtin validation. This marks the time field as invalid using ARIA if the user enters an invalid time.
|
||
|
||
9
|
||
|
||
:
|
||
|
||
45
|
||
|
||
AM
|
||
|
||
Selected Time: 9:45 AM
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField defaultValue={new Time(9, 45)} minValue={new Time(9)} maxValue={new Time(17)}>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
<TimeField.ErrorMessage>Select time between 9 AM and 5 PM.</TimeField.ErrorMessage>
|
||
</TimeField>
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField defaultValue={new Time(9, 45)} minValue={new Time(9)} maxValue={new Time(17)}>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
<TimeField.ErrorMessage>Select time between 9 AM and 5 PM.</TimeField.ErrorMessage>
|
||
</TimeField>
|
||
```
|
||
|
||
### Placeholder Value
|
||
|
||
When no value is set, a placeholder is shown. The format of the placeholder is influenced by the `granularity` and `placeholderValue` props. `placeholderValue` also controls the default values of each segment when the user first interacts with them, e.g. using the up and down arrow keys. By default, the `placeholderValue` is midnight.
|
||
|
||
9
|
||
|
||
:
|
||
|
||
––
|
||
|
||
AM
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField placeholderValue={new Time(9)}>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField placeholderValue={new Time(9)}>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>
|
||
```
|
||
|
||
### Hide Time Zone
|
||
|
||
When a `ZonedDateTime` object is provided as the value of a TimeField, the time zone abbreviation is displayed by default. It can be hidden using the `hideTimeZone` prop.
|
||
|
||
12
|
||
|
||
:
|
||
|
||
45
|
||
|
||
AM
|
||
|
||
Selected Time: 12:45 AM
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField defaultValue={parseZonedDateTime("2022-11-07T00:45[America/Los_Angeles]")} hideTimeZone>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField defaultValue={parseZonedDateTime("2022-11-07T00:45[America/Los_Angeles]")} hideTimeZone>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>
|
||
```
|
||
|
||
### Hour Cycle
|
||
|
||
By default, TimeField displays times in either 12 or 24 hour format depending on the user's locale. This can be overridden using the `hourCycle` prop.
|
||
|
||
––
|
||
|
||
:
|
||
|
||
––
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField hourCycle={24}>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField hourCycle={24}>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
</TimeField>
|
||
```
|
||
|
||
### Description
|
||
|
||
The `TimeField.Description` component can be used to associate additional help text with a time field.
|
||
|
||
Time
|
||
|
||
––
|
||
|
||
:
|
||
|
||
––
|
||
|
||
AM
|
||
|
||
Select a meeting time.
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField>
|
||
<TimeField.Label>Time</TimeField.Label>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
<TimeField.Description>Select a meeting time.</TimeField.Description>
|
||
</TimeField>
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
<TimeField>
|
||
<TimeField.Label>Time</TimeField.Label>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
<TimeField.Description>Select a meeting time.</TimeField.Description>
|
||
</TimeField>
|
||
```
|
||
|
||
### Error message
|
||
|
||
The `TimeField.ErrorMessage` component can be used to help the user fix a validation error. It should be combined with the `validationState` prop to semantically mark the time field as invalid for assistive technologies.
|
||
|
||
By default, it will render only when the `validationState` prop is set to `invalid`, use the `forceMount` prop to always render the error message (ex: for usage with animation libraries).
|
||
|
||
Time
|
||
|
||
––
|
||
|
||
:
|
||
|
||
––
|
||
|
||
AM
|
||
|
||
Please select a time.
|
||
|
||
```
|
||
Copytsx
|
||
import { createSignal } from "solid-js";
|
||
|
||
function ErrorMessageExample() {
|
||
const [value, setValue] = createSignal(undefined);
|
||
|
||
return (
|
||
<TimeField
|
||
value={value()}
|
||
onChange={setValue}
|
||
validationState={value() === undefined ? "invalid" : "valid"}
|
||
>
|
||
<TimeField.Label>Time</TimeField.Label>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
<TimeField.ErrorMessage>Please select a time.</TimeField.ErrorMessage>
|
||
</TimeField>
|
||
);
|
||
}
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
import { createSignal } from "solid-js";
|
||
|
||
function ErrorMessageExample() {
|
||
const [value, setValue] = createSignal(undefined);
|
||
|
||
return (
|
||
<TimeField
|
||
value={value()}
|
||
onChange={setValue}
|
||
validationState={value() === undefined ? "invalid" : "valid"}
|
||
>
|
||
<TimeField.Label>Time</TimeField.Label>
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
<TimeField.ErrorMessage>Please select a time.</TimeField.ErrorMessage>
|
||
</TimeField>
|
||
);
|
||
}
|
||
```
|
||
|
||
### HTML forms
|
||
|
||
The `name` prop can be used for integration with HTML forms.
|
||
|
||
––
|
||
|
||
:
|
||
|
||
––
|
||
|
||
AM
|
||
|
||
ResetSubmit
|
||
|
||
```
|
||
Copytsx
|
||
function HTMLFormExample() {
|
||
const onSubmit = (e: SubmitEvent) => {
|
||
// handle form submission.
|
||
};
|
||
|
||
return (
|
||
<form onSubmit={onSubmit}>
|
||
<TimeField name="time">
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
<TimeField.HiddenInput />
|
||
</TimeField>
|
||
<div>
|
||
<button type="reset">Reset</button>
|
||
<button type="submit">Submit</button>
|
||
</div>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
```
|
||
Copytsx
|
||
function HTMLFormExample() {
|
||
const onSubmit = (e: SubmitEvent) => {
|
||
// handle form submission.
|
||
};
|
||
|
||
return (
|
||
<form onSubmit={onSubmit}>
|
||
<TimeField name="time">
|
||
<TimeField.Field>{segment => <TimeField.Segment segment={segment()} />}</TimeField.Field>
|
||
<TimeField.HiddenInput />
|
||
</TimeField>
|
||
<div>
|
||
<button type="reset">Reset</button>
|
||
<button type="submit">Submit</button>
|
||
</div>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
## API Reference
|
||
|
||
### TimeField
|
||
|
||
`TimeField` is equivalent to the `Root` import from `@kobalte/core/time-field`.
|
||
|
||
| Prop | Description |
|
||
| --- | --- |
|
||
| value | `TimeValue`<br> The current value (controlled). |
|
||
| defaultValue | `TimeValue`<br> The default value (uncontrolled). |
|
||
| onChange | `(value: MappedTimeValue<TimeValue>) => void`<br> Handler that is called when the value changes. |
|
||
| hourCycle | `12 | 24`<br> Whether to display the time in 12 or 24-hour format. By default, this is determined by the user's locale. |
|
||
| granularity | `'hour' | 'minute' | 'second'`<br> Determines the smallest unit that is displayed in the time field. Defaults to `"minute"`. |
|
||
| hideTimeZone | `boolean`<br> Whether to hide the time zone abbreviation. |
|
||
| shouldForceLeadingZeros | `boolean`<br> Whether to always show leading zeros in the hour field. By default, this is determined by the user's locale. |
|
||
| placeholderValue | `TimeValue`<br> A placeholder time that influences the format of the placeholder shown when no value is selected. Defaults to 12:00 AM or 00:00 depending on the hour cycle. |
|
||
| minValue | `TimeValue`<br> The minimum allowed time that a user may select. |
|
||
| maxValue | `TimeValue`<br> The maximum allowed time that a user may select. |
|
||
| name | `string`<br> The name of the time field. Submitted with its owning form as part of a name/value pair. |
|
||
| validationState | `'valid' | 'invalid'`<br> Whether the time field should display its "valid" or "invalid" visual styling. |
|
||
| required | `boolean`<br> Whether the time field is required. |
|
||
| disabled | `boolean`<br> Whether the time field is disabled. |
|
||
| readOnly | `boolean`<br> Whether the time field is read only. |
|
||
| translations | `TimeFieldIntlTranslations`<br> The localized strings of the component. |
|
||
|
||
| Data attribute | Description |
|
||
| --- | --- |
|
||
| data-valid | Present when the time field is valid according to the validation rules. |
|
||
| data-invalid | Present when the time field is invalid according to the validation rules. |
|
||
| data-required | Present when the time field is required. |
|
||
| data-disabled | Present when the time field is disabled. |
|
||
| data-readonly | Present when the time field is read only. |
|
||
|
||
`TimeField.Label`, `TimeField.Field`, `TimeField.Segment`, `TimeField.Description` and `TimeField.ErrorMesssage` share the same data-attributes.
|
||
|
||
### TimeField.Segment
|
||
|
||
| Prop | Description |
|
||
| --- | --- |
|
||
| segment | `TimeSegment`<br> A segment of the time field. |
|
||
|
||
| Data attribute | Description |
|
||
| --- | --- |
|
||
| data-separator | Present when the segment is a separator. |
|
||
| data-type | Always present. |
|
||
| data-placeholder | Present when the segment's value is a placeholder. |
|
||
|
||
### TimeField.ErrorMessage
|
||
|
||
| Prop | Description |
|
||
| --- | --- |
|
||
| forceMount | `boolean`<br> Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries. |
|
||
|
||
## Rendered elements
|
||
|
||
| Component | Default rendered element |
|
||
| --- | --- |
|
||
| `TimeField` | `div` |
|
||
| `TimeField.Label` | `span` |
|
||
| `TimeField.Field` | `div` |
|
||
| `TimeField.Segment` | `div` |
|
||
| `TimeField.Description` | `div` |
|
||
| `TimeField.ErrorMessage` | `div` |
|
||
| `TimeField.HiddenInput` | `input` |
|
||
|
||
## Accessibility
|
||
|
||
### Keyboard Interactions
|
||
|
||
| Key | Description |
|
||
| --- | --- |
|
||
| `Backspace` | Deletes the value in the current segment and moves focus to the previous segment when empty. |
|
||
| `Delete` | Deletes the value in the current segment and moves focus to the previous segment when empty. |
|
||
| `ArrowRight` | Moves focus to the next segment. |
|
||
| `ArrowLeft` | Moves focus to the previous segment. |
|
||
| `ArrowUp` | Increments the given segment. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. |
|
||
| `ArrowDown` | Decrements the given segment. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. |
|
||
| `PageUp` | Increments the given segment by a larger amount, rounding it to the nearest increment. The amount to increment by depends on the segment, for example 2 hours, 15 minutes, and 15 seconds. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. |
|
||
| `PageDown` | Decrements the given segment by a larger amount, rounding it to the nearest decrement. The amount to decrement by depends on the segment, for example 2 hours, 15 minutes, and 15 seconds. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. |
|
||
| `Home` | Decrements the given segment by the segment's minimum value. |
|
||
| `End` | Increments the given segment by the segment's maximum value. |
|
||
|
||
Previous[←Text Field](https://kobalte.dev/docs/core/components/text-field)Next[Toast→](https://kobalte.dev/docs/core/components/toast) |