@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.
Files changed (135) hide show
  1. package/dist/{DateFilter-XURUmpMl.js → DateFilter-uN8OURoP.js} +1 -1
  2. package/dist/{NumberFilter-BZc0O8wV.js → NumberFilter-sm1dQNQi.js} +1 -1
  3. package/dist/{PeriodFilter-ZNdXcl3p.js → PeriodFilter-Cklsxnh9.js} +1 -1
  4. package/dist/{SelectFilter-DshYU5OK.js → SelectFilter-CWefj27Z.js} +1 -1
  5. package/dist/{TextFilter-D_c5dRPl.js → TextFilter-Ddyj885L.js} +1 -1
  6. package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +160 -0
  7. package/dist/components/Customs/SyCheckBoxGroup/locales.d.ts +3 -0
  8. package/dist/components/Customs/SyCheckBoxGroup/types.d.ts +10 -0
  9. package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +1545 -2
  10. package/dist/components/Customs/SyRadioGroup/SyRadioGroup.d.ts +1495 -2
  11. package/dist/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.d.ts +60 -0
  12. package/dist/components/ErrorPage/ErrorPage.d.ts +1 -12
  13. package/dist/components/ErrorPage/locales.d.ts +18 -3
  14. package/dist/components/FileUpload/FileUpload.d.ts +2 -0
  15. package/dist/components/MaintenancePage/locales.d.ts +18 -2
  16. package/dist/components/NotFoundPage/locales.d.ts +20 -4
  17. package/dist/components/StatusPage/StatusPage.d.ts +39 -0
  18. package/dist/components/UploadWorkflow/UploadWorkflow.d.ts +13 -3
  19. package/dist/components/index.d.ts +3 -0
  20. package/dist/design-system-v3.js +126 -123
  21. package/dist/design-system-v3.umd.cjs +163 -163
  22. package/dist/{main-CuI6xaPq.js → main-CWniLr0s.js} +15191 -14668
  23. package/dist/style.css +1 -1
  24. package/dist/utils/theme/index.d.ts +6 -0
  25. package/package.json +7 -4
  26. package/src/components/ContextualMenu/ContextualMenu.stories.ts +0 -3
  27. package/src/components/ContextualMenu/accessibilite/Accessibility.mdx +67 -11
  28. package/src/components/CookieBanner/CookieBanner.stories.ts +11 -20
  29. package/src/components/CookieBanner/CookieBanner.vue +20 -5
  30. package/src/components/CookieBanner/accessibilite/Accessibility.mdx +67 -11
  31. package/src/components/CookieBanner/tests/CookieBanner.spec.ts +48 -4
  32. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.mdx +32 -0
  33. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.stories.ts +856 -0
  34. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.vue +334 -0
  35. package/src/components/Customs/SyCheckBoxGroup/accessibilite/Accessibility.mdx +243 -0
  36. package/src/components/Customs/SyCheckBoxGroup/locales.ts +3 -0
  37. package/src/components/Customs/SyCheckBoxGroup/tests/SyCheckBoxGroup.a11y.spec.ts +30 -0
  38. package/src/components/Customs/SyCheckBoxGroup/tests/SyCheckBoxGroup.spec.ts +152 -0
  39. package/src/components/Customs/SyCheckBoxGroup/types.ts +10 -0
  40. package/src/components/Customs/SyCheckbox/SyCheckbox.vue +16 -27
  41. package/src/components/Customs/SyCheckbox/accessibilite/Accessibility.mdx +1 -1
  42. package/src/components/Customs/SyForm/SyForm.a11y.spec.ts +1 -1
  43. package/src/components/Customs/SyRadioGroup/SyRadioGroup.vue +16 -43
  44. package/src/components/DatePicker/CalendarMode/DatePicker.vue +35 -11
  45. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.stories.ts +43 -2
  46. package/src/components/DatePicker/DateTextInput/DateTextInput.vue +48 -21
  47. package/src/components/DatePicker/DateTextInput/NoCalendar.stories.ts +98 -0
  48. package/src/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.mdx +83 -0
  49. package/src/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.stories.ts +502 -0
  50. package/src/components/DeclarationAccessibilityPage/DeclarationAccessibilityPage.vue +428 -0
  51. package/src/components/DeclarationAccessibilityPage/accessibilite/Accessibility.mdx +75 -0
  52. package/src/components/DeclarationAccessibilityPage/tests/DeclarationAccessibilityPage.a11y.spec.ts +53 -0
  53. package/src/components/DeclarationAccessibilityPage/tests/DeclarationAccessibilityPage.spec.ts +59 -0
  54. package/src/components/DiacriticPicker/DiacriticPicker.vue +20 -1
  55. package/src/components/ErrorPage/ErrorPage.mdx +6 -16
  56. package/src/components/ErrorPage/ErrorPage.stories.ts +16 -87
  57. package/src/components/ErrorPage/ErrorPage.vue +38 -125
  58. package/src/components/ErrorPage/accessibilite/Accessibility.mdx +68 -6
  59. package/src/components/ErrorPage/assets/error-ap.svg +1774 -0
  60. package/src/components/ErrorPage/locales.ts +21 -3
  61. package/src/components/ErrorPage/tests/ErrorPage.a11y.spec.ts +5 -13
  62. package/src/components/ErrorPage/tests/ErrorPage.spec.ts +2 -41
  63. package/src/components/ErrorPage/tests/__snapshots__/ErrorPage.spec.ts.snap +8 -266
  64. package/src/components/FileUpload/FileUpload.vue +5 -0
  65. package/src/components/FooterBar/FooterBar.stories.ts +18 -14
  66. package/src/components/FooterBar/defaultSocialMediaLinks.ts +6 -4
  67. package/src/components/MaintenancePage/MaintenancePage.mdx +1 -1
  68. package/src/components/MaintenancePage/MaintenancePage.vue +15 -7
  69. package/src/components/MaintenancePage/accessibilite/Accessibility.mdx +61 -6
  70. package/src/components/MaintenancePage/assets/maintenance-ap.svg +1718 -0
  71. package/src/components/MaintenancePage/locales.ts +24 -3
  72. package/src/components/MaintenancePage/tests/MaintenancePage.a11y.spec.ts +75 -3
  73. package/src/components/MaintenancePage/tests/MaintenancePage.spec.ts +42 -2
  74. package/src/components/MaintenancePage/tests/__snapshots__/MaintenancePage.spec.ts.snap +3 -2
  75. package/src/components/NotFoundPage/NotFoundPage.mdx +1 -1
  76. package/src/components/NotFoundPage/NotFoundPage.stories.ts +3 -3
  77. package/src/components/NotFoundPage/NotFoundPage.vue +16 -11
  78. package/src/components/NotFoundPage/accessibilite/Accessibility.mdx +78 -6
  79. package/src/components/NotFoundPage/assets/not-found-ap.svg +2061 -0
  80. package/src/components/NotFoundPage/locales.ts +24 -4
  81. package/src/components/NotFoundPage/tests/NotFoundPage.a11y.spec.ts +168 -4
  82. package/src/components/NotFoundPage/tests/NotFoundPage.spec.ts +100 -12
  83. package/src/components/NotFoundPage/tests/__snapshots__/NotFoundPage.spec.ts.snap +2 -2
  84. package/src/components/NotificationBar/NotificationBar.mdx +2 -2
  85. package/src/components/NotificationBar/accessibilite/Accessibility.mdx +68 -8
  86. package/src/components/PageContainer/tests/PageContainer.a11y.spec.ts +14 -7
  87. package/src/components/PhoneField/PhoneField.stories.ts +46 -38
  88. package/src/components/SocialMediaLinks/DefaultSocialMediaLinks.ts +6 -4
  89. package/src/components/SocialMediaLinks/SocialMediaLinks.mdx +7 -5
  90. package/src/components/SocialMediaLinks/SocialMediaLinks.stories.ts +17 -13
  91. package/src/components/SocialMediaLinks/SocialMediaLinks.vue +9 -1
  92. package/src/components/SocialMediaLinks/accessibilite/Accessibility.mdx +63 -11
  93. package/src/components/SocialMediaLinks/tests/DefaultSocialMediaLinks.spec.ts +5 -5
  94. package/src/components/SocialMediaLinks/tests/SocialMediaLinks.a11y.spec.ts +59 -0
  95. package/src/components/SocialMediaLinks/tests/SocialMediaLinks.spec.ts +9 -7
  96. package/src/components/StatusPage/StatusPage.mdx +22 -0
  97. package/src/components/StatusPage/StatusPage.stories.ts +193 -0
  98. package/src/components/StatusPage/StatusPage.vue +145 -0
  99. package/src/components/StatusPage/accessibilite/Accessibility.mdx +81 -0
  100. package/src/components/StatusPage/tests/StatusPage.a11y.spec.ts +29 -0
  101. package/src/components/StatusPage/tests/StatusPage.spec.ts +50 -0
  102. package/src/components/StatusPage/tests/__snapshots__/StatusPage.spec.ts.snap +270 -0
  103. package/src/components/TableToolbar/TableToolbar.stories.ts +6 -6
  104. package/src/components/TableToolbar/TableToolbar.vue +1 -1
  105. package/src/components/TableToolbar/tests/__snapshots__/TableToolbar.spec.ts.snap +0 -5
  106. package/src/components/UploadWorkflow/UploadWorkflow.mdx +11 -1
  107. package/src/components/UploadWorkflow/UploadWorkflow.stories.ts +107 -3
  108. package/src/components/UploadWorkflow/UploadWorkflow.vue +35 -24
  109. package/src/components/UploadWorkflow/tests/UploadWorkflow.spec.ts +48 -0
  110. package/src/components/UploadWorkflow/tests/__snapshots__/UploadWorkflow.spec.ts.snap +9 -5
  111. package/src/components/UploadWorkflow/useFileList.ts +7 -0
  112. package/src/components/index.ts +3 -0
  113. package/src/composables/rules/tests/useFieldValidation.spec.ts +39 -3
  114. package/src/composables/rules/useFieldValidation.ts +24 -9
  115. package/src/stories/Accessibilite/KitDePreAudit/Preaudit.mdx +7 -0
  116. package/src/utils/theme/index.ts +19 -0
  117. package/src/utils/theme/tests/useThemeLocales.spec.ts +245 -0
  118. package/dist/components/MaintenancePage/index.d.ts +0 -2
  119. package/src/components/Customs/SyPagination/tests/SyPagination.a11y.spec.ts +0 -27
  120. package/src/components/Customs/SyTabs/tests/SyTabs.a11y.spec.ts +0 -51
  121. package/src/components/DataListItem/tests/DataListItem.a11y.spec.ts +0 -31
  122. package/src/components/DatePicker/CalendarMode/tests/DatePicker.a11y.spec.ts +0 -27
  123. package/src/components/DatePicker/ComplexDatePicker/tests/ComplexDatePicker.a11y.spec.ts +0 -26
  124. package/src/components/DatePicker/DateTextInput/tests/DateTextInput.a11y.spec.ts +0 -27
  125. package/src/components/DownloadBtn/tests/DownloadBtn.a11y.spec.ts +0 -26
  126. package/src/components/ExternalLinks/tests/ExternalLinks.a11y.spec.ts +0 -39
  127. package/src/components/HeaderNavigationBar/tests/HeaderNavigationBar.a11y.spec.ts +0 -45
  128. package/src/components/HeaderToolbar/tests/HeaderToolbar.a11y.spec.ts +0 -25
  129. package/src/components/LunarCalendar/tests/LunarCalendar.a11y.spec.ts +0 -31
  130. package/src/components/MaintenancePage/index.ts +0 -3
  131. package/src/components/PageContainer/Accessibilite/AccessibilityGuide.mdx +0 -0
  132. package/src/components/PaginatedTable/tests/PaginatedTable.a11y.spec.ts +0 -43
  133. package/src/components/PhoneField/tests/PhoneField.a11y.spec.ts +0 -34
  134. /package/src/components/NotFoundPage/assets/{not-found.svg → not-found-cnam.svg} +0 -0
  135. /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,3 @@
1
+ export const locales = {
2
+ labelledbyMessage: 'Sélectionner l\'option',
3
+ }
@@ -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
+ })