import type { StoryObj, Meta } from '@storybook/vue3' import PhoneField from './PhoneField.vue' import { ref, watch } from 'vue' import { indicatifs } from './indicatifs' import { getValidationDocumentation } from '@/composables/unifyValidation/documentationValidationProps' import { fn } from '@storybook/test' const meta = { title: 'Composants/Formulaires/PhoneField', component: PhoneField, parameters: { layout: 'fullscreen', actions: { argTypesRegex: '^on.*' }, controls: { exclude: /^on*|undefined/ }, }, argTypes: { 'modelValue': { control: 'string', description: 'Valeur du champ numéro de téléphone. Ne doit contenir que les chiffres du numéro national, sans l\'indicatif pays. Par exemple, pour un numéro français +33 6 12 34 56 78, le `modelValue` doit être `0612345678`.', table: { type: { summary: 'string', }, }, }, 'bgColor': { control: 'color', description: 'Couleur de fond du champ.', table: { type: { summary: 'string', }, }, }, 'dialCodeModel': { control: 'string', description: 'Valeur de l\'indicatif pays sélectionné. Peut être une chaîne (ex : "+33") ou un objet indicatif complet (ex : { code: "+33", country: "France", abbreviation: "FR" }).' }, 'outlined': { control: 'boolean', description: 'Affiche les champs avec une bordure.', table: { defaultValue: 'true', }, }, 'withCountryCode': { control: 'boolean', description: 'Affiche un sélecteur d\'indicatif pays à côté du champ numéro de téléphone. Lorsque cette prop est à `true`, le champ est divisé en deux parties : un sélecteur pour l\'indicatif pays et un champ pour le numéro national.', table: { type: { summary: 'boolean', }, }, }, 'displayFormat': { control: { type: 'select' }, description: 'Format d\'affichage des options d\'indicatif pays dans le sélecteur. Les formats disponibles sont :\n\n- `code` (ex : +33)\n- `code-abbreviation` (ex : +33 - FR)\n- `code-country` (ex : +33 - France)\n- `country` (ex : France)\n- `abbreviation` (ex : FR)', options: ['code', 'code-abbreviation', 'code-country', 'country', 'abbreviation'], table: { type: { summary: `'code' | 'code-abbreviation' | 'code-country' | 'country' | 'abbreviation'`, }, }, }, 'customIndicatifs': { table: { type: { summary: 'Indicatif[]', details: `{ code: string abbreviation: string country: string countryFr?: string mask?: string phoneLength?: number }[] ` }, }, control: 'object', description: 'Permet d\'ajouter des indicatifs à la liste pre-existante', }, 'useCustomIndicatifsOnly': { control: 'boolean', description: 'Permet d\'utiliser uniquement les indicatifs que vous renseignez dans la props customIndicatifs', }, 'helpText': { control: 'text', description: 'Texte d\'aide affiché sous le champ. Lorsque présent, les messages d\'erreur incluent un exemple concret distinct du texte d\'aide.', table: { type: { summary: 'string', }, }, }, 'autocompleteCountryCode': { control: 'text', description: 'Valeur de l\'attribut `autocomplete` pour le champ indicatif pays. Utiliser `tel-country-code` (défaut) lorsque le numéro est séparé en deux champs (indicatif + numéro national), conformément à [WHATWG](https://html.spec.whatwg.org/#autofill-field-tel-country-code) et [WCAG 1.3.5](https://www.w3.org/WAI/WCAG21/quickref/#identify-input-purpose).', table: { type: { summary: `'autocomplete' | 'tel-country-code' | undefined`, }, }, }, 'autocompletePhone': { control: 'text', table: { type: { summary: `'autocomplete' | 'tel-national' | 'tel' | 'tel-extension'`, }, }, description: 'Valeur de l\'attribut `autocomplete` pour le champ numéro de téléphone. Valeurs recommandées selon le scénario :\n\n- `tel-national` (défaut) — numéro sans indicatif, lorsque le composant est en mode deux champs (`withCountryCode`).\n- `tel` — numéro complet avec indicatif intégré, pour un champ unique sans sélecteur de pays.\n- `tel-extension` — poste ou extension téléphonique.', }, 'displayAsterisk': { control: 'boolean' }, 'update:modelValue': { action: 'update:modelValue', table: { type: { summary: 'string', }, }, }, 'isClearable': { control: 'boolean', table: { type: { summary: 'boolean', }, category: 'props', }, description: 'Affiche un bouton de nettoyage (clear) dans le champ lorsque du texte est saisi. Lorsque `true`, les utilisateurs peuvent facilement effacer le contenu du champ en cliquant sur ce bouton.', }, 'withoutFieldset': { control: 'boolean', table: { type: { summary: 'boolean', }, category: 'props', }, description: 'N\'entoure pas le champ d\'un élément `
`', }, 'update:dialCodeModel': { action: 'update:dialCodeModel', table: { type: { summary: 'Indicatif', details: `{ code: string abbreviation: string country: string countryFr?: string mask?: string phoneLength?: number } `, }, }, }, ...getValidationDocumentation('string'), } as Record, args: { 'onUpdate:modelValue': fn(), 'onUpdate:dialCodeModel': fn(), } as Record, } satisfies Meta export default meta type Story = StoryObj export const Default: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { modelValue: '', required: false, outlined: true, withCountryCode: true, displayFormat: 'code', customIndicatifs: [], useCustomIndicatifsOnly: false, isValidateOnBlur: true, bgColor: 'white', readonly: false, disabled: false, } as Record, render: (args) => { return { components: { PhoneField }, setup() { return { args } }, template: `
`, } }, } export const Required: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { modelValue: '', required: true, outlined: true, withCountryCode: true, displayFormat: 'code', customIndicatifs: [], useCustomIndicatifsOnly: false, isValidateOnBlur: true, bgColor: 'white', readonly: false, disabled: false, } as Record, render: (args) => { return { components: { PhoneField }, setup() { return { args } }, template: `
`, } }, } /** * Story avec champ requis et astérisque. */ export const RequiredWithAsterisk: Story = { args: { ...Default.args, required: true, displayAsterisk: true, bgColor: 'white', } as Record, parameters: { a11y: { disable: false, }, docs: { description: { story: 'Version du champ téléphone requis avec un astérisque visuel.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, render: (args) => { return { components: { PhoneField }, setup() { return { args } }, template: `
`, } }, } export const HelpText: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ``, }, { name: 'Script', code: ``, }, ], }, args: { required: true, helpText: 'Saisissez votre numéro de téléphone français au format 01 23 45 67 89', } as Record, render(args) { return { components: { PhoneField }, setup() { const phoneValue1 = ref('') const phoneValue2 = ref('') const dialCodeModel = ref('') return { args, phoneValue1, phoneValue2, dialCodeModel, } }, template: `

Avec aide à la saisie

Essayez de laisser le champ vide ou de saisir un numéro incorrect pour voir l'exemple dans le message d'erreur (différent de l'aide).

Avec aide à la saisie et indicatif pays

L'exemple dans le message d'erreur s'adapte au format du pays sélectionné.

Valeurs actuelles :

phoneValue1: {{ phoneValue1 }}
phoneValue2: {{ phoneValue2 }}
dialCodeModel: {{ dialCodeModel }}
`, } }, } export const Autocomplete: Story = { parameters: { a11y: { disable: false, }, docs: { description: { story: ` Les attributs \`autocomplete\` permettent aux navigateurs et aux outils d'assistance de remplir automatiquement les champs avec les bonnes informations utilisateur, conformément à [WCAG 1.3.5 — Identifier la finalité de la saisie](https://www.w3.org/WAI/WCAG21/quickref/#identify-input-purpose). | Scénario | Prop à utiliser | Valeur recommandée | Source | |---|---|---|---| | **Code pays** (ex : +33) — champ séparé | \`autocompleteCountryCode\` | \`tel-country-code\` | [WHATWG](https://html.spec.whatwg.org/#autofill-field-tel-country-code) | | **Numéro sans indicatif** (ex : 06 12 34 56 78) — avec \`withCountryCode\` | \`autocompletePhone\` | \`tel-national\` | [WHATWG](https://html.spec.whatwg.org/#autofill-field-tel-national) | | **Numéro complet** (indicatif intégré) — sans \`withCountryCode\` | \`autocompletePhone\` | \`tel\` | [WHATWG](https://html.spec.whatwg.org/#autofill-field-tel) | | **Extension / poste** | \`autocompletePhone\` | \`tel-extension\` | [WHATWG](https://html.spec.whatwg.org/#autofill-field-tel-extension) | `, }, }, sourceCode: [ { name: 'Template', code: ``, }, { name: 'Script', code: ``, }, ], }, args: { required: false, withCountryCode: true, autocompleteCountryCode: 'tel-country-code', autocompletePhone: 'tel-national', helpText: 'Utilisez les valeurs autocomplete appropriées pour l\'accessibilité', } as Record, render(args) { return { components: { PhoneField }, setup() { const phoneValue1 = ref('') const dialCodeModel1 = ref('') const phoneValue2 = ref('') const phoneValue3 = ref('') return { args, phoneValue1, dialCodeModel1, phoneValue2, phoneValue3, } }, template: `

Les attributs autocomplete permettent aux navigateurs de remplir automatiquement les champs avec les bonnes informations utilisateur.

Avec indicatif pays (autocomplete="tel-country-code")

Numéro complet (autocomplete="tel")

Extension téléphonique (autocomplete="tel-extension")

Valeurs actuelles :

phoneValue1: {{ phoneValue1 }}
dialCodeModel1: {{ dialCodeModel1 }}
phoneValue2: {{ phoneValue2 }}
phoneValue3: {{ phoneValue3 }}
`, } }, } export const CustomIndicatifs: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { modelValue: '', required: false, outlined: true, withCountryCode: true, displayFormat: 'code', customIndicatifs: [ { code: '+33', country: 'France', abbreviation: 'FR', phoneLength: 10, mask: '## ## ## ## ##' }, { code: '+34', country: 'Spain', abbreviation: 'ES', phoneLength: 9, mask: '### ### ###' }, { code: '+99', country: 'Utopia', abbreviation: 'UT', mask: '## ## ## ##', phoneLength: 8 }, { code: '+98', country: 'Paradise', abbreviation: 'PA', mask: '## ## ## ##', phoneLength: 18 }, ], useCustomIndicatifsOnly: true, bgColor: 'white', readonly: false, disabled: false, } as Record, render: (args) => { return { components: { PhoneField }, setup() { return { args } }, template: `
`, } }, } export const DisplayFormatCode: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { modelValue: '', required: false, outlined: true, withCountryCode: true, displayFormat: 'code', customIndicatifs: [], useCustomIndicatifsOnly: false, isValidateOnBlur: true, readonly: false, disabled: false, bgColor: 'white', } as Record, render: (args) => { return { components: { PhoneField }, setup() { return { args } }, template: `
`, } }, } export const DisplayFormatCodeAbbreviation: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { modelValue: '', required: false, outlined: true, withCountryCode: true, displayFormat: 'code-abbreviation', customIndicatifs: [], useCustomIndicatifsOnly: false, isValidateOnBlur: true, readonly: false, disabled: false, bgColor: 'white', } as Record, render: (args) => { return { components: { PhoneField }, setup() { return { args } }, template: `
`, } }, } export const DisplayFormatCodeCountry: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { modelValue: '', required: false, outlined: true, withCountryCode: true, displayFormat: 'code-country', customIndicatifs: [], useCustomIndicatifsOnly: false, isValidateOnBlur: true, readonly: false, disabled: false, bgColor: 'white', } as Record, render: (args) => { return { components: { PhoneField }, setup() { return { args } }, template: `
`, } }, } export const DisplayFormatCountry: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { modelValue: '', required: false, outlined: true, withCountryCode: true, displayFormat: 'country', customIndicatifs: [], useCustomIndicatifsOnly: false, isValidateOnBlur: true, readonly: false, disabled: false, bgColor: 'white', } as Record, render: (args) => { return { components: { PhoneField }, setup() { return { args } }, template: `
`, } }, } export const DisplayFormatAbbreviation: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { modelValue: '', required: false, outlined: true, withCountryCode: true, displayFormat: 'abbreviation', customIndicatifs: [], useCustomIndicatifsOnly: false, isValidateOnBlur: true, readonly: false, disabled: false, bgColor: 'white', } as Record, render: (args) => { return { components: { PhoneField }, setup() { return { args } }, template: `
`, } }, } export const DefaultDialCode: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { modelValue: '', dialCodeModel: { code: '+3433', country: 'Exemple', abbreviation: 'EX', phoneLength: 10, mask: '## ## ## ## ##' }, required: false, outlined: true, withCountryCode: true, displayFormat: 'code-country', customIndicatifs: [ { code: '+3433', country: 'Exemple', abbreviation: 'EX', phoneLength: 10, mask: '## ## ## ## ##' }, { code: '+34', country: 'Espagne', abbreviation: 'ES', phoneLength: 9, mask: '### ### ###' }, { code: '+41', country: 'Suisse', abbreviation: 'CH', phoneLength: 9, mask: '### ### ###' }, ], useCustomIndicatifsOnly: true, isValidateOnBlur: true, bgColor: 'white', readonly: false, disabled: false, } as Record, render: (args) => { return { components: { PhoneField }, setup() { return { args } }, template: `

PhoneField avec indicatif pré-rempli

Cette story montre comment pré-remplir l'indicatif téléphonique avec des indicatifs personnalisés.

`, } }, } export const DefaultDialCodeStandard: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { modelValue: '', dialCodeModel: indicatifs.find(ind => ind.country === 'France'), required: false, outlined: true, withCountryCode: true, displayFormat: 'code-country', customIndicatifs: [], useCustomIndicatifsOnly: false, isValidateOnBlur: true, bgColor: 'white', readonly: false, disabled: false, } as Record, render: (args) => { return { components: { PhoneField }, setup() { return { args } }, template: `

PhoneField avec indicatif standard pré-rempli

Cette story montre comment pré-remplir l'indicatif téléphonique avec les indicatifs standards.

Indicatif sélectionné : {{ args.dialCodeModel ? args.dialCodeModel.code + ' ' + args.dialCodeModel.country : 'Aucun' }}
`, } }, } export const DisplayModels: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { modelValue: '', dialCodeModel: '', required: false, outlined: true, withCountryCode: true, displayFormat: 'code-country', customIndicatifs: [], useCustomIndicatifsOnly: false, isValidateOnBlur: true, readonly: false, bgColor: 'white', } as Record, render: args => ({ components: { PhoneField }, setup() { const _args = args as Record const modelValue = ref(_args.modelValue as string) const dialCodeModel = ref(_args.dialCodeModel) // Sync ref -> args (pour afficher les modèles dans la story) watch(modelValue, (val) => { _args.modelValue = val }) watch(dialCodeModel, (val) => { _args.dialCodeModel = val }) // Sync args -> ref (quand on change les controls Storybook) watch(() => _args.modelValue, (val) => { modelValue.value = val as string }) watch(() => _args.dialCodeModel, (val) => { dialCodeModel.value = val }) return { args, modelValue, dialCodeModel, } }, template: `
Indicatif: {{ dialCodeModel }}
Numéro: {{ modelValue }}
`, }), } export const WithoutFieldset: Story = { parameters: { a11y: { disable: false, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, decorators: [ () => ({ template: '
', }), ], args: { modelValue: '', required: true, label: 'Numéro de téléphone', withCountryCode: true, withoutFieldset: true, } as Record, }