import type { Meta, StoryObj } from '@storybook/vue3' import SyTextArea from '../SyTextArea.vue' import SyForm from '../../Customs/SyForm/SyForm.vue' import { ref, onMounted, nextTick } from 'vue' import { fn } from '@storybook/test' import { VBtn, VForm } from 'vuetify/components' const meta: Meta = { title: 'Composants/Formulaires/SyTextArea/Validation', component: SyTextArea, parameters: { layout: 'fullscreen', }, args: { 'onUpdate:modelValue': fn(), }, } as Meta export default meta type Story = StoryObj export const WithError: Story = { parameters: { docs: { description: { story: 'Un message prédéfini est présélectionné et déclenche une erreur bloquante au chargement.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { 'label': 'Description', 'onUpdate:modelValue': fn(), }, render: args => ({ components: { SyTextArea }, setup() { const value = ref('Ce texte est trop long et provoque une erreur.') const textAreaRef = ref<{ validateOnSubmit: () => Promise } | null>(null) onMounted(() => { textAreaRef.value?.validateOnSubmit() }) return { args, value, textAreaRef } }, template: `
`, }), } export const WithWarning: Story = { parameters: { docs: { description: { story: 'Les règles d\'alerte (`customWarningRules`) sont non bloquantes : le formulaire peut être soumis même si elles échouent.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, ], }, args: { 'label': 'Observations', 'onUpdate:modelValue': fn(), }, render: args => ({ components: { SyTextArea }, setup() { const value = ref('Court.') const textAreaRef = ref<{ validateOnSubmit: () => Promise } | null>(null) onMounted(() => { textAreaRef.value?.validateOnSubmit() }) return { args, value, textAreaRef } }, template: `
`, }), } export const WithSuccess: Story = { parameters: { docs: { description: { story: 'Les règles de succès (`customSuccessRules`) affichent un message positif quand la valeur est valide.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, ], }, args: { 'label': 'Commentaire', 'showSuccessMessages': true, 'onUpdate:modelValue': fn(), }, render: args => ({ components: { SyTextArea }, setup() { const value = ref('Voici une description bien détaillée du problème rencontré.') const textAreaRef = ref<{ validateOnSubmit: () => Promise } | null>(null) onMounted(() => { textAreaRef.value?.validateOnSubmit() }) return { args, value, textAreaRef } }, template: `
`, }), } export const NoSuccessMessage: Story = { parameters: { docs: { description: { story: 'Avec `showSuccessMessages: false`, l\'état visuel de succès reste actif (bordure verte, icône) mais le message texte n\'est pas affiché. Utile quand un retour positif silencieux est suffisant.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { 'label': 'Commentaire', 'showSuccessMessages': false, 'onUpdate:modelValue': fn(), }, render: args => ({ components: { SyTextArea }, setup() { const value = ref('Voici une description bien détaillée du problème rencontré.') const textAreaRef = ref<{ validateOnSubmit: () => Promise } | null>(null) onMounted(() => { textAreaRef.value?.validateOnSubmit() }) return { args, value, textAreaRef } }, template: `
`, }), } export const NoValidateOnBlur: Story = { parameters: { docs: { description: { story: 'Avec `isValidateOnBlur: false`, la validation se déclenche **immédiatement** dès que la valeur change. Dans cette story, l\'état de validation est volontairement conservé uniquement pour les changements déclenchés par les boutons. Une saisie directe dans le textarea met donc à jour la valeur, puis réinitialise aussitôt les messages et l\'état visuel.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { 'label': 'Description', 'isValidateOnBlur': false, 'onUpdate:modelValue': fn(), }, render: args => ({ components: { SyTextArea, VBtn }, setup() { const value = ref('') const textAreaRef = ref<{ clearValidation: () => void } | null>(null) const isButtonDrivenChange = ref(false) const handleManualChange = async (newValue: string) => { value.value = newValue if (isButtonDrivenChange.value) { isButtonDrivenChange.value = false return } await nextTick() textAreaRef.value?.clearValidation() } const applyButtonValue = (newValue: string) => { isButtonDrivenChange.value = true value.value = newValue } return { args, value, textAreaRef, handleManualChange, applyButtonValue } }, template: `
Définir une valeur invalide Définir une valeur valide Réinitialiser
`, }), } export const DisableErrorHandling: Story = { parameters: { docs: { description: { story: 'Avec `disableErrorHandling: true`, les messages d\'erreur ne sont pas affichés même si des règles échouent. L\'état visuel reste inchangé.', }, }, }, render: () => ({ components: { SyTextArea }, setup() { const valueWithHandling = ref('') const valueWithout = ref('') const customRules = [ { type: 'required', options: {}, }, ] const textAreaRef1 = ref<{ validateOnSubmit: () => Promise } | null>(null) const textAreaRef2 = ref<{ validateOnSubmit: () => Promise } | null>(null) onMounted(async () => { await nextTick() textAreaRef1.value?.validateOnSubmit() textAreaRef2.value?.validateOnSubmit() }) return { valueWithHandling, valueWithout, customRules, textAreaRef1, textAreaRef2 } }, template: `

Avec gestion des erreurs (défaut)

Sans gestion des erreurs (disableErrorHandling)

`, }), } export const SyFormValidation: Story = { parameters: { docs: { description: { story: 'Le composant s\'enregistre automatiquement auprès de `SyForm` via `useValidatable`. Le champ reste neutre au chargement, puis la soumission déclenche la validation.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { 'label': 'Description obligatoire', 'required': true, 'displayAsterisk': true, 'onUpdate:modelValue': fn(), }, render: args => ({ components: { SyTextArea, SyForm, VBtn }, setup() { const value = ref('') function onSubmit(event: { isValid: boolean }) { if (event.isValid) { alert(`Formulaire valide : ${value.value}`) } else { alert('Formulaire invalide.') } } return { args, value, onSubmit } }, template: `
Soumettre
`, }), } export const VFormValidation: Story = { parameters: { docs: { description: { story: 'Intégration avec `VForm` natif Vuetify en conservant la validation Synapse. Le formulaire est rendu avec `novalidate` pour désactiver la validation HTML native du `textarea`. Le champ reste neutre au chargement, puis la soumission appelle `validateOnSubmit()` manuellement.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { 'label': 'Description obligatoire', 'required': true, 'displayAsterisk': true, 'onUpdate:modelValue': fn(), }, render: args => ({ components: { SyTextArea, VBtn, VForm }, setup() { const value = ref('') const textAreaRef = ref<{ validateOnSubmit: () => Promise } | null>(null) async function onSubmit() { const isValid = await textAreaRef.value?.validateOnSubmit() if (isValid) { alert(`Formulaire valide : ${value.value}`) } else { alert('Formulaire invalide.') } } return { args, value, textAreaRef, onSubmit } }, template: `
Soumettre
`, }), } export const SyFormVuetifyValidation: Story = { parameters: { docs: { description: { story: 'Validation native Vuetify (`useVuetifyValidation`) intégrée dans `SyForm`. Les règles sont définies au format Vuetify : des fonctions retournant `true` ou un message d\'erreur.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { 'label': 'Description', 'useVuetifyValidation': true, 'validateOn': 'submit', 'showSuccessMessages': false, 'onUpdate:modelValue': fn(), }, render: args => ({ components: { SyTextArea, SyForm, VBtn }, setup() { const value = ref('') const rules = [ (v: string) => !!v || 'Ce champ est requis', ] function onSubmit(event: { isValid: boolean }) { if (event.isValid) { alert(`Formulaire valide : ${value.value}`) } else { alert('Formulaire invalide.') } } return { args, value, rules, onSubmit } }, template: `
Soumettre
`, }), } export const VFormVuetifyValidation: Story = { parameters: { docs: { description: { story: 'Validation native Vuetify (`useVuetifyValidation`) intégrée dans `VForm`. Les règles sont définies au format Vuetify : des fonctions retournant `true` ou un message d\'erreur.', }, }, sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, args: { 'label': 'Description', 'useVuetifyValidation': true, 'validateOn': 'submit', 'showSuccessMessages': false, 'onUpdate:modelValue': fn(), }, render: args => ({ components: { SyTextArea, VBtn, VForm }, setup() { const value = ref('') const form = ref | null>(null) const rules = [ (v: string) => !!v || 'Ce champ est requis', ] async function onSubmit() { const { valid } = await form.value?.validate() || { valid: false } if (valid) { alert(`Formulaire valide : ${value.value}`) } else { alert('Formulaire invalide.') } } return { args, value, form, rules, onSubmit } }, template: `
Soumettre
`, }), }