import type { Meta, StoryObj } from '@storybook/vue3' import SyTextField from '@/components/Customs/SyTextField/SyTextField.vue' import { VIcon } from 'vuetify/components' import { ref, watch } from 'vue' import { mdiAccountBox } from '@mdi/js' import { getValidationDocumentation } from '@/composables/unifyValidation/documentationValidationProps' import { fn } from '@storybook/test' import type { SyTextFieldProps } from './types' const meta = { title: 'Composants/Formulaires/SyTextField', component: SyTextField, decorators: [ () => ({ template: '
', }), ], parameters: { layout: 'fullscreen', docs: { description: { component: `SyTextField`, }, }, controls: { exclude: /^on*/, }, }, argTypes: { ...getValidationDocumentation('string'), 'modelValue': { control: 'text' }, 'label': { description: 'Texte affiché comme label du champ', control: 'text', table: { type: { summary: 'string' }, }, }, 'autocomplete': { description: 'Valeur de l\'attribut autocomplete', control: 'text', table: { type: { summary: 'on|off' }, }, }, 'prependIcon': { control: 'select', options: ['info', 'success', 'warning', 'error', 'close'], table: { type: { summary: 'string' }, }, }, 'appendIcon': { control: 'select', options: ['info', 'success', 'warning', 'error', 'close'], table: { type: { summary: 'string' }, }, }, 'prependInnerIcon': { control: 'select', options: ['info', 'success', 'warning', 'error', 'close'], table: { type: { summary: 'string' }, }, }, 'appendInnerIcon': { control: 'select', options: ['info', 'success', 'warning', 'error', 'close'], table: { type: { summary: 'string' }, }, }, 'variantStyle': { control: 'select', options: ['outlined', 'plain', 'underlined', 'filled', 'solo', 'solo-inverted', 'solo-filled'], table: { type: { summary: 'string' }, }, }, 'color': { control: 'select', options: ['primary', 'secondary', 'success', 'error', 'warning'], description: 'Couleur du champ', table: { type: { summary: 'string' }, }, }, 'density': { control: 'select', options: ['default', 'comfortable', 'compact'], description: 'Densité du champ', table: { type: { summary: 'string' }, }, }, 'isActive': { description: 'Force l\'état actif du champ (label flottant et styles visuels)', control: 'boolean', default: false, table: { type: { summary: 'boolean' }, }, }, 'isClearable': { description: 'Affiche un bouton pour effacer le contenu du champ', control: 'boolean', default: false, table: { type: { summary: 'boolean' }, }, }, 'prependTooltip': { description: 'Si le texte du prepend tooltip est renseigné alors l\'icône du tooltip s\'affiche', control: 'text', table: { type: { summary: 'string' }, }, }, 'appendTooltip': { description: 'Si le texte du append tooltip est renseigné alors l\'icône du tooltip s\'affiche', control: 'text', table: { type: { summary: 'string' }, }, }, 'tooltipLocation': { description: 'Position des tooltips', control: 'select', options: ['top', 'bottom', 'start', 'end'], default: 'top', table: { type: { summary: 'string' }, }, }, 'displayAsterisk': { description: 'Affiche un astérisque à côté du label', control: 'boolean', default: false, table: { type: { summary: 'boolean' }, }, }, 'disableClickButton': { description: 'Désactive le click sur les icônes append et prepend', control: 'boolean', default: true, table: { type: { summary: 'boolean' }, }, }, 'baseColor': { description: 'Couleur de base du champ (par défaut hérite de color)', control: 'text', table: { type: { summary: 'string' }, }, }, 'bgColor': { description: 'Couleur de fond du champ', control: 'color', table: { type: { summary: 'string' }, }, }, 'centerAffix': { description: 'Centre verticalement les éléments ajoutés avant/après le champ', control: 'boolean', table: { type: { summary: 'boolean' }, }, }, 'counter': { description: 'Affiche un compteur de caractères', control: 'boolean', table: { type: { summary: 'boolean' }, }, }, 'counterValue': { description: 'Fonction personnalisée pour calculer la valeur du compteur', control: 'object', table: { type: { summary: '(value: unknown) => number' }, }, }, 'direction': { description: 'Direction du champ (horizontal ou vertical)', control: 'select', options: ['horizontal', 'vertical'], table: { type: { summary: 'horizontal | vertical' }, }, }, 'isDirty': { description: 'Indique si le champ a été modifié', control: 'boolean', table: { type: { summary: 'boolean' }, }, }, 'isFlat': { description: 'Supprime l\'élévation du champ', control: 'boolean', table: { type: { summary: 'boolean' }, }, }, 'isFocused': { description: 'Force l\'état focus du champ', control: 'boolean', table: { type: { summary: 'boolean' }, }, }, 'hideDetails': { description: 'Masque la section des détails (messages d\'erreur, compteur)', control: 'boolean', table: { type: { summary: 'boolean' }, }, }, 'areSpinButtonsHidden': { description: 'Masque les boutons d\'incrémentation pour les champs numériques', control: 'boolean', table: { type: { summary: 'boolean' }, }, }, 'hint': { description: 'Texte d\'aide affiché sous le champ', control: 'text', table: { type: { summary: 'string' }, }, }, 'helpText': { description: 'Texte d\'aide affiché sous le champ', control: 'text', table: { type: { summary: 'string' }, }, }, 'maxlength': { description: 'Nombre maximal de caractères autorisés dans le champ', control: { type: 'text' }, table: { type: { summary: 'number' }, }, }, 'loading': { description: 'Affiche un indicateur de chargement', control: 'boolean', table: { type: { summary: 'boolean' }, }, }, 'maxWidth': { description: 'Largeur maximale du champ', control: { type: 'text' }, table: { type: { summary: 'string' }, }, }, 'minWidth': { description: 'Largeur minimale du champ', control: { type: 'text' }, table: { type: { summary: 'string' }, }, }, 'name': { description: 'Nom du champ pour les formulaires', control: 'text', table: { type: { summary: 'string' }, }, }, 'displayPersistentClear': { description: 'Affiche toujours le bouton de réinitialisation', control: 'boolean', default: false, table: { type: { summary: 'boolean' }, }, }, 'displayPersistentCounter': { description: 'Affiche toujours le compteur', control: 'boolean', default: false, table: { type: { summary: 'boolean' }, }, }, 'displayPersistentHint': { description: 'Affiche toujours le texte d\'aide', control: 'boolean', default: false, table: { type: { summary: 'boolean' }, }, }, 'displayPersistentPlaceholder': { description: 'Garde le placeholder visible. Si le champ est vide, le placeholder reste affiché', control: 'boolean', default: false, table: { type: { summary: 'boolean' }, }, }, 'placeholder': { description: 'Texte affiché quand le champ est vide', control: 'text', default: 'Placeholder', table: { type: { summary: 'string' }, }, }, 'prefix': { description: 'Texte affiché avant la valeur: prefix="€" : affichera "€" avant la valeur saisie', control: 'text', table: { type: { summary: 'string' }, }, }, 'isReversed': { description: 'Inverse l\'ordre des éléments', control: 'boolean', default: false, table: { type: { summary: 'boolean' }, }, }, 'role': { description: 'Rôle ARIA du champ', control: 'text', table: { type: { summary: 'string' }, }, }, 'rounded': { description: 'Arrondit les coins du champ', control: { type: 'text' }, table: { type: { summary: 'string' }, }, }, 'isOnSingleLine': { description: 'Force l\'affichage sur une seule ligne', control: 'boolean', default: false, table: { type: { summary: 'boolean' }, }, }, 'suffix': { description: 'Texte affiché après la valeur', control: 'text', table: { type: { summary: 'string' }, }, }, 'theme': { description: 'Thème à appliquer au champ', control: 'text', table: { type: { summary: 'string' }, }, }, 'isTiled': { description: 'Applique un style tuile', control: 'boolean', default: false, table: { type: { summary: 'boolean' }, }, }, 'type': { description: 'Type du champ de saisie', control: 'select', options: ['text', 'number', 'password', 'email', 'tel', 'url', 'search'], default: 'text', table: { type: { summary: 'string', detail: 'text | number | password | email | tel | url | search', }, }, }, 'width': { description: 'Largeur du champ', control: { type: 'text' }, table: { type: { summary: 'string' }, }, }, 'validateOnSubmit': { description: 'Valide le champ avec la valeur donnée', table: { type: { summary: '(value: string | number | null) => Promise' }, }, }, 'append': { description: 'Slot pour ajouter du contenu à droite du champ', control: false, table: { type: { summary: 'VNode' }, category: 'slots', }, }, 'prepend': { description: 'Slot pour ajouter du contenu à gauche du champ', control: false, table: { type: { summary: 'VNode' }, category: 'slots', }, }, 'append-inner': { description: 'Slot pour ajouter du contenu à droite dans le champ', control: false, table: { type: { summary: 'VNode' }, category: 'slots', }, }, 'prepend-inner': { description: 'Slot pour ajouter du contenu à gauche dans le champ', control: false, table: { type: { summary: 'VNode' }, category: 'slots', }, }, 'details': { description: 'Slot pour personnaliser la section des détails (messages d\'erreur, compteur)', control: false, table: { type: { summary: 'VNode' }, category: 'slots', }, }, 'showDivider': { description: 'Affiche une ligne de séparation entre le champ et les icônes prepend-inner et append-inner', control: 'boolean', table: { type: { summary: 'boolean' }, defaultValue: { summary: 'false' }, category: 'props', }, }, }, args: { 'onUpdate:modelValue': fn(), 'onKeydown': fn(), 'onClear': fn(), 'onPrependIconClick': fn(), 'onAppendIconClick': fn(), 'onFocus': fn(), 'onBlur': fn(), }, } as Meta export default meta type Story = StoryObj export const Default: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { showDivider: false, variantStyle: 'outlined', color: 'primary', isClearable: true, label: 'Label', modelValue: '', }, render: (args) => { return { components: { SyTextField }, setup() { const value = ref(args.modelValue) watch(() => args.modelValue, (newValue) => { value.value = newValue }) return { args, value } }, template: `
`, } }, } export const HelpText: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { showDivider: false, variantStyle: 'outlined', color: 'primary', isClearable: true, label: 'Label', modelValue: '', helpText: 'Texte d\'aide à la saisie', }, render: (args) => { return { components: { SyTextField, VIcon }, setup() { const value = ref(args.modelValue) watch(() => args.modelValue, (newValue) => { value.value = newValue }) return { args, value } }, template: `
`, } }, } export const Required: Story = { args: { ...Default.args, required: true, }, render: (args) => { return { components: { SyTextField, VIcon }, setup() { const value = ref(args.modelValue) watch(() => args.modelValue, (newValue) => { value.value = newValue }) return { args, value } }, template: `

Ce champ est obligatoire

`, } }, parameters: { docs: { description: { story: ` ### Champ requis sans astérisque Cette story montre un champ requis sans astérisque. Pour afficher l'astérisque sur un champ requis, il faut activer la prop \`displayAsterisk\`.`, }, }, sourceCode: [ { name: 'Template', code: ``, }, { name: 'Script', code: ``, }, ], }, } export const RequiredWithAsterisk: Story = { args: { ...Default.args, required: true, displayAsterisk: true, }, render: (args) => { return { components: { SyTextField, VIcon }, setup() { const value = ref(args.modelValue) watch(() => args.modelValue, (newValue) => { value.value = newValue }) return { args, value } }, template: `
`, } }, parameters: { docs: { description: { story: ` ### Champ requis avec astérisque Cette story montre un champ requis avec astérisque. L'astérisque ne peut être affiché que sur un champ requis, en activant la prop \`displayAsterisk\`.`, }, }, sourceCode: [ { name: 'Template', code: ``, }, { name: 'Script', code: ``, }, ], }, } export const SlotPrepend: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { variantStyle: 'outlined', isClearable: true, showDivider: false, label: 'Label', color: 'primary', prependIcon: 'info', modelValue: '', }, render: (args) => { return { components: { SyTextField, VIcon }, setup() { const value = ref(args.modelValue) watch(() => args.modelValue, (newValue) => { value.value = newValue }) return { args, value } }, template: `
`, } }, } export const SlotAppend: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { variantStyle: 'outlined', isClearable: true, showDivider: false, label: 'champs de text', color: 'primary', appendIcon: 'success', modelValue: '', }, render: (args) => { return { components: { SyTextField, VIcon }, setup() { const value = ref(args.modelValue) watch(() => args.modelValue, (newValue) => { value.value = newValue }) return { args, value } }, template: `
`, } }, } export const SlotPrependInner: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { variantStyle: 'outlined', isClearable: true, showDivider: false, label: 'Label', color: 'primary', prependInnerIcon: 'info', modelValue: '', }, render: (args) => { return { components: { SyTextField, VIcon }, setup() { const value = ref(args.modelValue) watch(() => args.modelValue, (newValue) => { value.value = newValue }) return { args, value } }, template: `
`, } }, } export const SlotPrependInnerDivider: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { variantStyle: 'outlined', isClearable: true, showDivider: true, label: 'Label', color: 'primary', prependInnerIcon: 'info', modelValue: '', }, render: (args) => { return { components: { SyTextField, VIcon }, setup() { const value = ref(args.modelValue) watch(() => args.modelValue, (newValue) => { value.value = newValue }) return { args, value } }, template: `
`, } }, } export const SlotAppendInner: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { variantStyle: 'outlined', isClearable: true, showDivider: false, label: 'Label', color: 'primary', appendInnerIcon: 'success', modelValue: '', }, render: (args) => { return { components: { SyTextField, VIcon }, setup() { const value = ref(args.modelValue) watch(() => args.modelValue, (newValue) => { value.value = newValue }) return { args, value } }, template: `
`, } }, } export const SlotCustomIcon: Story = { parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { variantStyle: 'outlined', isClearable: true, showDivider: false, label: 'Label', color: 'primary', modelValue: '', }, render: (args) => { return { components: { SyTextField, VIcon }, setup() { const value = ref(args.modelValue) watch(() => args.modelValue, (newValue) => { value.value = newValue }) const iconName = ref(mdiAccountBox) return { args, value, iconName } }, template: `
`, } }, } export const PatternValidation: Story = { parameters: { docs: { description: { story: ` ### Validation par expression régulière Cette story montre l'utilisation de la règle \`matchPattern\` pour valider un format spécifique. Ici, un code postal français : - Doit contenir exactement 5 chiffres - Utilise une expression régulière pour la validation - Affiche des messages personnalisés `, }, }, sourceCode: [ { name: 'Template', code: ``, }, ], }, render: args => ({ components: { SyTextField }, setup() { const value = ref('') return { args, value } }, template: ` `, }), } // Persistent value for WithTooltips const withTooltipsValueMain = ref('') export const WithTooltips: Story = { args: { label: 'Champ avec tooltips', prependTooltip: 'Information à gauche du champ', appendTooltip: 'Information à droite du champ', tooltipLocation: 'top', isClearable: true, disableClickButton: true, }, render: args => ({ components: { SyTextField }, setup() { return { args, value: withTooltipsValueMain } }, template: `

Des icônes d'information avec tooltips sont affichées de chaque côté du champ. Survolez-les pour voir les messages d'aide qui apparaissent en haut grâce à la prop tooltipLocation="top".

`, }), parameters: { docs: { description: { story: 'Exemple de champ avec des tooltips d\'information. Les icônes d\'information apparaissent automatiquement lorsque les props prependTooltip et/ou appendTooltip sont renseignées. La position des tooltips peut être contrôlée avec la prop tooltipLocation.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, ], }, } export const WithPrefixAndSuffix: Story = { args: { modelValue: '42', label: 'Montant', prefix: '€', suffix: 'TTC', }, render: args => ({ components: { SyTextField }, setup() { const value = ref(args.modelValue) return { args, value } }, template: `

Utilisation des props prefix et suffix pour ajouter des unités ou des informations complémentaires directement dans le champ.

`, }), parameters: { docs: { description: { story: 'Exemple d\'utilisation des props prefix et suffix pour ajouter des informations complémentaires directement dans le champ de saisie.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, ], }, }