# Time Input

## Usage

`TimeInput` edits a time of day. The value is an ISO time, `HH:MM` or `HH:MM:SS`, or `null`.

```vue
<script setup lang="ts">
import { TimeInput } from '@astryx-vue/core/TimeInput'
import { shallowRef } from 'vue'

const time = shallowRef<string | null>('14:30')
</script>

<template>
  <TimeInput v-model="time" label="Time" />
</template>
```

The default hour format is 12-hour. Arrow keys step by `increment` minutes. A fine pointer gets a typed field. `adaptive-native` uses the platform time control on a coarse pointer. `adaptive-bottom-sheet` uses hour and minute wheels on a coarse pointer. `bottom-sheet` uses those wheels on every pointer. Seconds, a step other than one minute, or a preset interval send the platform control back to typed text; the wheels still show seconds. Placeholder and clear text follow the locale.

### Anatomy

- **Clock icon**: A leading clock icon that identifies the field and opens the browser/OS picker in native mode.
- **Time control**: A real input type=time in native modes, or Astryx's editable text field for fine pointers, presentation="text-input", seconds, and custom increments.
- **Clear button**: A trailing button to reset the value, shown when hasClear is true and a value is set.
- **Status icon**: A trailing icon indicating error, warning, or success state.
- **Spinner**: Replaces trailing content during loading to show an async action is in progress.

## Best practices

| Guideline | Practice |
| --- | --- |
| Do | Set `min` and `max` when only part of the day is valid. |
| Do | Use `hourFormat="24h"` when the surrounding product already speaks in 24-hour time. |
| Don't | Ask for a calendar date here. Use DateInput or DateTimeInput. |

## Examples

### Constrained

Dinner seating is limited to 5 PM through 10 PM.

```vue
<script setup lang="ts">
import { TimeInput } from '@astryx-vue/core/TimeInput'
import { shallowRef } from 'vue'

const evening = shallowRef<string | null>(null)
</script>

<template>
  <TimeInput
    v-model="evening"
    label="Dinner reservation"
    min="17:00"
    max="22:00"
    description="Evening seating: 5 PM – 10 PM"
    placeholder="Select reservation time"
    presentation="adaptive-bottom-sheet"
    has-clear
  />
</template>
```
### Formats

24-hour time, and a value that keeps seconds.

```vue
<script setup lang="ts">
import { VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
import { TimeInput } from '@astryx-vue/core/TimeInput'
import { shallowRef } from 'vue'

const time24 = shallowRef('14:30')
const timeSec = shallowRef('14:30:45')
</script>

<template>
  <VStack :gap="4">
    <Text type="supporting" color="secondary">
      Format variations for different contexts
    </Text>
    <VStack :gap="3">
      <TimeInput v-model="time24" label="24-hour" hour-format="24h" />
      <TimeInput v-model="timeSec" label="With seconds" has-seconds />
    </VStack>
  </VStack>
</template>
```
### Increment

Arrow keys move the value by 15 minutes.

```vue
<script setup lang="ts">
import { TimeInput } from '@astryx-vue/core/TimeInput'
import { shallowRef } from 'vue'

const slot = shallowRef('09:00')
</script>

<template>
  <TimeInput
    v-model="slot"
    label="Appointment slot"
    :increment="15"
    description="Use arrow keys to change by 15 minutes"
    has-clear
  />
</template>
```
### States

Disabled, error, warning, and success.

```vue
<script setup lang="ts">
import { VStack } from '@astryx-vue/core/Stack'
import { TimeInput } from '@astryx-vue/core/TimeInput'
import { shallowRef } from 'vue'

const disabledVal = shallowRef('10:00')
const errorVal = shallowRef('22:00')
const warningVal = shallowRef('07:00')
const successVal = shallowRef('10:00')
</script>

<template>
  <VStack :gap="3">
    <TimeInput v-model="disabledVal" label="Disabled field" is-disabled />
    <TimeInput v-model="errorVal" label="Error message" :status="{ type: 'error', message: 'Time must be during business hours' }" />
    <TimeInput v-model="warningVal" label="Warning message" :status="{ type: 'warning', message: 'Early morning — are you sure?' }" />
    <TimeInput v-model="successVal" label="Success message" :status="{ type: 'success', message: 'Time slot is available' }" />
  </VStack>
</template>
```
### Bottom sheet

Hour and minute wheels, on both a mouse and a coarse pointer.

```vue
<script setup lang="ts">
import { TimeInput } from '@astryx-vue/core/TimeInput'
import { shallowRef } from 'vue'

const time = shallowRef<string | null>('14:30')
</script>

<template>
  <div style="width: 240px; max-width: 100%">
    <TimeInput
      v-model="time"
      label="Start"
      presentation="bottom-sheet"
    />
  </div>
</template>
```

## TimeInput Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `v-model` | `string \| null` | `null` | ISO time, `HH:MM` or `HH:MM:SS`. |
| `label` | `string` | — | Required accessible name. |
| `hourFormat` | `'12h' \| '24h'` | `'12h'` | How the value is shown. The model stays 24-hour ISO. |
| `hasSeconds` | `boolean` | `false` | Keep seconds in the value. |
| `increment` | `number` | `1` | Minutes added or removed by the arrow keys. |
| `min` | `string` | — | Earliest ISO time. |
| `max` | `string` | — | Latest ISO time. |
| `presentation` | `'text-input' \| 'native' \| 'adaptive-native' \| 'adaptive-bottom-sheet' \| 'popover' \| 'bottom-sheet'` | `'adaptive-native'` | Typed field, platform time control, or hour and minute wheels. Omitted means typed text on a fine pointer and the platform control on a coarse pointer. adaptive-bottom-sheet uses wheels only on a coarse pointer; bottom-sheet always uses wheels. Seconds, a step other than 1, or a preset interval send the platform control back to typed text. The wheels are unaffected. An explicit value wins over nativePicker. |
| `isLabelHidden` | `boolean` | `false` | Hides the label visually while keeping it accessible. `labelHidden` is the older name; either one hides it. |
| `containerClass` | `string \| object \| array` | — | Class for the field container. Has no effect, and warns in development, inside InputGroup and in a horizontal-labels FormLayout. |
| `containerStyle` | `StyleValue` | — | Inline style for the field container. Same targeting as containerClass. |
| `controlClass` | `string \| object \| array` | — | Class for the bordered control box. A class written on the component lands here too, not on the inner input. |
| `controlStyle` | `StyleValue` | — | Inline style for the bordered control box. |
| `hasClear` | `boolean` | `false` | Shows a clear button. |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Field height. |
| `status` | `FieldStatusInput` | — | Error, warning, or success. |
| `statusVariant` | `'attached' \| 'detached' \| 'tooltip'` | `'attached'` | Where the status message sits. |
| `isDisabled` | `boolean` | `false` | Disables the field. |
| `disabledMessage` | `string` | — | Explains why the field is disabled. With `isDisabled`, the message shows in a tooltip on hover or keyboard focus and the input stays focusable with `aria-disabled`; the value still cannot change. |
| `isLoading` | `boolean` | `false` | The field cannot be edited and shows a spinner. |
| `loadingLabel` | `string` | `Loading` / `加载中` | Screen-reader text for the spinner. |
| `labelTooltip` | `string` | — | Hint shown from the info icon at the end of the label. |
| `placeholder` | `string` | `Select a time` | Empty-state text. While the field is focused and empty, a format hint replaces it. |
