@cnamts/synapse 0.0.7-alpha → 0.0.9-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 (198) hide show
  1. package/dist/design-system-v3.d.ts +785 -372
  2. package/dist/design-system-v3.js +4993 -3357
  3. package/dist/design-system-v3.umd.cjs +1 -10
  4. package/dist/style.css +1 -1
  5. package/package.json +10 -2
  6. package/src/assets/settings.scss +2 -2
  7. package/src/assets/tokens.scss +107 -112
  8. package/src/components/BackBtn/BackBtn.vue +4 -4
  9. package/src/components/BackToTopBtn/BackToTopBtn.vue +1 -0
  10. package/src/components/CollapsibleList/CollapsibleList.mdx +1 -1
  11. package/src/components/CollapsibleList/CollapsibleList.vue +43 -44
  12. package/src/components/ContextualMenu/Accessibilite.mdx +14 -0
  13. package/src/components/ContextualMenu/Accessibilite.stories.ts +191 -0
  14. package/src/components/ContextualMenu/AccessibiliteItems.ts +89 -0
  15. package/src/components/ContextualMenu/ContextualMenu.mdx +118 -0
  16. package/src/components/ContextualMenu/ContextualMenu.stories.ts +430 -0
  17. package/src/components/ContextualMenu/ContextualMenu.vue +101 -0
  18. package/src/components/ContextualMenu/constants/ExpertiseLevelEnum.ts +4 -0
  19. package/src/components/ContextualMenu/tests/ContextualMenu.spec.ts +115 -0
  20. package/src/components/ContextualMenu/tests/__snapshots__/ContextualMenu.spec.ts.snap +10 -0
  21. package/src/components/ContextualMenu/types.ts +5 -0
  22. package/src/components/CookieBanner/CookieBanner.stories.ts +1 -2
  23. package/src/components/CookieBanner/CookieBanner.vue +13 -10
  24. package/src/components/CookieBanner/tests/__snapshots__/CookieBanner.spec.ts.snap +17 -15
  25. package/src/components/CookiesSelection/CookiesInformation/CookiesInformation.vue +6 -1
  26. package/src/components/CookiesSelection/CookiesInformation/locales.ts +1 -0
  27. package/src/components/CookiesSelection/CookiesTable/CookiesTable.vue +1 -0
  28. package/src/components/CookiesSelection/tests/__snapshots__/CookiesSelection.spec.ts.snap +17 -15
  29. package/src/components/CopyBtn/CopyBtn.vue +7 -7
  30. package/src/components/Customs/SyBtnSelect/SyBtnSelect.vue +26 -26
  31. package/src/components/Customs/SyInputSelect/SyInputSelect.vue +24 -24
  32. package/src/components/Customs/SySelect/SySelect.stories.ts +7 -7
  33. package/src/components/Customs/SySelect/SySelect.vue +36 -30
  34. package/src/components/Customs/SySelect/tests/SySelect.spec.ts +2 -2
  35. package/src/components/Customs/SyTextField/SyTextField.stories.ts +187 -2
  36. package/src/components/Customs/SyTextField/SyTextField.vue +185 -16
  37. package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +2 -4
  38. package/src/components/Customs/SyTextField/tests/__snapshots__/SyTextField.spec.ts.snap +18 -16
  39. package/src/components/Customs/SyTextField/types.d.ts +2 -2
  40. package/src/components/DataList/DataList.stories.ts +3 -2
  41. package/src/components/DataList/DataList.vue +1 -1
  42. package/src/components/DataListGroup/DataListGroup.stories.ts +3 -2
  43. package/src/components/DataListItem/DataListItem.vue +12 -12
  44. package/src/components/DatePicker/DatePicker.mdx +191 -0
  45. package/src/components/DatePicker/DatePicker.stories.ts +787 -0
  46. package/src/components/DatePicker/DatePicker.vue +560 -0
  47. package/src/components/DatePicker/DateTextInput.vue +409 -0
  48. package/src/components/DatePicker/tests/DatePicker.spec.ts +266 -0
  49. package/src/components/DialogBox/DialogBox.mdx +28 -2
  50. package/src/components/DialogBox/DialogBox.stories.ts +2 -2
  51. package/src/components/DialogBox/DialogBox.vue +3 -2
  52. package/src/components/DownloadBtn/DownloadBtn.vue +2 -1
  53. package/src/components/ExternalLinks/Accessibilite.mdx +14 -0
  54. package/src/components/ExternalLinks/Accessibilite.stories.ts +191 -0
  55. package/src/components/ExternalLinks/AccessibiliteItems.ts +197 -0
  56. package/src/components/ExternalLinks/ExternalLinks.mdx +86 -0
  57. package/src/components/ExternalLinks/ExternalLinks.stories.ts +553 -0
  58. package/src/components/ExternalLinks/ExternalLinks.vue +200 -0
  59. package/src/components/ExternalLinks/config.ts +34 -0
  60. package/src/components/ExternalLinks/constants/ExpertiseLevelEnum.ts +4 -0
  61. package/src/components/ExternalLinks/locales.ts +4 -0
  62. package/src/components/ExternalLinks/tests/ExternalLinks.spec.ts +154 -0
  63. package/src/components/ExternalLinks/tests/__snapshots__/ExternalLinks.spec.ts.snap +159 -0
  64. package/src/components/FileUpload/FileUpload.mdx +165 -0
  65. package/src/components/FileUpload/FileUpload.stories.ts +429 -0
  66. package/src/components/FileUpload/FileUpload.vue +195 -0
  67. package/src/components/FileUpload/FileUploadContent.vue +109 -0
  68. package/src/components/FileUpload/locales.ts +10 -0
  69. package/src/components/FileUpload/tests/FileUpload.spec.ts +332 -0
  70. package/src/components/FileUpload/tests/__snapshots__/FileUpload.spec.ts.snap +7 -0
  71. package/src/components/FileUpload/useFileDrop.ts +23 -0
  72. package/src/components/FileUpload/validateFiles.ts +39 -0
  73. package/src/components/FooterBar/FooterBar.vue +105 -80
  74. package/src/components/FranceConnectBtn/FranceConnectBtn.vue +14 -13
  75. package/src/components/HeaderBar/HeaderBar.vue +3 -3
  76. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderBurgerMenu.vue +11 -7
  77. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.vue +5 -5
  78. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuSection/HeaderMenuSection.vue +2 -2
  79. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/HeaderSubMenu.vue +10 -8
  80. package/src/components/HeaderBar/HeaderLogo/HeaderLogo.vue +2 -2
  81. package/src/components/HeaderBar/HeaderLogo/logos/Logo-mobile.vue +2 -1
  82. package/src/components/HeaderBar/HeaderLogo/logos/Logo.vue +2 -1
  83. package/src/components/HeaderBar/HeaderMenuBtn/HeaderMenuBtn.vue +10 -10
  84. package/src/components/HeaderBar/consts.scss +1 -1
  85. package/src/components/HeaderLoading/HeaderLoading.vue +12 -11
  86. package/src/components/HeaderNavigationBar/HeaderNavigationBar.vue +2 -1
  87. package/src/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.vue +9 -9
  88. package/src/components/HeaderToolbar/HeaderToolbar.vue +215 -202
  89. package/src/components/LangBtn/LangBtn.vue +8 -6
  90. package/src/components/LogoBrandSection/LogoBrandSection.stories.ts +2 -2
  91. package/src/components/NirField/NirField.stories.ts +8 -8
  92. package/src/components/NirField/NirField.vue +46 -48
  93. package/src/components/NotFoundPage/NotFoundPage.stories.ts +33 -2
  94. package/src/components/NotFoundPage/NotFoundPage.vue +17 -0
  95. package/src/components/NotificationBar/NotificationBar.mdx +5 -5
  96. package/src/components/NotificationBar/NotificationBar.stories.ts +410 -314
  97. package/src/components/NotificationBar/NotificationBar.vue +43 -41
  98. package/src/components/PageContainer/PageContainer.vue +4 -4
  99. package/src/components/PasswordField/Accessibilite.mdx +14 -0
  100. package/src/components/PasswordField/Accessibilite.stories.ts +191 -0
  101. package/src/components/PasswordField/AccessibiliteItems.ts +184 -0
  102. package/src/components/PasswordField/PasswordField.mdx +70 -0
  103. package/src/components/PasswordField/PasswordField.stories.ts +213 -0
  104. package/src/components/PasswordField/PasswordField.vue +189 -0
  105. package/src/components/PasswordField/config.ts +11 -0
  106. package/src/components/PasswordField/constants/ExpertiseLevelEnum.ts +4 -0
  107. package/src/components/PasswordField/locales.ts +4 -0
  108. package/src/components/PasswordField/tests/PasswordField.spec.ts +96 -0
  109. package/src/components/PhoneField/PhoneField.mdx +0 -2
  110. package/src/components/PhoneField/PhoneField.stories.ts +10 -50
  111. package/src/components/PhoneField/PhoneField.vue +77 -93
  112. package/src/components/PhoneField/tests/PhoneField.spec.ts +0 -15
  113. package/src/components/RangeField/RangeField.mdx +54 -0
  114. package/src/components/RangeField/RangeField.stories.ts +189 -0
  115. package/src/components/RangeField/RangeField.vue +157 -0
  116. package/src/components/RangeField/RangeSlider/RangeSlider.vue +387 -0
  117. package/src/components/RangeField/RangeSlider/Tooltip/Tooltip.vue +64 -0
  118. package/src/components/RangeField/RangeSlider/tests/__snapshots__/rangeSlider.spec.ts.snap +27 -0
  119. package/src/components/RangeField/RangeSlider/tests/rangeSlider.spec.ts +100 -0
  120. package/src/components/RangeField/RangeSlider/tests/useDoubleSlider.spec.ts +246 -0
  121. package/src/components/RangeField/RangeSlider/tests/useMouseSlide.spec.ts +204 -0
  122. package/src/components/RangeField/RangeSlider/tests/useThumb.spec.ts +22 -0
  123. package/src/components/RangeField/RangeSlider/tests/useThumbKeyboard.spec.ts +233 -0
  124. package/src/components/RangeField/RangeSlider/tests/useTooltipsNudge.spec.ts +150 -0
  125. package/src/components/RangeField/RangeSlider/tests/useTrack.spec.ts +314 -0
  126. package/src/components/RangeField/RangeSlider/tests/vAnimateClick.spec.ts +32 -0
  127. package/src/components/RangeField/RangeSlider/types.ts +15 -0
  128. package/src/components/RangeField/RangeSlider/useMouseSlide.ts +109 -0
  129. package/src/components/RangeField/RangeSlider/useRangeSlider.ts +126 -0
  130. package/src/components/RangeField/RangeSlider/useThumb.ts +18 -0
  131. package/src/components/RangeField/RangeSlider/useThumbKeyboard.ts +84 -0
  132. package/src/components/RangeField/RangeSlider/useTooltipsNudge.ts +92 -0
  133. package/src/components/RangeField/RangeSlider/useTrack.ts +116 -0
  134. package/src/components/RangeField/RangeSlider/vAnimateClick.ts +19 -0
  135. package/src/components/RangeField/config.ts +7 -0
  136. package/src/components/RangeField/locales.ts +4 -0
  137. package/src/components/RangeField/tests/RangeField.spec.ts +224 -0
  138. package/src/components/RangeField/tests/__snapshots__/RangeField.spec.ts.snap +379 -0
  139. package/src/components/RatingPicker/EmotionPicker/EmotionPicker.vue +205 -0
  140. package/src/components/RatingPicker/EmotionPicker/locales.ts +3 -0
  141. package/src/components/RatingPicker/EmotionPicker/tests/EmotionPicker.spec.ts +104 -0
  142. package/src/components/RatingPicker/EmotionPicker/tests/__snapshots__/EmotionPicker.spec.ts.snap +66 -0
  143. package/src/components/RatingPicker/NumberPicker/NumberPicker.vue +159 -0
  144. package/src/components/RatingPicker/NumberPicker/locales.ts +4 -0
  145. package/src/components/RatingPicker/NumberPicker/tests/NumberPicker.spec.ts +73 -0
  146. package/src/components/RatingPicker/NumberPicker/tests/__snapshots__/NumberPicker.spec.ts.snap +105 -0
  147. package/src/components/RatingPicker/Rating.ts +45 -0
  148. package/src/components/RatingPicker/RatingPicker.mdx +56 -0
  149. package/src/components/RatingPicker/RatingPicker.stories.ts +515 -0
  150. package/src/components/RatingPicker/RatingPicker.vue +122 -0
  151. package/src/components/RatingPicker/StarsPicker/StarsPicker.vue +116 -0
  152. package/src/components/RatingPicker/StarsPicker/tests/StarsPicker.spec.ts +95 -0
  153. package/src/components/RatingPicker/StarsPicker/tests/__snapshots__/StarsPicker.spec.ts.snap +36 -0
  154. package/src/components/RatingPicker/locales.ts +3 -0
  155. package/src/components/RatingPicker/tests/Rating.spec.ts +104 -0
  156. package/src/components/RatingPicker/tests/RatingPicker.spec.ts +187 -0
  157. package/src/components/RatingPicker/tests/__snapshots__/RatingPicker.spec.ts.snap +108 -0
  158. package/src/components/SearchListField/SearchListField.mdx +74 -0
  159. package/src/components/SearchListField/SearchListField.stories.ts +126 -0
  160. package/src/components/SearchListField/SearchListField.vue +194 -0
  161. package/src/components/SearchListField/locales.ts +5 -0
  162. package/src/components/SearchListField/tests/SearchListField.spec.ts +323 -0
  163. package/src/components/SearchListField/types.d.ts +4 -0
  164. package/src/components/SelectBtnField/SelectBtnField.mdx +50 -0
  165. package/src/components/SelectBtnField/SelectBtnField.stories.ts +763 -0
  166. package/src/components/SelectBtnField/SelectBtnField.vue +283 -0
  167. package/src/components/SelectBtnField/config.ts +11 -0
  168. package/src/components/SelectBtnField/tests/SelectBtnField.spec.ts +327 -0
  169. package/src/components/SelectBtnField/tests/__snapshots__/SelectBtnField.spec.ts.snap +125 -0
  170. package/src/components/SelectBtnField/types.d.ts +11 -0
  171. package/src/components/SkipLink/SkipLink.vue +10 -10
  172. package/src/components/SocialMediaLinks/SocialMediaLinks.vue +28 -26
  173. package/src/components/SubHeader/SubHeader.vue +32 -31
  174. package/src/components/SyAlert/SyAlert.vue +12 -12
  175. package/src/components/UserMenuBtn/UserMenuBtn.vue +1 -1
  176. package/src/components/UserMenuBtn/config.ts +1 -1
  177. package/src/components/index.ts +17 -7
  178. package/src/composables/rules/useFieldValidation.ts +172 -44
  179. package/src/designTokens/index.ts +6 -4
  180. package/src/designTokens/{bootstrapColors.md → paColors.md} +1 -1
  181. package/src/designTokens/tokens/cnam/cnamLightTheme.ts +2 -0
  182. package/src/designTokens/tokens/pa/paColors.ts +171 -0
  183. package/src/designTokens/tokens/pa/paContextual.ts +58 -0
  184. package/src/designTokens/tokens/pa/paDarkTheme.ts +5 -0
  185. package/src/designTokens/tokens/pa/paLightTheme.ts +123 -0
  186. package/src/designTokens/tokens/pa/paSemantic.ts +87 -0
  187. package/src/stories/Fondamentaux/CustomisationEtThemes.mdx +52 -2
  188. package/src/stories/GuideDuDev/CreerUneIssue.mdx +64 -0
  189. package/src/stories/GuideDuDev/{CommentUtiliserLesRules.mdx → UtiliserLesRules.mdx} +2 -2
  190. package/src/stories/GuideDuDev/components.stories.ts +9 -7
  191. package/src/stories/Guidelines/Vuetify/Vuetify.stories.ts +163 -88
  192. package/src/stories/Guidelines/Vuetify/VuetifyItems.ts +250 -23
  193. package/src/temp/TestDTComponent.vue +5 -6
  194. package/src/utils/calcHumanFileSize/index.ts +12 -0
  195. package/src/utils/calcHumanFileSize/tests/calcHumanFileSize.spec.ts +21 -0
  196. package/src/designTokens/tokens/bootstrap/bootstrapColors.ts +0 -158
  197. package/src/designTokens/tokens/bootstrap/bootstrapLightTheme.ts +0 -22
  198. package/src/stories/GuideDuDev/CommentContribuer.mdx +0 -22
