@cnamts/synapse 0.0.5-alpha → 0.0.7-alpha

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 (229) hide show
  1. package/dist/design-system-v3.d.ts +487 -415
  2. package/dist/design-system-v3.js +2954 -2203
  3. package/dist/design-system-v3.umd.cjs +6 -6
  4. package/dist/style.css +1 -1
  5. package/package.json +17 -17
  6. package/src/assets/settings.scss +3 -0
  7. package/src/assets/tokens.scss +16 -16
  8. package/src/components/BackBtn/AccessibiliteItems.ts +0 -30
  9. package/src/components/BackBtn/BackBtn.mdx +1 -1
  10. package/src/components/BackBtn/BackBtn.stories.ts +12 -9
  11. package/src/components/BackBtn/BackBtn.vue +0 -1
  12. package/src/components/BackToTopBtn/AccessibiliteItems.ts +0 -30
  13. package/src/components/BackToTopBtn/BackToTopBtn.mdx +1 -1
  14. package/src/components/BackToTopBtn/BackToTopBtn.stories.ts +3 -3
  15. package/src/components/CollapsibleList/Accessibilite.stories.ts +4 -0
  16. package/src/components/CollapsibleList/AccessibiliteItems.ts +55 -62
  17. package/src/components/CollapsibleList/CollapsibleList.stories.ts +1 -1
  18. package/src/components/CookieBanner/Accessibilite.stories.ts +219 -0
  19. package/src/components/CookieBanner/AccessibiliteItems.ts +356 -0
  20. package/src/components/CookieBanner/CookieBanner.mdx +219 -0
  21. package/src/components/CookieBanner/CookieBanner.stories.ts +680 -0
  22. package/src/components/CookieBanner/CookieBanner.vue +225 -0
  23. package/src/components/CookieBanner/config.ts +38 -0
  24. package/src/components/CookieBanner/locales.ts +12 -0
  25. package/src/components/CookieBanner/tests/CookieBanner.spec.ts +129 -0
  26. package/src/components/CookieBanner/tests/__snapshots__/CookieBanner.spec.ts.snap +197 -0
  27. package/src/components/CookiesSelection/CookiesInformation/CookiesInformation.vue +123 -0
  28. package/src/components/CookiesSelection/CookiesInformation/locales.ts +21 -0
  29. package/src/components/CookiesSelection/CookiesInformation/tests/CookiesInformation.spec.ts +121 -0
  30. package/src/components/CookiesSelection/CookiesSelection.vue +127 -0
  31. package/src/components/CookiesSelection/CookiesTable/CookiesTable.vue +47 -0
  32. package/src/components/CookiesSelection/CookiesTable/headers.ts +14 -0
  33. package/src/components/CookiesSelection/CookiesTable/tests/CookiesTable.spec.ts +30 -0
  34. package/src/components/CookiesSelection/CookiesTable/tests/__snapshots__/CookiesTable.spec.ts.snap +3 -0
  35. package/src/components/CookiesSelection/locales.ts +10 -0
  36. package/src/components/CookiesSelection/tests/CookiesSelection.spec.ts +184 -0
  37. package/src/components/CookiesSelection/tests/__snapshots__/CookiesSelection.spec.ts.snap +192 -0
  38. package/src/components/CookiesSelection/types.ts +15 -0
  39. package/src/components/CopyBtn/AccessibiliteItems.ts +0 -30
  40. package/src/components/CopyBtn/CopyBtn.mdx +1 -1
  41. package/src/components/CopyBtn/CopyBtn.stories.ts +9 -6
  42. package/src/components/CopyBtn/CopyBtn.vue +0 -1
  43. package/src/components/Customs/SyBtnSelect/Accessibilite.mdx +14 -0
  44. package/src/components/Customs/SyBtnSelect/Accessibilite.stories.ts +193 -0
  45. package/src/components/Customs/SyBtnSelect/AccessibiliteItems.ts +139 -0
  46. package/src/components/Customs/SyBtnSelect/SyBtnSelect.mdx +3 -2
  47. package/src/components/Customs/SyBtnSelect/SyBtnSelect.stories.ts +30 -14
  48. package/src/components/Customs/SyBtnSelect/SyBtnSelect.vue +21 -13
  49. package/src/components/Customs/SyBtnSelect/constants/ExpertiseLevelEnum.ts +4 -0
  50. package/src/components/Customs/SyInputSelect/Accessibilite.mdx +14 -0
  51. package/src/components/Customs/SyInputSelect/Accessibilite.stories.ts +166 -0
  52. package/src/components/Customs/SyInputSelect/AccessibiliteItems.ts +96 -0
  53. package/src/components/Customs/SyInputSelect/SyInputSelect.mdx +7 -3
  54. package/src/components/Customs/SyInputSelect/SyInputSelect.stories.ts +24 -65
  55. package/src/components/Customs/SyInputSelect/SyInputSelect.vue +36 -4
  56. package/src/components/Customs/SyInputSelect/constants/ExpertiseLevelEnum.ts +4 -0
  57. package/src/components/Customs/SySelect/Accessibilite.mdx +14 -0
  58. package/src/components/Customs/SySelect/Accessibilite.stories.ts +217 -0
  59. package/src/components/Customs/SySelect/AccessibiliteItems.ts +173 -0
  60. package/src/components/Customs/SySelect/SySelect.mdx +4 -4
  61. package/src/components/Customs/SySelect/SySelect.stories.ts +4 -60
  62. package/src/components/Customs/SySelect/SySelect.vue +35 -8
  63. package/src/components/Customs/SySelect/constants/ExpertiseLevelEnum.ts +4 -0
  64. package/src/components/Customs/SyTextField/Accessibilite.mdx +14 -0
  65. package/src/components/Customs/SyTextField/Accessibilite.stories.ts +224 -0
  66. package/src/components/Customs/SyTextField/AccessibiliteItems.ts +198 -0
  67. package/src/components/Customs/SyTextField/SyTextField.mdx +1 -1
  68. package/src/components/Customs/SyTextField/SyTextField.stories.ts +118 -7
  69. package/src/components/Customs/SyTextField/SyTextField.vue +27 -7
  70. package/src/components/Customs/SyTextField/constants/ExpertiseLevelEnum.ts +4 -0
  71. package/src/components/Customs/SyTextField/tests/__snapshots__/SyTextField.spec.ts.snap +2 -1
  72. package/src/components/DataList/Accessibilite.mdx +14 -0
  73. package/src/components/DataList/Accessibilite.stories.ts +166 -0
  74. package/src/components/DataList/AccessibiliteItems.ts +47 -0
  75. package/src/components/DataList/DataList.mdx +1 -1
  76. package/src/components/DataList/DataList.stories.ts +10 -10
  77. package/src/components/DataList/constants/ExpertiseLevelEnum.ts +4 -0
  78. package/src/components/DataListGroup/Accessibilite.mdx +14 -0
  79. package/src/components/DataListGroup/Accessibilite.stories.ts +225 -0
  80. package/src/components/DataListGroup/AccessibiliteItems.ts +79 -0
  81. package/src/components/DataListGroup/DataListGroup.mdx +1 -1
  82. package/src/components/DataListGroup/DataListGroup.stories.ts +7 -7
  83. package/src/components/DataListGroup/constants/ExpertiseLevelEnum.ts +4 -0
  84. package/src/components/DialogBox/Accessibilite.mdx +14 -0
  85. package/src/components/DialogBox/Accessibilite.stories.ts +189 -0
  86. package/src/components/DialogBox/AccessibiliteItems.ts +167 -0
  87. package/src/components/DialogBox/constants/ExpertiseLevelEnum.ts +4 -0
  88. package/src/components/DownloadBtn/AccessibiliteItems.ts +1 -31
  89. package/src/components/DownloadBtn/DownloadBtn.mdx +5 -6
  90. package/src/components/DownloadBtn/DownloadBtn.stories.ts +25 -26
  91. package/src/components/ErrorPage/Accessibilite.mdx +14 -0
  92. package/src/components/ErrorPage/Accessibilite.stories.ts +189 -0
  93. package/src/components/ErrorPage/AccessibiliteItems.ts +205 -0
  94. package/src/components/ErrorPage/ErrorPage.vue +1 -1
  95. package/src/components/ErrorPage/constants/ExpertiseLevelEnum.ts +4 -0
  96. package/src/components/ErrorPage/tests/__snapshots__/ErrorPage.spec.ts.snap +4 -4
  97. package/src/components/FooterBar/Accessibilite.mdx +14 -0
  98. package/src/components/FooterBar/Accessibilite.stories.ts +223 -0
  99. package/src/components/FooterBar/AccessibiliteItems.ts +257 -0
  100. package/src/components/FooterBar/FooterBar.mdx +2 -2
  101. package/src/components/FooterBar/FooterBar.stories.ts +14 -14
  102. package/src/components/FooterBar/FooterBar.vue +86 -75
  103. package/src/components/FooterBar/constants/ExpertiseLevelEnum.ts +4 -0
  104. package/src/components/FooterBar/tests/__snapshots__/FooterBar.spec.ts.snap +22 -20
  105. package/src/components/FranceConnectBtn/AccessibiliteItems.ts +0 -30
  106. package/src/components/FranceConnectBtn/FranceConnectBtn.mdx +1 -1
  107. package/src/components/FranceConnectBtn/FranceConnectBtn.stories.ts +5 -4
  108. package/src/components/HeaderBar/Accessibilite.mdx +14 -0
  109. package/src/components/HeaderBar/Accessibilite.stories.ts +223 -0
  110. package/src/components/HeaderBar/AccessibiliteItems.ts +194 -0
  111. package/src/components/HeaderBar/HeaderBar.stories.ts +19 -12
  112. package/src/components/HeaderBar/HeaderBar.vue +9 -12
  113. package/src/components/HeaderBar/HeaderBurgerMenu/Accessibilite.mdx +14 -0
  114. package/src/components/HeaderBar/HeaderBurgerMenu/Accessibilite.stories.ts +223 -0
  115. package/src/components/HeaderBar/HeaderBurgerMenu/AccessibiliteItems.ts +174 -0
  116. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderBurgerMenu.vue +1 -0
  117. package/src/components/HeaderBar/HeaderBurgerMenu/constants/ExpertiseLevelEnum.ts +4 -0
  118. package/src/components/HeaderBar/constants/ExpertiseLevelEnum.ts +4 -0
  119. package/src/components/HeaderBar/tests/__snapshots__/HeaderBar.spec.ts.snap +1 -1
  120. package/src/components/HeaderLoading/Accessibilite.mdx +14 -0
  121. package/src/components/HeaderLoading/Accessibilite.stories.ts +167 -0
  122. package/src/components/HeaderLoading/AccessibiliteItems.ts +29 -0
  123. package/src/components/HeaderLoading/HeaderLoading.mdx +1 -1
  124. package/src/components/HeaderLoading/HeaderLoading.stories.ts +1 -1
  125. package/src/components/HeaderLoading/constants/ExpertiseLevelEnum.ts +4 -0
  126. package/src/components/HeaderNavigationBar/HeaderNavigationBar.stories.ts +104 -32
  127. package/src/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.vue +35 -33
  128. package/src/components/HeaderToolbar/Accessibilite.mdx +14 -0
  129. package/src/components/HeaderToolbar/Accessibilite.stories.ts +203 -0
  130. package/src/components/HeaderToolbar/AccessibiliteItems.ts +200 -0
  131. package/src/components/HeaderToolbar/HeaderToolbar.mdx +1 -1
  132. package/src/components/HeaderToolbar/HeaderToolbar.stories.ts +2 -2
  133. package/src/components/HeaderToolbar/HeaderToolbar.vue +24 -1
  134. package/src/components/HeaderToolbar/constants/ExpertiseLevelEnum.ts +4 -0
  135. package/src/components/LangBtn/Accessibilite.stories.ts +3 -1
  136. package/src/components/LangBtn/AccessibiliteItems.ts +0 -31
  137. package/src/components/LangBtn/LangBtn.mdx +1 -1
  138. package/src/components/LangBtn/LangBtn.stories.ts +4 -4
  139. package/src/components/LangBtn/LangBtn.vue +3 -2
  140. package/src/components/Logo/Accessibilite.mdx +14 -0
  141. package/src/components/Logo/Accessibilite.stories.ts +223 -0
  142. package/src/components/Logo/AccessibiliteItems.ts +155 -0
  143. package/src/components/Logo/Logo.mdx +1 -1
  144. package/src/components/Logo/Logo.stories.ts +8 -8
  145. package/src/components/Logo/constants/ExpertiseLevelEnum.ts +4 -0
  146. package/src/components/LogoBrandSection/Accessibilite.mdx +14 -0
  147. package/src/components/LogoBrandSection/Accessibilite.stories.ts +223 -0
  148. package/src/components/LogoBrandSection/AccessibiliteItems.ts +194 -0
  149. package/src/components/LogoBrandSection/constants/ExpertiseLevelEnum.ts +4 -0
  150. package/src/components/MaintenancePage/Accessibilite.mdx +14 -0
  151. package/src/components/MaintenancePage/Accessibilite.stories.ts +189 -0
  152. package/src/components/MaintenancePage/AccessibiliteItems.ts +173 -0
  153. package/src/components/MaintenancePage/constants/ExpertiseLevelEnum.ts +4 -0
  154. package/src/components/NirField/Accessibilite.mdx +14 -0
  155. package/src/components/NirField/Accessibilite.stories.ts +214 -0
  156. package/src/components/NirField/AccessibiliteItems.ts +243 -0
  157. package/src/components/NirField/NirField.mdx +213 -0
  158. package/src/components/NirField/NirField.stories.ts +412 -0
  159. package/src/components/NirField/NirField.vue +453 -0
  160. package/src/components/NirField/config.ts +16 -0
  161. package/src/components/NirField/constants/ExpertiseLevelEnum.ts +4 -0
  162. package/src/components/NirField/locales.ts +12 -0
  163. package/src/components/NirField/nirValidation.ts +42 -0
  164. package/src/components/NirField/tests/NirField.spec.ts +120 -0
  165. package/src/components/NotFoundPage/Accessibilite.mdx +14 -0
  166. package/src/components/NotFoundPage/Accessibilite.stories.ts +190 -0
  167. package/src/components/NotFoundPage/AccessibiliteItems.ts +205 -0
  168. package/src/components/NotFoundPage/constants/ExpertiseLevelEnum.ts +4 -0
  169. package/src/components/NotFoundPage/tests/__snapshots__/NotFoundPage.spec.ts.snap +4 -4
  170. package/src/components/NotificationBar/AccessibiliteItems.ts +0 -30
  171. package/src/components/NotificationBar/NotificationBar.mdx +1 -1
  172. package/src/components/PageContainer/PageContainer.mdx +1 -1
  173. package/src/components/PageContainer/PageContainer.stories.ts +9 -9
  174. package/src/components/PageContainer/PageContainer.vue +24 -18
  175. package/src/components/PageContainer/tests/PageContainer.spec.ts +2 -2
  176. package/src/components/PageContainer/tests/__snapshots__/PageContainer.spec.ts.snap +2 -2
  177. package/src/components/PhoneField/Accessibilite.mdx +14 -0
  178. package/src/components/PhoneField/Accessibilite.stories.ts +216 -0
  179. package/src/components/PhoneField/AccessibiliteItems.ts +238 -0
  180. package/src/components/PhoneField/PhoneField.mdx +1 -1
  181. package/src/components/PhoneField/PhoneField.stories.ts +2 -2
  182. package/src/components/PhoneField/PhoneField.vue +0 -1
  183. package/src/components/PhoneField/constants/ExpertiseLevelEnum.ts +4 -0
  184. package/src/components/SkipLink/Accessibilite.stories.ts +1 -1
  185. package/src/components/SkipLink/SkipLink.stories.ts +2 -2
  186. package/src/components/SocialMediaLinks/Accessibilite.mdx +14 -0
  187. package/src/components/SocialMediaLinks/Accessibilite.stories.ts +170 -0
  188. package/src/components/SocialMediaLinks/AccessibiliteItems.ts +160 -0
  189. package/src/components/SocialMediaLinks/SocialMediaLinks.mdx +1 -1
  190. package/src/components/SocialMediaLinks/SocialMediaLinks.stories.ts +1 -1
  191. package/src/components/SocialMediaLinks/SocialMediaLinks.vue +7 -1
  192. package/src/components/SocialMediaLinks/constants/ExpertiseLevelEnum.ts +4 -0
  193. package/src/components/SocialMediaLinks/tests/__snapshots__/SocialMediaLinks.spec.ts.snap +2 -2
  194. package/src/components/SubHeader/Accessibilite.mdx +14 -0
  195. package/src/components/SubHeader/Accessibilite.stories.ts +166 -0
  196. package/src/components/SubHeader/AccessibiliteItems.ts +146 -0
  197. package/src/components/SubHeader/SubHeader.mdx +1 -1
  198. package/src/components/SubHeader/SubHeader.stories.ts +17 -14
  199. package/src/components/SubHeader/constants/ExpertiseLevelEnum.ts +4 -0
  200. package/src/components/SyAlert/Accessibilite.mdx +14 -0
  201. package/src/components/{Alert → SyAlert}/Accessibilite.stories.ts +1 -1
  202. package/src/components/{Alert → SyAlert}/AccessibiliteItems.ts +2 -32
  203. package/src/components/{Alert/Alert.mdx → SyAlert/SyAlert.mdx} +9 -9
  204. package/src/components/{Alert/Alert.stories.ts → SyAlert/SyAlert.stories.ts} +22 -22
  205. package/src/components/{Alert/Alert.vue → SyAlert/SyAlert.vue} +7 -0
  206. package/src/components/SyAlert/constants/ExpertiseLevelEnum.ts +4 -0
  207. package/src/components/{Alert/tests/Alert.spec.ts → SyAlert/tests/SyAlert.spec.ts} +5 -5
  208. package/src/components/{Alert/tests/__snapshots__/Alert.spec.ts.snap → SyAlert/tests/__snapshots__/SyAlert.spec.ts.snap} +2 -2
  209. package/src/components/UserMenuBtn/Accessibilite.mdx +14 -0
  210. package/src/components/UserMenuBtn/Accessibilite.stories.ts +189 -0
  211. package/src/components/UserMenuBtn/AccessibiliteItems.ts +155 -0
  212. package/src/components/UserMenuBtn/UserMenuBtn.mdx +17 -17
  213. package/src/components/UserMenuBtn/UserMenuBtn.stories.ts +121 -19
  214. package/src/components/UserMenuBtn/UserMenuBtn.vue +25 -29
  215. package/src/components/UserMenuBtn/config.ts +1 -1
  216. package/src/components/UserMenuBtn/constants/ExpertiseLevelEnum.ts +4 -0
  217. package/src/components/index.ts +11 -4
  218. package/src/composables/rules/tests/useFieldValidation.spec.ts +60 -58
  219. package/src/composables/rules/useFieldValidation.ts +65 -28
  220. package/src/main.ts +1 -0
  221. package/src/stories/GuideDuDev/CommentUtiliserLesRules.mdx +67 -79
  222. package/src/stories/GuideDuDev/components.stories.ts +5 -5
  223. package/src/stories/GuideDuDev/moduleDeNotification.mdx +1 -1
  224. package/src/stories/Guidelines/Vuetify/Vuetify.mdx +11 -0
  225. package/src/stories/Guidelines/Vuetify/Vuetify.stories.ts +138 -0
  226. package/src/stories/Guidelines/Vuetify/VuetifyItems.ts +350 -0
  227. /package/src/components/{Alert → CookieBanner}/Accessibilite.mdx +0 -0
  228. /package/src/components/{Alert → CookieBanner}/constants/ExpertiseLevelEnum.ts +0 -0
  229. /package/src/components/{Alert → SyAlert}/locales.ts +0 -0
