Skip to content

Commit 82fae73

Browse files
committed
feat(DsfrCheckboxSet)!: améliore l’API modelValue
- cf. documentation BREAKING CHANGE: `modelValue` contient un tableau des `value`
1 parent 4af58b6 commit 82fae73

File tree

12 files changed

+211
-75
lines changed

12 files changed

+211
-75
lines changed

demo-app/views/AppForm.vue

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
<script setup>
1+
<script setup lang="ts">
22
import DsfrAlert from '@/components/DsfrAlert/DsfrAlert.vue'
3-
43
import DsfrButton from '@/components/DsfrButton/DsfrButton.vue'
4+
import DsfrCheckboxSet from '@/components/DsfrCheckbox/DsfrCheckboxSet.vue'
55
import DsfrFileUpload from '@/components/DsfrFileUpload/DsfrFileUpload.vue'
66
import DsfrRadioButtonSet from '@/components/DsfrRadioButton/DsfrRadioButtonSet.vue'
77
import { ref } from 'vue'
8+
import type { DsfrCheckboxSetProps } from '@/components/DsfrCheckbox/DsfrCheckbox.types'
89
910
const inputValue = ref('')
11+
const showAlert = ref(true)
1012
const filesToUpload = ref(undefined)
1113
1214
const updateFiles = (files) => {
@@ -20,6 +22,27 @@ const sendFile = () => {
2022
2123
const whatever = ref('')
2224
const radioTest = ref('')
25+
26+
const selectedCheckbox = ref(false)
27+
const selectedCheckboxes = ref([])
28+
const cbLegend = 'Légende des cases à cocher'
29+
const cbOptions: DsfrCheckboxSetProps['options'] = [
30+
{
31+
value: 'test1',
32+
modelValue: 'test1',
33+
label: 'Test 1',
34+
},
35+
{
36+
value: 'test2',
37+
modelValue: 'test2',
38+
label: 'Test 2',
39+
},
40+
{
41+
value: 'test3',
42+
modelValue: 'test3',
43+
label: 'Test 3',
44+
},
45+
]
2346
</script>
2447

2548
<template>
@@ -72,6 +95,7 @@ const radioTest = ref('')
7295
</template>
7396
</DsfrRadioButtonSet>
7497
</div>
98+
7599
<DsfrButton
76100
type="submit"
77101
label="Bouton de soumission du formulaire"
@@ -89,5 +113,21 @@ const radioTest = ref('')
89113
/>
90114
</template>
91115
</DsfrInput>
116+
117+
<h2>ChecboxSet :</h2>
118+
<DsfrCheckboxSet
119+
v-model="selectedCheckboxes"
120+
:legend="cbLegend"
121+
:options="cbOptions"
122+
/>
123+
Sélectionné(s) : {{ selectedCheckboxes }}
124+
125+
<h2>Checbox seule :</h2>
126+
<DsfrCheckbox
127+
v-model="selectedCheckbox"
128+
label="Une seule checkbox"
129+
value="test_only_cb"
130+
/>
131+
Sélectionné : {{ selectedCheckbox }}
92132
</form>
93133
</template>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script lang="ts" setup>
2+
import DsfrCheckboxSet from '@/components/DsfrCheckbox/DsfrCheckboxSet.vue'
3+
import { ref } from 'vue'
4+
import type { DsfrCheckboxSetProps } from '@/components/DsfrCheckbox/DsfrCheckbox.types'
5+
6+
const selectedCheckboxes = ref([])
7+
const cbLegend = 'Légende des cases à cocher'
8+
const cbOptions: DsfrCheckboxSetProps['options'] = [
9+
{
10+
value: 'une chaîne',
11+
modelValue: 'test1',
12+
label: 'Test 1',
13+
},
14+
{
15+
value: 42,
16+
modelValue: 'test2',
17+
label: 'Test 2',
18+
},
19+
{
20+
value: { objet: 'complexe' },
21+
modelValue: 'test3',
22+
label: 'Test 3',
23+
},
24+
]
25+
</script>
26+
27+
<template>
28+
<form>
29+
<DsfrCheckboxSet
30+
v-model="selectedCheckboxes"
31+
:legend="cbLegend"
32+
:options="cbOptions"
33+
/>
34+
<p>
35+
Sélectionné(s) : {{ selectedCheckboxes }}
36+
</p>
37+
<input
38+
type="submit"
39+
class="fr-btn"
40+
>
41+
</form>
42+
</template>

docs/guide/migrations.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
# Migrations
22

3-
## Migration vers 6.x (depuis 4.x ou 5.x)
3+
## Migration vers 7.x (depuis 4.x, 5.x ou 6.x)
44

5-
Dans cette version majeure, il y a plusieurs sujets à traiter lorsque vous migrerez :
5+
Avant la v7, le tableau `modelValue` de [`DsfrCheckboxSet`](/composants/DsfrCheckboxSet) était un tableau de `string` avec les valeurs des propriétés de l’attribut `name` de chaque case à cocher.
6+
7+
Ce n’était ni une API idéale, ni le comportement attendu en Vue natif ou en HTML/JS natif.
8+
9+
::: code-group
10+
<Story data-title="Démo" min-h="350px">
11+
<DsfrCheckboxSetV7Demo />
12+
</Story>
13+
14+
<<< ../docs-demo/DsfrCheckboxSetV7Demo.vue [Code de la démo]
15+
:::
616

7-
1. Les icônes
817
2. Les onglets
918
3. Les accordéons
1019

@@ -250,3 +259,7 @@ export default defineNuxtConfig({
250259
],
251260
})
252261
```
262+
263+
<script setup>
264+
import DsfrCheckboxSetV7Demo from '../docs-demo/DsfrCheckboxSetV7Demo.vue'
265+
</script>

src/components/DsfrCheckbox/DsfrCheckbox.stories.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,11 @@ export const Checkbox = (args) => ({
7171
:required="required"
7272
:small="small"
7373
:hint="hint"
74+
:value="value"
7475
:name="name || 'name1'"
7576
v-model="modelValue"
7677
/>
78+
{{ modelValue }}
7779
`,
7880
watch: {
7981
modelValue (newValue) {
@@ -88,6 +90,7 @@ Checkbox.args = {
8890
small: false,
8991
label: 'Checkbox 1',
9092
name: 'name1',
93+
value: 'name1',
9194
hint: 'Description 1',
9295
}
9396
Checkbox.play = async ({ canvasElement }) => {

src/components/DsfrCheckbox/DsfrCheckbox.types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ export type DsfrCheckboxProps = {
44
id?: string
55
name: string
66
required?: boolean
7-
modelValue?: boolean
7+
value: unknown
8+
checked?: boolean
9+
modelValue: Array<unknown>
810
small?: boolean
911
inline?: boolean
1012
label?: string
@@ -23,6 +25,6 @@ export type DsfrCheckboxSetProps = {
2325
validMessage?: string
2426
legend?: string
2527
options?: (DsfrCheckboxProps & InputHTMLAttributes)[]
26-
modelValue?: string[]
28+
modelValue?: Array<unknown>
2729
ariaInvalid?: boolean
2830
}

src/components/DsfrCheckbox/DsfrCheckbox.vue

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,10 @@ const props = withDefaults(defineProps<DsfrCheckboxProps>(), {
1818
label: '',
1919
})
2020
21-
const emit = defineEmits<{ (event: 'update:modelValue', value: boolean): void }>()
22-
2321
const message = computed(() => props.errorMessage || props.validMessage)
2422
2523
const additionalMessageClass = computed(() => props.errorMessage ? 'fr-error-text' : 'fr-valid-text')
26-
27-
const emitNewValue = ($event: InputEvent) => {
28-
// @ts-expect-error This is a checkbox input event, so `checked` property is present
29-
emit('update:modelValue', $event.target.checked)
30-
}
24+
const modelValue = defineModel()
3125
</script>
3226

3327
<template>
@@ -45,14 +39,15 @@ const emitNewValue = ($event: InputEvent) => {
4539
>
4640
<input
4741
:id="id"
42+
v-model="modelValue"
4843
:name="name"
4944
type="checkbox"
50-
:checked="modelValue"
45+
:value="value"
46+
:checked="modelValue === true || (Array.isArray(modelValue) && modelValue.includes(value))"
5147
:required
5248
v-bind="$attrs"
5349
:data-testid="`input-checkbox-${id}`"
5450
:data-test="`input-checkbox-${id}`"
55-
@change="emitNewValue($event as InputEvent)"
5651
>
5752
<label
5853
:for="id"

src/components/DsfrCheckbox/DsfrCheckboxSet.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Le composant `DsfrCheckboxSet` est composé des éléments suivants :
1616
| Nom | Type | Description | Obligatoire |
1717
|-----------------------|-------------------------------------------|----------------------------------------------------------------|--------------|
1818
| `options` | *`(DsfrCheckboxProps & InputHTMLAttributes)[]`* | Tableau d'options définissant les cases à cocher individuelles ||
19-
| `modelValue` | *`string[]`* | Valeur courante du composant, un tableau de noms des cases cochées ||
19+
| `modelValue` | *`string[]`* | Valeur courante du composant, un tableau de valeurs (propriété `value` de chaque option de la prop `options`) des cases cochées ||
2020
| `disabled` | *`boolean`* | Indique si l'ensemble des cases à cocher est désactivé | |
2121
| `errorMessage` | *`string`* | Message d'erreur global à afficher | |
2222
| `inline` | *`boolean`* | Affiche les cases à cocher en ligne (par défaut : `false`) | |
@@ -26,6 +26,14 @@ Le composant `DsfrCheckboxSet` est composé des éléments suivants :
2626
| `titleId` | *`string`* | Identifiant unique du champ (générée automatiquement si non fournie) | |
2727
| `validMessage` | *`string`* | Message de validation global à afficher | |
2828

29+
::: danger Attention
30+
31+
Avant la v7, le tableau `modelValue` était un tableau de `string` avec les valeurs des propriétés de l’attribut `name` de chaque case à cocher.
32+
33+
Ce n’était ni une API idéale, ni le comportement attendu en Vue natif ou en HTML/JS natif.
34+
35+
:::
36+
2937
## 📡 Événements
3038

3139
`DsfrCheckboxSet` émet l'événement suivant :
@@ -43,7 +51,7 @@ Le composant `DsfrCheckboxSet` est composé des éléments suivants :
4351

4452
## 🪆 Relation avec `DsfrCheckbox`
4553

46-
`DsfrChecboxSet` utilise en interne `DsfrCheckbox`, et permet de récupérer dans `modelValue` sous forme de tableau non pas les états de chaque case à cocher, mais un tableau de `string` contenant les valeurs de la prop `name` de chaque case à cocher qui est cochée.
54+
`DsfrChecboxSet` utilise en interne `DsfrCheckbox`, et permet de récupérer dans `modelValue` sous forme de tableau les valeurs de la prop `value` de chaque case à cocher qui est cochée.
4755

4856
Cf. les exemples ci-dessous
4957

src/components/DsfrCheckbox/DsfrCheckboxSet.spec.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,30 @@ describe('DsfrCheckboxSet', () => {
1212
const secondHintText = 'Deuxième indice'
1313
const thirdLabelText = 'Troisième label'
1414
const thirdHintText = 'Troisième indice'
15+
const modelValue = []
1516
const options = [
1617
{
1718
id: '1',
1819
label: firstLabelText,
19-
checked: false,
20+
value: 'un',
2021
hint: firstHintText,
22+
modelValue,
2123
name: '1',
2224
},
2325
{
2426
id: '2',
2527
label: secondLabelText,
26-
checked: false,
28+
value: 'deux',
2729
hint: secondHintText,
30+
modelValue,
2831
name: '2',
2932
},
3033
{
3134
id: '3',
3235
label: thirdLabelText,
33-
checked: false,
36+
value: 'trois',
3437
hint: thirdHintText,
38+
modelValue,
3539
name: '3',
3640
},
3741
]
@@ -64,30 +68,35 @@ describe('DsfrCheckboxSet', () => {
6468
const secondHintText = 'Deuxième indice'
6569
const thirdLabelText = 'Troisième label'
6670
const thirdHintText = 'Troisième indice'
71+
const modelValue = ['name2']
72+
6773
const options = [
6874
{
6975
id: '1',
7076
name: 'name1',
77+
value: 'name1',
7178
label: firstLabelText,
7279
hint: firstHintText,
7380
},
7481
{
7582
id: '2',
7683
name: 'name2',
84+
value: 'name2',
7785
label: secondLabelText,
7886
hint: secondHintText,
7987
},
8088
{
8189
id: '3',
8290
name: 'name3',
91+
value: 'name3',
8392
label: thirdLabelText,
8493
hint: thirdHintText,
8594
},
8695
]
8796
const legendText = 'Légende de l’ensemble des champs'
8897

8998
// When
90-
const { getByText, getByTestId } = render(DsfrCheckboxSet, {
99+
const { getByText, getByLabelText, getByTestId } = render(DsfrCheckboxSet, {
91100
global: {
92101
components: {
93102
VIcon,
@@ -96,26 +105,34 @@ describe('DsfrCheckboxSet', () => {
96105
props: {
97106
legend: legendText,
98107
options,
108+
modelValue,
99109
validMessage: 'Message d’erreur',
100-
modelValue: ['name3'],
101110
},
102111
})
103112

104113
const firstLabelEl = getByText(firstLabelText)
105114
const secondLabelEl = getByText(secondLabelText)
115+
const thirdLabelEl = getByText(`${thirdLabelText}`)
116+
const thirdInput = getByLabelText(`${thirdLabelText} ${thirdHintText}`)
106117
const firstInput = getByTestId('input-checkbox-1')
107118
const secondInput = getByTestId('input-checkbox-2')
119+
// @ts-expect-error This is a checkbox input event, so `checked` property is present
120+
expect((firstInput).checked).toBe(false)
121+
// @ts-expect-error This is a checkbox input event, so `checked` property is present
122+
expect(secondInput.checked).toBe(true)
108123
await fireEvent.click(firstLabelEl)
109124
await fireEvent.click(secondLabelEl)
110-
await fireEvent.click(secondLabelEl)
125+
await fireEvent.click(thirdLabelEl)
111126

112127
// Then
113128
expect(firstInput).toBeInTheDocument()
114129
expect(firstInput).toHaveAttribute('name', 'name1')
130+
expect(secondInput).toHaveAttribute('name', 'name2')
115131
// @ts-expect-error This is a checkbox input event, so `checked` property is present
116132
expect((firstInput).checked).toBe(true)
117133
// @ts-expect-error This is a checkbox input event, so `checked` property is present
118134
expect(secondInput.checked).toBe(false)
135+
expect(thirdInput.checked).toBe(true)
119136
})
120137

121138
it('should render no checkboxes', async () => {

0 commit comments

Comments
 (0)