MenuChevron Down
DatePicker Sélecteur de Date - Docs - Artefact

DatePicker Sélecteur de Date

Forms
Tier 2

Introduction

Un sélecteur de date qui combine une saisie de texte avec un calendrier contextuel. Il prend en charge la sélection unique, multiple et par plage, les vues de panneau mois/année, les préréglages de sélection rapide, les numéros de semaine et la saisie manuelle de la date – rendus côté serveur avec HonoX et hydratés comme un îlot uniquement lorsque l'interactivité est nécessaire.

Le composant est sans tête de par sa conception : app/components/ui/date-picker-primitive.tsx produit un balisage et un état sémantiques et accessibles, tandis que app/islands/date-picker.tsx ajoute le comportement côté client (navigation au clavier, aperçu au survol, clic extérieur, saisie). Aucune nouvelle dépendance d'exécution n'est introduite - il s'appuie sur Hono JSX et Panda CSS, la même pile que le reste du système de conception.

Quoi de neuf dans cette révision

  • Les valeurs sélectionnées survivent désormais au rendu statique. L'entrée rendait auparavant sa valeur via defaultValue, que hono/jsx sérialise comme un attribut defaultValue="…" mort — un sélecteur rendu statiquement avec une value/defaultValue a montré une entrée vide. Il restitue désormais un véritable attribut « valeur » ; la frappe reste incontrôlée car le runtime DOM n'attribue la propriété value que lorsque la prop change réellement.- Les listes déroulantes Mois/année sont présélectionnées correctement dans SSR. MonthSelect/YearSelect utilisait value sur <select>, qui n'est pas un attribut HTML, donc le mois/l'année ciblé n'a jamais été présélectionné dans la sortie du serveur. Le <option> correspondant porte désormais selected.- Texte du calendrier localisé. Les en-têtes de jours de la semaine, les grilles mensuelles et la liste déroulante des mois sont générés à partir de « Intl.DateTimeFormat » pour les « paramètres régionaux » configurés (mis en cache par paramètres régionaux, avec une solution de secours en anglais lorsque les paramètres régionaux sont inconnus). Auparavant, seul le titre respectait « locale ».- Les points de terminaison de la plage se connectent à la bande. Les jours de début/fin d'une plage portent désormais data-range-start / data-range-end, et la recette aligne leurs coins intérieurs afin que les points de terminaison rejoignent la surbrillance dans la plage de manière transparente.- Le focus clavier survit aux commutateurs de vue. Le zoom entre les vues jour/mois/année (via l'en-tête ou en explorant un mois/année) était utilisé pour déposer le focus DOM sur <body> car le contrôle précédemment ciblé est masqué. Le focus se déplace désormais sur la cellule active de la nouvelle vue.- Sémantique de la grille renforcée. Le role="row" parasite sur <thead> (qui a remplacé son rôle implicite rowgroup) a disparu, et la grille annonce aria-multiselectable dans les modes multiple/range.- La fenêtre contextuelle reste à l'écran. La fenêtre contextuelle du calendrier limite sa largeur à la fenêtre d'affichage sur les écrans étroits au lieu de déborder horizontalement. Reprise de la révision précédente : navigation dans la grille avec le clavier en premier, limitée à « [min, max] », vues mois/année délimitées, aperçu du survol de la plage, saisie de texte libre avec validation sur entrée/flou, soumission de formulaire natif via des entrées cachées, numéros de semaine ISO, déclencheur d'effacement conditionnel, sémantique de grille ARIA avec un taquet de tabulation itinérant et animations d'ouverture/fermeture liées à « état des données ».

Fonctionnalité

  • Calendrier contextuel : s'ouvre à partir du déclencheur du calendrier ou en focalisant/cliquant sur l'entrée ; se ferme sur un clic extérieur, Escape (le focus revient sur le déclencheur), ou après une sélection terminée lorsque closeOnSelect est défini.- Vues du panneau — cliquez sur l'en-tête mois/année pour effectuer un zoom arrière entre jours → mois → années ; la sélection d'une année ou d'un mois redescend. La page des flèches précédente/suivante par mois, année ou décennie selon la vue.- Sélection limitéemin/max sont appliqués dans chaque vue : les jours hors plage sont désactivés, les mois et les années qui se situent entièrement en dehors de la plage sont également désactivés et le focus du clavier est bloqué afin qu'il ne puisse jamais reposer sur une cellule désactivée.- Saisie manuelle — saisissez une date dans la saisie et appuyez sur Entrée (ou flou). Les valeurs valides « AAAA-MM-JJ » sont validées et normalisées ; les valeurs non valides ou hors plage reviennent à la dernière valeur validée. Effacer l’entrée efface la sélection.- Sélection de plage — le premier clic définit le début, le second définit la fin (automatiquement ordonné) ; les jours intermédiaires sont mis en surbrillance et le survol donne un aperçu de la période avant de vous engager.- Sélection multiple : cliquez pour activer et désactiver des jours individuels.- Numéros de semaine — avec showWeekNumbers, une colonne ISO de numéro de semaine est affichée à côté de la grille des jours.- Soumission de formulaire — transmettez un accessoire name et une entrée cachée est rendue pour chaque date sélectionnée, de sorte que le sélecteur fonctionne à l'intérieur d'un simple <form> sans code de colle supplémentaire.- PréréglagesDatePicker.PresetTrigger prend en charge les valeurs today, last3Days, last7Days, last14Days, last30Days et last90Days. En mode plage, un préréglage sélectionne toute la plage ; sinon, il sélectionne la date d'ancrage unique.

Prise en charge du clavier

Lorsque la fenêtre contextuelle est ouverte et que le focus est à l'intérieur du calendrier, les touches suivantes sont actives :

KeyDay viewMonth viewYear view
/ Previous / next dayPrevious / next monthPrevious / next year
/ ±1 week±3 months±3 years
Home / EndStart / end of week
PageUp / PageDownPrevious / next monthPrevious / next yearPrevious / next decade
Shift+PageUp / Shift+PageDownPrevious / next year
Enter / SpaceSelect focused dayDrill into monthDrill into year
EscClose popup, return focus to trigger

Le déclencheur et l'entrée sont accessibles par Tab ; l'ouverture à partir du clavier déplace le focus directement dans la grille afin que le calendrier puisse être utilisé sans souris.

Accessibilité

  • La grille utilise la sémantique de la grille ARIA (role="grid", row, gridcell, columnheader, rowheader) avec aria-selected, aria-current="date" sur la cellule d'aujourd'hui et aria-multiselectable dans les modes multiple/range.- Un tabindex itinérant conserve un seul taquet de tabulation sur la date ciblée ; les touches fléchées déplacent le focus sans parcourir chaque cellule.- Le focus est géré : l'ouverture depuis le clavier focalise la journée active ; la fermeture renvoie le focus sur le déclencheur ; le basculement entre les vues jour/mois/année déplace le focus sur la cellule active de la nouvelle vue au lieu de la supprimer.- Tous les contrôles interactifs portent des aria-label (Ouvrir le sélecteur de date, Effacer les dates sélectionnées, Précédent, Suivant, Changer la vue du calendrier, Sélectionner le mois, Sélectionner l'année).- La couleur n'est jamais le seul signal : les états sélectionné, aujourd'hui, à portée et désactivé combinent le remplissage, le poids et (pour aujourd'hui) un marqueur de points.- Respecte disabled / readOnly / invalid via aria-disabled, readonly et aria-invalid.

Emplacements de style

La recette d'emplacement Panda CSS (app/theme/recipes/date-picker.ts) expose les emplacements data-part suivants pour la thématisation. Remplacez via les accessoires class/className ou une carte sémantique classNames :

root, label, control, input, trigger, clearTrigger, positioner, content, view, viewControl, prevTrigger, nextTrigger, viewTrigger, rangeText, table, tableHead, tableHeader, tableRow, tableBody, tableCell, tableCellTrigger, weekNumber, monthSelect, yearSelect, presetTrigger, valueText. La partie hidden-input est rendue uniquement lorsque name est défini et ne porte aucun style visible.

Les états sélectionnés / aujourd'hui / dans la plage / aperçu de la plage / désactivés sont pilotés par les attributs « données sélectionnées », « données-aujourd'hui », « données dans la plage », « données hors plage », « données- plage-aperçu » et « données désactivées » (appliqués dans les trois vues du panneau), de sorte qu'ils peuvent être relookés indépendamment de la recette. Les points de terminaison de la plage portent en outre « data-range-start » / « data-range-end » (uniquement lorsque la plage s'étend sur plus d'un jour), que la recette utilise pour aligner leurs coins intérieurs par rapport à la bande dans la plage. L'animation d'ouverture/fermeture désactive data-state="open" / data-state="closed".

Hydratation

Niveau 1 — interactif par défaut. Un DatePicker s'hydrate comme une île à moins qu'il ne soit explicitement désactivé avec interactive={false}, auquel cas il s'affiche au format HTML statique sans client JS.

interactive propRésultat
omittedHydrates as an island
trueHydrates as an island
falseStatic — no client JS

Toutes les décisions d'interactivité dans la bibliothèque sont acheminées via l'assistant partagé shouldHydrate() dans app/components/ui/island-utils.ts.

Utilisation

import { DatePicker } from "../components/ui";

export default function MyPage() {
  return (
    <>
      {/* Single date */}
      <DatePicker label="Choose Date" selectionMode="single" />

      {/* Date range with bounds */}
      <DatePicker
        label="Travel Dates"
        selectionMode="range"
        min="2026-01-01"
        max="2026-12-31"
      />

      {/* Week numbers + accent colour */}
      <DatePicker
        label="Pick a day"
        selectionMode="single"
        showWeekNumbers
        colorPalette="purple"
      />

      {/* Preselected value */}
      <DatePicker label="Due Date" defaultValue="2026-07-15" />

      {/* Inside a native form — the selected date submits as `due` */}
      <form method="post" action="/submit">
        <DatePicker label="Due Date" name="due" selectionMode="single" />
        <button type="submit">Save</button>
      </form>

      {/* Range submission — start/end submit as two `range` entries */}
      <form method="post" action="/report">
        <DatePicker
          label="Reporting Window"
          name="range"
          selectionMode="range"
        />
        <button type="submit">Run</button>
      </form>
    </>
  );
}

Constructeur de pages CMS

Ce composant est disponible sous forme de bloc datePicker dans Page Builder (content/pages/*.json) :

{
  "type": "datePicker",
  "label": "Check-in Date",
  "selectionMode": "single",
  "placeholder": "YYYY-MM-DD",
  "colorPalette": "blue"
}

Propriétés

PropriétéTypeDescription
labelstringRenvoie une étiquette associée à la première entrée (structure par défaut uniquement).
placeholderstringEspace réservé pour l’entrée. La valeur par défaut est « AAAA-MM-JJ ».

| selectionMode | `"single" \ | "plusieurs" \ | "range"` | How dates are selected. Defaults to "single". Range mode renders start and end inputs. |

| value | `CalendarDate[] \ | chaîne[] \ | string \ | Date[]` | Selected date(s) (controlled). Strings use the YYYY-MM-DD format. | | defaultValue | `CalendarDate[] \ | chaîne[] \ | string \ | Date[]` | Initial selected date(s) (uncontrolled). |

| focusedValue | `CalendarDate \ | chaîne \ | Date` | The date the calendar panel is focused on (controlled). | | defaultFocusedValue | `CalendarDate \ | chaîne \ | Date` | Initial panel date (uncontrolled). | | min | `CalendarDate \ | chaîne \ | Date` | Earliest selectable date. Earlier days, months, and years are disabled; typed dates outside the range are rejected; keyboard focus is clamped to the range. | | max | `CalendarDate \ | chaîne \ | Date` | Latest selectable date. |

| isDateUnavailable | (date, locale) => boolean | Marque les dates individuelles comme non sélectionnables (utilisation statique/composée). |

| view | `"day" \ | "mois" \ | "year"` | The active panel view (controlled). |

| open | boolean | Si le calendrier contextuel est ouvert (contrôlé). | | closeOnSelect | boolean | Fermez la fenêtre contextuelle après une sélection terminée. La valeur par défaut est « vrai ». | | showWeekNumbers | boolean | Affiche une colonne de numéro de semaine ISO-8601. La valeur par défaut est « false ». | | numOfMonths | number | Réservé au rendu sur plusieurs mois. Actuellement, un panneau d'un seul mois est toujours affiché ; les valeurs supérieures à « 1 » sont acceptées mais pas encore rendues. | | name | string | Lorsqu'il est défini, affiche une entrée masquée par date sélectionnée sous ce nom, permettant la soumission native <form>. En mode plage/multiple, chaque date sélectionnée devient une entrée distincte (ordonnée). | | locale | string | Paramètres régionaux BCP 47 utilisés pour l'en-tête, les en-têtes de jours de la semaine, les noms de mois et les étiquettes de cellules (via Intl.DateTimeFormat, avec une solution de secours en anglais). La valeur par défaut est « en-US ». | | disabled | boolean | Désactive tout le sélecteur. | | readOnly | boolean | Rend l'entrée en lecture seule. | | invalid | boolean | Marque l'entrée invalide (aria-invalid + style d'erreur). |

| colorPalette | `"blue" \ | "vert" \ | "red" \ | "orange" \ | "gray" \ | "cyan" \ | "amber" \ | "purple"` | Accent color for the selected date, today indicator, and range highlight. Defaults to "blue". |

| interactive | boolean | Force (ou supprime) l’hydratation comme une île. | | onValueChange | (details: { value: CalendarDate[] }) => void | Appelé lorsque la sélection change. | | onOpenChange | (details: { open: boolean }) => void | Appelé lorsque la fenêtre contextuelle s'ouvre ou se ferme. |

Date du calendrier

Les dates sont représentées par la classe CalendarDate ({ année, mois, jour }, le mois est basé sur 1) pour éviter la dérive du fuseau horaire. Les assistants sont exportés avec le composant :

HelperDescription
parseDate(str)Analyse une chaîne AAAA-MM-JJ, en limitant les parties hors plage.
isValidDateString(str)Valide strictement une chaîne « AAAA-MM-JJ » (y compris la durée des mois et les années bissextiles).
daysInMonth(year, month)Nombre de jours dans le mois donné.
fromJSDate(date)Convertit un Date JavaScript en CalendarDate.
getWeekNumber(date)Numéro de semaine ISO-8601 pour un CalendarDate (utilisé par showWeekNumbers).
getWeekDays(locale)Noms de jours de la semaine localisés ({ court, étroit, long }, dimanche premier), mis en cache par paramètres régionaux.
getMonthNames(locale, format?)Noms de mois localisés ("short" ou "long"), premier janvier, mis en cache par langue.

Notes de production

  • Aucune nouvelle dépendance. Construit entièrement sur Hono JSX + Panda CSS, cohérent avec le reste du système de conception.- Sûr SSR. Le balisage est rendu sur le serveur ; seule la succursale de l'île prend en compte le comportement du client, et uniquement lorsqu'elle est signalée.- Piloté par des jetons. Les couleurs, l'espacement, les rayons et les ombres proviennent des jetons de thème partagés, de sorte que le sélecteur hérite automatiquement du mode sombre et de la « palette de couleurs » configurée.- Valeurs de type sécurisé. CalendarDate évite les pièges du fuseau horaire liés à la gestion brute de Date/string.- Délimité par la conception. min/max sont appliqués de manière cohérente dans les vues jour, mois et année – à la fois pour la sélection de la souris et la mise au point du clavier – de sorte qu'une valeur hors plage ne peut jamais être validée ou ciblée.- Prêt pour le formulaire. La prop facultative name restitue les entrées masquées, de sorte que le sélecteur passe aux formulaires natifs sans gestionnaires de soumission personnalisés.- Panneau d'un seul mois. numOfMonths est accepté pour des raisons de compatibilité ascendante mais affiche actuellement un seul mois. Le rendu sur plusieurs mois est sur la feuille de route.