Raccourci clavier : Ctrl + K
Commencer

Champ d'heure

Un champ d'heure sous l'une de trois formes : saisi avec un masque, rempli depuis une horloge, ou une liste d'heures à intervalle fixe. La valeur est toujours une chaîne HH:mm sur 24 heures.

Utilisation

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VTimeInput } from 'vectis-ui'

const time = ref<string | null>(null)
</script>

<template>
  <VTimeInput v-model="time" label="Start time" />
</template>

Exemples

Libellé, indication et icône

label et hint se comportent comme sur n'importe quel champ. pickerIcon change le glyphe qui ouvre l'horloge, iconStart pose une icône au début du champ, et loading affiche un indicateur à la place de l'icône d'horloge. pickerIconLabel, clearLabel, loadingText et iconStartLabel renomment ce que chacun annonce.

vue

Type it or pick it from the clock

The glyph that opens the clock is yours to choose

Chargement…

An icon at the start, a spinner at the end while something loads

<script setup lang="ts">
import { ref } from 'vue'
import { VTimeInput } from 'vectis-ui'
import { expand_more as expandMore, search } from 'vectis-ui/icons'

const start = ref<string | null>('09:15')
const meeting = ref<string | null>(null)
</script>

<template>
  <div class="column">
    <VTimeInput
      v-model="start"
      label="Start time"
      hint="Type it or pick it from the clock"
      show-picker
    />

    <VTimeInput
      v-model="meeting"
      label="Meeting"
      hint="The glyph that opens the clock is yours to choose"
      mode="picker"
      :picker-icon="expandMore"
    />

    <!-- The start icon is rendered before whatever else fills that end of the field. -->
    <VTimeInput
      v-model="start"
      label="Filter by time"
      hint="An icon at the start, a spinner at the end while something loads"
      :icon-start="search"
      loading
    />
  </div>
</template>

<style scoped>
.column {
  display: flex;
  flex-direction: column;
  gap: var(--vectis-space-5);
  max-inline-size: 26rem;
}
</style>

Tailles

size définit la hauteur du champ à 32, 40 ou 48 pixels, et compact lui retire 4px. L'horloge garde ses propres mesures.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VTimeInput } from 'vectis-ui'

const rows = ref(
  (['sm', 'md', 'lg'] as const).flatMap((size) => [
    { key: size, size, compact: false, label: size, time: '09:15' as string | null },
    {
      key: `${size}-compact`,
      size,
      compact: true,
      label: `${size}, compact`,
      time: '09:15' as string | null,
    },
  ]),
)
</script>

<template>
  <div class="column">
    <VTimeInput
      v-for="row in rows"
      :key="row.key"
      v-model="row.time"
      :size="row.size"
      :compact="row.compact"
      :label="row.label"
      show-picker
    />
  </div>
</template>

<style scoped>
.column {
  display: flex;
  flex-direction: column;
  gap: var(--vectis-space-5);
  max-inline-size: 26rem;
}
</style>

Modes

mode choisit la forme du champ : input le masque pour n'y saisir que des chiffres, l'horloge devenant alors optionnelle via showPicker ; picker fait de l'horloge la seule entrée, qui y est donc imposée ; list abandonne l'horloge au profit d'une liste d'heures que l'on filtre.

vue

No icon, no panel: the mask is the whole control

showPicker adds the icon and the panel it opens

Nothing can be typed, so the clock is the only way in

Every half hour, found by typing rather than by scrolling

<script setup lang="ts">
import { ref } from 'vue'
import { VTimeInput } from 'vectis-ui'

const typed = ref<string | null>('09:15')
const withPicker = ref<string | null>('09:15')
const readOnly = ref<string | null>('09:15')
const fromList = ref<string | null>('09:30')
</script>

<template>
  <div class="column">
    <VTimeInput
      v-model="typed"
      label="Typed, the default"
      hint="No icon, no panel: the mask is the whole control"
    />

    <VTimeInput
      v-model="withPicker"
      show-picker
      label="Typed, with the clock"
      hint="showPicker adds the icon and the panel it opens"
    />

    <VTimeInput
      v-model="readOnly"
      mode="picker"
      label="Read-only"
      hint="Nothing can be typed, so the clock is the only way in"
    />

    <VTimeInput
      v-model="fromList"
      mode="list"
      :minute-step="30"
      label="List"
      hint="Every half hour, found by typing rather than by scrolling"
    />
  </div>
