import type { Meta, StoryObj } from '@storybook/vue3' import DatePicker from '@/components/DatePicker/CalendarMode/DatePicker.vue' import { ref } from 'vue' import { fn } from '@storybook/test' const meta = { title: 'Composants/Formulaires/DatePicker/DateInput', component: DatePicker, decorators: [ () => ({ template: '
', }), ], parameters: { layout: 'fullscreen', controls: { exclude: ['modelValue'] }, actions: { argTypesRegex: '^on.*' }, events: { remapEvents: { 'update:model-value': 'onUpdate:modelValue', 'focus': 'onFocus', 'blur': 'onBlur', 'input': 'onInput', 'date-selected': 'onDate-selected', }, }, docs: { description: { component: '\n## DatePicker en mode text input (noCalendar) - Incompatibilités entre props\n\n### Contrôle d\'affichage des icônes\n- `noIcon: true` masque toutes les icônes, rendant `displayIcon`, `displayAppendIcon` et `displayPrependIcon` sans effet\n- `displayIcon: false` désactive les icônes, rendant `displayAppendIcon` et `displayPrependIcon` sans effet\n- `displayAppendIcon` et `displayPrependIcon` sont mutuellement exclusifs; si les deux sont définis à `true`, `displayAppendIcon` est prioritaire\n\n### Validation et états de champ\n- `readonly: true` désactive toutes les validations, y compris `required` et les règles personnalisées\n- `disabled` et `readonly` sont mutuellement exclusifs\n- `disableErrorHandling: true` peut créer une incohérence avec `showSuccessMessages: true`\n\n### Format et saisie\n- `birthDate` et `isBirthDate` sont des alias, utiliser l\'un ou l\'autre mais pas les deux\n- `displayRange: true` nécessite que modelValue soit un tableau de deux dates `[startDate, endDate]`\n- `autoClamp: true` peut court-circuiter certaines validations manuelles\n', }, }, }, argTypes: { 'onUpdate:modelValue': { description: 'Émis lorsque la valeur du champ est mise à jour', table: { category: 'events', type: { summary: '(value: DateValue) => void' }, }, }, 'onFocus': { description: 'Émis lorsque le champ reçoit le focus', table: { category: 'events', type: { summary: '() => void' }, }, }, 'onBlur': { description: 'Émis lorsque le champ perd le focus', table: { category: 'events', type: { summary: '() => void' }, }, }, 'onInput': { description: 'Émis lors de la saisie dans le champ', table: { category: 'events', type: { summary: '(value: string) => void' }, }, }, 'onDate-selected': { description: 'Émis lorsqu\'une date complète est saisie manuellement', table: { category: 'events', type: { summary: '(value: DateValue) => void' }, }, }, 'validateOnSubmit': { description: 'Valide le champ et retourne true si valide, false sinon', table: { category: 'exposed', type: { summary: '() => boolean' }, }, }, 'focus': { description: 'Met le focus sur le champ de saisie de date', table: { category: 'exposed', type: { summary: '() => void' }, }, }, 'blur': { description: 'Retire le focus du champ de saisie', table: { category: 'exposed', type: { summary: '() => void' }, }, }, 'placeholder': { control: 'text', description: 'Texte indicatif affiché lorsque le champ est vide pour guider l\'utilisateur sur le format attendu', defaultValue: 'JJ/MM/AAAA', }, 'format': { control: 'select', options: ['DD/MM/YYYY', 'MM/DD/YYYY', 'YYYY-MM-DD'], description: 'Format d\'affichage de la date dans le champ (ex: DD/MM/YYYY pour jour/mois/année)', defaultValue: 'DD/MM/YYYY', }, 'dateFormatReturn': { control: 'select', options: ['', 'DD/MM/YYYY', 'MM/DD/YYYY', 'YYYY-MM-DD'], description: 'Format de la date émise par le v-model. Si vide, utilise le même format que la prop "format"', defaultValue: '', }, 'label': { control: 'text', description: 'Libellé du champ affiché au-dessus ou dans le champ de saisie', defaultValue: 'Date', }, 'required': { control: 'boolean', description: 'Définit si le champ est obligatoire et active la validation correspondante', defaultValue: false, }, 'disabled': { control: 'boolean', description: 'Désactive le champ, empêchant toute interaction utilisateur et appliquant un style grisé. ⚠️ Incompatible avec readonly.', defaultValue: false, }, 'readonly': { control: 'boolean', description: 'Rend le champ en lecture seule, la valeur peut être affichée mais pas modifiée par l\'utilisateur. ⚠️ Désactive toutes les validations (required, customRules, customWarningRules). Incompatible avec disabled.', defaultValue: false, }, 'isOutlined': { control: 'boolean', description: 'Affiche le champ avec un contour complet (style outlined de Vuetify) plutôt qu\'un souligné simple', defaultValue: true, }, 'displayIcon': { control: 'boolean', description: 'Contrôle l\'affichage de l\'icône calendrier, à utiliser en conjonction avec displayPrependIcon ou displayAppendIcon. ⚠️ Sans effet si noIcon est true.', defaultValue: true, }, 'displayAppendIcon': { control: 'boolean', description: 'Affiche l\'icône calendrier à la fin du champ (à droite). ⚠️ Sans effet si displayIcon est false ou si noIcon est true. Prioritaire sur displayPrependIcon si les deux sont true.', defaultValue: false, }, 'noIcon': { control: 'boolean', description: 'Masque toutes les icônes du composant, remplace les props displayIcon, displayAppendIcon et displayPrependIcon. ⚠️ Incompatible avec displayIcon, displayAppendIcon et displayPrependIcon.', defaultValue: false, }, 'customRules': { control: 'object', description: 'Règles de validation personnalisées pour la date saisie, affichant des erreurs si non respectées', defaultValue: [], }, 'customWarningRules': { control: 'object', description: 'Règles d\'avertissement pour afficher des messages d\'attention sans bloquer la validation', defaultValue: [], }, 'displayPrependIcon': { control: 'boolean', description: 'Affiche l\'icône calendrier au début du champ (à gauche). ⚠️ Sans effet si displayIcon est false, si noIcon est true, ou si displayAppendIcon est true.', defaultValue: true, }, 'disableErrorHandling': { control: 'boolean', description: 'Désactive la gestion interne des erreurs, permettant à l\'application parente de gérer les validations. ⚠️ Peut créer une incohérence si showSuccessMessages est true.', defaultValue: false, }, 'showSuccessMessages': { control: 'boolean', description: 'Affiche les messages de succès quand la validation est passée avec succès', defaultValue: true, }, 'bgColor': { control: 'color', description: 'Couleur de fond du champ de saisie (ex: white, transparent, #f5f5f5)', defaultValue: 'white', }, 'displayRange': { control: 'boolean', description: 'Active la sélection de plage de dates (date début - date fin), le v-model retournera un tableau de deux dates. ⚠️ Nécessite que modelValue soit un tableau de deux dates [startDate, endDate] pour fonctionner correctement.', defaultValue: false, }, 'autoClamp': { control: 'boolean', description: 'Active la mise en forme automatique lors de la saisie (ajout des séparateurs automatiquement). ⚠️ Peut court-circuiter certaines validations manuelles.', defaultValue: false, }, 'noCalendar': { control: 'boolean', description: 'Désactive l\'affichage du calendrier, permettant uniquement la saisie manuelle (utile pour les tests automatisés)', defaultValue: true, }, 'displayAsterisk': { control: 'boolean', description: 'Affiche un astérisque (*) à côté du label pour indiquer visuellement que le champ est obligatoire', defaultValue: false, }, 'birthDate': { control: 'boolean', description: '⚠️ **DEPRECATED** — Utilisez `isBirthDate` à la place.', defaultValue: false, }, 'isBirthDate': { control: 'boolean', description: 'Active le mode date de naissance qui commence la navigation du calendrier à l\'année en cours moins 30 ans', defaultValue: false, }, 'width': { control: 'text', description: 'Largeur du champ (peut être en px, %, em, rem ou toute unité CSS valide)', defaultValue: '100%', }, 'isValidateOnBlur': { control: 'boolean', description: 'Active la validation automatique lorsque le champ perd le focus (onBlur)', defaultValue: true, }, 'density': { control: 'select', options: ['default', 'comfortable', 'compact'], description: 'Densité du champ, affecte l\'espacement interne et la hauteur (standard Vuetify)', defaultValue: 'default', }, 'title': { control: 'text', }, }, } as Meta export default meta type Story = StoryObj export const Default: Story = { parameters: { sourceCode: [ { name: 'Script', code: ` `, }, { name: 'Template', code: ` `, }, ], }, args: { 'noCalendar': true, 'format': 'DD/MM/YYYY', 'dateFormatReturn': '', 'placeholder': 'JJ/MM/AAAA', 'label': 'Date avec règles de validation', 'required': true, 'disabled': false, 'readonly': false, 'isOutlined': true, 'displayIcon': true, 'displayAppendIcon': false, 'noIcon': false, 'displayRange': false, 'displayPrependIcon': false, 'showSuccessMessages': true, 'disableErrorHandling': false, 'onUpdate:modelValue': fn(), 'onFocus': fn(), 'onBlur': fn(), }, render(args) { const date = ref(null) return { components: { DatePicker }, setup() { return { args, date } }, template: `
Valeur : {{ date }}
`, } }, } export const Required: Story = { parameters: { sourceCode: [ { name: 'Script', code: ` `, }, { name: 'Template', code: ` `, }, ], }, args: { 'noCalendar': true, 'format': 'DD/MM/YYYY', 'dateFormatReturn': '', 'placeholder': 'JJ/MM/AAAA', 'label': 'Date avec règles de validation', 'required': true, 'disabled': false, 'readonly': false, 'isOutlined': true, 'displayIcon': true, 'displayAppendIcon': false, 'noIcon': false, 'displayRange': false, 'displayPrependIcon': false, 'showSuccessMessages': true, 'disableErrorHandling': false, 'onUpdate:modelValue': fn(), 'onFocus': fn(), 'onBlur': fn(), }, render(args) { const date = ref(null) return { components: { DatePicker }, setup() { return { args, date } }, template: `

Sans astérisque :

Avec astérisque :

`, } }, } export const EuropeanFormat: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, ], }, args: { 'noCalendar': true, 'format': 'DD/MM/YYYY', 'dateFormatReturn': 'YYYY/MM/DD', 'placeholder': 'JJ/MM/AAAA', 'label': 'Date avec règles de validation', 'required': true, 'noIcon': true, 'onUpdate:modelValue': fn(), 'onFocus': fn(), 'onBlur': fn(), }, render(args) { const date = ref(null) return { components: { DatePicker }, setup() { return { args, date } }, template: `

Format européen avec règles de base (format de date valide) :

Valeur (dateFormatReturn: 'YYYY/MM/DD') : {{ date }}
`, } }, } export const CustomRules: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, ], }, args: { 'noCalendar': true, 'format': 'DD/MM/YYYY', 'dateFormatReturn': 'DD/MM/YYYY', 'label': 'Date avec règles personnalisées', 'placeholder': 'DD/MM/YYYY', 'required': true, 'customRules': [{ type: 'custom', options: { validate: (value: string) => !value || !value.includes('2024'), message: 'Les dates en 2024 ne sont pas autorisées', successMessage: 'Les dates hors 2024 sont autorisées', fieldIdentifier: 'date', }, }], 'onUpdate:modelValue': fn(), 'onFocus': fn(), 'onBlur': fn(), }, render(args) { const date = ref('21/12/2024') return { components: { DatePicker }, setup() { return { args, date } }, template: `

Format avec règles personnalisées :

Les dates en 2024 ne sont pas autorisées

Valeur : {{ date }}
`, } }, } export const WarningRules: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, ], }, args: { 'noCalendar': true, 'format': 'DD/MM/YYYY', 'placeholder': 'JJ/MM/AAAA', 'label': 'Date avec règles d\'avertissement', 'customWarningRules': [{ type: 'custom', options: { validate: (value: string) => !value || !value.includes('2025'), warningMessage: 'Les dates en 2025 ne sont pas autorisées', successMessage: 'Date hors 2025', fieldIdentifier: 'date', isWarning: true, }, }], 'onUpdate:modelValue': fn(), 'onFocus': fn(), 'onBlur': fn(), }, render(args) { const date = ref('20/12/2025') return { components: { DatePicker }, setup() { return { args, date } }, template: `

Format avec règles d'avertissement :

Les dates en 2025 ne sont pas autorisées

Valeur : {{ date }}
`, } }, } export const WithAppendIcon: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, ], }, args: { 'noCalendar': true, 'format': 'DD/MM/YYYY', 'placeholder': 'JJ/MM/AAAA', 'label': 'Date avec icône en suffixe', 'displayAppendIcon': true, 'onUpdate:modelValue': fn(), 'onFocus': fn(), 'onBlur': fn(), }, render(args) { const date = ref(null) return { components: { DatePicker }, setup() { return { args, date } }, template: `

Format avec icône en suffixe

Valeur : {{ date }}
`, } }, } export const WithErrorDisabled: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, ], }, args: { noCalendar: true, format: 'DD/MM/YYYY', dateFormatReturn: 'YYYY/MM/DD', placeholder: 'Date requise sans erreur', label: 'Date requise sans erreur', required: true, noIcon: true, disableErrorHandling: true, }, render(args) { const date1 = ref(null) const date2 = ref(null) return { components: { DatePicker }, setup() { return { args, date1, date2 } }, template: `

DateTextInput avec désactivation des erreurs

Avec disableErrorHandling:

Valeur : {{ date1 }}

Sans disableErrorHandling:

Valeur : {{ date2 }}
`, } }, } export const AutoClampFeature: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, render: () => { return { components: { DatePicker }, setup() { const dateSlash = ref('') const dateDash = ref('') const dateDot = ref('') const dateRange = ref('') return { dateSlash, dateDash, dateDot, dateRange } }, template: `

Démonstration de l'auto clamp dans DateTextInput

Saisissez uniquement des chiffres - les séparateurs seront ajoutés automatiquement selon le format défini

Format JJ/MM/AAAA (séparateur /)

Valeur actuelle: {{ dateSlash || 'aucune date saisie' }}

Format JJ-MM-AAAA (séparateur -)

Valeur actuelle: {{ dateDash || 'aucune date saisie' }}

Format AAAA.MM.JJ (séparateur .)

Valeur actuelle: {{ dateDot || 'aucune date saisie' }}

Mode plage de dates (séparateur /)

Valeur actuelle: {{ dateRange || 'aucune plage saisie' }}
`, } }, } export const DifferentFormats: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, render: () => { return { components: { DatePicker: DatePicker }, setup() { const value1 = ref('24/12/2025') const value2 = ref('12/24/2025') const value3 = ref('2025-12-24') const value4 = ref('24-12-25') const value5 = ref('25.12.2025') return { value1, value2, value3, value4, value5 } }, template: `
`, } }, }