@cnamts/synapse 1.0.20 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DateFilter-XURUmpMl.js → DateFilter-uN8OURoP.js} +1 -1
- package/dist/{NumberFilter-BZc0O8wV.js → NumberFilter-sm1dQNQi.js} +1 -1
- package/dist/{PeriodFilter-ZNdXcl3p.js → PeriodFilter-Cklsxnh9.js} +1 -1
- package/dist/{SelectFilter-DshYU5OK.js → SelectFilter-CWefj27Z.js} +1 -1
- package/dist/{TextFilter-D_c5dRPl.js → TextFilter-Ddyj885L.js} +1 -1
- package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +160 -0
- package/dist/components/Customs/SyCheckBoxGroup/locales.d.ts +3 -0
- package/dist/components/Customs/SyCheckBoxGroup/types.d.ts +10 -0
- package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +1545 -2
- package/dist/components/Customs/SyRadioGroup/SyRadioGroup.d.ts +1495 -2
- package/dist/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.d.ts +60 -0
- package/dist/components/ErrorPage/ErrorPage.d.ts +1 -12
- package/dist/components/ErrorPage/locales.d.ts +18 -3
- package/dist/components/FileUpload/FileUpload.d.ts +2 -0
- package/dist/components/MaintenancePage/locales.d.ts +18 -2
- package/dist/components/NotFoundPage/locales.d.ts +20 -4
- package/dist/components/StatusPage/StatusPage.d.ts +39 -0
- package/dist/components/UploadWorkflow/UploadWorkflow.d.ts +13 -3
- package/dist/components/index.d.ts +3 -0
- package/dist/design-system-v3.js +126 -123
- package/dist/design-system-v3.umd.cjs +163 -163
- package/dist/{main-CuI6xaPq.js → main-CWniLr0s.js} +15191 -14668
- package/dist/style.css +1 -1
- package/dist/utils/theme/index.d.ts +6 -0
- package/package.json +7 -4
- package/src/components/ContextualMenu/ContextualMenu.stories.ts +0 -3
- package/src/components/ContextualMenu/accessibilite/Accessibility.mdx +67 -11
- package/src/components/CookieBanner/CookieBanner.stories.ts +11 -20
- package/src/components/CookieBanner/CookieBanner.vue +20 -5
- package/src/components/CookieBanner/accessibilite/Accessibility.mdx +67 -11
- package/src/components/CookieBanner/tests/CookieBanner.spec.ts +48 -4
- package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.mdx +32 -0
- package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.stories.ts +856 -0
- package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.vue +334 -0
- package/src/components/Customs/SyCheckBoxGroup/accessibilite/Accessibility.mdx +243 -0
- package/src/components/Customs/SyCheckBoxGroup/locales.ts +3 -0
- package/src/components/Customs/SyCheckBoxGroup/tests/SyCheckBoxGroup.a11y.spec.ts +30 -0
- package/src/components/Customs/SyCheckBoxGroup/tests/SyCheckBoxGroup.spec.ts +152 -0
- package/src/components/Customs/SyCheckBoxGroup/types.ts +10 -0
- package/src/components/Customs/SyCheckbox/SyCheckbox.vue +16 -27
- package/src/components/Customs/SyCheckbox/accessibilite/Accessibility.mdx +1 -1
- package/src/components/Customs/SyForm/SyForm.a11y.spec.ts +1 -1
- package/src/components/Customs/SyRadioGroup/SyRadioGroup.vue +16 -43
- package/src/components/DatePicker/CalendarMode/DatePicker.vue +35 -11
- package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.stories.ts +43 -2
- package/src/components/DatePicker/DateTextInput/DateTextInput.vue +48 -21
- package/src/components/DatePicker/DateTextInput/NoCalendar.stories.ts +98 -0
- package/src/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.mdx +83 -0
- package/src/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.stories.ts +502 -0
- package/src/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.vue +428 -0
- package/src/components/DeclarationAccessibilityPage/accessibilite/Accessibility.mdx +75 -0
- package/src/components/DeclarationAccessibilityPage/tests/DeclarationAccessibilityPage.a11y.spec.ts +53 -0
- package/src/components/DeclarationAccessibilityPage/tests/DeclarationAccessibilityPage.spec.ts +59 -0
- package/src/components/DiacriticPicker/DiacriticPicker.vue +20 -1
- package/src/components/ErrorPage/ErrorPage.mdx +6 -16
- package/src/components/ErrorPage/ErrorPage.stories.ts +16 -87
- package/src/components/ErrorPage/ErrorPage.vue +38 -125
- package/src/components/ErrorPage/accessibilite/Accessibility.mdx +68 -6
- package/src/components/ErrorPage/assets/error-ap.svg +1774 -0
- package/src/components/ErrorPage/locales.ts +21 -3
- package/src/components/ErrorPage/tests/ErrorPage.a11y.spec.ts +5 -13
- package/src/components/ErrorPage/tests/ErrorPage.spec.ts +2 -41
- package/src/components/ErrorPage/tests/__snapshots__/ErrorPage.spec.ts.snap +8 -266
- package/src/components/FileUpload/FileUpload.vue +5 -0
- package/src/components/FooterBar/FooterBar.stories.ts +18 -14
- package/src/components/FooterBar/defaultSocialMediaLinks.ts +6 -4
- package/src/components/MaintenancePage/MaintenancePage.mdx +1 -1
- package/src/components/MaintenancePage/MaintenancePage.vue +15 -7
- package/src/components/MaintenancePage/accessibilite/Accessibility.mdx +61 -6
- package/src/components/MaintenancePage/assets/maintenance-ap.svg +1718 -0
- package/src/components/MaintenancePage/locales.ts +24 -3
- package/src/components/MaintenancePage/tests/MaintenancePage.a11y.spec.ts +75 -3
- package/src/components/MaintenancePage/tests/MaintenancePage.spec.ts +42 -2
- package/src/components/MaintenancePage/tests/__snapshots__/MaintenancePage.spec.ts.snap +3 -2
- package/src/components/NotFoundPage/NotFoundPage.mdx +1 -1
- package/src/components/NotFoundPage/NotFoundPage.stories.ts +3 -3
- package/src/components/NotFoundPage/NotFoundPage.vue +16 -11
- package/src/components/NotFoundPage/accessibilite/Accessibility.mdx +78 -6
- package/src/components/NotFoundPage/assets/not-found-ap.svg +2061 -0
- package/src/components/NotFoundPage/locales.ts +24 -4
- package/src/components/NotFoundPage/tests/NotFoundPage.a11y.spec.ts +168 -4
- package/src/components/NotFoundPage/tests/NotFoundPage.spec.ts +100 -12
- package/src/components/NotFoundPage/tests/__snapshots__/NotFoundPage.spec.ts.snap +2 -2
- package/src/components/NotificationBar/NotificationBar.mdx +2 -2
- package/src/components/NotificationBar/accessibilite/Accessibility.mdx +68 -8
- package/src/components/PageContainer/tests/PageContainer.a11y.spec.ts +14 -7
- package/src/components/PhoneField/PhoneField.stories.ts +46 -38
- package/src/components/SocialMediaLinks/DefaultSocialMediaLinks.ts +6 -4
- package/src/components/SocialMediaLinks/SocialMediaLinks.mdx +7 -5
- package/src/components/SocialMediaLinks/SocialMediaLinks.stories.ts +17 -13
- package/src/components/SocialMediaLinks/SocialMediaLinks.vue +9 -1
- package/src/components/SocialMediaLinks/accessibilite/Accessibility.mdx +63 -11
- package/src/components/SocialMediaLinks/tests/DefaultSocialMediaLinks.spec.ts +5 -5
- package/src/components/SocialMediaLinks/tests/SocialMediaLinks.a11y.spec.ts +59 -0
- package/src/components/SocialMediaLinks/tests/SocialMediaLinks.spec.ts +9 -7
- package/src/components/StatusPage/StatusPage.mdx +22 -0
- package/src/components/StatusPage/StatusPage.stories.ts +193 -0
- package/src/components/StatusPage/StatusPage.vue +145 -0
- package/src/components/StatusPage/accessibilite/Accessibility.mdx +81 -0
- package/src/components/StatusPage/tests/StatusPage.a11y.spec.ts +29 -0
- package/src/components/StatusPage/tests/StatusPage.spec.ts +50 -0
- package/src/components/StatusPage/tests/__snapshots__/StatusPage.spec.ts.snap +270 -0
- package/src/components/TableToolbar/TableToolbar.stories.ts +6 -6
- package/src/components/TableToolbar/TableToolbar.vue +1 -1
- package/src/components/TableToolbar/tests/__snapshots__/TableToolbar.spec.ts.snap +0 -5
- package/src/components/UploadWorkflow/UploadWorkflow.mdx +11 -1
- package/src/components/UploadWorkflow/UploadWorkflow.stories.ts +107 -3
- package/src/components/UploadWorkflow/UploadWorkflow.vue +35 -24
- package/src/components/UploadWorkflow/tests/UploadWorkflow.spec.ts +48 -0
- package/src/components/UploadWorkflow/tests/__snapshots__/UploadWorkflow.spec.ts.snap +9 -5
- package/src/components/UploadWorkflow/useFileList.ts +7 -0
- package/src/components/index.ts +3 -0
- package/src/composables/rules/tests/useFieldValidation.spec.ts +39 -3
- package/src/composables/rules/useFieldValidation.ts +24 -9
- package/src/stories/Accessibilite/KitDePreAudit/Preaudit.mdx +7 -0
- package/src/utils/theme/index.ts +19 -0
- package/src/utils/theme/tests/useThemeLocales.spec.ts +245 -0
- package/dist/components/MaintenancePage/index.d.ts +0 -2
- package/src/components/Customs/SyPagination/tests/SyPagination.a11y.spec.ts +0 -27
- package/src/components/Customs/SyTabs/tests/SyTabs.a11y.spec.ts +0 -51
- package/src/components/DataListItem/tests/DataListItem.a11y.spec.ts +0 -31
- package/src/components/DatePicker/CalendarMode/tests/DatePicker.a11y.spec.ts +0 -27
- package/src/components/DatePicker/ComplexDatePicker/tests/ComplexDatePicker.a11y.spec.ts +0 -26
- package/src/components/DatePicker/DateTextInput/tests/DateTextInput.a11y.spec.ts +0 -27
- package/src/components/DownloadBtn/tests/DownloadBtn.a11y.spec.ts +0 -26
- package/src/components/ExternalLinks/tests/ExternalLinks.a11y.spec.ts +0 -39
- package/src/components/HeaderNavigationBar/tests/HeaderNavigationBar.a11y.spec.ts +0 -45
- package/src/components/HeaderToolbar/tests/HeaderToolbar.a11y.spec.ts +0 -25
- package/src/components/LunarCalendar/tests/LunarCalendar.a11y.spec.ts +0 -31
- package/src/components/MaintenancePage/index.ts +0 -3
- package/src/components/PageContainer/Accessibilite/AccessibilityGuide.mdx +0 -0
- package/src/components/PaginatedTable/tests/PaginatedTable.a11y.spec.ts +0 -43
- package/src/components/PhoneField/tests/PhoneField.a11y.spec.ts +0 -34
- /package/src/components/NotFoundPage/assets/{not-found.svg → not-found-cnam.svg} +0 -0
- /package/src/components/PageContainer/{Accessibilite → accessibilite}/Accessibility.mdx +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
3
|
+
import { VMessages } from 'vuetify/components'
|
|
4
|
+
import { useValidation, type ValidationRule } from '@/composables/validation/useValidation'
|
|
5
|
+
import { useValidatable } from '@/composables/validation/useValidatable'
|
|
6
|
+
import SyCheckbox from '@/components/Customs/SyCheckbox/SyCheckbox.vue'
|
|
7
|
+
import { locales } from './locales'
|
|
8
|
+
import type { Option } from './types'
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(
|
|
11
|
+
defineProps<{
|
|
12
|
+
ariaLabel?: string
|
|
13
|
+
ariaLabelledby?: string
|
|
14
|
+
color?: string
|
|
15
|
+
customRules?: ValidationRule[]
|
|
16
|
+
customSuccessRules?: ValidationRule[]
|
|
17
|
+
customWarningRules?: ValidationRule[]
|
|
18
|
+
density?: 'default' | 'comfortable' | 'compact'
|
|
19
|
+
disabled?: boolean
|
|
20
|
+
disableErrorHandling?: boolean
|
|
21
|
+
displayAsterisk?: boolean
|
|
22
|
+
errorMessages?: string[] | null
|
|
23
|
+
hideDetails?: boolean | 'auto'
|
|
24
|
+
id?: string
|
|
25
|
+
isValidateOnBlur?: boolean
|
|
26
|
+
label?: string
|
|
27
|
+
modelValue?: (string | number) | (string | number)[] | null
|
|
28
|
+
multiple?: boolean
|
|
29
|
+
name?: string
|
|
30
|
+
options?: Option[]
|
|
31
|
+
readonly?: boolean
|
|
32
|
+
required?: boolean
|
|
33
|
+
showSuccessMessages?: boolean
|
|
34
|
+
successMessages?: string[] | null
|
|
35
|
+
title?: string
|
|
36
|
+
warningMessages?: string[] | null
|
|
37
|
+
}>(),
|
|
38
|
+
{
|
|
39
|
+
ariaLabel: undefined,
|
|
40
|
+
ariaLabelledby: undefined,
|
|
41
|
+
color: 'primary',
|
|
42
|
+
customRules: () => [],
|
|
43
|
+
customSuccessRules: () => [],
|
|
44
|
+
customWarningRules: () => [],
|
|
45
|
+
density: 'default',
|
|
46
|
+
disabled: false,
|
|
47
|
+
disableErrorHandling: false,
|
|
48
|
+
displayAsterisk: false,
|
|
49
|
+
errorMessages: null,
|
|
50
|
+
hideDetails: 'auto',
|
|
51
|
+
id: undefined,
|
|
52
|
+
isValidateOnBlur: false,
|
|
53
|
+
label: undefined,
|
|
54
|
+
modelValue: null,
|
|
55
|
+
multiple: false,
|
|
56
|
+
name: undefined,
|
|
57
|
+
options: () => [],
|
|
58
|
+
readonly: false,
|
|
59
|
+
required: false,
|
|
60
|
+
showSuccessMessages: true,
|
|
61
|
+
successMessages: null,
|
|
62
|
+
title: undefined,
|
|
63
|
+
warningMessages: null,
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const emit = defineEmits(['update:modelValue', 'change'])
|
|
68
|
+
|
|
69
|
+
const isMultiple = computed(() => props.multiple)
|
|
70
|
+
|
|
71
|
+
const model = computed({
|
|
72
|
+
get() {
|
|
73
|
+
return props.modelValue
|
|
74
|
+
},
|
|
75
|
+
set(value) {
|
|
76
|
+
emit('update:modelValue', value)
|
|
77
|
+
emit('change', value)
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const generatedLabel = computed(() => (props.label || '') + (props.displayAsterisk ? '*' : ''))
|
|
82
|
+
|
|
83
|
+
const isSubmitted = ref(false)
|
|
84
|
+
|
|
85
|
+
const validation = useValidation({
|
|
86
|
+
customRules: props.customRules,
|
|
87
|
+
warningRules: props.customWarningRules,
|
|
88
|
+
successRules: props.customSuccessRules,
|
|
89
|
+
showSuccessMessages: props.showSuccessMessages,
|
|
90
|
+
fieldIdentifier: props.label,
|
|
91
|
+
disableErrorHandling: props.disableErrorHandling,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
watch(() => props.errorMessages, value => (validation.errors.value = value || []), { immediate: true })
|
|
95
|
+
watch(() => props.warningMessages, value => (validation.warnings.value = value || []), { immediate: true })
|
|
96
|
+
watch(() => props.successMessages, value => (validation.successes.value = value || []), { immediate: true })
|
|
97
|
+
|
|
98
|
+
const defaultRules = computed<ValidationRule[]>(() =>
|
|
99
|
+
props.required
|
|
100
|
+
? [{
|
|
101
|
+
type: 'required',
|
|
102
|
+
options: {
|
|
103
|
+
message: `Le champ ${props.label || 'ce champ'} est requis.`,
|
|
104
|
+
fieldIdentifier: props.label,
|
|
105
|
+
},
|
|
106
|
+
}]
|
|
107
|
+
: [],
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
function isOptionChecked(value: string | number): boolean {
|
|
111
|
+
if (isMultiple.value) {
|
|
112
|
+
return Array.isArray(model.value) && model.value.includes(value)
|
|
113
|
+
}
|
|
114
|
+
return model.value === value
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function toggleOption(value: string | number): void {
|
|
118
|
+
if (props.readonly || props.disabled) return
|
|
119
|
+
|
|
120
|
+
if (isMultiple.value) {
|
|
121
|
+
const current = Array.isArray(model.value) ? model.value : []
|
|
122
|
+
model.value = current.includes(value)
|
|
123
|
+
? current.filter(v => v !== value)
|
|
124
|
+
: [...current, value]
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
model.value = model.value === value ? null : value
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getValidationValue(): (string | number) | (string | number)[] | null {
|
|
132
|
+
if (isMultiple.value) {
|
|
133
|
+
return Array.isArray(model.value) ? model.value : []
|
|
134
|
+
}
|
|
135
|
+
return model.value as (string | number) | null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const validateField = (value: (string | number) | (string | number)[] | null) => {
|
|
139
|
+
if (props.readonly) {
|
|
140
|
+
validation.clearValidation()
|
|
141
|
+
return true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!props.required && (value === null || (Array.isArray(value) && value.length === 0))) {
|
|
145
|
+
validation.clearValidation()
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const result = validation.validateField(
|
|
150
|
+
value,
|
|
151
|
+
[...defaultRules.value, ...props.customRules],
|
|
152
|
+
props.customWarningRules,
|
|
153
|
+
props.customSuccessRules,
|
|
154
|
+
)
|
|
155
|
+
return !result.hasError
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const validateOnSubmit = () => {
|
|
159
|
+
isSubmitted.value = true
|
|
160
|
+
return validateField(getValidationValue())
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const checkErrorOnBlur = () => {
|
|
164
|
+
validateField(getValidationValue())
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
watch(model, (newValue) => {
|
|
168
|
+
if (!props.isValidateOnBlur || isSubmitted.value) {
|
|
169
|
+
validateField(newValue as (string | number) | (string | number)[] | null)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const hasError = computed(() => validation.hasError.value)
|
|
174
|
+
const hasWarning = computed(() => validation.hasWarning.value)
|
|
175
|
+
const hasSuccess = computed(() => validation.hasSuccess.value)
|
|
176
|
+
|
|
177
|
+
const checkboxColor = computed(() => (hasError.value ? 'error' : props.color))
|
|
178
|
+
|
|
179
|
+
const errors = computed(() => validation.errors.value)
|
|
180
|
+
const warnings = computed(() => validation.warnings.value)
|
|
181
|
+
const successes = computed(() => validation.successes.value)
|
|
182
|
+
|
|
183
|
+
const labelId = computed(() => (props.id ? `${props.id}-label` : undefined))
|
|
184
|
+
const computedAriaLabelledby = computed(() => {
|
|
185
|
+
if (props.label && labelId.value) {
|
|
186
|
+
return [props.ariaLabelledby, labelId.value].filter(Boolean).join(' ')
|
|
187
|
+
}
|
|
188
|
+
return props.ariaLabelledby
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const messagesRef = ref<InstanceType<typeof VMessages> | null>(null)
|
|
192
|
+
const vMessagesId = computed(() => messagesRef.value?.$el.id)
|
|
193
|
+
|
|
194
|
+
const requiredHintId = computed(() => (props.id ? `${props.id}-required-hint` : undefined))
|
|
195
|
+
const computedAriaDescribedby = computed(() => {
|
|
196
|
+
const ids: string[] = []
|
|
197
|
+
|
|
198
|
+
const shouldShowMessages = props.hideDetails !== true && (hasError.value || hasWarning.value || (hasSuccess.value && props.showSuccessMessages))
|
|
199
|
+
if (vMessagesId.value && shouldShowMessages) {
|
|
200
|
+
ids.push(vMessagesId.value)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (requiredHintId.value && props.required && !props.ariaLabel && !computedAriaLabelledby.value) {
|
|
204
|
+
ids.push(requiredHintId.value)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return ids.length > 0 ? ids.join(' ') : undefined
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
onMounted(() => {
|
|
211
|
+
if (!props.isValidateOnBlur && !props.required) {
|
|
212
|
+
validateField(getValidationValue())
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
useValidatable(validateOnSubmit)
|
|
217
|
+
|
|
218
|
+
defineExpose({
|
|
219
|
+
validation,
|
|
220
|
+
validateOnSubmit,
|
|
221
|
+
checkErrorOnBlur,
|
|
222
|
+
})
|
|
223
|
+
</script>
|
|
224
|
+
|
|
225
|
+
<template>
|
|
226
|
+
<fieldset
|
|
227
|
+
:id="props.id"
|
|
228
|
+
class="sy-checkbox-group"
|
|
229
|
+
:class="{
|
|
230
|
+
'warning-field': hasWarning && !hasError,
|
|
231
|
+
'success-field': hasSuccess && !hasError && !hasWarning,
|
|
232
|
+
'error-field': hasError,
|
|
233
|
+
}"
|
|
234
|
+
:aria-describedby="computedAriaDescribedby"
|
|
235
|
+
:title="props.title"
|
|
236
|
+
>
|
|
237
|
+
<legend
|
|
238
|
+
v-if="props.label"
|
|
239
|
+
:id="labelId"
|
|
240
|
+
class="v-label sy-checkbox-group__label"
|
|
241
|
+
>
|
|
242
|
+
{{ generatedLabel }}
|
|
243
|
+
</legend>
|
|
244
|
+
|
|
245
|
+
<div
|
|
246
|
+
class="sy-checkbox-group__options"
|
|
247
|
+
>
|
|
248
|
+
<SyCheckbox
|
|
249
|
+
v-for="opt in props.options"
|
|
250
|
+
:id="opt.id"
|
|
251
|
+
:key="opt.value"
|
|
252
|
+
:model-value="isOptionChecked(opt.value)"
|
|
253
|
+
:label="opt.label"
|
|
254
|
+
:color="checkboxColor"
|
|
255
|
+
:disabled="props.disabled || opt.disabled"
|
|
256
|
+
:readonly="props.readonly || opt.readonly"
|
|
257
|
+
:name="opt.name || props.name"
|
|
258
|
+
:aria-label="opt.ariaLabel || `Option ${opt.value}`"
|
|
259
|
+
:title="opt.title"
|
|
260
|
+
:hide-details="props.hideDetails"
|
|
261
|
+
:density="props.density"
|
|
262
|
+
@update:model-value="() => toggleOption(opt.value)"
|
|
263
|
+
@blur="checkErrorOnBlur"
|
|
264
|
+
/>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<div
|
|
268
|
+
v-if="props.hideDetails !== true && (hasError || hasWarning || (hasSuccess && props.showSuccessMessages))"
|
|
269
|
+
class="v-input__details sy-checkbox-group__messages"
|
|
270
|
+
>
|
|
271
|
+
<VMessages
|
|
272
|
+
ref="messagesRef"
|
|
273
|
+
:active="hasError || hasWarning || (hasSuccess && props.showSuccessMessages)"
|
|
274
|
+
:messages="hasError ? errors : (hasWarning ? warnings : (hasSuccess && props.showSuccessMessages ? successes : []))"
|
|
275
|
+
/>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<span
|
|
279
|
+
v-if="requiredHintId && props.required && !props.ariaLabel && !computedAriaLabelledby"
|
|
280
|
+
:id="requiredHintId"
|
|
281
|
+
class="d-sr-only"
|
|
282
|
+
>
|
|
283
|
+
{{ locales.labelledbyMessage }} <span v-if="props.label">{{ props.label + (props.displayAsterisk ? '*' : '')
|
|
284
|
+
}}</span>.
|
|
285
|
+
</span>
|
|
286
|
+
</fieldset>
|
|
287
|
+
</template>
|
|
288
|
+
|
|
289
|
+
<style scoped>
|
|
290
|
+
.sy-checkbox-group {
|
|
291
|
+
border: 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.sy-checkbox-group__label {
|
|
295
|
+
margin-bottom: 4px;
|
|
296
|
+
font-weight: 500;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
:deep(.v-messages) {
|
|
300
|
+
opacity: 1;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.warning-field :deep(.v-messages__message) {
|
|
304
|
+
color: rgb(var(--v-theme-warning)) !important;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.error-field :deep(.v-messages__message) {
|
|
308
|
+
color: rgb(var(--v-theme-error)) !important;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.error-field :deep(.v-selection-control__input > .v-icon) {
|
|
312
|
+
opacity: 1 !important;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.success-field :deep(.v-messages__message) {
|
|
316
|
+
color: rgb(var(--v-theme-success)) !important;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
:deep(.v-messages__message) {
|
|
320
|
+
animation: sy-messages-in 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
@keyframes sy-messages-in {
|
|
324
|
+
from {
|
|
325
|
+
opacity: 0;
|
|
326
|
+
transform: translateY(-8px);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
to {
|
|
330
|
+
opacity: 1;
|
|
331
|
+
transform: translateY(0);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
</style>
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { Meta, Primary } from '@storybook/blocks';
|
|
2
|
+
import * as SyCheckBoxGroupStories from '../SyCheckBoxGroup.stories.ts';
|
|
3
|
+
import AccessibilityIcon from '@/common/imgs/accessibility-svgrepo-com.svg';
|
|
4
|
+
import '@/stories/styles/shared.css';
|
|
5
|
+
|
|
6
|
+
<Meta of={SyCheckBoxGroupStories} name="Accessibility" />
|
|
7
|
+
|
|
8
|
+
<div className="accessibility-guide">
|
|
9
|
+
<div className="header">
|
|
10
|
+
<h1>Guide d'Accessibilité du Composant SyCheckBoxGroup</h1>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div className="intro-section">
|
|
14
|
+
<img
|
|
15
|
+
src={AccessibilityIcon}
|
|
16
|
+
alt="Icône d'accessibilité"
|
|
17
|
+
className="accessibility-icon"
|
|
18
|
+
/>
|
|
19
|
+
<p className="intro-text">
|
|
20
|
+
Le composant SyCheckBoxGroup a été conçu en suivant rigoureusement les recommandations d'accessibilité du W3C,
|
|
21
|
+
notamment le modèle <a href="https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/" target="_blank" rel="noopener noreferrer">WAI-ARIA pour les cases à cocher</a>.
|
|
22
|
+
Ce guide détaille comment notre implémentation respecte ces standards et garantit une expérience utilisateur inclusive.
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div className="criteria-section">
|
|
27
|
+
<h2>Critères d'accessibilité respectés</h2>
|
|
28
|
+
|
|
29
|
+
<div className="criteria-card">
|
|
30
|
+
<div className="criteria-header">
|
|
31
|
+
<span className="criteria-icon">🔍</span>
|
|
32
|
+
<h3>Structure sémantique</h3>
|
|
33
|
+
</div>
|
|
34
|
+
<ul>
|
|
35
|
+
<li><strong>Regroupement</strong> : Les cases à cocher sont regroupées dans une structure cohérente (fieldset/legend ou équivalent).</li>
|
|
36
|
+
<li><strong>Étiquetage explicite</strong> : Le groupe dispose d’un libellé clair via la prop <code>label</code>.</li>
|
|
37
|
+
<li><strong>Association label / contrôle</strong> : Chaque option dispose d’un label associé à la case à cocher.</li>
|
|
38
|
+
</ul>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div className="criteria-card">
|
|
42
|
+
<div className="criteria-header">
|
|
43
|
+
<span className="criteria-icon">⌨️</span>
|
|
44
|
+
<h3>Navigation clavier complète</h3>
|
|
45
|
+
</div>
|
|
46
|
+
<ul>
|
|
47
|
+
<li><strong>Touche Tab</strong> : Navigation séquentielle à travers les cases à cocher.</li>
|
|
48
|
+
<li><strong>Touche Espace</strong> : (Dé)cocher une option.</li>
|
|
49
|
+
<li><strong>Focus visible</strong> : Indication claire de l’élément actuellement focalisé.</li>
|
|
50
|
+
</ul>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div className="criteria-card">
|
|
54
|
+
<div className="criteria-header">
|
|
55
|
+
<span className="criteria-icon">📱</span>
|
|
56
|
+
<h3>États et retours d'information</h3>
|
|
57
|
+
</div>
|
|
58
|
+
<ul>
|
|
59
|
+
<li><strong>État requis</strong> : Support du mode <code>required</code> et affichage d’un message d’erreur en cas de soumission invalide.</li>
|
|
60
|
+
<li><strong>États disabled / readonly</strong> : Les états sont communiqués visuellement et empêchent l’interaction selon le cas.</li>
|
|
61
|
+
<li><strong>Retour textuel</strong> : Les messages de validation sont affichés dans la zone dédiée aux détails.</li>
|
|
62
|
+
</ul>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="criteria-card">
|
|
66
|
+
<div className="criteria-header">
|
|
67
|
+
<span className="criteria-icon">🎨</span>
|
|
68
|
+
<h3>Personnalisation accessible</h3>
|
|
69
|
+
</div>
|
|
70
|
+
<ul>
|
|
71
|
+
<li><strong>Densité ajustable</strong> : La prop <code>density</code> permet d’adapter l’espacement sans casser l’accessibilité.</li>
|
|
72
|
+
<li><strong>Couleurs</strong> : La prop <code>color</code> permet d’adapter l’apparence tout en conservant les états d’erreur (prioritaires).</li>
|
|
73
|
+
</ul>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div className="demo-section">
|
|
78
|
+
<h2>Démonstration interactive</h2>
|
|
79
|
+
<p>
|
|
80
|
+
Explorez ci-dessous un exemple de SyCheckBoxGroup. Essayez de naviguer en utilisant uniquement votre clavier (Tab pour naviguer, Espace pour activer)
|
|
81
|
+
pour tester l'accessibilité.
|
|
82
|
+
</p>
|
|
83
|
+
<Primary />
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div className="best-practices">
|
|
87
|
+
<h2>Bonnes pratiques d'utilisation</h2>
|
|
88
|
+
<ul>
|
|
89
|
+
<li>Utilisez un libellé de groupe explicite et des libellés d’options concis.</li>
|
|
90
|
+
<li>Pour un groupe requis, expliquez clairement la contrainte (ex. « choisissez au moins une option »).</li>
|
|
91
|
+
<li>Évitez de baser le sens uniquement sur la couleur (toujours fournir un message textuel).</li>
|
|
92
|
+
<li>Assurez-vous que les zones cliquables restent confortables sur mobile.</li>
|
|
93
|
+
</ul>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div className="resources-section">
|
|
97
|
+
<h2>Ressources supplémentaires</h2>
|
|
98
|
+
<ul>
|
|
99
|
+
<li><a href="https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/" target="_blank" rel="noopener noreferrer">Guide des pratiques d'auteur WAI-ARIA : Checkbox</a></li>
|
|
100
|
+
<li><a href="https://www.w3.org/WAI/WCAG21/quickref/" target="_blank" rel="noopener noreferrer">Référence rapide WCAG 2.1</a></li>
|
|
101
|
+
<li><a href="https://www.w3.org/TR/wai-aria-1.2/#checkbox" target="_blank" rel="noopener noreferrer">Spécification WAI-ARIA 1.2 : role checkbox</a></li>
|
|
102
|
+
</ul>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<style>
|
|
107
|
+
{`
|
|
108
|
+
.accessibility-guide {
|
|
109
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
110
|
+
max-width: 1200px;
|
|
111
|
+
margin: 0 auto;
|
|
112
|
+
padding: 20px;
|
|
113
|
+
color: #333;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.intro-section {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
margin-bottom: 30px;
|
|
120
|
+
background-color: #f8f9fa;
|
|
121
|
+
padding: 20px;
|
|
122
|
+
border-radius: 8px;
|
|
123
|
+
border-left: 5px solid #0077cc;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.accessibility-icon {
|
|
127
|
+
width: 60px;
|
|
128
|
+
height: 60px;
|
|
129
|
+
margin-right: 20px;
|
|
130
|
+
flex-shrink: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.intro-text {
|
|
134
|
+
font-size: 1.1em;
|
|
135
|
+
line-height: 1.6;
|
|
136
|
+
margin: 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.criteria-section {
|
|
140
|
+
margin-bottom: 40px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.criteria-section h2,
|
|
144
|
+
.demo-section h2,
|
|
145
|
+
.best-practices h2,
|
|
146
|
+
.resources-section h2 {
|
|
147
|
+
border-bottom: 2px solid #eaecef;
|
|
148
|
+
padding-bottom: 10px;
|
|
149
|
+
margin-top: 30px;
|
|
150
|
+
margin-bottom: 20px;
|
|
151
|
+
color: #0077cc;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.criteria-card {
|
|
155
|
+
background-color: #fff;
|
|
156
|
+
border-radius: 8px;
|
|
157
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
158
|
+
padding: 20px;
|
|
159
|
+
margin-bottom: 20px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.criteria-header {
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
margin-bottom: 15px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.criteria-icon {
|
|
169
|
+
font-size: 1.8em;
|
|
170
|
+
margin-right: 15px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.criteria-header h3 {
|
|
174
|
+
margin: 0;
|
|
175
|
+
font-size: 1.3em;
|
|
176
|
+
color: #0077cc;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.criteria-card ul {
|
|
180
|
+
margin: 0;
|
|
181
|
+
padding-left: 20px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.criteria-card li {
|
|
185
|
+
margin-bottom: 8px;
|
|
186
|
+
line-height: 1.5;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.demo-section {
|
|
190
|
+
background-color: #f8f9fa;
|
|
191
|
+
padding: 20px;
|
|
192
|
+
border-radius: 8px;
|
|
193
|
+
margin-bottom: 40px;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.best-practices {
|
|
197
|
+
background-color: #f5f5f5;
|
|
198
|
+
padding: 20px;
|
|
199
|
+
border-radius: 8px;
|
|
200
|
+
margin-bottom: 30px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.best-practices ul {
|
|
204
|
+
padding-left: 20px;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.best-practices li {
|
|
208
|
+
margin-bottom: 10px;
|
|
209
|
+
line-height: 1.5;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.resources-section {
|
|
213
|
+
background-color: #f8f9fa;
|
|
214
|
+
padding: 20px;
|
|
215
|
+
border-radius: 8px;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.resources-section ul {
|
|
219
|
+
padding-left: 20px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.resources-section li {
|
|
223
|
+
margin-bottom: 10px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.resources-section a {
|
|
227
|
+
color: #0077cc;
|
|
228
|
+
text-decoration: none;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.resources-section a:hover {
|
|
232
|
+
text-decoration: underline;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
code {
|
|
236
|
+
background-color: #f0f0f0;
|
|
237
|
+
padding: 2px 5px;
|
|
238
|
+
border-radius: 3px;
|
|
239
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
240
|
+
font-size: 0.9em;
|
|
241
|
+
}
|
|
242
|
+
`}
|
|
243
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { describe, it } from 'vitest'
|
|
4
|
+
import { mount } from '@vue/test-utils'
|
|
5
|
+
import { axe } from 'vitest-axe'
|
|
6
|
+
import { assertNoA11yViolations } from '@tests/unit/accessibility/axeUtils'
|
|
7
|
+
import SyCheckBoxGroup from '../SyCheckBoxGroup.vue'
|
|
8
|
+
|
|
9
|
+
// Scénario d’accessibilité : groupe de cases à cocher requis avec label.
|
|
10
|
+
|
|
11
|
+
describe('SyCheckBoxGroup – accessibility (axe)', () => {
|
|
12
|
+
it('has no obvious axe violations for required checkbox group', async () => {
|
|
13
|
+
const wrapper = mount(SyCheckBoxGroup, {
|
|
14
|
+
props: {
|
|
15
|
+
label: 'Choisissez une option',
|
|
16
|
+
modelValue: null,
|
|
17
|
+
required: true,
|
|
18
|
+
options: [
|
|
19
|
+
{ label: 'Option A', value: 'A', id: 'opt-a' },
|
|
20
|
+
{ label: 'Option B', value: 'B', id: 'opt-b' },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const results = await axe(wrapper.element as HTMLElement)
|
|
26
|
+
assertNoA11yViolations(results, 'SyCheckBoxGroup – required group', {
|
|
27
|
+
ignoreRules: ['region'],
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
})
|