</template>

<style scoped>
.column {
  display: flex;
  flex-direction: column;
  gap: var(--vectis-space-5);
  max-inline-size: 26rem;
}
</style>

Pas

minuteStep est ce que propose le cadran, le pas des flèches du clavier et la découpe de la liste. Il laisse le masque tranquille, et mérite d'être posé sur une liste avant toute chose : la minute par défaut fait 1440 lignes.

vue

The face offers four minutes an hour, and the arrow keys move by the same step

The step is what the rows are cut at: 48 of them here rather than 1440

The mask still takes any time; the step drives the clock and the arrow keys

<script setup lang="ts">
import { ref } from 'vue'
import { VTimeInput } from 'vectis-ui'

const appointment = ref<string | null>('14:15')
const slot = ref<string | null>('09:30')
const typed = ref<string | null>('08:00')
</script>

<template>
  <div class="column">
    <VTimeInput
      v-model="appointment"
      mode="picker"
      :minute-step="15"
      label="Quarter hours, on the clock"
      hint="The face offers four minutes an hour, and the arrow keys move by the same step"
    />

    <VTimeInput
      v-model="slot"
      mode="list"
      :minute-step="30"
      label="Half hours, as a list"
      hint="The step is what the rows are cut at: 48 of them here rather than 1440"
    />

    <VTimeInput
      v-model="typed"
      show-picker
      :minute-step="5"
      label="Five minutes, typed"
      hint="The mask still takes any time; the step drives the clock and the arrow keys"
    />
  </div>
</template>

<style scoped>
.column {
  display: flex;
  flex-direction: column;
  gap: var(--vectis-space-5);
  max-inline-size: 26rem;
}
</style>

Ce que l’on peut choisir

min, max, allowedHours et allowedMinutes restreignent ce qui peut être choisi. La liste et l'horloge écartent ce qui ne peut pas l'être ; le champ saisi valide l'entrée et se déclare invalide par la validité du contrôle.

vue

Type 08:00 and leave the field: the value stands and the field turns invalid

The rows outside the bounds are not offered at all

The face prints only the hours it can take

<script setup lang="ts">
import { ref } from 'vue'
import { VTimeInput } from 'vectis-ui'

const typed = ref<string | null>('09:30')
const listed = ref<string | null>('09:30')
const picked = ref<string | null>('09:30')
</script>

<template>
  <div class="column">
    <VTimeInput
      v-model="typed"
      format="24h"
      min="09:00"
      max="17:00"
      label="Typed"
      hint="Type 08:00 and leave the field: the value stands and the field turns invalid"
    />

    <VTimeInput
      v-model="listed"
      mode="list"
      format="24h"
      :minute-step="30"
      min="09:00"
      max="17:00"
      label="List"
      hint="The rows outside the bounds are not offered at all"
    />

    <VTimeInput
      v-model="picked"
      mode="picker"
      format="24h"
      :minute-step="30"
      min="09:00"
      max="17:00"
      label="Clock"
      hint="The face prints only the hours it can take"
    />
  </div>
</template>

<style scoped>
.column {
  display: flex;
  flex-direction: column;
  gap: var(--vectis-space-5);
  max-inline-size: 26rem;
}
</style>

Effaçable

clearable ajoute une croix qui vide la valeur, à gauche de l'icône d'horloge et non à sa place. La forme liste prend sa propre croix du combobox sur lequel elle est bâtie, formulation comprise.

vue

The cross sits to the left of the clock icon, never in its place

Emptying the field is then the reader's own business

Here the cross is the combobox's own, chevron included

<script setup lang="ts">
import { ref } from 'vue'
import { VTimeInput } from 'vectis-ui'

const withCross = ref<string | null>('09:15')
const withoutCross = ref<string | null>('09:15')
const inList = ref<string | null>('09:30')
</script>

<template>
  <div class="column">
    <VTimeInput
      v-model="withCross"
      clearable
      show-picker
      label="Clearable"
      hint="The cross sits to the left of the clock icon, never in its place"
    />

    <VTimeInput
      v-model="withoutCross"
      show-picker
      label="Not clearable, the default"
      hint="Emptying the field is then the reader's own business"
    />

    <VTimeInput
      v-model="inList"
      clearable
      mode="list"
      :minute-step="30"
      label="Clearable, as a list"
      hint="Here the cross is the combobox's own, chevron included"
    />
  </div>
