@cnamts/synapse 0.0.8-alpha → 0.0.10-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 (156) hide show
  1. package/dist/design-system-v3.d.ts +1152 -127
  2. package/dist/design-system-v3.js +4888 -2605
  3. package/dist/design-system-v3.umd.cjs +1 -1
  4. package/dist/style.css +1 -1
  5. package/package.json +1 -1
  6. package/src/assets/settings.scss +1 -1
  7. package/src/components/ContextualMenu/Accessibilite.mdx +14 -0
  8. package/src/components/ContextualMenu/Accessibilite.stories.ts +191 -0
  9. package/src/components/ContextualMenu/AccessibiliteItems.ts +89 -0
  10. package/src/components/ContextualMenu/constants/ExpertiseLevelEnum.ts +4 -0
  11. package/src/components/Customs/SySelect/SySelect.stories.ts +7 -7
  12. package/src/components/Customs/SySelect/SySelect.vue +9 -4
  13. package/src/components/Customs/SySelect/tests/SySelect.spec.ts +2 -2
  14. package/src/components/Customs/SyTextField/SyTextField.stories.ts +187 -2
  15. package/src/components/Customs/SyTextField/SyTextField.vue +185 -16
  16. package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +2 -4
  17. package/src/components/Customs/SyTextField/tests/__snapshots__/SyTextField.spec.ts.snap +18 -16
  18. package/src/components/Customs/SyTextField/types.d.ts +2 -2
  19. package/src/components/DatePicker/Accessibilite.mdx +14 -0
  20. package/src/components/DatePicker/Accessibilite.stories.ts +191 -0
  21. package/src/components/DatePicker/AccessibiliteItems.ts +233 -0
  22. package/src/components/DatePicker/DatePicker.mdx +186 -0
  23. package/src/components/DatePicker/DatePicker.stories.ts +787 -0
  24. package/src/components/DatePicker/DatePicker.vue +574 -0
  25. package/src/components/DatePicker/DateTextInput.vue +409 -0
  26. package/src/components/DatePicker/constants/ExpertiseLevelEnum.ts +4 -0
  27. package/src/components/DatePicker/tests/DatePicker.spec.ts +266 -0
  28. package/src/components/DialogBox/DialogBox.stories.ts +1 -1
  29. package/src/components/ExternalLinks/Accessibilite.mdx +14 -0
  30. package/src/components/ExternalLinks/Accessibilite.stories.ts +191 -0
  31. package/src/components/ExternalLinks/AccessibiliteItems.ts +197 -0
  32. package/src/components/ExternalLinks/constants/ExpertiseLevelEnum.ts +4 -0
  33. package/src/components/ExternalLinks/tests/__snapshots__/ExternalLinks.spec.ts.snap +9 -9
  34. package/src/components/FileList/FileList.mdx +103 -0
  35. package/src/components/FileList/FileList.stories.ts +562 -0
  36. package/src/components/FileList/FileList.vue +78 -0
  37. package/src/components/FileList/UploadItem/UploadItem.vue +270 -0
  38. package/src/components/FileList/UploadItem/locales.ts +9 -0
  39. package/src/components/FileList/tests/FileList.spec.ts +176 -0
  40. package/src/components/FilePreview/FilePreview.mdx +82 -0
  41. package/src/components/FilePreview/FilePreview.stories.ts +242 -0
  42. package/src/components/FilePreview/FilePreview.vue +68 -0
  43. package/src/components/FilePreview/config.ts +10 -0
  44. package/src/components/FilePreview/locales.ts +4 -0
  45. package/src/components/FilePreview/tests/FilePreview.spec.ts +124 -0
  46. package/src/components/FilePreview/tests/__snapshots__/FilePreview.spec.ts.snap +11 -0
  47. package/src/components/FileUpload/FileUpload.mdx +165 -0
  48. package/src/components/FileUpload/FileUpload.stories.ts +429 -0
  49. package/src/components/FileUpload/FileUpload.vue +195 -0
  50. package/src/components/FileUpload/FileUploadContent.vue +109 -0
  51. package/src/components/FileUpload/locales.ts +10 -0
  52. package/src/components/FileUpload/tests/FileUpload.spec.ts +332 -0
  53. package/src/components/FileUpload/tests/__snapshots__/FileUpload.spec.ts.snap +7 -0
  54. package/src/components/FileUpload/useFileDrop.ts +23 -0
  55. package/src/components/FileUpload/validateFiles.ts +39 -0
  56. package/src/components/NirField/NirField.stories.ts +1 -1
  57. package/src/components/NirField/NirField.vue +2 -1
  58. package/src/components/PasswordField/Accessibilite.mdx +14 -0
  59. package/src/components/PasswordField/Accessibilite.stories.ts +191 -0
  60. package/src/components/PasswordField/AccessibiliteItems.ts +184 -0
  61. package/src/components/PasswordField/PasswordField.vue +3 -3
  62. package/src/components/PasswordField/constants/ExpertiseLevelEnum.ts +4 -0
  63. package/src/components/PeriodField/PeriodField.mdx +32 -0
  64. package/src/components/PeriodField/PeriodField.stories.ts +807 -0
  65. package/src/components/PeriodField/PeriodField.vue +355 -0
  66. package/src/components/PeriodField/tests/PeriodField.spec.ts +348 -0
  67. package/src/components/PhoneField/PhoneField.vue +44 -60
  68. package/src/components/PhoneField/tests/PhoneField.spec.ts +0 -15
  69. package/src/components/RangeField/Accessibilite.mdx +14 -0
  70. package/src/components/RangeField/Accessibilite.stories.ts +191 -0
  71. package/src/components/RangeField/AccessibiliteItems.ts +179 -0
  72. package/src/components/RangeField/RangeField.mdx +54 -0
  73. package/src/components/RangeField/RangeField.stories.ts +189 -0
  74. package/src/components/RangeField/RangeField.vue +157 -0
  75. package/src/components/RangeField/RangeSlider/RangeSlider.vue +387 -0
  76. package/src/components/RangeField/RangeSlider/Tooltip/Tooltip.vue +64 -0
  77. package/src/components/RangeField/RangeSlider/tests/__snapshots__/rangeSlider.spec.ts.snap +27 -0
  78. package/src/components/RangeField/RangeSlider/tests/rangeSlider.spec.ts +100 -0
  79. package/src/components/RangeField/RangeSlider/tests/useDoubleSlider.spec.ts +246 -0
  80. package/src/components/RangeField/RangeSlider/tests/useMouseSlide.spec.ts +204 -0
  81. package/src/components/RangeField/RangeSlider/tests/useThumb.spec.ts +22 -0
  82. package/src/components/RangeField/RangeSlider/tests/useThumbKeyboard.spec.ts +233 -0
  83. package/src/components/RangeField/RangeSlider/tests/useTooltipsNudge.spec.ts +150 -0
  84. package/src/components/RangeField/RangeSlider/tests/useTrack.spec.ts +314 -0
  85. package/src/components/RangeField/RangeSlider/tests/vAnimateClick.spec.ts +32 -0
  86. package/src/components/RangeField/RangeSlider/types.ts +15 -0
  87. package/src/components/RangeField/RangeSlider/useMouseSlide.ts +109 -0
  88. package/src/components/RangeField/RangeSlider/useRangeSlider.ts +126 -0
  89. package/src/components/RangeField/RangeSlider/useThumb.ts +18 -0
  90. package/src/components/RangeField/RangeSlider/useThumbKeyboard.ts +84 -0
  91. package/src/components/RangeField/RangeSlider/useTooltipsNudge.ts +92 -0
  92. package/src/components/RangeField/RangeSlider/useTrack.ts +116 -0
  93. package/src/components/RangeField/RangeSlider/vAnimateClick.ts +19 -0
  94. package/src/components/RangeField/config.ts +7 -0
  95. package/src/components/RangeField/constants/ExpertiseLevelEnum.ts +4 -0
  96. package/src/components/RangeField/locales.ts +4 -0
  97. package/src/components/RangeField/tests/RangeField.spec.ts +224 -0
  98. package/src/components/RangeField/tests/__snapshots__/RangeField.spec.ts.snap +379 -0
  99. package/src/components/RatingPicker/Accessibilite.mdx +14 -0
  100. package/src/components/RatingPicker/Accessibilite.stories.ts +191 -0
  101. package/src/components/RatingPicker/AccessibiliteItems.ts +208 -0
  102. package/src/components/RatingPicker/EmotionPicker/EmotionPicker.vue +205 -0
  103. package/src/components/RatingPicker/EmotionPicker/locales.ts +3 -0
  104. package/src/components/RatingPicker/EmotionPicker/tests/EmotionPicker.spec.ts +104 -0
  105. package/src/components/RatingPicker/EmotionPicker/tests/__snapshots__/EmotionPicker.spec.ts.snap +66 -0
  106. package/src/components/RatingPicker/NumberPicker/NumberPicker.vue +159 -0
  107. package/src/components/RatingPicker/NumberPicker/locales.ts +4 -0
  108. package/src/components/RatingPicker/NumberPicker/tests/NumberPicker.spec.ts +73 -0
  109. package/src/components/RatingPicker/NumberPicker/tests/__snapshots__/NumberPicker.spec.ts.snap +105 -0
  110. package/src/components/RatingPicker/Rating.ts +45 -0
  111. package/src/components/RatingPicker/RatingPicker.mdx +56 -0
  112. package/src/components/RatingPicker/RatingPicker.stories.ts +515 -0
  113. package/src/components/RatingPicker/RatingPicker.vue +122 -0
  114. package/src/components/RatingPicker/StarsPicker/StarsPicker.vue +116 -0
  115. package/src/components/RatingPicker/StarsPicker/tests/StarsPicker.spec.ts +95 -0
  116. package/src/components/RatingPicker/StarsPicker/tests/__snapshots__/StarsPicker.spec.ts.snap +36 -0
  117. package/src/components/RatingPicker/constants/ExpertiseLevelEnum.ts +4 -0
  118. package/src/components/RatingPicker/locales.ts +3 -0
  119. package/src/components/RatingPicker/tests/Rating.spec.ts +104 -0
  120. package/src/components/RatingPicker/tests/RatingPicker.spec.ts +187 -0
  121. package/src/components/RatingPicker/tests/__snapshots__/RatingPicker.spec.ts.snap +108 -0
  122. package/src/components/SearchListField/Accessibilite.mdx +14 -0
  123. package/src/components/SearchListField/Accessibilite.stories.ts +191 -0
  124. package/src/components/SearchListField/AccessibiliteItems.ts +310 -0
  125. package/src/components/SearchListField/SearchListField.mdx +74 -0
  126. package/src/components/SearchListField/SearchListField.stories.ts +126 -0
  127. package/src/components/SearchListField/SearchListField.vue +194 -0
  128. package/src/components/SearchListField/constants/ExpertiseLevelEnum.ts +4 -0
  129. package/src/components/SearchListField/locales.ts +5 -0
  130. package/src/components/SearchListField/tests/SearchListField.spec.ts +323 -0
  131. package/src/components/SearchListField/types.d.ts +4 -0
  132. package/src/components/SelectBtnField/Accessibilite.mdx +14 -0
  133. package/src/components/SelectBtnField/Accessibilite.stories.ts +191 -0
  134. package/src/components/SelectBtnField/AccessibiliteItems.ts +191 -0
  135. package/src/components/SelectBtnField/SelectBtnField.mdx +50 -0
  136. package/src/components/SelectBtnField/SelectBtnField.stories.ts +763 -0
  137. package/src/components/SelectBtnField/SelectBtnField.vue +283 -0
  138. package/src/components/SelectBtnField/config.ts +11 -0
  139. package/src/components/SelectBtnField/constants/ExpertiseLevelEnum.ts +4 -0
  140. package/src/components/SelectBtnField/tests/SelectBtnField.spec.ts +327 -0
  141. package/src/components/SelectBtnField/tests/__snapshots__/SelectBtnField.spec.ts.snap +125 -0
  142. package/src/components/SelectBtnField/types.d.ts +11 -0
  143. package/src/components/SyAlert/SyAlert.vue +11 -9
  144. package/src/components/TableToolbar/TableToolbar.mdx +130 -0
  145. package/src/components/TableToolbar/TableToolbar.stories.ts +935 -0
  146. package/src/components/TableToolbar/TableToolbar.vue +168 -0
  147. package/src/components/TableToolbar/config.ts +24 -0
  148. package/src/components/TableToolbar/locales.ts +6 -0
  149. package/src/components/TableToolbar/tests/TableToolbar.spec.ts +166 -0
  150. package/src/components/TableToolbar/tests/__snapshots__/TableToolbar.spec.ts.snap +359 -0
  151. package/src/components/index.ts +11 -1
  152. package/src/composables/rules/useFieldValidation.ts +174 -44
  153. package/src/designTokens/index.ts +3 -3
  154. package/src/stories/Fondamentaux/CustomisationEtThemes.mdx +52 -2
  155. package/src/utils/calcHumanFileSize/index.ts +12 -0
  156. package/src/utils/calcHumanFileSize/tests/calcHumanFileSize.spec.ts +21 -0
