Skip to content

Commit 82f7136

Browse files
committed
refactor(DsfrToggleSwitch): ♻️ modernise le composant et convertit les stories en CSF3
## Pourquoi les changements ont été faits : - Modernisation du composant DsfrToggleSwitch selon les conventions Vue 3 - Migration des 5 stories Storybook vers le format CSF3 moderne - Amélioration de la cohérence avec les autres composants du projet - Modernisation de l'utilisation des événements avec la fonction emit() ## Quelles modifications ont été apportées : - Ajout de documentation JSDoc pour les événements émis - Modernisation de la syntaxe emit avec types explicites - Remplacement de $emit par emit() dans le template - Conversion des 5 stories au format CSF3 avec Meta/StoryObj et satisfies - Migration de l'options API vers la Composition API avec setup(), ref et watch - Préservation des tests interactifs sur InterrupteurAvecTextePersonnalise - Gestion réactive du modelValue avec watch pour déclencher onChange - Échappement des apostrophes dans les descriptions argTypes françaises
1 parent 811fd29 commit 82f7136

File tree

2 files changed

+164
-138
lines changed

2 files changed

+164
-138
lines changed
Lines changed: 159 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import type { Meta, StoryObj } from '@storybook/vue3'
2+
13
import { expect, fn, within } from 'storybook/test'
4+
import { ref, watch } from 'vue'
25

36
import DsfrToggleSwitch from './DsfrToggleSwitch.vue'
47

58
/**
69
* [Voir quand l’utiliser sur la documentation du DSFR](https://www.systeme-de-design.gouv.fr/version-courante/fr/composants/interrupteur)
710
*/
8-
export default {
11+
const meta = {
912
component: DsfrToggleSwitch,
1013
title: 'Composants/DsfrToggleSwitch',
1114
tags: ['formulaire'],
@@ -43,6 +46,7 @@ export default {
4346
action: fn(),
4447
description:
4548
'Appelé à chaque changement de la valeur `checked`.\n\n*N.B. : Ne fait pas partie du composant.*',
49+
table: { category: 'Hors composant' },
4650
},
4751
activeText: {
4852
control: 'text',
@@ -61,168 +65,187 @@ export default {
6165
'Evènement de mise à jour de la valeur contenue dans modelValue',
6266
},
6367
},
64-
}
68+
} satisfies Meta<typeof DsfrToggleSwitch>
6569