</template>

<style scoped>
.column {
  display: flex;
  flex-direction: column;
  gap: var(--vectis-space-5);
  max-inline-size: 26rem;
}
</style>

États

invalid sert à une règle que le navigateur ne peut pas vérifier lui-même. disabled grise le champ et empêche l'ouverture du panneau. readonly montre la valeur figée : rien ne se saisit, aucune horloge n'est rendue et le bouton AM/PM disparaît avec elle, tandis que le champ garde son contraste et prend le focus.

vue

For a rule the browser cannot check by itself

Greyed through the colour tokens, and the panel can no longer be opened

No typing, no clock, no clear cross

<script setup lang="ts">
import { ref } from 'vue'
import { VTimeInput } from 'vectis-ui'

const invalid = ref<string | null>('09:15')
</script>

<template>
  <div class="column">
    <VTimeInput
      v-model="invalid"
      invalid
      show-picker
      label="Invalid"
      hint="For a rule the browser cannot check by itself"
    />

    <VTimeInput
      model-value="09:15"
      disabled
      show-picker
      label="Disabled, with a value"
      hint="Greyed through the colour tokens, and the panel can no longer be opened"
    />

    <VTimeInput model-value="09:15" disabled mode="picker" label="Disabled, picker only" />

    <!-- Frozen rather than out of reach: it still takes the focus and can be copied from. -->
    <VTimeInput
      model-value="09:15"
      readonly
      show-picker
      clearable
      label="Read-only"
      hint="No typing, no clock, no clear cross"
    />

    <VTimeInput
      model-value="09:30"
      disabled
      mode="list"
      :minute-step="30"
      label="Disabled, as a list"
    />
  </div>
</template>

<style scoped>
.column {
  display: flex;
  flex-direction: column;
  gap: var(--vectis-space-5);
  max-inline-size: 26rem;
}
</style>

Horloge de douze heures

La valeur est une chaîne sur 24 heures quoi qu'il y ait à l'écran. Là où se choisit la moitié de la journée dépend de la forme : un bouton dans le champ saisi, la paire de l'horloge à côté de ses chiffres, et rien dans une liste, chaque ligne énonçant la sienne.

vue

The mask says nothing about the half of the day, so a button in the field does

Here the pair belongs to the clock, beside its two large numerals

Every row spells its own half of the day, so neither control is needed

07:00, 19:00, 19:30
<script setup lang="ts">
import { ref } from 'vue'
import { VTimeInput } from 'vectis-ui'

/* The value stays a 24-hour string in all three: 19:00 is what the model holds while the
   field reads 7:00 PM. */
const typed = ref<string | null>('07:00')
const onTheClock = ref<string | null>('19:00')
const fromList = ref<string | null>('19:30')
</script>

<template>
  <div class="column">
    <VTimeInput
      v-model="typed"
      format="12h"
      show-picker
      label="Typed"
      hint="The mask says nothing about the half of the day, so a button in the field does"
    />

    <VTimeInput
      v-model="onTheClock"
      format="12h"
      mode="picker"
      label="Read-only"
      hint="Here the pair belongs to the clock, beside its two large numerals"
    />

    <VTimeInput
      v-model="fromList"
      format="12h"
      mode="list"
      :minute-step="30"
      label="List"
      hint="Every row spells its own half of the day, so neither control is needed"
    />

    <output class="value" aria-label="The three values held">
      {{ typed }}, {{ onTheClock }}, {{ fromList }}
    </output>
  </div>
</template>

<style scoped>
.column {
  display: flex;
  flex-direction: column;
  gap: var(--vectis-space-5);
  max-inline-size: 26rem;
}
.value {
  font-family: var(--vectis-text-family-code);
  font-size: var(--vectis-text-body-sm-size);
  color: var(--vectis-color-text-muted);
}
</style>

Localisation

locale décide de l'horloge, du masque et de la façon d'écrire une heure, et l'emporte sur la locale globale. format passe au-dessus des deux, pour un champ qui doit se lire d'une seule façon quelle que soit la langue.

vue

