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