66-
export const Interrupteur = (args) => ({
67-
components: { DsfrToggleSwitch },
68-
data () {
69-
return args
70-
},
71-
template: `
70+
export default meta
71+
72+
type Story = StoryObj<typeof meta>
73+
74+
export const Interrupteur: Story = {
75+
render: (args) => ({
76+
components: { DsfrToggleSwitch },
77+
setup () {
78+
watch(() => args.modelValue, (newVal) => {
79+
args.onChange?.(newVal)
80+
})
81+
82+
return {
83+
args,
84+
}
85+
},
86+
template: `
7287
<DsfrToggleSwitch
73-
v-model="modelValue"
74-
:label="label"
75-
:hint="hint"
76-
:disabled="disabled"
77-
:input-id="inputId"
88+
v-model="args.modelValue"
89+
:label="args.label"
90+
:hint="args.hint"
91+
:disabled="args.disabled"
92+
:input-id="args.inputId"
7893
/>
7994
`,
80-
watch: {
81-
modelValue (newVal) {
82-
this.onChange(newVal)
83-
},
95+
}),
96+
args: {
97+
label: 'Interrupteur 1',
98+
hint: 'Indice',
99+
disabled: false,
100+
inputId: 'toggle-1',
101+
modelValue: true,
84102
},
85-
})
86-
Interrupteur.args = {
87-
label: 'Interrupteur 1',
88-
hint: 'Indice',
89-
disabled: false,
90-
inputId: 'toggle-1',
91-
modelValue: true,
92103
}
93104

94-
export const InterrupteurAvecLabelAGauche = (args) => ({
95-
components: { DsfrToggleSwitch },
96-
data () {
97-
return args
98-
},
99-
template: `
105+
export const InterrupteurAvecLabelAGauche: Story = {
106+
name: 'Interrupteur Avec Label À Gauche',
107+
render: (args) => ({
108+
components: { DsfrToggleSwitch },
109+
setup () {
110+
watch(() => args.modelValue, (newVal) => {
111+
args.onChange?.(newVal)
112+
})
113+
114+
return {
115+
args,
116+
}
117+
},
118+
template: `
100119
<DsfrToggleSwitch
101-
v-model="modelValue"
102-
:label="label"
103-
:hint="hint"
104-
:disabled="disabled"
105-
:input-id="inputId"
106-
:labelLeft="labelLeft"
120+
v-model="args.modelValue"
121+
:label="args.label"
122+
:hint="args.hint"
123+
:disabled="args.disabled"
124+
:input-id="args.inputId"
125+
:labelLeft="args.labelLeft"
107126
/>
108127
`,
109-
watch: {
110-
modelValue (newVal) {
111-
this.onChange(newVal)
112-
},
128+
}),
129+
args: {
130+
label: 'Interrupteur 1',
131+
hint: 'Indice',
132+
disabled: false,
133+
inputId: 'toggle-2',
134+
modelValue: true,
135+
labelLeft: true,
113136
},
114-
})
115-
InterrupteurAvecLabelAGauche.args = {
116-
label: 'Interrupteur 1',
117-
hint: 'Indice',
118-
disabled: false,
119-
inputId: 'toggle-2',
120-
modelValue: true,
121-
labelLeft: true,
122137
}
123138

124-
export const InterrupteurAvecBordure = (args) => ({
125-
components: { DsfrToggleSwitch },
126-
data () {
127-
return args
128-
},
129-
template: `
139+
export const InterrupteurAvecBordure: Story = {
140+
render: (args) => ({
141+
components: { DsfrToggleSwitch },
142+
setup () {
143+
watch(() => args.modelValue, (newVal) => {
144+
args.onChange?.(newVal)
145+
})
146+
147+
return {
148+
args,
149+
}
150+
},
151+
template: `
130152
<DsfrToggleSwitch
131-
v-model="modelValue"
132-
:label="label"
133-
:hint="hint"
134-
:disabled="disabled"
135-
:input-id="inputId"
136-
:borderBottom="borderBottom"
153+
v-model="args.modelValue"
154+
:label="args.label"
155+
:hint="args.hint"
156+
:disabled="args.disabled"
157+
:input-id="args.inputId"
158+
:borderBottom="args.borderBottom"
137159
/>
138160
`,
139-
watch: {
140-
modelValue (newVal) {
141-
this.onChange(newVal)
142-
},
161+
}),
162+
args: {
163+
label: 'Interrupteur 1',
164+
hint: 'Indice',
165+
disabled: false,
166+
inputId: 'toggle-3',
167+
modelValue: true,
168+
borderBottom: true,
143169
},
144-
})
145-
InterrupteurAvecBordure.args = {
146-
label: 'Interrupteur 1',
147-
hint: 'Indice',
148-
disabled: false,
149-
inputId: 'toggle-3',
150-
modelValue: true,
151-
borderBottom: true,
152170
}
153171

154-
export const InterrupteurAvecTextePersonnalisé = (args) => ({
155-
components: { DsfrToggleSwitch },
156-
data () {
157-
return args
158-
},
159-
template: `
172+
export const InterrupteurAvecTextePersonnalise: Story = {
173+
name: 'Interrupteur Avec Texte Personnalisé',
174+
render: (args) => ({
175+
components: { DsfrToggleSwitch },
176+
setup () {
177+
watch(() => args.modelValue, (newVal) => {
178+
args.onChange?.(newVal)
179+
})
180+
181+
return {
182+
args,
183+
}
184+
},
185+
template: `
160186
<DsfrToggleSwitch
161-
v-model="modelValue"
162-
:label="label"
163-
:hint="hint"
164-
:input-id="inputId"
165-
:active-text="activeText"
166-
:inactive-text="inactiveText"
187+
v-model="args.modelValue"
188+
:label="args.label"
189+
:hint="args.hint"
190+
:disabled="args.disabled"
191+
:input-id="args.inputId"
192+
:active-text="args.activeText"
193+
:inactive-text="args.inactiveText"
167194
/>
168195
`,
169-
watch: {
170-
modelValue (newVal) {
171-
this.onChange(newVal)
172-
},
196+
}),
197+
args: {
198+
label: 'Interrupteur 1',
199+
hint: 'Indice',
200+
inputId: 'toggle-4',
201+
modelValue: true,
202+
disabled: false,
203+
activeText: 'Autorisé',
204+
inactiveText: 'Interdit',
173205
},
174-
})
175-
InterrupteurAvecTextePersonnalisé.args = {
176-
label: 'Interrupteur 1',
177-
hint: 'Indice',
178-
inputId: 'toggle-4',
179-
modelValue: true,
180-
activeText: 'Autorisé',
181-
inactiveText: 'Interdit',
182-
}
206+
async play ({ canvasElement }) {
207+
const canvas = within(canvasElement)
208+
const toggleSwitch = canvas.getByLabelText('Interrupteur 1') as HTMLInputElement
209+
expect(toggleSwitch).toHaveProperty('checked', true)
210+
toggleSwitch.click()
211+
expect(toggleSwitch).toHaveProperty('checked', false)
212+
toggleSwitch.click()
213+
expect(toggleSwitch).toHaveProperty('checked', true)
183214

184-
export const InterrupteurSansTexte = (args) => ({
185-
components: { DsfrToggleSwitch },
186-
data () {
187-
return args
215+
const hint = canvas.getByText('Indice') as HTMLParagraphElement
216+
expect(hint).toBeVisible()
188217
},
189-
template: `
218+
}
219+
220+
export const InterrupteurSansTexte: Story = {
221+
render: (args) => ({
222+
components: { DsfrToggleSwitch },
223+
setup () {
224+
watch(() => args.modelValue, (newVal) => {
225+
args.onChange?.(newVal)
226+
})
227+
228+
return {
229+
args,
230+
}
231+
},
232+
template: `
190233
<DsfrToggleSwitch
191-
v-model="modelValue"
192-
:label="label"
193-
:hint="hint"
194-
:input-id="inputId"
195-
:no-text="noText"
234+
v-model="args.modelValue"
235+
:label="args.label"
236+
:hint="args.hint"
237+
:disabled="args.disabled"
238+
:input-id="args.inputId"
239+
:no-text="args.noText"
196240
/>
197241
`,
198-
watch: {
199-
modelValue (newVal) {
200-
this.onChange(newVal)
201-
},
242+
}),
243+
args: {
244+
label: 'Interrupteur 1',
245+
hint: 'Indice',
246+
inputId: 'toggle-5',
247+
modelValue: true,
248+
disabled: true,
249+
noText: true,
202250
},
203-
})
204-
InterrupteurSansTexte.args = {
205-
label: 'Interrupteur 1',
206-
hint: 'Indice',
207-
inputId: 'toggle-5',
208-
modelValue: true,
209-
noText: true,
210-
}
211-
212-
InterrupteurAvecTextePersonnalisé.play = async ({ canvasElement }) => {
213-
const canvas = within(canvasElement)
214-
const toggleSwitch = canvas.getByLabelText('Interrupteur 1') as HTMLInputElement
215-
expect(toggleSwitch.checked).toBe(true)
216-
toggleSwitch.click()
217-
expect(toggleSwitch.checked).toBe(false)
218-
toggleSwitch.click()
219-
220-
const toggleSwitchLabel = canvas.getByText('Interrupteur 1') as HTMLLabelElement
221-
toggleSwitchLabel.click()
222-
expect(toggleSwitch.checked).toBe(false)
223-
toggleSwitchLabel.click()
224-
expect(toggleSwitch.checked).toBe(true)
225-
226-
const hint = canvas.getByText('Indice') as HTMLParagraphElement
227-
expect(hint).toBeVisible()
228251
}

src/components/DsfrToggleSwitch/DsfrToggleSwitch.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ const props = withDefaults(defineProps<DsfrToggleSwitchProps>(), {
1919
name: undefined,
2020
})
2121
22-
defineEmits<{ (e: 'update:modelValue', payload: boolean): void }>()
22+
const emit = defineEmits<{
23+
/** Émis par le composant DsfrToggleSwitch */
24+
'update:modelValue': [payload: boolean]
25+
}>()
2326
2427
const labelId = computed(() => {
2528
return `${props.inputId}-hint-text`
@@ -44,7 +47,7 @@ const labelId = computed(() => {
4447
class="fr-toggle__input"
4548
:aria-describedby="labelId"
4649
:name="name"
47-
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).checked)"
50+
@input="emit('update:modelValue', ($event.target as HTMLInputElement).checked)"
4851
>
4952
<label
5053
:id="labelId"

0 commit comments

Comments
 (0)