@@ -0,0 +1,213 @@
1
+ import type { StoryObj, Meta } from '@storybook/vue3'
2
+ import PasswordField from './PasswordField.vue'
3
+
4
+ const meta = {
5
+ title: 'Composants/Formulaires/PasswordField',
6
+ component: PasswordField,
7
+ decorators: [
8
+ () => ({
9
+ template: '<div style="padding: 20px;"><story/></div>',
10
+ }),
11
+ ],
12
+ parameters: {
13
+ layout: 'fullscreen',
14
+ },
15
+ argTypes: {
16
+ modelValue: {
17
+ description: 'La valeur du modèle pour le champ.',
18
+ control: 'text',
19
+ default: null,
20
+ table: {
21
+ type: {
22
+ summary: 'string | null',
23
+ },
24
+ },
25
+ },
26
+ outlined: {
27
+ description: 'Définit la variante du champ (outlined ou underlined).',
28
+ control: 'boolean',
29
+ default: true,
30
+ table: {
31
+ type: {
32
+ summary: 'boolean',
33
+ },
34
+ },
35
+ },
36
+ required: {
37
+ description: 'Indique si le champ est requis.',
38
+ control: 'boolean',
39
+ default: false,
40
+ table: {
41
+ type: {
42
+ summary: 'boolean',
43
+ },
44
+ },
45
+ },
46
+ isValidateOnBlur: {
47
+ description: 'Active ou non la validation lors du blur.',
48
+ control: 'boolean',
49
+ default: true,
50
+ table: {
51
+ type: {
52
+ summary: 'boolean',
53
+ },
54
+ },
55
+ },
56
+ customRules: {
57
+ description: 'Règles de validation personnalisées.',
58
+ control: 'object',
59
+ table: {
60
+ type: {
61
+ summary: 'array',
62
+ },
63
+ },
64
+ },
65
+ },
66
+ } satisfies Meta<typeof PasswordField>
67
+
68
+ export default meta
69
+
70
+ type Story = StoryObj<typeof meta>
71
+
72
+ /**
73
+ * Story par défaut
74
+ */
75
+ export const Default: Story = {
76
+ args: {
77
+ modelValue: '',
78
+ outlined: true,
79
+ required: false,
80
+ isValidateOnBlur: true,
81
+ customRules: [],
82
+ },
83
+ render: (args) => {
84
+ return {
85
+ components: { PasswordField },
86
+ setup() {
87
+ return { args }
88
+ },
89
+ template: `
90
+ <PasswordField v-bind="args" v-model="args.modelValue"/>
91
+ `,
92
+ }
93
+ },
94
+ parameters: {
95
+ sourceCode: [
96
+ {
97
+ name: 'Template',
98
+ code: `
99
+ <template>
100
+ <PasswordField
101
+ v-model="password"
102
+ :required="false"
103
+ :isValidateOnBlur="true"
104
+ />
105
+ </template>
106
+ `,
107
+ },
108
+ {
109
+ name: 'Script',
110
+ code: `
111
+ <script setup lang="ts">
112
+ import { ref } from 'vue'
113
+ import PasswordField from '@cnamts/synapse'
114
+
115
+ const password = ref('')
116
+ </script>
117
+ `,
118
+ },
119
+ ],
120
+ },
121
+ }
122
+
123
+ /**
124
+ * Story avec champ requis
125
+ */
126
+ export const Required: Story = {
127
+ args: {
128
+ ...Default.args,
129
+ required: true,
130
+ },
131
+ parameters: {
132
+ ...Default.parameters,
133
+ sourceCode: [
134
+ {
135
+ name: 'Template',
136
+ code: `
137
+ <template>
138
+ <PasswordField
139
+ v-model="password"
140
+ :required="true"
141
+ :isValidateOnBlur="true"
142
+ />
143
+ </template>
144
+ `,
145
+ },
146
+ {
147
+ name: 'Script',
148
+ code: `
149
+ <script setup lang="ts">
150
+ import { ref } from 'vue'
151
+ import PasswordField from '@cnamts/synapse'
152
+
153
+ const password = ref('')
154
+ </script>
155
+ `,
156
+ },
157
+ ],
158
+ },
159
+ }
160
+
161
+ export const WithCustomRules: Story = {
162
+ args: {
163
+ ...Default.args,
164
+ customRules: [
165
+ {
166
+ type: 'minLength',
167
+ options: {
168
+ length: 8,
169
+ message: 'Le mot de passe doit comporter au moins 8 caractères.',
170
+ successMessage: 'Le mot de passe est suffisamment long.',
171
+ },
172
+ },
173
+ ],
174
+ },
175
+ parameters: {
176
+ ...Default.parameters,
177
+ sourceCode: [
178
+ {
179
+ name: 'Template',
180
+ code: `
181
+ <template>
182
+ <PasswordField
183
+ v-model="password"
184
+ :required="false"
185
+ :isValidateOnBlur="true"
186
+ :customRules="[
187
+ {
188
+ type: 'minLength',
189
+ options: {
190
+ length: 8,
191
+ message: 'Le mot de passe doit comporter au moins 8 caractères.',
192
+ successMessage: 'Le mot de passe est suffisamment long.'
193
+ }
194
+ },
195
+ ]"
196
+ />
197
+ </template>
198
+ `,
199
+ },
200
+ {
201
+ name: 'Script',
202
+ code: `
203
+ <script setup lang="ts">
204
+ import { ref } from 'vue'
205
+ import PasswordField from '@cnamts/synapse'
206
+
207
+ const password = ref('')
208
+ </script>
209
+ `,
210
+ },
211
+ ],
212
+ },
213
+ }
@@ -0,0 +1,189 @@
1
+ <script lang="ts" setup>
2
+ import { ref, computed, watch } from 'vue'
3
+ import { config } from './config'
4
+ import { locales } from './locales'
5
+ import { useFieldValidation } from '@/composables/rules/useFieldValidation'
6
+ import { mdiEye, mdiEyeOff } from '@mdi/js'
7
+ // import deepMerge from 'deepmerge'
8
+ import useCustomizableOptions, { type CustomizableOptions } from '@/composables/useCustomizableOptions'
9
+
10
+ type Rule = (value: string | null) => { error?: string, success?: string }
11
+
12
+ const props = withDefaults(defineProps<{
13
+ modelValue?: string | null
14
+ outlined?: boolean
15
+ required?: boolean
16
+ isValidateOnBlur?: boolean
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
18
+ customRules?: any
19
+ } & CustomizableOptions>(), {
20
+ modelValue: null,
21
+ outlined: true,
22
+ required: false,
23
+ isValidateOnBlur: true,
24
+ customRules: [],
25
+ })
26
+
27
+ const options = useCustomizableOptions(config, props)
28
+ const emit = defineEmits(['update:modelValue', 'submit'])
29
+
30
+ const eyeIcon = mdiEye
31
+ const eyeOffIcon = mdiEyeOff
32
+ const showEyeIcon = ref(false)
33
+
34
+ const btnLabel = computed(() => {
35
+ return showEyeIcon.value ? locales.hidePassword : locales.showPassword
36
+ })
37
+
38
+ const password = ref<string | null>(props.modelValue)
39
+ watch(
40
+ () => props.modelValue,
41
+ (newVal) => {
42
+ password.value = newVal
43
+ },
44
+ )
45
+
46
+ const { generateRules } = useFieldValidation()
47
+
48
+ const defaultRules = [
49
+ ...(props.required
50
+ ? [{
51
+ type: 'required',
52
+ options: { message: 'Le mot de passe est requis.', fieldIdentifier: 'password' },
53
+ }]
54
+ : []),
55
+ ]
56
+
57
+ const rules = computed(() => {
58
+ const baseRules = (props.required ? defaultRules : [])
59
+ return props.customRules ? generateRules([...baseRules, ...props.customRules]) : generateRules(baseRules)
60
+ })
61
+
62
+ const errors = ref<string[]>([])
63
+ const successes = ref<string[]>([])
64
+
65
+ const isValidating = ref(false)
66
+
67
+ watch(() => password.value, () => {
68
+ validateFields()
69
+ }, { immediate: true })
70
+
71
+ watch(
72
+ () => props.isValidateOnBlur,
73
+ () => {
74
+ validateFields()
75
+ },
76
+ { immediate: true },
77
+ )
78
+
79
+ watch(
80
+ () => props.required,
81
+ () => {
82
+ validateFields()
83
+ },
84
+ { immediate: true },
85
+ )
86
+
87
+ function validateFieldSet(value: string | null, rules: Rule[]) {
88
+ rules.forEach((rule) => {
89
+ const { error, success } = rule(value)
90
+ if (error) errors.value.push(error)
91
+ if (success && success !== 'Le champ est valide.') successes.value.push(success)
92
+ })
93
+ }
94
+
95
+ function validateFields(onBlur = false): void {
96
+ errors.value = []
97
+ successes.value = []
98
+
99
+ const shouldValidate = onBlur || !props.isValidateOnBlur
100
+
101
+ if (!shouldValidate) return
102
+
103
+ validateFieldSet(password.value, rules.value)
104
+ }
105
+
106
+ function emitChangeEvent(value: string): void {
107
+ emit('update:modelValue', value)
108
+ validateFields()
109
+ }
110
+
111
+ function handleKeydown(event: KeyboardEvent): void {
112
+ if (event.key === 'Enter') {
113
+ emit('submit')
114
+ }
115
+ }
116
+
117
+ function validateOnSubmit() {
118
+ isValidating.value = true
119
+ validateFields(true)
120
+ return errors.value.length === 0
121
+ }
122
+
123
+ defineExpose({
124
+ validateOnSubmit,
125
+ })
126
+ </script>
127
+
128
+ <template>
129
+ <VTextField
130
+ v-model="password"
131
+ :class="{
132
+ 'v-messages__message--success': successes.length > 0
133
+ }"
134
+ :error-messages="errors"
135
+ :messages="successes"
136
+ :type="showEyeIcon ? 'text' : 'password'"
137
+ :variant="outlined ? 'outlined' : 'underlined'"
138
+ class="vd-password"
139
+ color="primary"
140
+ title="password"
141
+ validate-on="blur lazy"
142
+ @blur="validateFields(true)"
143
+ @keydown="handleKeydown"
144
+ @update:model-value="emitChangeEvent"
145
+ >
146
+ <template #append-inner>
147
+ <VBtn
148
+ :aria-label="btnLabel"
149
+ class="mx-auto"
150
+ v-bind="options.btn"
151
+ @click="showEyeIcon = !showEyeIcon"
152
+ >
153
+ <VIcon v-bind="options.icon">
154
+ {{ showEyeIcon ? eyeIcon : eyeOffIcon }}
155
+ </VIcon>
156
+ </VBtn>
157
+ </template>
158
+ </VTextField>
159
+ </template>
160
+
161
+ <style lang="scss" scoped>
162
+ @use '@/assets/tokens';
163
+
164
+ .vd-password {
165
+ .v-btn--icon.v-btn--density-default {
166
+ width: var(--v-btn-height);
167
+ height: var(--v-btn-height);
168
+ }
169
+
170
+ :deep(.v-field.v-field--variant-underlined .v-field__append-inner) {
171
+ padding-top: 0;
172
+ padding-bottom: 0;
173
+ display: flex;
174
+ align-items: center;
175
+ }
176
+
177
+ :deep(.v-field.v-field--variant-underlined .v-field__input) {
178
+ padding-top: calc(var(--v-field-input-padding-top) - 15px);
179
+ }
180
+ }
181
+
182
+ .v-messages__message--success {
183
+ color: tokens.$colors-border-success !important;
184
+
185
+ .v-field--active & {
186
+ color: tokens.$colors-border-success !important;
187
+ }
188
+ }
189
+ </style>
@@ -0,0 +1,11 @@
1
+ import { cnamColorsTokens } from '@/designTokens/tokens/cnam/cnamColors'
2
+
3
+ export const config = {
4
+ btn: {
5
+ variant: 'text',
6
+ icon: true,
7
+ },
8
+ icon: {
9
+ color: cnamColorsTokens.grey.lighten20,
10
+ },
11
+ } as const
@@ -0,0 +1,4 @@
1
+ export enum ExpertiseLevelEnum {
2
+ DEV = 'dev',
3
+ DESIGN = 'design',
4
+ }
@@ -0,0 +1,4 @@
1
+ export const locales = {
2
+ hidePassword: 'Masquer le mot de passe',
3
+ showPassword: 'Afficher le mot de passe',
4
+ }
@@ -0,0 +1,96 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import PasswordField from '../PasswordField.vue'
3
+ import { describe, it, expect, beforeEach } from 'vitest'
4
+ import { createVuetify } from 'vuetify'
5
+
6
+ // 1. Define an interface for the properties/methods you're testing
7
+ interface PasswordFieldVM {
8
+ showEyeIcon: boolean
9
+ errors: string[]
10
+ isValidating: boolean
11
+ validateOnSubmit: () => boolean
12
+ }
13
+
14
+ describe('PasswordField.vue', () => {
15
+ let vuetify
16
+
17
+ beforeEach(() => {
18
+ vuetify = createVuetify()
19
+ })
20
+
21
+ it('renders the password field', () => {
22
+ const wrapper = mount(PasswordField, {
23
+ global: {
24
+ plugins: [vuetify],
25
+ },
26
+ })
27
+ expect(wrapper.exists()).toBe(true)
28
+ })
29
+
30
+ it('toggles password visibility', async () => {
31
+ const wrapper = mount(PasswordField, {
32
+ global: {
33
+ plugins: [vuetify],
34
+ },
35
+ })
36
+ // 2. Cast wrapper.vm as your interface
37
+ const vm = wrapper.vm as unknown as PasswordFieldVM
38
+
39
+ const button = wrapper.find('button')
40
+ expect(vm.showEyeIcon).toBe(false) // from your interface
41
+ await button.trigger('click')
42
+ expect(vm.showEyeIcon).toBe(true)
43
+ await button.trigger('click')
44
+ expect(vm.showEyeIcon).toBe(false)
45
+ })
46
+
47
+ it('emits update:modelValue event on input', async () => {
48
+ const wrapper = mount(PasswordField, {
49
+ global: {
50
+ plugins: [vuetify],
51
+ },
52
+ })
53
+ const input = wrapper.find('input')
54
+ await input.setValue('new-password')
55
+ expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['new-password'])
56
+ })
57
+
58
+ it('validates the password field on blur', async () => {
59
+ const wrapper = mount(PasswordField, {
60
+ global: {
61
+ plugins: [vuetify],
62
+ },
63
+ props: {
64
+ required: true,
65
+ },
66
+ })
67
+ const vm = wrapper.vm as unknown as PasswordFieldVM
68
+
69
+ const input = wrapper.find('input')
70
+ await input.trigger('blur')
71
+ expect(vm.errors).toContain('Le mot de passe est requis.')
72
+ })
73
+
74
+ it('validates fields on submit and sets validating flag', async () => {
75
+ const wrapper = mount(PasswordField, {
76
+ global: {
77
+ plugins: [vuetify],
78
+ },
79
+ props: {
80
+ modelValue: '',
81
+ outlined: false,
82
+ required: true,
83
+ },
84
+ })
85
+ const vm = wrapper.vm as unknown as PasswordFieldVM
86
+
87
+ const result = vm.validateOnSubmit()
88
+ expect(vm.isValidating).toBe(true)
89
+ expect(result).toBe(false)
90
+
91
+ // This awaits the Vue microtask queue to let any reactive data settle
92
+ await wrapper.vm.$nextTick()
93
+
94
+ expect(vm.errors).toContain('Le mot de passe est requis.')
95
+ })
96
+ })
@@ -41,8 +41,6 @@ const customIndicatifs = ref([
41
41
  display-format="code-abbreviation"
42
42
  :custom-indicatifs="customIndicatifs"
43
43
  :use-custom-indicatifs-only="true"
44
- outlined
45
- outlined-indicatif
46
44
  @change="handleChange"
47
45
  />
48
46
  </template>