format overrides what the tag would have chosen

<script setup lang="ts">
import { ref } from 'vue'
import { VTimeInput } from 'vectis-ui'

/* Nothing here is tabulated: the clock, the mask and the wording all come from the tag.
   en-US and en-GB share every word and differ only in the clock they count on. */
const locales = ref([
  { tag: 'en-US', label: 'en-US, twelve hours', time: '19:30' },
  { tag: 'en-GB', label: 'en-GB, the same words on a 24-hour clock', time: '19:30' },
  { tag: 'fr-FR', label: 'fr-FR', time: '19:30' },
  { tag: 'ja-JP', label: 'ja-JP', time: '19:30' },
])

const forced = ref<string | null>('19:30')
</script>

<template>
  <div class="column">
    <VTimeInput
      v-for="locale in locales"
      :key="locale.tag"
      v-model="locale.time"
      :locale="locale.tag"
      :label="locale.label"
      show-picker
    />

    <VTimeInput
      v-model="forced"
      locale="en-US"
      format="24h"
      show-picker
      label="en-US, forced onto a 24-hour clock"
      hint="format overrides what the tag would have chosen"
    />
  </div>
</template>

<style scoped>
.column {
  display: flex;
  flex-direction: column;
  gap: var(--vectis-space-5);
  max-inline-size: 26rem;
}
</style>

Positionnement

placement nomme la direction d'ouverture préférée du panneau, au-dessus ou en dessous du champ.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VTimeInput } from 'vectis-ui'

const placements = ['bottom-start', 'bottom-end', 'top-start', 'top-end'] as const

const times = ref<Record<string, string | null>>({
  'bottom-start': '09:15',
  'bottom-end': '09:15',
  'top-start': '09:15',
  'top-end': '09:15',
})
</script>

<template>
  <div class="grid">
    <VTimeInput
      v-for="placement in placements"
      :key="placement"
      v-model="times[placement]"
      :placement="placement"
      :label="placement"
      mode="picker"
    />
  </div>
</template>

<style scoped>
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--vectis-space-5);
  max-inline-size: 26rem;
}
</style>

API

Props

