import type { Meta, StoryObj } from '@storybook/vue3'
import { ref } from 'vue'
import { VBtn, VForm } from 'vuetify/components'
import MonthPicker from '../MonthPicker.vue'
import { fn, userEvent, within } from '@storybook/test'
import SyForm from '@/components/Customs/SyForm/SyForm.vue'
import type { MonthPickerProps } from '../types'
import { default as MonthPickerMeta } from '../MonthPicker.stories'
const meta = {
title: 'Composants/Formulaires/MonthPicker/Validation',
component: MonthPicker,
decorators: [
() => ({
template: '
',
}),
],
parameters: {
layout: 'fullscreen',
docs: {
description: {
component: `Exemples de validation pour le composant MonthPicker`,
},
controls: {
exclude: ['onUpdate:modelValue', 'onUpdate:open'],
},
},
controls: {
exclude: ['width', 'undefined', 'onUpdate:modelValue', 'onUpdate:open'],
},
},
argTypes: MonthPickerMeta.argTypes,
args: {
'modelValue': '',
'label': 'Mois de début',
'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: {
modelValue: '13/2025',
customRules: [
{
type: 'custom',
options: {
validate: (value: string) => {
return /^(0[1-9]|1[0-2])\/\d{4}$/.test(value)
},
message: 'Le format doit être MM/YYYY avec un mois entre 01 et 12 (ex: 03/2026).',
fieldIdentifier: 'selectedMonth',
},
},
],
},
play: async ({ canvasElement }) => {
const input = within(canvasElement).getByRole('textbox')
await userEvent.clear(input)
await userEvent.type(input, '13/2025')
input.blur()
},
}
export const WithWarning: Story = {
parameters: {
a11y: {
disable: true,
},
sourceCode: [
{
name: 'Template',
code: `
`,
},
{
name: 'Script',
code: `
`,
},
],
},
args: {
modelValue: '01/2035',
customWarningRules: [
{
type: 'custom',
options: {
validate: (value: string) => {
if (!value || value === '') return true // Ne pas afficher de warning si le champ est vide
const [month, year] = value.split('/').map(Number) as [number, number]
const currentDate = new Date()
const currentYear = currentDate.getFullYear()
const currentMonth = currentDate.getMonth() + 1
if (year > currentYear + 5 || (year === currentYear + 5 && month > currentMonth)) {
return false
}
return true
},
warningMessage: 'La date est plus de 5 ans dans le futur.',
fieldIdentifier: 'selectedMonth',
},
},
],
},
play: async ({ canvasElement }) => {
const input = within(canvasElement).getByRole('textbox')
await userEvent.clear(input)
await userEvent.type(input, '01/2035')
input.blur()
},
}
export const WithSuccess: Story = {
parameters: {
a11y: {
disable: true,
},
sourceCode: [
{
name: 'Template',
code: `
`,
},
{
name: 'Script',
code: `
`,
},
],
},
args: {
modelValue: '06/2026',
showSuccessMessages: true,
customSuccessRules: [
{
type: 'custom',
options: {
validate: (value: string) => {
if (!value || value === '') return true // Ne pas afficher de success si le champ est vide
const [, year] = value.split('/').map(Number) as [number, number]
const currentYear = new Date().getFullYear()
return year === currentYear
},
successMessage: 'La date est dans l\'année en cours.',
fieldIdentifier: 'selectedMonth',
},
},
],
},
play: async ({ canvasElement }) => {
const input = within(canvasElement).getByRole('textbox')
await userEvent.clear(input)
const currentYear = new Date().getFullYear()
await userEvent.type(input, `06/${currentYear}`)
input.blur()
},
}
export const WithCustomRules: Story = {
parameters: {
a11y: {
disable: true,
},
sourceCode: [
{
name: 'Template',
code: `
`,
},
{
name: 'Script',
code: `
`,
},
],
},
render: args => ({
components: { MonthPicker },
setup() {
const selectedMonth = ref(args.modelValue ?? '')
const customRules = [
{
type: 'custom',
options: {
validate: (value: string) => {
if (!value || !/^\d{2}\/\d{4}$/.test(value)) {
return false
}
return true
},
message: 'Le format doit être MM/YYYY (ex: 03/2026).',
fieldIdentifier: 'selectedMonth',
},
},
{
type: 'custom',
options: {
validate: (value: string) => {
if (!value || value === '') return true // Ne pas afficher d'erreur si le champ est vide
const [month] = value.split('/').map(Number)
if (!month || month < 1 || month > 12) {
return false
}
return true
},
message: 'Le mois doit être compris entre 01 et 12.',
fieldIdentifier: 'selectedMonth',
},
},
{
type: 'custom',
options: {
validate: (value: string) => {
if (!value || value === '') return true // Ne pas afficher d'erreur si le champ est vide
const [, year] = value.split('/').map(Number) as [number, number]
const currentYear = new Date().getFullYear()
if (year < currentYear - 10) {
return false
}
return true
},
message: 'La date ne peut pas être antérieure à 10 ans.',
fieldIdentifier: 'selectedMonth',
},
},
]
return { args, selectedMonth, customRules }
},
template: `
Saisissez un mois pour voir les différents types de validation :
Conseils pour tester :
- Laissez le champ vide pour voir l'erreur de champ requis
- Saisissez un mois invalide (ex: 13/2026) pour voir l'erreur de format
- Saisissez une date à plus de 5 ans (ex: 01/2032) pour voir l'avertissement
- Saisissez une date entre aujourd'hui et dans 5 ans pour voir le message de succès
`,
}),
}
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: { MonthPicker },
setup() {
const value1 = ref('')
const value2 = ref('')
const customRules = [
{
type: 'custom',
options: {
validate: (value: string) => {
if (!value || !/^(0[1-9]|1[0-2])\/\d{4}$/.test(value)) {
return 'Le format doit être MM/YYYY avec un mois valide (ex: 03/2026).'
}
return true
},
fieldIdentifier: 'selectedMonth',
},
},
]
return { value1, value2, customRules }
},
template: `
Cette démonstration compare un MonthPicker 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: { MonthPicker },
setup() {
const selectedMonth = ref('')
return { args, selectedMonth }
},
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: ``,
},
],
},
render: args => ({
components: { MonthPicker, VBtn },
setup() {
const selectedMonth = ref('')
const errorMessages = ref(null)
const warningMessages = ref(null)
const successMessages = ref(null)
function setError() {
errorMessages.value = ['Ce mois est déjà utilisé dans un autre dossier']
warningMessages.value = null
successMessages.value = null
}
function setWarning() {
errorMessages.value = null
warningMessages.value = ['Ce mois est proche d\'une échéance importante']
successMessages.value = null
}
function setSuccess() {
errorMessages.value = null
warningMessages.value = null
successMessages.value = ['Mois accepté par le serveur']
}
function reset() {
errorMessages.value = null
warningMessages.value = null
successMessages.value = null
}
return { args, selectedMonth, 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 SyFormValidation: Story = {
parameters: {
sourceCode: [
{
name: 'Template',
code: `
Valider
`,
},
{
name: 'Script',
code: ``,
},
],
},
render: args => ({
components: { MonthPicker, VBtn, SyForm },
setup() {
const selectedMonth = ref('')
const customRules = [
{
type: 'custom',
options: {
validate: (value: string) => {
if (!value || !/^(0[1-9]|1[0-2])\/\d{4}$/.test(value)) {
return false
}
return true
},
message: 'Le format doit être MM/YYYY avec un mois valide (ex: 03/2026).',
fieldIdentifier: 'selectedMonth',
},
},
]
function handleSubmit(e: { isValid: boolean }) {
const isValid = e.isValid
alert(isValid ? 'Mois valide !' : 'Veuillez corriger les erreurs.')
}
return { args, selectedMonth, customRules, handleSubmit }
},
template: `
`,
}),
}
export const VFormValidation: Story = {
parameters: {
sourceCode: [
{
name: 'Template',
code: `
Valider
`,
},
{
name: 'Script',
code: ``,
},
],
},
render: args => ({
components: { MonthPicker, VBtn, VForm },
setup() {
const selectedMonth = ref('')
const fieldRef = ref | null>(null)
const customRules = [
{
type: 'custom',
options: {
validate: (value: string) => {
if (!value || !/^(0[1-9]|1[0-2])\/\d{4}$/.test(value)) {
return false
}
return true
},
message: 'Le format doit être MM/YYYY avec un mois valide (ex: 03/2026).',
fieldIdentifier: 'selectedMonth',
},
},
]
async function handleSubmit() {
const result = await (fieldRef.value!.validateOnSubmit! as () => Promise)()
alert(result ? 'Mois valide !' : 'Veuillez corriger les erreurs.')
}
return { args, selectedMonth, customRules, handleSubmit, fieldRef }
},
template: `
`,
}),
}
export const SyFormVuetifyValidation: Story = {
parameters: {
sourceCode: [
{
name: 'Template',
code: `
Valider
`,
},
{
name: 'Script',
code: ``,
},
],
},
render: args => ({
components: { MonthPicker, VBtn, SyForm },
setup() {
const selectedMonth = ref('')
const vuetifyRules = [
(value: string) => !!value || 'Le mois est requis',
(value: string) => /^(0[1-9]|1[0-2])\/\d{4}$/.test(value) || 'Le format doit être MM/YYYY (ex: 03/2026)',
]
function handleSubmit(e: { isValid: boolean }) {
const isValid = e.isValid
alert(isValid ? 'Mois valide !' : 'Veuillez corriger les erreurs.')
}
return { args, selectedMonth, vuetifyRules, handleSubmit }
},
template: `
`,
}),
}
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: { MonthPicker, VBtn, VForm },
setup() {
const selectedMonth = ref('')
const fieldRef = ref()
const rules = [
(value: string) => !!value || 'Le mois est requis',
(value: string) => /^(0[1-9]|1[0-2])\/\d{4}$/.test(value) || 'Le format doit être MM/YYYY (ex: 03/2026)',
]
async function handleSubmit(e: Promise<{ valid: boolean }>) {
const result = await e
alert(result.valid ? 'Mois valide !' : 'Veuillez corriger les erreurs.')
}
return { args, selectedMonth, fieldRef, 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
`,
}),
args: {
showSuccessMessages: true,
},
}