@@ -0,0 +1,453 @@
1
+ <script lang="ts" setup>
2
+ import { ref, watch, computed, nextTick } from 'vue'
3
+ import { useFieldValidation } from '@/composables/rules/useFieldValidation'
4
+ import { vMaska } from 'maska/vue'
5
+ import { checkNIR, isNIRKeyValid } from './nirValidation'
6
+ import useCustomizableOptions, { type CustomizableOptions } from '@/composables/useCustomizableOptions'
7
+ import SyTextField from '../Customs/SyTextField/SyTextField.vue'
8
+ import { mdiInformationOutline } from '@mdi/js'
9
+ import { locales } from './locales'
10
+ import defaultOptions from './config'
11
+
12
+ type Rule = (value: string) => { error?: string, success?: string }
13
+
14
+ const props = withDefaults(defineProps<CustomizableOptions & {
15
+ modelValue?: string | undefined
16
+ outlined?: boolean
17
+ required?: boolean
18
+ nirTooltip?: string
19
+ keyTooltip?: string
20
+ numberLabel?: string
21
+ keyLabel?: string
22
+ displayKey?: boolean
23
+ showSuccessMessages?: boolean
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
25
+ customNumberRules?: any
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
27
+ customKeyRules?: any
28
+ }>(), {
29
+ modelValue: undefined,
30
+ outlined: true,
31
+ required: false,
32
+ nirTooltip: undefined,
33
+ keyTooltip: undefined,
34
+ numberLabel: 'Numéro de sécurité sociale',
35
+ keyLabel: 'Clé',
36
+ displayKey: true,
37
+ showSuccessMessages: false,
38
+ customNumberRules: [],
39
+ customKeyRules: [],
40
+ })
41
+
42
+ const emit = defineEmits(['update:modelValue'])
43
+ const options = useCustomizableOptions(defaultOptions, props)
44
+ const infoIcon = mdiInformationOutline
45
+
46
+ // Champs
47
+ const numberValue = ref('')
48
+ const keyValue = ref('')
49
+ const keyDeleted = ref(false)
50
+
51
+ // Refs pour les champs
52
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
53
+ const keyField = ref<any | null>(null)
54
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
55
+ const numberField = ref<any | null>(null)
56
+
57
+ const unmaskedNumberValue = computed(() => numberValue.value.replace(/\s/g, ''))
58
+
59
+ watch(() => props.modelValue, async (value) => {
60
+ numberValue.value = value?.slice(0, 13) ?? ''
61
+ keyValue.value = value?.slice(13, 15) ?? ''
62
+ }, { immediate: true })
63
+
64
+ // Etats d’erreur/succès
65
+ const errors = ref<string[]>([])
66
+ const successes = ref<string[]>([])
67
+
68
+ // Flags de validation
69
+ const isValidating = ref(false)
70
+
71
+ // Masques
72
+ const numberMask = {
73
+ mask: '# ## ## #C ### ###',
74
+ preProcess: (value: string) => value.toUpperCase(),
75
+ tokens: {
76
+ C: {
77
+ pattern: /[0-9AB]/,
78
+ transform: (char: string) => char.toUpperCase(),
79
+ },
80
+ },
81
+ }
82
+ const keyMask = { mask: '##' }
83
+
84
+ // Règles de validation par défaut
85
+ const { generateRules } = useFieldValidation()
86
+
87
+ const defaultNumberRules = [
88
+ {
89
+ type: 'exactLength',
90
+ options: { length: 13, message: locales.errorLengthNumber(13), ignoreSpace: true, fieldIdentifier: 'numéro' },
91
+ },
92
+ {
93
+ type: 'custom',
94
+ options: {
95
+ validate: checkNIR,
96
+ message: 'Le numéro de sécurité sociale est invalide.',
97
+ successMessage: 'Le numéro de sécurité sociale est valide.',
98
+ fieldIdentifier: 'numéro',
99
+ },
100
+ },
101
+ ...(props.required
102
+ ? [{
103
+ type: 'required',
104
+ options: { message: 'Le numéro de sécurité sociale est requis.', fieldIdentifier: 'numéro' },
105
+ }]
106
+ : []),
107
+ ]
108
+
109
+ const defaultKeyRules = [
110
+ {
111
+ type: 'exactLength',
112
+ options: { length: 2, message: locales.errorLengthKey(2), ignoreSpace: true, fieldIdentifier: 'clé' },
113
+ },
114
+ {
115
+ type: 'custom',
116
+ options: {
117
+ validate: () => isNIRKeyValid(`${numberValue.value}${keyValue.value}`),
118
+ message: 'La clé du numéro de sécurité sociale est invalide.',
119
+ successMessage: 'Le champ clé est valide.',
120
+ fieldIdentifier: 'clé',
121
+ },
122
+ },
123
+ ...(props.required
124
+ ? [{
125
+ type: 'required',
126
+ options: { message: 'La clé est requise.', fieldIdentifier: 'clé' },
127
+ }]
128
+ : []),
129
+ ]
130
+
131
+ // Computed pour statut des champs
132
+ const fieldIdentifierNumber = defaultNumberRules[0]?.options?.fieldIdentifier
133
+ const fieldIdentifierKey = defaultKeyRules[0]?.options?.fieldIdentifier
134
+
135
+ const hasNumberErrors = computed(() => errors.value.some(error => error.includes(fieldIdentifierNumber)))
136
+ const hasKeyErrors = computed(() => errors.value.some(error => error.includes(fieldIdentifierKey)))
137
+ const hasNumberSuccess = computed(() => successes.value.some(success => success.includes(fieldIdentifierNumber)))
138
+ const hasKeySuccess = computed(() => successes.value.some(success => success.includes(fieldIdentifierKey)))
139
+
140
+ // Génération des règles finales
141
+ const numberRules = props.customNumberRules?.length
142
+ ? generateRules(props.customNumberRules)
143
+ : generateRules(defaultNumberRules)
144
+
145
+ const keyRules = props.displayKey
146
+ ? (props.customKeyRules?.length
147
+ ? generateRules(props.customKeyRules)
148
+ : generateRules(defaultKeyRules))
149
+ : []
150
+
151
+ /**
152
+ * Valide une liste de règles sur une valeur et met à jour les tableaux d'erreurs et de succès.
153
+ * @param value Valeur du champ à valider
154
+ * @param rules Ensemble de règles
155
+ */
156
+ function validateFieldSet(value: string, rules: Rule[]) {
157
+ rules.forEach((rule) => {
158
+ const { error, success } = rule(value)
159
+ if (error) errors.value.push(error)
160
+ if (success && success !== 'Le champ est valide.') successes.value.push(success)
161
+ })
162
+ }
163
+
164
+ /**
165
+ * Valide les champs numéro et clé (si activée).
166
+ * @param onBlur Si true, la validation est lancée suite à un blur, sinon validation continue
167
+ */
168
+ function validateFields(onBlur = false) {
169
+ errors.value = []
170
+ successes.value = []
171
+
172
+ const shouldValidateNumber = onBlur || isValidating.value || numberValue.value.length === 18
173
+ const shouldValidateKey = props.displayKey && (onBlur || isValidating.value || keyValue.value.length === 2)
174
+
175
+ if (shouldValidateNumber) {
176
+ validateFieldSet(numberValue.value, numberRules)
177
+ }
178
+
179
+ if (shouldValidateKey) {
180
+ validateFieldSet(keyValue.value, keyRules)
181
+ }
182
+
183
+ // Unicité des succès
184
+ successes.value = Array.from(new Set(successes.value))
185
+ }
186
+
187
+ // Compteurs
188
+ const numberCounter = computed(() => {
189
+ const length = numberValue.value.replace(/\s/g, '').length
190
+ return `${Math.min(length, 13)}/13`
191
+ })
192
+
193
+ const keyCounter = computed(() => {
194
+ const length = keyValue.value.replace(/\s/g, '').length
195
+ return `${Math.min(length, 2)}/2`
196
+ })
197
+
198
+ watch([unmaskedNumberValue, keyValue], () => {
199
+ validateFields()
200
+ if (unmaskedNumberValue.value + keyValue.value !== props.modelValue) {
201
+ emit('update:modelValue', `${unmaskedNumberValue.value}${keyValue.value}`)
202
+ }
203
+ })
204
+
205
+ watch(keyValue, (newValue, oldValue) => {
206
+ keyDeleted.value = !!(!newValue && oldValue)
207
+ })
208
+
209
+ watch(numberValue, () => {
210
+ if (unmaskedNumberValue.value.length < 13) {
211
+ keyDeleted.value = false
212
+ }
213
+ })
214
+
215
+ // Déplacement du focus sur la clé quand le numéro est rempli
216
+ const focusElement = computed(() => {
217
+ if (props.displayKey && numberValue.value.length === 18) {
218
+ if (!keyDeleted.value) {
219
+ return keyField.value?.$el?.querySelector('input')
220
+ }
221
+ else {
222
+ return numberField.value?.$el?.querySelector('input')
223
+ }
224
+ }
225
+ return null
226
+ })
227
+
228
+ watch(focusElement, (newEl) => {
229
+ nextTick(() => {
230
+ newEl?.focus()
231
+ })
232
+ })
233
+
234
+ function validateOnSubmit() {
235
+ isValidating.value = true
236
+ validateFields()
237
+ return errors.value.length === 0
238
+ }
239
+
240
+ defineExpose({
241
+ validateOnSubmit,
242
+ })
243
+ </script>
244
+
245
+ <template>
246
+ <div class="d-flex align-start">
247
+ <v-input
248
+ ref="vInput"
249
+ :class="{
250
+ 'v-messages__message--success': successes.length > 0 && props.showSuccessMessages,
251
+ 'v-messages__message--error': errors.length > 0
252
+ }"
253
+ :error-messages="errors"
254
+ :label="numberLabel"
255
+ :max-errors="3"
256
+ :messages="props.showSuccessMessages ? successes : []"
257
+ :model-value="[numberValue, keyValue]"
258
+ class="vd-nir-field__fields-wrapper multi-line"
259
+ validate-on="blur lazy"
260
+ >
261
+ <VTooltip v-if="nirTooltip">
262
+ <template #activator="{ props: iconProps }">
263
+ <VIcon
264
+ class="vd-tooltip-icon mt-4 mr-4"
265
+ v-bind="{ ...iconProps, ...options.tooltip }"
266
+ >
267
+ {{ infoIcon }}
268
+ </VIcon>
269
+ </template>
270
+ <slot name="nirTooltip">
271
+ {{ nirTooltip }}
272
+ </slot>
273
+ </VTooltip>
274
+ <SyTextField
275
+ ref="numberField"
276
+ v-model="numberValue"
277
+ v-maska="numberMask"
278
+ :append-inner-icon="hasNumberErrors ? 'error' : (hasNumberSuccess ? 'success' : undefined)"
279
+ :aria-errormessage="hasNumberErrors ? 'number-field-errors' : undefined"
280
+ :aria-invalid="hasNumberErrors"
281
+ :aria-required="required"
282
+ :color="hasNumberErrors ? 'error' : 'primary'"
283
+ :error="hasNumberErrors"
284
+ :hint="locales.numberHint"
285
+ :label="numberLabel"
286
+ :variant="outlined ? 'outlined' : 'underlined'"
287
+ class="vd-number-field"
288
+ title="nirField"
289
+ @blur="validateFields(true)"
290
+ >
291
+ <template #details>
292
+ <span class="custom-counter">
293
+ {{ numberCounter }}
294
+ </span>
295
+ </template>
296
+ </SyTextField>
297
+
298
+ <template v-if="displayKey">
299
+ <SyTextField
300
+ ref="keyField"
301
+ v-model="keyValue"
302
+ v-maska="keyMask"
303
+ :append-inner-icon="hasKeyErrors ? 'error' : (hasKeySuccess ? 'success' : undefined)"
304
+ :aria-errormessage="hasKeyErrors ? 'key-field-errors' : undefined"
305
+ :aria-invalid="hasKeyErrors"
306
+ :aria-required="required"
307
+ :color="hasKeyErrors ? 'error' : 'primary'"
308
+ :error="hasKeyErrors"
309
+ :hint="locales.keyHint"
310
+ :label="keyLabel"
311
+ :variant="outlined ? 'outlined' : 'underlined'"
312
+ class="vd-key-field"
313
+ title="nirKeyField"
314
+ @blur="validateFields(true)"
315
+ >
316
+ <template #details>
317
+ <span class="custom-counter">
318
+ {{ keyCounter }}
319
+ </span>
320
+ </template>
321
+ </SyTextField>
322
+
323
+ <VTooltip v-if="keyTooltip">
324
+ <template #activator="{ props: iconProps }">
325
+ <VIcon
326
+ class="vd-tooltip-icon mt-4 ml-4"
327
+ v-bind="{ ...iconProps, ...options.icon }"
328
+ >
329
+ {{ infoIcon }}
330
+ </VIcon>
331
+ </template>
332
+ <slot name="keyTooltip">
333
+ {{ keyTooltip }}
334
+ </slot>
335
+ </VTooltip>
336
+ </template>
337
+ </v-input>
338
+ </div>
339
+ </template>
340
+
341
+ <style lang="scss" scoped>
342
+ @use '@/assets/tokens.scss';
343
+
344
+ .v-messages__message--success {
345
+ color: tokens.$colors-border-success !important;
346
+
347
+ .v-field--active & {
348
+ color: tokens.$colors-border-success !important;
349
+ }
350
+ }
351
+
352
+ .v-messages__message--error {
353
+ color: tokens.$colors-border-error;
354
+
355
+ .v-field--active & {
356
+ color: tokens.$colors-border-error;
357
+ }
358
+ }
359
+
360
+ :deep(.v-field.v-field--active .v-label.v-field-label--floating) {
361
+ opacity: 1;
362
+ }
363
+
364
+ .multi-line {
365
+ white-space: pre-line !important;
366
+ }
367
+
368
+ .vd-number-field {
369
+ max-width: 296px;
370
+ }
371
+
372
+ .vd-key-field {
373
+ width: 104px;
374
+ }
375
+
376
+ .custom-counter {
377
+ color: rgba(0, 0, 0, 0.54);
378
+ }
379
+
380
+ .vd-nir-field :deep(.v-input__append-inner),
381
+ .vd-tooltip-icon {
382
+ flex: none;
383
+ color: rgba(0, 0, 0, 0.54);
384
+ }
385
+
386
+ :deep(.v-overlay__content) {
387
+ background: rgba(84, 88, 89, 0.95) !important;
388
+ }
389
+
390
+ .vd-key-field {
391
+ flex: none;
392
+ }
393
+
394
+ .vd-nir-field--outlined :deep(.v-messages.error--text) {
395
+ padding: 6px;
396
+ }
397
+
398
+ .vd-nir-field {
399
+ container-name: nirFieldwrapper;
400
+ }
401
+
402
+ :deep(.v-input__append) {
403
+ margin-inline-start: 0 !important;
404
+ }
405
+ :deep(.vd-number-field > .v-input__prepend) {
406
+ margin-right: 0 !important;
407
+ }
408
+
409
+ :deep(.vd-key-field > .v-input__prepend) {
410
+ @media screen and (max-width: 360px) {
411
+ margin-inline-end: 0 !important;
412
+ }
413
+ }
414
+
415
+ :deep(.v-text-field .v-input__details) {
416
+ padding-inline-start: 0 !important;
417
+ padding-inline-end: 0 !important;
418
+ flex: none !important;
419
+ }
420
+
421
+ :deep(.v-text-field .v-input__details .v-messages) {
422
+ color: rgba(0, 0, 0, 1) !important;
423
+ }
424
+
425
+ @mixin responsive-nir-wrapper {
426
+ .vd-nir-field__fields-wrapper :deep(> .v-input__control) {
427
+ justify-content: start;
428
+ flex-wrap: wrap;
429
+ gap: 4px;
430
+ margin-bottom: 4px;
431
+
432
+ .vd-number-field {
433
+ flex: 100% 0 0;
434
+ }
435
+ }
436
+ }
437
+
438
+ @container nirFieldwrapper (max-width: 300px) {
439
+ @include responsive-nir-wrapper;
440
+ }
441
+
442
+ @media screen and (max-width: 360px) {
443
+ @include responsive-nir-wrapper;
444
+ }
445
+
446
+ .v-text-field .v-input__append-inner {
447
+ padding-left: 0 !important;
448
+ }
449
+
450
+ :deep(.v-text-field > .v-input__control > .v-input__slot > .v-text-field__slot) {
451
+ width: min-content !important;
452
+ }
453
+ </style>
@@ -0,0 +1,16 @@
1
+ const defaultOptions = {
2
+ persistentHint: {
3
+ persistentHint: true,
4
+ },
5
+ validateOn: {
6
+ validateOn: 'blur',
7
+ },
8
+ icon: {
9
+ color: '',
10
+ },
11
+ tooltip: {
12
+ location: 'top',
13
+ },
14
+ }
15
+
16
+ export default defaultOptions
@@ -0,0 +1,4 @@
1
+ export enum ExpertiseLevelEnum {
2
+ DEV = 'dev',
3
+ DESIGN = 'design',
4
+ }
@@ -0,0 +1,12 @@
1
+ export const locales = {
2
+ errorRequiredNumber: 'Le numéro de sécurité sociale est requis.',
3
+ errorLengthNumber: (length: number) => `Le numéro de sécurité sociale doit contenir ${length} caractères.`,
4
+ errorInvalidFormat: 'Le format du numéro de sécurité sociale est invalide.',
5
+ errorRequiredKey: 'La clé du numéro de sécurité sociale est requise.',
6
+ errorLengthKey: (length: number) => `La clé du numéro de sécurité sociale doit contenir ${length} caractères.`,
7
+ errorInvalidKey: 'La clé du numéro de sécurité sociale est invalide.',
8
+ numberLabel: 'Numéro de sécurité sociale',
9
+ numberHint: '13 caractères',
10
+ keyLabel: 'Clé',
11
+ keyHint: '2 chiffres',
12
+ } as const
@@ -0,0 +1,42 @@
1
+ export const NUMBER_LENGTH = 13
2
+ export const KEY_LENGTH = 2
3
+
4
+ export function checkNIR(nir: string): boolean {
5
+ nir = nir.replace(/\s+/g, '').toUpperCase()
6
+
7
+ const nirRegex = new RegExp(
8
+ '^'
9
+ + '(?<sexe>[1-4]|7|8)'
10
+ + '(?<anneeNaissance>\\d{2})'
11
+ + '(?<moisNaissance>0[1-9]|1[0-2]|2[0-9]|3[0-9]|4[0-2])'
12
+ + '(?<departementNaissance>\\d{2}|2A|2B|96|97\\d|98\\d)'
13
+ + '(?<communeNaissance>\\d{3})'
14
+ + '(?<rangInscription>\\d{3})'
15
+ + '(?<cle>[0-9]{2})?'
16
+ + '$',
17
+ 'i',
18
+ )
19
+ return nirRegex.test(nir)
20
+ }
21
+
22
+ export function computeNIRKey(nir: string): string {
23
+ nir = nir.replace(/\s+/g, '').toUpperCase()
24
+
25
+ let nirNumberPart = nir.substring(0, 13)
26
+
27
+ nirNumberPart = nirNumberPart.replace('2A', '19').replace('2B', '18')
28
+
29
+ const nirNumber = BigInt(nirNumberPart)
30
+
31
+ const key = 97n - (nirNumber % 97n)
32
+
33
+ return key.toString().padStart(2, '0')
34
+ }
35
+
36
+ export function isNIRKeyValid(nir: string): boolean {
37
+ nir = nir.replace(/\s+/g, '').toUpperCase()
38
+
39
+ const providedKey = nir.substring(13, 15)
40
+ const computedKey = computeNIRKey(nir)
41
+ return providedKey === computedKey
42
+ }
@@ -0,0 +1,120 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import NirField from '../NirField.vue'
3
+ import { describe, it, expect, beforeEach } from 'vitest'
4
+ import { createVuetify } from 'vuetify'
5
+
6
+ const vuetify = createVuetify()
7
+
8
+ describe('NirField.vue', () => {
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
10
+ let wrapper: any
11
+
12
+ beforeEach(() => {
13
+ wrapper = mount(NirField, {
14
+ global: {
15
+ plugins: [vuetify],
16
+ },
17
+ props: {
18
+ modelValue: '',
19
+ required: true,
20
+ showSuccessMessages: true,
21
+ },
22
+ })
23
+ })
24
+
25
+ it('renders correctly', () => {
26
+ expect(wrapper.exists()).toBe(true)
27
+ expect(wrapper.find('.vd-number-field').exists()).toBe(true)
28
+ expect(wrapper.find('.vd-key-field').exists()).toBe(true)
29
+ })
30
+
31
+ it('displays error message for invalid NIR length', async () => {
32
+ const numberField = wrapper.find('.vd-number-field input')
33
+ await numberField.setValue('123') // Invalid length
34
+ await numberField.trigger('blur')
35
+ expect(wrapper.find('.v-messages__message').text()).toContain('Le numéro de sécurité sociale doit contenir 13 caractères.')
36
+ })
37
+
38
+ it('validates the NIR field successfully', async () => {
39
+ const numberField = wrapper.find('.vd-number-field input')
40
+ await numberField.setValue('2940375120005') // Valid NIR length
41
+ expect(wrapper.find('.v-messages__message--success').exists()).toBe(true)
42
+ })
43
+
44
+ it('displays error message for invalid key length', async () => {
45
+ const numberField = wrapper.find('.vd-number-field input')
46
+ await numberField.setValue('2940375120005') // Valid NIR length
47
+ const keyField = wrapper.find('.vd-key-field input')
48
+ await keyField.setValue('1') // Invalid length
49
+ await keyField.trigger('blur')
50
+ expect(wrapper.find('.v-messages__message').text()).toContain('La clé du numéro de sécurité sociale doit contenir 2 caractères.')
51
+ })
52
+
53
+ it('validates the key field successfully', async () => {
54
+ const numberField = wrapper.find('.vd-number-field input')
55
+ const keyField = wrapper.find('.vd-key-field input')
56
+ await numberField.setValue('2940375120005')
57
+ await keyField.setValue('91')
58
+ expect(wrapper.find('.v-messages__message--success').exists()).toBe(true)
59
+ })
60
+
61
+ it('hides the key field when displayKey is false', async () => {
62
+ await wrapper.setProps({ displayKey: false })
63
+ expect(wrapper.find('.vd-key-field').exists()).toBe(false)
64
+ })
65
+
66
+ it('calls validateOnSubmit and returns true if no errors', async () => {
67
+ const numberField = wrapper.find('.vd-number-field input')
68
+ const keyField = wrapper.find('.vd-key-field input')
69
+
70
+ await numberField.setValue('2940375120005')
71
+ await keyField.setValue('91')
72
+
73
+ await wrapper.vm.$nextTick()
74
+
75
+ wrapper.vm.validateFields()
76
+
77
+ const isValid = wrapper.vm.validateOnSubmit()
78
+
79
+ expect(isValid).toBe(true)
80
+ expect(wrapper.vm.errors.length).toBe(0)
81
+ })
82
+ it('applies custom key rules when provided', async () => {
83
+ const customKeyRules = [
84
+ {
85
+ type: 'custom',
86
+ options: {
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
88
+ validate: (value: any) => value === '91',
89
+ message: 'Custom key validation failed.',
90
+ successMessage: 'Custom key validation passed.',
91
+ },
92
+ },
93
+ ]
94
+
95
+ wrapper = mount(NirField, {
96
+ global: {
97
+ plugins: [vuetify],
98
+ },
99
+ props: {
100
+ modelValue: '',
101
+ required: true,
102
+ customKeyRules,
103
+ },
104
+ })
105
+ const numberField = wrapper.find('.vd-number-field input')
106
+ const keyField = wrapper.find('.vd-key-field input')
107
+ await numberField.setValue('2940375120005')
108
+ await keyField.setValue('91')
109
+
110
+ await wrapper.vm.$nextTick()
111
+
112
+ expect(wrapper.vm.errors.length).toBe(0)
113
+ expect(wrapper.vm.successes).toContain('Custom key validation passed.')
114
+
115
+ await keyField.setValue('11')
116
+ await wrapper.vm.$nextTick()
117
+
118
+ expect(wrapper.vm.errors).toContain('Custom key validation failed.')
119
+ })
120
+ })
@@ -0,0 +1,14 @@
1
+ import { Meta, Story } from '@storybook/addon-docs';
2
+ import * as AccessStories from './Accessibilite.stories.ts';
3
+
4
+ <Meta of={AccessStories} />
5
+
6
+ Accessibilité
7
+ =============
8
+ <Story of={AccessStories.Legende} />
9
+ <br />
10
+
11
+ --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
12
+
13
+ <Story of={AccessStories.AccessibilitePanel} />
14
+ <br />