# TimeInput

**When to use:** Time-of-day selection — appointment times, opening hours, shift starts, reminders. Built on top of `Input`: the user can **type** the time directly (`1430` → `14:30`) or pick it on a **Material-style clock dial**. Renders as a text input with a floating label by default; can also be inline or modal, mirroring `DatePicker`.

**Keywords:** time picker, clock input

**Import:** `import { TimeInput } from '@ahrowe/ui'`

The value is a `"HH:MM"` 24-hour string (e.g. `"09:30"`) everywhere — also when `use24Hour={false}` is set (that flag only changes the dial UI, not the stored value).

With a [DatePicker](DatePicker.md) beside it, `dayTimeToDate(day, time)` makes the two one `Date`, and `dateToTime(date)` gives a `Date`'s time back for the field. See "When a day stands for a moment" there.

```tsx
import { TimeInput, FormValidator } from '@ahrowe/ui';

// Controlled input with floating label (type or pick on the dial)
<TimeInput
  label="Start time"
  value={time}
  onChange={(t) => setTime(t ?? '')}
/>

// With FormValidator
const timeValidator = new FormValidator<string | null>(null);
<TimeInput label="Opening time" formValidator={timeValidator} isRequired />

// Inline dial (no input field)
<TimeInput
  value={time}
  onChange={setTime}
  isInline
  noInput
/>

// Inside a modal (dial opens in a Modal overlay)
<TimeInput
  label="Appointment"
  value={time}
  onChange={setTime}
  isModal
/>

// 15-minute steps, with Now / Clear actions
<TimeInput
  label="Slot"
  value={time}
  onChange={setTime}
  minuteStep={15}
  showNowButton
  showClearButton
/>

// 12-hour dial with AM/PM (value is still stored as 24-hour "HH:MM")
<TimeInput
  label="Reminder"
  value={time}
  onChange={setTime}
  use24Hour={false}
/>

// Constrained to business hours
<TimeInput
  label="Drop-off"
  value={time}
  onChange={setTime}
  minTime="08:00"
  maxTime="18:00"
/>
```

**Interaction:** Click the field (or the clock icon) to open the dial. Pick the hour, the dial auto-advances to minutes; pick the minute to commit. The big `HH : MM` header lets you jump back to either segment, and drag works on the dial too. Typing in the field is clamped so an impossible time (e.g. `29:75`) can't be entered.

**Width:** the field fills its container, like `Input`, down to 105px.

**Key props:**

| Prop | Type | Description |
|------|------|-------------|
| `label` | `string` | Floating label on the input |
| `value` | `string \| null` | Current time as `"HH:MM"` (24-hour) |
| `selected` | `string \| null` | Alias for `value` (mirrors DatePicker) |
| `onChange` | `(time: string \| null) => void` | Called on every change (typing or dial); `null` when cleared/invalid |
| `onSelect` | `(time: string \| null) => void` | Called when a time is committed on the dial |
| `formValidator` | `FormValidator` | Connects to validation (stores the `"HH:MM"` string) |
| `isRequired` | `boolean` | Appends `*` to the label |
| `isValid` | `boolean` | Manual valid state |
| `errorMessage` | `string` | Manual error message. A non-empty message puts the field in error by itself, so pass it only while there is one |
| `disabled` | `boolean` | Disable the field and dial |
| `noInput` | `boolean` | Hide the text input (show dial only) |
| `noHeader` | `boolean` | Hide the big `HH:MM` dial header |
| `isModal` | `boolean` | Open the dial in a Modal overlay |
| `isInline` | `boolean` | Render the dial always-visible inline |
| `minuteStep` | `number` | Snap dial minutes to this step (default `1`) |
| `use24Hour` | `boolean` | 24-hour double-ring dial (default `true`); `false` → 12-hour dial + AM/PM |
| `minTime` | `string \| null` | Earliest selectable time, `"HH:MM"` |
| `maxTime` | `string \| null` | Latest selectable time, `"HH:MM"` |
| `showClearButton` | `boolean` | Show a clear action in the footer |
| `showNowButton` | `boolean` | Show a "now" quick-select action in the footer |

**Slots:** `root` `clock` `clockHeader` `headerSegment` `meridiem` `clockFace` `clockHand` `clockNumber` `footer`