@@ -0,0 +1,574 @@
1
+ <script lang="ts" setup>
2
+ import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
3
+ import SyTextField from '@/components/Customs/SyTextField/SyTextField.vue'
4
+ import DateTextInput from './DateTextInput.vue'
5
+ import { VDatePicker } from 'vuetify/components'
6
+ import { useFieldValidation } from '@/composables/rules/useFieldValidation'
7
+ import type { RuleOptions } from '@/composables/rules/useFieldValidation'
8
+
9
+ type DateValue = string | [string, string]
10
+ type DateInput = string | string[] | null | object
11
+
12
+ const props = withDefaults(defineProps<{
13
+ modelValue?: DateInput
14
+ placeholder?: string
15
+ format?: string
16
+ dateFormatReturn?: string
17
+ isBirthDate?: boolean
18
+ showWeekNumber?: boolean
19
+ required?: boolean
20
+ displayRange?: boolean
21
+ displayIcon?: boolean
22
+ displayAppendIcon?: boolean
23
+ customRules?: { type: string, options: RuleOptions }[]
24
+ customWarningRules?: { type: string, options: RuleOptions }[]
25
+ isDisabled?: boolean
26
+ noIcon?: boolean
27
+ noCalendar?: boolean
28
+ isOutlined?: boolean
29
+ }>(), {
30
+ modelValue: undefined,
31
+ placeholder: 'Sélectionner une date',
32
+ format: 'DD/MM/YYYY',
33
+ dateFormatReturn: '',
34
+ isBirthDate: false,
35
+ showWeekNumber: false,
36
+ required: false,
37
+ displayRange: false,
38
+ displayIcon: true,
39
+ displayAppendIcon: false,
40
+ customRules: () => [],
41
+ customWarningRules: () => [],
42
+ isDisabled: false,
43
+ noIcon: false,
44
+ noCalendar: false,
45
+ isOutlined: true,
46
+ })
47
+
48
+ const emit = defineEmits<{
49
+ (e: 'update:model-value', value: DateValue): void
50
+ (e: 'closed'): void
51
+ }>()
52
+
53
+ // Fonction pour parser les dates selon le format spécifié
54
+ const parseDate = (dateString: string): Date | null => {
55
+ if (!dateString) return null
56
+
57
+ // Créer un mapping des positions des éléments de date selon le format
58
+ const format = props.format || 'DD/MM/YYYY'
59
+ const separator = format.includes('/') ? '/' : format.includes('-') ? '-' : '.'
60
+ const parts = format.split(separator)
61
+ const dateParts = dateString.split(separator)
62
+
63
+ if (parts.length !== dateParts.length) return null
64
+
65
+ let day = '', month = '', year = ''
66
+
67
+ // Extraire les valeurs selon leur position dans le format
68
+ parts.forEach((part, index) => {
69
+ const value = dateParts[index]
70
+ if (part.includes('DD')) day = value
71
+ else if (part.includes('MM')) month = value
72
+ else if (part.includes('YYYY')) year = value
73
+ else if (part.includes('YY')) year = '20' + value // Assumons que nous sommes au 21ème siècle
74
+ })
75
+
76
+ // Vérifier que nous avons toutes les parties nécessaires
77
+ if (!day || !month || !year) return null
78
+
79
+ const date = new Date(`${year}-${month}-${day}`)
80
+ return isNaN(date.getTime()) ? null : date
81
+ }
82
+
83
+ function initializeSelectedDates(
84
+ modelValue: DateInput | null,
85
+ ): Date | Date[] | null {
86
+ if (!modelValue) return null
87
+
88
+ if (Array.isArray(modelValue)) {
89
+ if (modelValue.length >= 2) {
90
+ const dates = [parseDate(modelValue[0]), parseDate(modelValue[1])]
91
+ // Vérifie si l'une des dates est invalide
92
+ if (dates.some(date => date === null)) {
93
+ return []
94
+ }
95
+ // Vérifie si la première date est après la seconde
96
+ if (dates[0] && dates[1] && dates[0] > dates[1]) {
97
+ return []
98
+ }
99
+ // Filtrer les dates nulles et convertir en tableau de Date
100
+ return dates.filter((date): date is Date => date !== null)
101
+ }
102
+ if (modelValue.length === 1) {
103
+ const date = parseDate(modelValue[0])
104
+ return date === null ? [] : [date]
105
+ }
106
+ return []
107
+ }
108
+
109
+ // Si modelValue est un objet, on le convertit en chaîne
110
+ if (typeof modelValue === 'object') {
111
+ return null
112
+ }
113
+
114
+ const date = parseDate(modelValue)
115
+ return date === null ? null : date
116
+ }
117
+
118
+ // Utilisation de la fonction pour initialiser `selectedDates`
119
+ const selectedDates = ref<Date | Date[] | null>(
120
+ initializeSelectedDates(props.modelValue as DateInput | null),
121
+ )
122
+
123
+ const isDatePickerVisible = ref(false)
124
+ const errorMessages = ref<string[]>([])
125
+ const successMessages = ref<string[]>([])
126
+ const warningMessages = ref<string[]>([])
127
+ const displayFormattedDate = ref('')
128
+
129
+ const getMessageClasses = () => ({
130
+ 'dp-width': true,
131
+ 'v-messages__message--success': successMessages.value.length > 0,
132
+ 'v-messages__message--error': errorMessages.value.length > 0,
133
+ 'v-messages__message--warning': warningMessages.value.length > 0 && errorMessages.value.length < 1,
134
+ })
135
+
136
+ // Formate une date unique au format spécifié
137
+ const formatDate = (date: Date, format: string): string => {
138
+ if (!date) return ''
139
+ const day = date.getDate().toString().padStart(2, '0')
140
+ const month = (date.getMonth() + 1).toString().padStart(2, '0')
141
+ const year = date.getFullYear().toString()
142
+ const shortYear = year.slice(-2)
143
+ return format.replace('YYYY', year).replace('YY', shortYear).replace('MM', month).replace('DD', day)
144
+ }
145
+
146
+ // Date(s) formatée(s) en chaîne de caractères pour la valeur de retour
147
+ const formattedDate = computed<DateValue>(() => {
148
+ if (!selectedDates.value) return ''
149
+
150
+ const returnFormat = props.dateFormatReturn || props.format
151
+
152
+ if (Array.isArray(selectedDates.value)) {
153
+ if (selectedDates.value.length >= 2) {
154
+ return [
155
+ formatDate(selectedDates.value[0], returnFormat),
156
+ formatDate(selectedDates.value[1], returnFormat),
157
+ ] as [string, string]
158
+ }
159
+ return ''
160
+ }
161
+
162
+ return formatDate(selectedDates.value, returnFormat)
163
+ })
164
+
165
+ // Date(s) formatée(s) en chaîne de caractères pour l'affichage
166
+ const displayFormattedDateComputed = computed(() => {
167
+ if (!selectedDates.value) return null
168
+
169
+ if (Array.isArray(selectedDates.value)) {
170
+ if (selectedDates.value.length >= 2) {
171
+ return `${formatDate(selectedDates.value[0], props.format)} - ${formatDate(
172
+ selectedDates.value[selectedDates.value.length - 1],
173
+ props.format,
174
+ )}`
175
+ }
176
+ return formatDate(selectedDates.value[0], props.format)
177
+ }
178
+
179
+ return formatDate(selectedDates.value, props.format)
180
+ })
181
+
182
+ const validateDateValue = (value: string | string[]): DateValue => {
183
+ if (Array.isArray(value)) {
184
+ if (value.length >= 2) {
185
+ return [value[0], value[1]] as [string, string]
186
+ }
187
+ return value[0] || ''
188
+ }
189
+ return value
190
+ }
191
+
192
+ watch(formattedDate, (newValue) => {
193
+ const validValue = validateDateValue(newValue)
194
+ emit('update:model-value', validValue)
195
+ })
196
+
197
+ watch(displayFormattedDateComputed, (newValue) => {
198
+ if (!props.noCalendar && newValue) {
199
+ displayFormattedDate.value = newValue
200
+ }
201
+ })
202
+
203
+ const updateSelectedDates = (input: DateValue) => {
204
+ if (Array.isArray(input)) {
205
+ const dates = input
206
+ .map(date => (date ? parseDate(date) : null))
207
+ .filter((date): date is Date => date !== null)
208
+
209
+ if (dates.length === 0) {
210
+ selectedDates.value = null
211
+ return
212
+ }
213
+
214
+ selectedDates.value = dates
215
+ return
216
+ }
217
+
218
+ const date = input ? parseDate(input) : null
219
+ selectedDates.value = date === null ? null : date
220
+ }
221
+
222
+ watch(selectedDates, (newValue) => {
223
+ validateDates()
224
+ if (props.displayRange) {
225
+ if (Array.isArray(newValue) && newValue.length >= 2) {
226
+ isDatePickerVisible.value = false
227
+ emit('closed')
228
+ }
229
+ }
230
+ else {
231
+ isDatePickerVisible.value = false
232
+ emit('closed')
233
+ }
234
+ })
235
+
236
+ // Gestionnaire de clic en dehors
237
+ const handleClickOutside = (event: MouseEvent) => {
238
+ if (!isDatePickerVisible.value) return
239
+
240
+ const target = event.target as HTMLElement
241
+ const container = target.closest('.date-picker-container')
242
+
243
+ // Si on clique dans le conteneur du DatePicker, on ne fait rien
244
+ if (container) return
245
+
246
+ isDatePickerVisible.value = false
247
+ emit('closed')
248
+ }
249
+
250
+ const todayInString = computed(() => {
251
+ return (new Date().toLocaleDateString('fr-FR', {
252
+ weekday: 'long',
253
+ month: 'long',
254
+ day: 'numeric',
255
+ })).replace(/\b\w/g, l => l.toUpperCase())
256
+ })
257
+
258
+ onMounted(() => {
259
+ document.addEventListener('click', handleClickOutside)
260
+ if (selectedDates.value !== null) {
261
+ validateDates()
262
+ // Force format application on mount
263
+ emit('update:model-value', formattedDate.value)
264
+ }
265
+ if (displayFormattedDateComputed.value) {
266
+ displayFormattedDate.value = displayFormattedDateComputed.value
267
+ }
268
+ })
269
+
270
+ onBeforeUnmount(() => {
271
+ document.removeEventListener('click', handleClickOutside)
272
+ })
273
+
274
+ const dateTextInputRef = ref()
275
+
276
+ const validateOnSubmit = () => {
277
+ if (props.noCalendar) {
278
+ return dateTextInputRef.value?.validateOnSubmit()
279
+ }
280
+ validateDates()
281
+ return errorMessages.value.length === 0
282
+ }
283
+
284
+ defineExpose({
285
+ validateOnSubmit,
286
+ isDatePickerVisible,
287
+ selectedDates,
288
+ errorMessages,
289
+ handleClickOutside,
290
+ initializeSelectedDates,
291
+ })
292
+
293
+ // les btns du date picker ne sont pas accessibles, on les rend accessibles
294
+ watch(isDatePickerVisible, async (newValue) => {
295
+ if (newValue) {
296
+ await nextTick()
297
+ const arrowDown = document.querySelector('.v-btn.v-btn--icon.v-theme--light.v-btn--density-comfortable.v-btn--size-default.v-btn--variant-text.v-date-picker-controls__mode-btn')
298
+ const arrowLeftButtons = document.querySelectorAll('.v-btn.v-btn--icon.v-theme--light.v-btn--density-default.v-btn--size-default.v-btn--variant-text')
299
+
300
+ if (arrowDown) {
301
+ arrowDown.setAttribute('aria-label', 'Fleche vers le bas')
302
+ }
303
+
304
+ arrowLeftButtons.forEach((button, index) => {
305
+ if (index === 0) {
306
+ button.setAttribute('aria-label', 'Fleche vers la gauche')
307
+ }
308
+ else if (index === 1) {
309
+ button.setAttribute('aria-label', 'Fleche vers la droite')
310
+ }
311
+ })
312
+ }
313
+ })
314
+
315
+ const handlePrependIconClick = () => {
316
+ isDatePickerVisible.value = true
317
+ }
318
+
319
+ const handleAppendIconClick = () => {
320
+ isDatePickerVisible.value = true
321
+ }
322
+
323
+ type Rule = { type: string, options: RuleOptions }
324
+
325
+ const customRules = ref<Rule[]>(props.customRules || [])
326
+ const customWarningRules = ref<Rule[]>(props.customWarningRules || [])
327
+
328
+ const { generateRules } = useFieldValidation()
329
+ const validationRules = generateRules(customRules.value)
330
+ const warningValidationRules = generateRules(customWarningRules.value)
331
+
332
+ const validateDates = () => {
333
+ errorMessages.value = []
334
+ successMessages.value = []
335
+ warningMessages.value = []
336
+
337
+ const addMessages = (dates, rules) => {
338
+ dates.forEach((date) => {
339
+ rules.forEach((rule) => {
340
+ const result = rule(date)
341
+ if (result?.error) {
342
+ errorMessages.value.push(result.error)
343
+ errorMessages.value = [...new Set(errorMessages.value)]
344
+ }
345
+ else if (result?.warning) {
346
+ warningMessages.value.push(result.warning)
347
+ warningMessages.value = [...new Set(warningMessages.value)]
348
+ }
349
+ else if (result?.success) {
350
+ successMessages.value.push(result.success)
351
+ successMessages.value = [...new Set(successMessages.value)]
352
+ }
353
+ })
354
+ })
355
+ }
356
+
357
+ const handleValidation = (dates) => {
358
+ if (Array.isArray(dates) && dates.length > 1) {
359
+ // Pour une plage, on ne vérifie que le premier et le dernier jour
360
+ const [firstDate, ...rest] = dates
361
+ const lastDate = rest[rest.length - 1]
362
+ const datesToValidate = [firstDate, lastDate]
363
+
364
+ // Validation des règles
365
+ addMessages(datesToValidate, validationRules)
366
+ addMessages(datesToValidate, warningValidationRules)
367
+ }
368
+ else {
369
+ // Pour une date unique, on valide normalement
370
+ const datesToValidate = Array.isArray(dates) ? dates : [dates]
371
+ addMessages(datesToValidate, validationRules)
372
+ addMessages(datesToValidate, warningValidationRules)
373
+ }
374
+ }
375
+
376
+ if (
377
+ props.required
378
+ && (!selectedDates.value || (Array.isArray(selectedDates.value) && selectedDates.value.length === 0))
379
+ ) {
380
+ errorMessages.value.push('La date est requise.')
381
+ }
382
+ else if (selectedDates.value) {
383
+ handleValidation(Array.isArray(selectedDates.value) ? selectedDates.value : [selectedDates.value])
384
+ }
385
+ }
386
+
387
+ const getIcon = () => {
388
+ if (props.noCalendar) {
389
+ return
390
+ }
391
+ switch (true) {
392
+ case errorMessages.value.length > 0:
393
+ return 'error'
394
+ case warningMessages.value.length > 0:
395
+ return 'warning'
396
+ case successMessages.value.length > 0:
397
+ return 'success'
398
+ default:
399
+ return
400
+ }
401
+ }
402
+ </script>
403
+
404
+ <template>
405
+ <div class="date-picker-container">
406
+ <template v-if="props.noCalendar">
407
+ <DateTextInput
408
+ ref="dateTextInputRef"
409
+ v-model="displayFormattedDate"
410
+ :class="[getMessageClasses(), 'label-hidden-on-focus']"
411
+ :date-format-return="props.dateFormatReturn"
412
+ :format="props.format"
413
+ :label="props.placeholder"
414
+ :placeholder="props.placeholder"
415
+ :range="props.displayRange"
416
+ :required="props.required"
417
+ :rules="props.customRules"
418
+ :warning-rules="props.customWarningRules"
419
+ title="Date text input"
420
+ @update:model-value="updateSelectedDates"
421
+ />
422
+ </template>
423
+ <template v-else>
424
+ <SyTextField
425
+ v-model="displayFormattedDate"
426
+ :append-icon="displayIcon && displayAppendIcon ? 'calendar' : undefined"
427
+ :append-inner-icon="getIcon()"
428
+ :class="[getMessageClasses(), 'label-hidden-on-focus']"
429
+ :error-messages="errorMessages"
430
+ :is-disabled="props.isDisabled"
431
+ :is-read-only="true"
432
+ :label="props.placeholder"
433
+ :messages="warningMessages.length > 0 ? [...warningMessages] : [ ...successMessages]"
434
+ :no-icon="props.noIcon"
435
+ :prepend-icon="displayIcon && !displayAppendIcon ? 'calendar' : undefined"
436
+ :variant-style="props.isOutlined ? 'outlined' : 'underlined'"
437
+ color="primary"
438
+ is-clearable
439
+ title="Date Picker"
440
+ @focus="isDatePickerVisible = true"
441
+ @update:model-value="updateSelectedDates"
442
+ @prepend-icon-click="handlePrependIconClick"
443
+ @append-icon-click="handleAppendIconClick"
444
+ />
445
+ </template>
446
+ <transition name="fade">
447
+ <v-locale-provider locale="fr">
448
+ <VDatePicker
449
+ v-if="isDatePickerVisible && !props.noCalendar"
450
+ v-model="selectedDates"
451
+ :first-day-of-week="1"
452
+ :multiple="props.displayRange ? 'range' : false"
453
+ :show-adjacent-months="true"
454
+ :show-week="props.showWeekNumber"
455
+ :view-mode="props.isBirthDate ? 'year' : 'month'"
456
+ color="primary"
457
+ >
458
+ <template #title>
459
+ Sélectionnez une date
460
+ </template>
461
+ <template #header>
462
+ <h3 class="mx-auto my-auto ml-5 mb-4">
463
+ {{ todayInString }}
464
+ </h3>
465
+ </template>
466
+ </VDatePicker>
467
+ </v-locale-provider>
468
+ </transition>
469
+ </div>
470
+ </template>
471
+
472
+ <style lang="scss" scoped>
473
+ @use '@/assets/tokens';
474
+
475
+ .label-hidden-on-focus:focus + label {
476
+ display: none;
477
+ }
478
+
479
+ .dp-width {
480
+ min-width: 345px;
481
+ }
482
+
483
+ .v-messages__message--success {
484
+ :deep(.v-input__control),
485
+ :deep(.v-messages__message) {
486
+ color: tokens.$colors-text-success !important;
487
+
488
+ --v-medium-emphasis-opacity: 1;
489
+ }
490
+
491
+ .v-field--active & {
492
+ color: tokens.$colors-border-success !important;
493
+ }
494
+ }
495
+
496
+ .v-messages__message--error {
497
+ :deep(.v-input__control),
498
+ :deep(.v-messages__message) {
499
+ color: tokens.$colors-text-error !important;
500
+ }
501
+
502
+ .v-field--active & {
503
+ color: tokens.$colors-border-error !important;
504
+ }
505
+ }
506
+
507
+ .v-messages__message--warning {
508
+ :deep(.v-input__control) {
509
+ color: tokens.$colors-text-warning !important;
510
+
511
+ --v-medium-emphasis-opacity: 1;
512
+ }
513
+
514
+ :deep(.v-messages__message) {
515
+ color: tokens.$colors-text-warning !important;
516
+ }
517
+
518
+ .v-field--active & {
519
+ color: tokens.$colors-text-warning !important;
520
+ }
521
+ }
522
+
523
+ :deep(.v-btn__content) {
524
+ font-size: tokens.$font-size-body-text + 3;
525
+ font-weight: bold;
526
+ }
527
+
528
+ :deep(.v-messages) {
529
+ opacity: 1;
530
+ }
531
+
532
+ :deep(.v-field--dirty) {
533
+ opacity: 1 !important;
534
+
535
+ --v-medium-emphasis-opacity: 1;
536
+ }
537
+
538
+ :deep(.v-field--focused) {
539
+ opacity: 1 !important;
540
+
541
+ --v-medium-emphasis-opacity: 1;
542
+ }
543
+
544
+ .date-picker-container {
545
+ max-width: 345px;
546
+ position: relative;
547
+
548
+ :deep(.v-date-picker) {
549
+ width: 345px;
550
+ position: absolute;
551
+ top: 56px;
552
+ left: 0;
553
+ z-index: 2;
554
+ box-shadow:
555
+ 0 5px 5px -3px rgb(0 0 0 / 20%),
556
+ 0 8px 10px 1px rgb(0 0 0 / 14%),
557
+ 0 3px 14px 2px rgb(0 0 0 / 12%) !important;
558
+ }
559
+ }
560
+
561
+ :deep(.v-date-picker-month__day--selected, .v-date-picker-month__day--adjacent) {
562
+ opacity: 1;
563
+ }
564
+
565
+ .fade-enter-active,
566
+ .fade-leave-active {
567
+ transition: opacity 0.5s ease;
568
+ }
569
+
570
+ .fade-enter-from,
571
+ .fade-leave-to {
572
+ opacity: 0;
573
+ }
574
+ </style>