Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(DsfrCheckboxSet)!: améliore l’API modelValue #942

Merged
merged 1 commit into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions demo-app/views/AppForm.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<script setup>
<script setup lang="ts">
import DsfrAlert from '@/components/DsfrAlert/DsfrAlert.vue'
import DsfrButton from '@/components/DsfrButton/DsfrButton.vue'
import DsfrCheckboxSet from '@/components/DsfrCheckbox/DsfrCheckboxSet.vue'
import DsfrFileUpload from '@/components/DsfrFileUpload/DsfrFileUpload.vue'
import DsfrRadioButtonSet from '@/components/DsfrRadioButton/DsfrRadioButtonSet.vue'
import { ref } from 'vue'
import type { DsfrCheckboxSetProps } from '@/components/DsfrCheckbox/DsfrCheckbox.types'
const inputValue = ref('')
const showAlert = ref(true)
const filesToUpload = ref(undefined)
const updateFiles = (files) => {
Expand All @@ -20,6 +22,27 @@ const sendFile = () => {
const whatever = ref('')
const radioTest = ref('')
const selectedCheckbox = ref(false)
const selectedCheckboxes = ref([])
const cbLegend = 'Légende des cases à cocher'
const cbOptions: DsfrCheckboxSetProps['options'] = [
{
value: 'test1',
modelValue: 'test1',
label: 'Test 1',
},
{
value: 'test2',
modelValue: 'test2',
label: 'Test 2',
},
{
value: 'test3',
modelValue: 'test3',
label: 'Test 3',
},
]
</script>

<template>
Expand Down Expand Up @@ -72,6 +95,7 @@ const radioTest = ref('')
</template>
</DsfrRadioButtonSet>
</div>

<DsfrButton
type="submit"
label="Bouton de soumission du formulaire"
Expand All @@ -89,5 +113,21 @@ const radioTest = ref('')
/>
</template>
</DsfrInput>

<h2>ChecboxSet :</h2>
<DsfrCheckboxSet
v-model="selectedCheckboxes"
:legend="cbLegend"
:options="cbOptions"
/>
Sélectionné(s) : {{ selectedCheckboxes }}

<h2>Checbox seule :</h2>
<DsfrCheckbox
v-model="selectedCheckbox"
label="Une seule checkbox"
value="test_only_cb"
/>
Sélectionné : {{ selectedCheckbox }}
</form>
</template>
42 changes: 42 additions & 0 deletions docs/docs-demo/DsfrCheckboxSetV7Demo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<script lang="ts" setup>
import DsfrCheckboxSet from '@/components/DsfrCheckbox/DsfrCheckboxSet.vue'
import { ref } from 'vue'
import type { DsfrCheckboxSetProps } from '@/components/DsfrCheckbox/DsfrCheckbox.types'
const selectedCheckboxes = ref([])
const cbLegend = 'Légende des cases à cocher'
const cbOptions: DsfrCheckboxSetProps['options'] = [
{
value: 'une chaîne',
modelValue: 'test1',
label: 'Test 1',
},
{
value: 42,
modelValue: 'test2',
label: 'Test 2',
},
{
value: { objet: 'complexe' },
modelValue: 'test3',
label: 'Test 3',
},
]
</script>

<template>
<form>
<DsfrCheckboxSet
v-model="selectedCheckboxes"
:legend="cbLegend"
:options="cbOptions"
/>
<p>
Sélectionné(s) : {{ selectedCheckboxes }}
</p>
<input
type="submit"
class="fr-btn"
>
</form>
</template>
19 changes: 16 additions & 3 deletions docs/guide/migrations.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
# Migrations

## Migration vers 6.x (depuis 4.x ou 5.x)
## Migration vers 7.x (depuis 4.x, 5.x ou 6.x)

Dans cette version majeure, il y a plusieurs sujets à traiter lorsque vous migrerez :
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.

Ce n’était ni une API idéale, ni le comportement attendu en Vue natif ou en HTML/JS natif.

::: code-group
<Story data-title="Démo" min-h="350px">
<DsfrCheckboxSetV7Demo />
</Story>

<<< ../docs-demo/DsfrCheckboxSetV7Demo.vue [Code de la démo]
:::

1. Les icônes
2. Les onglets
3. Les accordéons

Expand Down Expand Up @@ -250,3 +259,7 @@ export default defineNuxtConfig({
],
})
```

<script setup>
import DsfrCheckboxSetV7Demo from '../docs-demo/DsfrCheckboxSetV7Demo.vue'
</script>
3 changes: 3 additions & 0 deletions src/components/DsfrCheckbox/DsfrCheckbox.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ export const Checkbox = (args) => ({
:required="required"
:small="small"
:hint="hint"
:value="value"
:name="name || 'name1'"
v-model="modelValue"
/>
{{ modelValue }}
`,
watch: {
modelValue (newValue) {
Expand All @@ -88,6 +90,7 @@ Checkbox.args = {
small: false,
label: 'Checkbox 1',
name: 'name1',
value: 'name1',
hint: 'Description 1',
}
Checkbox.play = async ({ canvasElement }) => {
Expand Down
6 changes: 4 additions & 2 deletions src/components/DsfrCheckbox/DsfrCheckbox.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ export type DsfrCheckboxProps = {
id?: string
name: string
required?: boolean
modelValue?: boolean
value: unknown
checked?: boolean
modelValue: Array<unknown>
small?: boolean
inline?: boolean
label?: string
Expand All @@ -23,6 +25,6 @@ export type DsfrCheckboxSetProps = {
validMessage?: string
legend?: string
options?: (DsfrCheckboxProps & InputHTMLAttributes)[]
modelValue?: string[]
modelValue?: Array<unknown>
ariaInvalid?: boolean
}
13 changes: 4 additions & 9 deletions src/components/DsfrCheckbox/DsfrCheckbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,10 @@ const props = withDefaults(defineProps<DsfrCheckboxProps>(), {
label: '',
})
const emit = defineEmits<{ (event: 'update:modelValue', value: boolean): void }>()
const message = computed(() => props.errorMessage || props.validMessage)
const additionalMessageClass = computed(() => props.errorMessage ? 'fr-error-text' : 'fr-valid-text')
const emitNewValue = ($event: InputEvent) => {
// @ts-expect-error This is a checkbox input event, so `checked` property is present
emit('update:modelValue', $event.target.checked)
}
const modelValue = defineModel()
</script>

<template>
Expand All @@ -45,14 +39,15 @@ const emitNewValue = ($event: InputEvent) => {
>
<input
:id="id"
v-model="modelValue"
:name="name"
type="checkbox"
:checked="modelValue"
:value="value"
:checked="modelValue === true || (Array.isArray(modelValue) && modelValue.includes(value))"
:required
v-bind="$attrs"
:data-testid="`input-checkbox-${id}`"
:data-test="`input-checkbox-${id}`"
@change="emitNewValue($event as InputEvent)"
>
<label
:for="id"
Expand Down
12 changes: 10 additions & 2 deletions src/components/DsfrCheckbox/DsfrCheckboxSet.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Le composant `DsfrCheckboxSet` est composé des éléments suivants :
| Nom | Type | Description | Obligatoire |
|-----------------------|-------------------------------------------|----------------------------------------------------------------|--------------|
| `options` | *`(DsfrCheckboxProps & InputHTMLAttributes)[]`* | Tableau d'options définissant les cases à cocher individuelles ||
| `modelValue` | *`string[]`* | Valeur courante du composant, un tableau de noms des cases cochées ||
| `modelValue` | *`string[]`* | Valeur courante du composant, un tableau de valeurs (propriété `value` de chaque option de la prop `options`) des cases cochées ||
| `disabled` | *`boolean`* | Indique si l'ensemble des cases à cocher est désactivé | |
| `errorMessage` | *`string`* | Message d'erreur global à afficher | |
| `inline` | *`boolean`* | Affiche les cases à cocher en ligne (par défaut : `false`) | |
Expand All @@ -26,6 +26,14 @@ Le composant `DsfrCheckboxSet` est composé des éléments suivants :
| `titleId` | *`string`* | Identifiant unique du champ (générée automatiquement si non fournie) | |
| `validMessage` | *`string`* | Message de validation global à afficher | |

::: danger Attention

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.

Ce n’était ni une API idéale, ni le comportement attendu en Vue natif ou en HTML/JS natif.

:::

## 📡 Événements

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

## 🪆 Relation avec `DsfrCheckbox`

`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.
`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.

Cf. les exemples ci-dessous

Expand Down
29 changes: 23 additions & 6 deletions src/components/DsfrCheckbox/DsfrCheckboxSet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,30 @@ describe('DsfrCheckboxSet', () => {
const secondHintText = 'Deuxième indice'
const thirdLabelText = 'Troisième label'
const thirdHintText = 'Troisième indice'
const modelValue = []
const options = [
{
id: '1',
label: firstLabelText,
checked: false,
value: 'un',
hint: firstHintText,
modelValue,
name: '1',
},
{
id: '2',
label: secondLabelText,
checked: false,
value: 'deux',
hint: secondHintText,
modelValue,
name: '2',
},
{
id: '3',
label: thirdLabelText,
checked: false,
value: 'trois',
hint: thirdHintText,
modelValue,
name: '3',
},
]
Expand Down Expand Up @@ -64,30 +68,35 @@ describe('DsfrCheckboxSet', () => {
const secondHintText = 'Deuxième indice'
const thirdLabelText = 'Troisième label'
const thirdHintText = 'Troisième indice'
const modelValue = ['name2']

const options = [
{
id: '1',
name: 'name1',
value: 'name1',
label: firstLabelText,
hint: firstHintText,
},
{
id: '2',
name: 'name2',
value: 'name2',
label: secondLabelText,
hint: secondHintText,
},
{
id: '3',
name: 'name3',
value: 'name3',
label: thirdLabelText,
hint: thirdHintText,
},
]
const legendText = 'Légende de l’ensemble des champs'

// When
const { getByText, getByTestId } = render(DsfrCheckboxSet, {
const { getByText, getByLabelText, getByTestId } = render(DsfrCheckboxSet, {
global: {
components: {
VIcon,
Expand All @@ -96,26 +105,34 @@ describe('DsfrCheckboxSet', () => {
props: {
legend: legendText,
options,
modelValue,
validMessage: 'Message d’erreur',
modelValue: ['name3'],
},
})

const firstLabelEl = getByText(firstLabelText)
const secondLabelEl = getByText(secondLabelText)
const thirdLabelEl = getByText(`${thirdLabelText}`)
const thirdInput = getByLabelText(`${thirdLabelText} ${thirdHintText}`)
const firstInput = getByTestId('input-checkbox-1')
const secondInput = getByTestId('input-checkbox-2')
// @ts-expect-error This is a checkbox input event, so `checked` property is present
expect((firstInput).checked).toBe(false)
// @ts-expect-error This is a checkbox input event, so `checked` property is present
expect(secondInput.checked).toBe(true)
await fireEvent.click(firstLabelEl)
await fireEvent.click(secondLabelEl)
await fireEvent.click(secondLabelEl)
await fireEvent.click(thirdLabelEl)

// Then
expect(firstInput).toBeInTheDocument()
expect(firstInput).toHaveAttribute('name', 'name1')
expect(secondInput).toHaveAttribute('name', 'name2')
// @ts-expect-error This is a checkbox input event, so `checked` property is present
expect((firstInput).checked).toBe(true)
// @ts-expect-error This is a checkbox input event, so `checked` property is present
expect(secondInput.checked).toBe(false)
expect(thirdInput.checked).toBe(true)
})

it('should render no checkboxes', async () => {
Expand Down
Loading
Loading