PropTypeDéfaut
formatTimePickerFormat'12h' | '24h'aucune
Si les heures sont montrées sur une horloge de 12 ou de 24 heures. Omise, la langue du lecteur décide, ce qui est presque toujours ce que l'on veut.
modeTimeInputMode'picker' | 'input' | 'list''input'
La forme que prend le champ : saisissable, en mode picker où l'horloge est la seule voie d'entrée et se trouve donc forcée, ou une LISTE d'heures à intervalle fixe, où une horloge n'aurait aucun sens. C'est une autre question que readonly, qui gèle le champ par toutes les voies à la fois.
showPickerbooleanfalse
Propose le sélecteur à côté d'un champ saisissable : une icône en fin de champ, et un panneau qui s'ouvre au focus. L'horloge suit ce qui est saisi et, sans valeur, s'ouvre vide. Cela ne signifie rien en mode picker, où l'horloge est imposée, ni en mode list, où la liste tient lieu de panneau.
minuteStepnumber1
L'intervalle entre deux heures qui peuvent être choisies. Il s'applique au sélecteur, aux flèches et aux lignes de la liste.
minstringaucune
L'heure la plus tôt que l'on puisse choisir, incluse, en chaîne canonique sur 24 heures. Le sélecteur et la liste retirent tous deux ce qu'elle exclut, et une heure saisie en dehors rend le champ invalide.
maxstringaucune
L'heure la plus tard que l'on puisse choisir, incluse, écrite comme min.
allowedHoursTimePickerAllowedaucune
Les heures que l'on peut choisir : leur liste, ou une règle qui répond pour l'une d'elles. L'heure passée à une règle est toujours celle sur 24 heures, quelle que soit l'horloge affichée.
allowedMinutesTimePickerAllowedaucune
Les minutes que l'on peut choisir : leur liste, ou une règle qui répond pour l'une d'elles.
localestringaucune
Une locale BCP 47, qui décide de l'horloge et de la façon dont une heure est écrite. Elle l'emporte sur la locale globale du design system et retombe dessus.
labelstringaucune
Le libellé au-dessus du champ.
hintstringaucune
Une ligne d'aide sous le champ.
placeholderstringaucune
Ce que dit le champ quand il est vide.
sizeTimeInputSize'sm' | 'md' | 'lg''md'
La hauteur du champ : 32, 40 ou 48 pixels.
compactbooleanfalse
Retire 4px à la hauteur.
disabledbooleanfalse
Rend le champ inutilisable, grisé par les tokens de couleur.
readonlybooleanfalse
Montre l'heure sans permettre de la changer : rien ne se tape, il n'y a ni horloge ni croix de vidage, et les attributs qui annonçaient un panneau disparaissent avec eux. Le champ garde le focus et reste copiable, ce qui le distingue de disabled.
invalidbooleanfalse
Marque le champ comme invalide, pour une règle à vous.
iconStartIconSourceaucune
Une icône dans le champ, au début. Décorative jusqu'à ce qu'un écouteur @click:icon-start en fasse un bouton.
iconStartLabelstringaucune
Ce que fait l'icône de début, en mots, une fois cliquable.
pickerIconLabelstringaucune
Ce que fait l'icône de fin, en mots. Elle nomme le bouton qui ouvre l'horloge, et sa valeur par défaut vient du dictionnaire du design system.
loadingbooleanfalse
Affiche une roue à la place de l'icône de l'horloge. Elle dit que quelque chose se charge et ne change rien d'autre : le champ reste saisissable et le panneau s'ouvre toujours.
loadingTextstringaucune
Ce que les lecteurs d'écran annoncent pendant que la roue tourne. Sa valeur par défaut vient du dictionnaire du design system.
clearablebooleanfalse
Propose une croix qui vide la valeur, affichée avant l'icône de fin.
clearLabelstringaucune
Ce que fait cette croix, en mots. Sa valeur par défaut vient du dictionnaire du design system.
pickerIconIconSourceschedule
L'icône qui ouvre l'horloge, en fin de champ. Sans effet sur la forme liste, dont le chevron suit la convention de la liste déroulante. La croix d'effacement apparaît à sa gauche plutôt qu'à sa place.
placementTimeInputPlacement'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end''bottom-start'
Où le panneau s'ouvre par rapport au champ.
v-modelstring | nullnull
L'heure, toujours en chaîne sur 24 heures quelle que soit l'horloge affichée : vous n'avez donc jamais à savoir laquelle la langue du lecteur utilise.

Événements

ÉvénementType
click:icon-start[event: MouseEvent]
L'icône de début a été cliquée. Attacher cet écouteur est ce qui en fait un vrai bouton, qui demande alors iconStartLabel.
clear[]
La croix de vidage a vidé le champ. La valeur est déjà remise à zéro.

Slots

SlotType
start{}
Du contenu au début du champ, rendu après iconStart plutôt qu'à sa place.
value-end{}
Des contrôles à vous à l'intérieur du champ, placés avant ceux que le champ possède : la croix d'effacement et l'icône qui ouvre le panneau. Ces deux-là sont l'affordance propre du composant, ce qui explique l'absence de slot end ici.
footerTimeInputFooterSlotProps
La bande au pied de l'horloge, qui REMPLACE les boutons Annuler et OK au lieu de s'y ajouter. Elle reçoit les deux actions, et c'est ce qui la rend utilisable : l'horloge écrit un brouillon que seul confirm valide, donc un pied à vous sans lui laisserait la valeur inchangeable depuis le panneau. Elle reçoit aussi cancel, et close, la même fonction sous le nom que donne le pied de VDateInput. Elle n'est pas rendue en mode liste, qui n'a pas de panneau propre.

Types

Les types que les tables ci-dessus nomment, écrits comme la librairie les déclare. Ceux qui portent export s'importent depuis vectis-ui pour typer votre propre code ; les autres décrivent la forme de ce qu'un slot fournit.

export interface BuiltinIcon {
  name: string
  paths: readonly [string] | readonly [string, string]
}
export type IconRender =
  | { path: string; viewBox?: string }
  | { component: Component; props?: Record<string, unknown> }
  | { src: string }
  | { text: string; class?: string }
  | { class: string }
export type IconSource = string | BuiltinIcon | IconRender
export interface TimeInputFooterSlotProps {
  confirm: () => void
  cancel: () => void
  close: () => void
}
export type TimePickerAllowed = number[] | ((value: number) => boolean)