import type { Meta, StoryObj } from '@storybook/vue3'
import { ref, watch, onMounted } from 'vue'
import { VBtn, VForm } from 'vuetify/components'
import SyTextField from '@/components/Customs/SyTextField/SyTextField.vue'
import { fn } from '@storybook/test'
import SyForm from '@/components/Customs/SyForm/SyForm.vue'
import { getValidationDocumentation } from '@/composables/unifyValidation/documentationValidationProps'
import type { FieldValidationProps } from '@/composables/unifyValidation/useValidation'
const meta = {
title: 'Composants/Formulaires/SyTextField/Validation',
component: SyTextField,
decorators: [
() => ({
template: '
',
}),
],
parameters: {
layout: 'fullscreen',
docs: {
description: {
component: `Exemples de validation pour le composant SyTextField`,
},
},
},
argTypes: {
...getValidationDocumentation('string'),
modelValue: {
control: 'text',
description: 'Valeur du champ texte',
},
label: {
control: 'text',
description: 'Libellé du champ',
},
},
args: {
'modelValue': '',
'label': 'Nom',
'required': false,
'errorMessages': null,
'warningMessages': null,
'successMessages': null,
'readonly': false,
'disabled': false,
'customRules': [],
'customWarningRules': [],
'customSuccessRules': [],
'isValidateOnBlur': true,
'onUpdate:modelValue': fn(),
},
} as Meta
export default meta
type Story = StoryObj
export const WithError: Story = {
parameters: {
a11y: {
disable: true,
},
sourceCode: [
{
name: 'Template',
code: `
`,
},
{
name: 'Script',
code: `
`,
},
],
},
args: {
label: 'Adresse email',
modelValue: 'not-an-email',
customRules: [
{
type: 'custom',
options: {
validate: (value: string) => {
const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
if (!valid) {
return 'L\'adresse email est invalide.'
}
return true
},
fieldIdentifier: 'email',
},
},
],
},
render: args => ({
components: { SyTextField },
setup() {
const value = ref(args.modelValue)
const fieldRef = ref<{ validateOnSubmit: () => Promise } | null>(null)
watch(() => args.modelValue, (newValue) => {
value.value = newValue
})
onMounted(() => {
fieldRef.value?.validateOnSubmit()
})
return { args, value, fieldRef }
},
template: `
`,
}),
}
export const WithWarning: Story = {
parameters: {
a11y: {
disable: true,
},
sourceCode: [
{
name: 'Template',
code: `
`,
},
{
name: 'Script',
code: `
`,
},
],
},
args: {
label: 'Nom d\'utilisateur',
modelValue: 'ab',
customWarningRules: [
{
type: 'custom',
options: {
validate: (value: string) => value.length >= 3,
warningMessage: 'Le nom d\'utilisateur est très court (moins de 3 caractères).',
fieldIdentifier: 'username',
},
},
],
},
render: args => ({
components: { SyTextField },
setup() {
const value = ref(args.modelValue)
const fieldRef = ref<{ validateOnSubmit: () => Promise } | null>(null)
watch(() => args.modelValue, (newValue) => {
value.value = newValue
})
onMounted(() => {
fieldRef.value?.validateOnSubmit()
})
return { args, value, fieldRef }
},
template: `
`,
}),
}
export const WithSuccess: Story = {
parameters: {
a11y: {
disable: true,
},
sourceCode: [
{
name: 'Template',
code: `
`,
},
{
name: 'Script',
code: `
`,
},
],
},
args: {
label: 'Adresse email',
modelValue: 'exemple@domaine.fr',
showSuccessMessages: true,
customSuccessRules: [
{
type: 'custom',
options: {
validate: (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
successMessage: 'L\'adresse email est valide.',
fieldIdentifier: 'email',
},
},
],
},
render: args => ({
components: { SyTextField },
setup() {
const value = ref(args.modelValue)
const fieldRef = ref<{ validateOnSubmit: () => Promise } | null>(null)
watch(() => args.modelValue, (newValue) => {
value.value = newValue
})
onMounted(() => {
fieldRef.value?.validateOnSubmit()
})
return { args, value, fieldRef }
},
template: `
`,
}),
}
export const WithCustomRules: Story = {
parameters: {
a11y: {
disable: true,
},
sourceCode: [
{
name: 'Template',
code: `
`,
},
{
name: 'Script',
code: `
`,
},
],
},
render: args => ({
components: { SyTextField },
setup() {
const value = ref(args.modelValue)
watch(() => args.modelValue, (newValue) => {
value.value = newValue
})
const customRules = [
{
type: 'custom',
options: {
validate: (value: string) => {
if (value.length < 8) return false
return true
},
message: 'Le mot de passe doit contenir au moins 8 caractères.',
fieldIdentifier: 'password',
},
},
{
type: 'custom',
options: {
validate: (value: string) => {
if (!/[A-Z]/.test(value)) return false
return true
},
message: 'Le mot de passe doit contenir au moins une majuscule.',
fieldIdentifier: 'password',
},
},
]
return { args, value, customRules }
},
template: `
`,
}),
}
export const DisableErrorHandling: Story = {
parameters: {
a11y: {
disable: true,
},
docs: {
description: {
story: `
### Désactivation de la gestion des erreurs
Cette story illustre l'utilisation de la propriété \`disableErrorHandling\` qui permet de désactiver complètement
la gestion et l'affichage des erreurs dans un champ, même si des règles de validation sont définies.
`,
},
},
sourceCode: [
{
name: 'Template',
code: `
`,
},
],
},
render: () => ({
components: { SyTextField },
setup() {
const value1 = ref('')
const value2 = ref('')
const customRules = [
{
type: 'custom',
options: {
validate: (value: string) => {
if (!value || value.trim().length === 0) {
return false
}
return true
},
message: 'Ce champ est requis.',
fieldIdentifier: 'field',
},
},
]
return { value1, value2, customRules }
},
template: `
Cette démonstration compare un SyTextField standard et un avec disableErrorHandling=true.
Instructions :
- Cliquez dans un champ puis en dehors pour déclencher la validation
- Le champ de gauche affichera une erreur requise, mais pas celui de droite
`,
}),
}
/**
* Validation déclenchée à chaque frappe (isValidateOnBlur: false).
*/
export const ValidateOnInput: Story = {
parameters: {
docs: {
description: {
story: `
### Validation à la saisie
Lorsque \`isValidateOnBlur\` vaut \`false\`, la validation se déclenche à chaque modification
de la valeur plutôt qu'à la perte de focus. Utile pour un retour immédiat à l'utilisateur.
`,
},
},
sourceCode: [
{
name: 'Template',
code: `
`,
},
{
name: 'Script',
code: ``,
},
],
},
render: args => ({
components: { SyTextField },
setup() {
const value = ref(args.modelValue)
watch(() => args.modelValue, (newValue) => {
value.value = newValue
})
return { args, value }
},
template: `
La validation se déclenche à chaque frappe (isValidateOnBlur="false").
`,
}),
}
/**
* Messages de validation injectés directement par le parent (errorMessages, warningMessages, successMessages).
*/
export const ExternalMessages: Story = {
parameters: {
docs: {
description: {
story: `
### Messages externes
Les props \`errorMessages\`, \`warningMessages\` et \`successMessages\` permettent d'injecter
des messages depuis le parent sans déclencher de règle de validation.
`,
},
},
sourceCode: [
{
name: 'Template',
code: `
Simuler une erreur
Simuler un avertissement
Simuler un succès
Réinitialiser
`,
},
{
name: 'Script',
code: ``,
},
],
},
args: {
showSuccessMessages: true,
},
render: args => ({
components: { SyTextField, VBtn },
setup() {
const value = ref(args.modelValue)
watch(() => args.modelValue, (newValue) => {
value.value = newValue
})
const errorMessages = ref(null)
const warningMessages = ref(null)
const successMessages = ref(null)
function setError() {
errorMessages.value = ['Ce nom est déjà utilisé']
warningMessages.value = null
successMessages.value = null
}
function setWarning() {
errorMessages.value = null
warningMessages.value = ['Ce nom ressemble à un nom générique']
successMessages.value = null
}
function setSuccess() {
errorMessages.value = null
warningMessages.value = null
successMessages.value = ['Nom accepté par le serveur']
}
function reset() {
errorMessages.value = null
warningMessages.value = null
successMessages.value = null
}
return { args, value, errorMessages, warningMessages, successMessages, setError, setWarning, setSuccess, reset }
},
template: `
Les messages ci-dessous sont injectés par le parent sans déclencher de règle de validation.
Simuler une erreur
Simuler un avertissement
Simuler un succès
Réinitialiser
`,
}),
}
export const VFormVuetifyValidation: Story = {
parameters: {
docs: {
description: {
story: `
### Validation de style Vuetify
En passant \`useVuetifyValidation="true"\`, le composant délègue la validation à Vuetify.
Les règles sont de simples fonctions qui retournent \`true\` si la valeur est valide,
ou un message d'erreur (chaîne de caractères) sinon — exactement comme avec la prop \`rules\`
native de Vuetify.
`,
},
},
sourceCode: [
{
name: 'Template',
code: `
Valider
`,
},
{
name: 'Script',
code: ``,
},
],
},
render: args => ({
components: { SyTextField, VBtn, VForm },
setup() {
const value = ref(args.modelValue)
watch(() => args.modelValue, (newValue) => {
value.value = newValue
})
const rules = [
(value: string) => !!value || 'Ce champ est requis',
(value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || 'L\'adresse email est invalide',
]
async function handleSubmit(e: Promise<{ valid: boolean }>) {
const result = await e
alert(result.valid ? 'Valeur valide !' : 'Veuillez corriger les erreurs.')
}
return { args, value, rules, handleSubmit }
},
template: `
Les règles sont des fonctions Vuetify natives (value) => true | 'message'.
Cliquez sur Valider ou quittez le champ pour déclencher la validation.
Valider
`,
}),
}
export const SyFormValidation: Story = {
parameters: {
sourceCode: [
{
name: 'Template',
code: `
Valider
`,
},
{
name: 'Script',
code: ``,
},
],
},
render: args => ({
components: { SyTextField, VBtn, SyForm },
setup() {
const value = ref(args.modelValue)
watch(() => args.modelValue, (newValue) => {
value.value = newValue
})
const customRules = [
{
type: 'custom',
options: {
validate: (value: string) => {
if (!value || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return false
}
return true
},
message: 'L\'adresse email est invalide.',
fieldIdentifier: 'email',
},
},
]
function handleSubmit(e: { isValid: boolean }) {
const isValid = e.isValid
alert(isValid ? 'Valeur valide !' : 'Veuillez corriger les erreurs.')
}
return { args, value, customRules, handleSubmit }
},
template: `
`,
}),
}
export const SyFormVuetifyValidation: Story = {
parameters: {
sourceCode: [
{
name: 'Template',
code: `
Valider
`,
},
{
name: 'Script',
code: ``,
},
],
},
render: args => ({
components: { SyTextField, VBtn, SyForm },
setup() {
const value = ref(args.modelValue)
watch(() => args.modelValue, (newValue) => {
value.value = newValue
})
const vuetifyRules = [
(value: string) => !!value || 'Ce champ est requis',
(value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || 'L\'adresse email est invalide',
]
function handleSubmit(e: { isValid: boolean }) {
const isValid = e.isValid
alert(isValid ? 'Valeur valide !' : 'Veuillez corriger les erreurs.')
}
return { args, value, vuetifyRules, handleSubmit }
},
template: `
`,
}),
}
export const VFormValidation: Story = {
parameters: {
sourceCode: [
{
name: 'Template',
code: `
Valider
`,
},
{
name: 'Script',
code: ``,
},
],
},
render: args => ({
components: { SyTextField, VBtn, VForm },
setup() {
const value = ref(args.modelValue)
watch(() => args.modelValue, (newValue) => {
value.value = newValue
})
const textFieldRef = ref()
async function handleSubmit() {
if (textFieldRef.value) {
const result = await textFieldRef.value.validateOnSubmit()
alert(result ? 'Valeur valide !' : 'Veuillez corriger les erreurs.')
}
}
return { args, value, textFieldRef, handleSubmit }
},
template: `
Il faut privilégier l'utilisation de SyForm pour bénéficier de intégration.
Valider
`,
}),
}
export const EmailValidation: Story = {
parameters: {
docs: {
description: {
story: `
### Validation d'email
Cette story montre un cas d'usage courant : la validation d'une adresse email. Le champ :
- Est requis
- Vérifie le format de l'email
- Affiche un message de succès quand l'email est valide
`,
},
},
sourceCode: [
{
name: 'Template',
code: ``,
},
],
},
render: args => ({
components: { SyTextField },
setup() {
const value = ref(args.modelValue)
watch(() => args.modelValue, (newValue) => {
value.value = newValue
})
return { args, value }
},
template: `
`,
}),
}