# Date Time Input

## Usage

`DateTimeInput` combines a date and a time. The value is `YYYY-MM-DDTHH:MM`, or `null` until both parts are set.

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

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

<template>
  <DateTimeInput v-model="meeting" label="Meeting time" has-clear />
</template>
```

The date uses the date field. The time uses the same field as the time input. The two fields stay side by side and shrink with the row. They wrap only when the row is narrower than 16rem. `bottom-sheet` puts the date and the time in one bottom sheet, with tabs to move between them. Clear removes both parts. The date half can show two months and can start the week on a chosen day. While a change is saving, neither field can be edited.

### Anatomy

- **Label**: Text above the input describing what datetime is expected.
- **Segment row**: The unbordered layout row that holds the two bordered segments and wraps them when its container is narrow.
- **Date input**: A typed date field with calendar popover on the fine-pointer Astryx surface, a real input type=date in native modes, or a read-only segment opening the Astryx touch sheet under presentation="adaptive-bottom-sheet" on a coarse pointer.
- **Calendar icon**: A button that opens the active date surface: the platform picker, Astryx calendar popover, or Astryx touch sheet.
- **Date picker**: The browser/OS picker in native modes, an Astryx month-grid popover on a fine pointer, or the Date panel of the Astryx bottom sheet on a coarse pointer with presentation="adaptive-bottom-sheet".
- **Time input**: A real input type=time for the default minute-precision native mode, a text/combobox time field when seconds, custom increments, or preset options are requested, or a read-only segment opening accessible time wheels under presentation="adaptive-bottom-sheet" on a coarse pointer.
- **Clock icon**: The time glyph shown as a leading identifier on the Astryx pointer field and as the picker affordance in native and touch-sheet time segments.
- **Time options popover**: A list of preset times at the timeOptionInterval cadence. Setting the prop retains Astryx's text/combobox time field even when nativePicker otherwise selects native controls; the Astryx touch sheet uses wheels instead.
- **Time option**: One selectable preset time in the optional time-options popover.
- **Clear button**: A × button that resets the datetime value.
- **Status message**: An error, warning, or success message below the inputs.

## Best practices

| Guideline | Practice |
| --- | --- |
| Do | Turn on `hasClear` when the appointment is optional. |
| Don't | Use it for a date with no time of day. DateInput is the smaller control. |

## Examples

### Validation

Error, warning, and success on a filled date and time.

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

const errorVal = shallowRef('2026-01-25T09:00')
const warningVal = shallowRef('2026-12-25T14:00')
const successVal = shallowRef('2026-03-10T10:30')
</script>

<template>
  <div class="date-time-demo">
    <VStack :gap="4">
      <DateTimeInput v-model="errorVal" label="Meeting time" :status="{ type: 'error', message: 'This time slot is already booked' }" />
      <DateTimeInput v-model="warningVal" label="Preferred time" :status="{ type: 'warning', message: 'This falls outside business hours' }" />
      <DateTimeInput v-model="successVal" label="Start time" :status="{ type: 'success', message: 'Time confirmed' }" />
    </VStack>
  </div>
</template>

<style scoped>
.date-time-demo {
  width: 400px;
  max-width: 100%;
}
</style>
```
### Bottom sheet

One bottom sheet switches between the date and the time.

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

const meeting = shallowRef<string | null>('2026-03-21T14:30')
</script>

<template>
  <div style="width: 320px; max-width: 100%">
    <DateTimeInput
      v-model="meeting"
      label="Meeting"
      presentation="bottom-sheet"
      has-clear
    />
  </div>
</template>
```

## DateTimeInput Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `v-model` | `string \| null` | `null` | `YYYY-MM-DDTHH:MM`. Null until both parts exist. |
| `label` | `string` | — | Required accessible name of the pair. |
| `placeholder` | `string` | — | Placeholder for the date field. |
| `hourFormat` | `'12h' \| '24h'` | `'12h'` | Display format of the time. The model stays ISO. |
| `presentation` | `'popover' \| 'bottom-sheet' \| 'native' \| 'adaptive-bottom-sheet' \| 'adaptive-native' \| 'text-input'` | — | How both halves open. bottom-sheet, and adaptive-bottom-sheet on a coarse pointer, use one bottom sheet with Date and Time tabs. text-input applies only to the time. Omitted means a popover on a fine pointer and the platform controls on a coarse pointer. 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. |
| `controlStyle` | `StyleValue` | — | Inline style for the bordered control box. |
| `hasClear` | `boolean` | `false` | Clears the date and the time together. |
| `min` / `max` | `string` | — | ISO bounds for the time portion. |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Height of both fields. |
| `status` | `FieldStatusInput` | — | Error, warning, or success for the pair. |
| `statusVariant` | `'attached' \| 'detached' \| 'tooltip'` | `'attached'` | Where the status message sits. |
| `isDisabled` | `boolean` | `false` | Disables both fields. |
| `disabledMessage` | `string` | — | Explains why both fields are disabled. With `isDisabled`, the message shows in a tooltip on hover or keyboard focus and both halves stay focusable with `aria-disabled`. |
| `hasSeconds` | `boolean` | `false` | Includes seconds in the time half. The model then uses `YYYY-MM-DDTHH:MM:SS`. The system time control cannot express seconds, so the Astryx time field is kept. |
| `timeIncrement` | `1 \| 5 \| 10 \| 15 \| 30` | `1` | Minute step for the arrow keys in the time half. A non-default value keeps the Astryx time field. |
| `timeOptionInterval` | `5 \| 10 \| 15 \| 30 \| 60` | — | Minute cadence of preset times. Setting it opens a list of times under the clock icon, trimmed to the `min` and `max` time of day, and keeps the Astryx time field even in native modes. |
| `timeLabel` | `string` | `Time` / `时间` | Accessible name of the time half. |
| `timePlaceholder` | `string` | `Select a time` | Placeholder of the time half when no time is set. |
| `dateConstraints` | `Array<(date: Date) => boolean>` | — | On the date half, a day is disabled when any function returns false. |
| `numberOfMonths` | `1 \| 2` | — | Months shown side by side in the date calendar. |
| `weekStartsOn` | `0-6 \| 'sun' \| 'mon' \| ...` | — | First day of the week in the date calendar. |
| `nativePicker` | `'touch' \| 'always' \| 'never'` | — | Older surface name, passed to both the date and the time. |
| `isLoading` | `boolean` | `false` | Neither field can be edited. |
| `changeAction` | `(value) => void \| Promise<void>` | — | Runs once both the date and the time exist. A promise keeps the pair busy. Clearing runs it too. |
| `isOptional` | `boolean` | — | Marks the label as optional. |
| `isRequired` | `boolean` | — | Marks the pair required. |
| `labelTooltip` | `string` | — | Hint shown from the info icon at the end of the label. |
