@cnamts/synapse 1.0.22 → 1.0.23

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 (190) hide show
  1. package/dist/{DateFilter-B5n-ZkLi.js → DateFilter-Dc-gSGwk.js} +1 -1
  2. package/dist/{NumberFilter-CtiZ9uj8.js → NumberFilter-vP38Wp6j.js} +1 -1
  3. package/dist/{PeriodFilter-DzqiMb-b.js → PeriodFilter-Ba1uYUnT.js} +1 -1
  4. package/dist/{SelectFilter-BOYlF7rX.js → SelectFilter-BioGT6Nn.js} +1 -1
  5. package/dist/{TextFilter-BOFRNfcX.js → TextFilter-B84dpnoq.js} +1 -1
  6. package/dist/components/Accordion/Accordion.d.ts +13 -2
  7. package/dist/components/Accordion/composables/useAccordionState.d.ts +2 -1
  8. package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +7 -7
  9. package/dist/components/Amelipro/AmeliproCheckbox/AmeliproCheckbox.d.ts +1 -1
  10. package/dist/components/Amelipro/AmeliproCustomSelector/AmeliproCustomSelector.d.ts +1 -1
  11. package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressField.d.ts +1 -1
  12. package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +7 -7
  13. package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +16 -16
  14. package/dist/components/Amelipro/AmeliproTextArea/AmeliproTextArea.d.ts +1 -1
  15. package/dist/components/Amelipro/AmeliproTextField/AmeliproTextField.d.ts +1 -1
  16. package/dist/components/Customs/Selects/SyAutocomplete/SyAutocomplete.d.ts +22 -1
  17. package/dist/components/Customs/Selects/SyAutocomplete/locales.d.ts +5 -0
  18. package/dist/components/Customs/Selects/SyInputSelect/SyInputSelect.d.ts +1 -1
  19. package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +1 -1
  20. package/dist/components/Customs/Selects/SySelect/locales.d.ts +1 -0
  21. package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +1 -1
  22. package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +1 -1
  23. package/dist/components/Customs/SyRadioGroup/SyRadioGroup.d.ts +1 -1
  24. package/dist/components/Customs/SyTextField/SyTextField.d.ts +5 -2
  25. package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +13 -9
  26. package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +7 -5
  27. package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +2 -1
  28. package/dist/components/ErrorPage/ErrorPage.d.ts +3 -1
  29. package/dist/components/FileList/UploadItem/UploadItem.d.ts +6 -0
  30. package/dist/components/FileList/UploadItem/locales.d.ts +1 -4
  31. package/dist/components/FileUpload/FileUploadContent.d.ts +2 -0
  32. package/dist/components/FileUpload/validateFiles.d.ts +2 -1
  33. package/dist/components/HeaderBar/HeaderBar.d.ts +2 -1
  34. package/dist/components/HeaderBar/HeaderLogo/HeaderLogo.d.ts +2 -1
  35. package/dist/components/HeaderNavigationBar/HeaderNavigationBar.d.ts +2 -1
  36. package/dist/components/MonthPicker/MonthPicker.d.ts +1939 -0
  37. package/dist/components/MonthPicker/MonthPickerText/MonthPickerInput.d.ts +1899 -0
  38. package/dist/components/MonthPicker/MonthPickerText/useTextField.d.ts +21 -0
  39. package/dist/components/MonthPicker/MonthPickerVisual/MonthPickerVisual.d.ts +21 -0
  40. package/dist/components/MonthPicker/MonthPickerVisual/MonthPickerVisualProps.d.ts +12 -0
  41. package/dist/components/MonthPicker/MonthPickerVisual/MonthSelector.d.ts +11 -0
  42. package/dist/components/MonthPicker/MonthPickerVisual/VisualPickerFooter.d.ts +6 -0
  43. package/dist/components/MonthPicker/MonthPickerVisual/VisualPickerHeader.d.ts +14 -0
  44. package/dist/components/MonthPicker/MonthPickerVisual/YearSelector.d.ts +14 -0
  45. package/dist/components/MonthPicker/MonthPickerVisual/useMonthGrid.d.ts +9 -0
  46. package/dist/components/MonthPicker/MonthPickerVisual/useYearGrid.d.ts +8 -0
  47. package/dist/components/MonthPicker/MonthPickerVisual/utils.d.ts +8 -0
  48. package/dist/components/MonthPicker/locales.d.ts +12 -0
  49. package/dist/components/MonthPicker/useMonthPickerValidation.d.ts +25 -0
  50. package/dist/components/NirField/NirField.d.ts +3 -1
  51. package/dist/components/NotificationBar/Notification/Notification.d.ts +3 -0
  52. package/dist/components/PasswordField/PasswordField.d.ts +1 -1
  53. package/dist/components/PeriodField/PeriodField.d.ts +29 -21
  54. package/dist/components/PhoneField/PhoneField.d.ts +2 -1
  55. package/dist/components/SyBtnMenu/SyBtnMenu.d.ts +1 -1
  56. package/dist/components/SyHeading/SyHeading.a11y.test.d.ts +1 -0
  57. package/dist/components/SyHeading/SyHeading.d.ts +4 -2
  58. package/dist/components/SyHeading/SyHeading.test.d.ts +1 -0
  59. package/dist/components/SyTextArea/SyTextArea.d.ts +1 -1
  60. package/dist/components/Tables/SyServerTable/SyServerTable.d.ts +4 -4
  61. package/dist/components/Tables/SyTable/SyTable.d.ts +4 -4
  62. package/dist/components/Tables/common/SyTablePagination.d.ts +6 -6
  63. package/dist/components/index.d.ts +1 -0
  64. package/dist/design-system-v3.js +102 -99
  65. package/dist/design-system-v3.umd.cjs +126 -126
  66. package/dist/designTokens/tokens/cnam/cnamContextual.d.ts +5 -0
  67. package/dist/{main-CEl4J8_T.js → main-aLKwdMi1.js} +11167 -10522
  68. package/dist/main.d.ts +1 -0
  69. package/dist/style.css +1 -1
  70. package/package.json +10 -4
  71. package/src/assets/apTokens.scss +2 -2
  72. package/src/assets/overrides/_btns.scss +8 -0
  73. package/src/assets/overrides/_forms.scss +9 -0
  74. package/src/assets/overrides/_icons.scss +38 -9
  75. package/src/assets/overrides/_tables.scss +19 -0
  76. package/src/components/Accordion/Accordion.mdx +23 -9
  77. package/src/components/Accordion/Accordion.stories.ts +153 -3
  78. package/src/components/Accordion/Accordion.vue +7 -6
  79. package/src/components/Accordion/composables/__tests__/useAccordionState.spec.ts +40 -12
  80. package/src/components/Accordion/composables/useAccordionState.ts +3 -4
  81. package/src/components/Accordion/tests/accordion.spec.ts +131 -19
  82. package/src/components/Amelipro/AmeliproPagination/AmeliproPagination.mdx +3 -1
  83. package/src/components/Amelipro/AmeliproPagination/AmeliproPagination.stories.ts +8 -0
  84. package/src/components/BackBtn/accessibilite/Accessibility.mdx +62 -10
  85. package/src/components/BackToTopBtn/BackToTopBtn.stories.ts +9 -3
  86. package/src/components/BackToTopBtn/accessibilite/Accessibility.mdx +86 -6
  87. package/src/components/Captcha/tests/Captcha.spec.ts +0 -29
  88. package/src/components/Captcha/tests/__snapshots__/Captcha.spec.ts.snap +2 -110
  89. package/src/components/Customs/Selects/SelectBtnField/accessibilite/Accessibility.mdx +133 -10
  90. package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.stories.ts +379 -93
  91. package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.vue +144 -83
  92. package/src/components/Customs/Selects/SyAutocomplete/accessibilite/Accessibilite.stories.ts +40 -1
  93. package/src/components/Customs/Selects/SyAutocomplete/accessibilite/Accessibility.mdx +7 -1
  94. package/src/components/Customs/Selects/SyAutocomplete/locales.ts +5 -0
  95. package/src/components/Customs/Selects/SyAutocomplete/tests/SyAutocomplete.a11y.spec.ts +96 -0
  96. package/src/components/Customs/Selects/SyAutocomplete/tests/SyAutocomplete.spec.ts +234 -9
  97. package/src/components/Customs/Selects/SyAutocomplete/utils/ariaManager.ts +13 -3
  98. package/src/components/Customs/Selects/SyAutocomplete/utils/useSelectionLogic.ts +9 -10
  99. package/src/components/Customs/Selects/SySelect/SySelect.vue +46 -3
  100. package/src/components/Customs/Selects/SySelect/locales.ts +1 -0
  101. package/src/components/Customs/SyIcon/SyIcon.vue +1 -1
  102. package/src/components/Customs/SyIcon/tests/SyIcon.a11y.spec.ts +20 -0
  103. package/src/components/Customs/SyIconButton/SyIconButton.mdx +46 -0
  104. package/src/components/Customs/SyIconButton/SyIconButton.stories.ts +184 -0
  105. package/src/components/Customs/SyIconButton/SyIconButton.vue +38 -0
  106. package/src/components/Customs/SyIconButton/accessibilite/Accessibility.mdx +64 -0
  107. package/src/components/Customs/SyIconButton/tests/SyIconButton.a11y.spec.ts +87 -0
  108. package/src/components/Customs/SyIconButton/tests/SyIconButton.spec.ts +152 -0
  109. package/src/components/Customs/SyIconButton/tests/__snapshots__/SyIconButton.spec.ts.snap +61 -0
  110. package/src/components/Customs/SyPagination/SyPagination.vue +5 -5
  111. package/src/components/Customs/SyTextField/SyTextField.vue +20 -2
  112. package/src/components/Customs/SyTextField/accessibilite/Accessibility.mdx +67 -9
  113. package/src/components/Customs/SyTextField/tests/SyTextField.a11y.spec.ts +15 -0
  114. package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +36 -0
  115. package/src/components/DataList/accessibilite/Accessibility.mdx +79 -11
  116. package/src/components/DataListGroup/accessibilite/Accessibility.mdx +80 -11
  117. package/src/components/DownloadBtn/tests/DownloadBtn.a11y.spec.ts +25 -0
  118. package/src/components/ErrorPage/ErrorPage.stories.ts +113 -19
  119. package/src/components/ErrorPage/ErrorPage.vue +17 -2
  120. package/src/components/ErrorPage/tests/ErrorPage.a11y.spec.ts +17 -0
  121. package/src/components/ErrorPage/tests/ErrorPage.spec.ts +21 -1
  122. package/src/components/ErrorPage/tests/__snapshots__/ErrorPage.spec.ts.snap +0 -1
  123. package/src/components/ExternalLinks/tests/ExternalLinks.a11y.spec.ts +23 -0
  124. package/src/components/FileList/FileList.stories.ts +51 -1
  125. package/src/components/FileList/UploadItem/UploadItem.vue +13 -6
  126. package/src/components/FileList/UploadItem/locales.ts +3 -12
  127. package/src/components/FileList/accessibilite/Accessibility.mdx +3 -0
  128. package/src/components/FileUpload/FileUpload.vue +2 -1
  129. package/src/components/FileUpload/FileUploadContent.vue +2 -1
  130. package/src/components/FileUpload/tests/FileUpload.spec.ts +47 -0
  131. package/src/components/FileUpload/validateFiles.ts +5 -2
  132. package/src/components/FranceConnectBtn/accessibilite/Accessibility.mdx +62 -9
  133. package/src/components/HeaderBar/HeaderBar.vue +2 -1
  134. package/src/components/HeaderBar/HeaderLogo/HeaderLogo.vue +2 -1
  135. package/src/components/HeaderNavigationBar/HeaderNavigationBar.vue +2 -1
  136. package/src/components/LunarCalendar/accessibilite/Accessibility.mdx +74 -8
  137. package/src/components/LunarCalendar/tests/LunarCalendar.a11y.spec.ts +163 -0
  138. package/src/components/MaintenancePage/MaintenancePage.vue +1 -1
  139. package/src/components/MaintenancePage/tests/MaintenancePage.spec.ts +4 -5
  140. package/src/components/MaintenancePage/tests/__snapshots__/MaintenancePage.spec.ts.snap +0 -1
  141. package/src/components/MonthPicker/MonthPicker.mdx +35 -0
  142. package/src/components/MonthPicker/MonthPicker.stories.ts +527 -0
  143. package/src/components/MonthPicker/MonthPicker.vue +79 -0
  144. package/src/components/MonthPicker/MonthPickerText/MonthPickerInput.vue +89 -0
  145. package/src/components/MonthPicker/MonthPickerText/useTextField.ts +27 -0
  146. package/src/components/MonthPicker/MonthPickerVisual/MonthPickerVisual.vue +154 -0
  147. package/src/components/MonthPicker/MonthPickerVisual/MonthPickerVisualProps.ts +13 -0
  148. package/src/components/MonthPicker/MonthPickerVisual/MonthSelector.vue +137 -0
  149. package/src/components/MonthPicker/MonthPickerVisual/VisualPickerFooter.vue +60 -0
  150. package/src/components/MonthPicker/MonthPickerVisual/VisualPickerHeader.vue +149 -0
  151. package/src/components/MonthPicker/MonthPickerVisual/YearSelector.vue +143 -0
  152. package/src/components/MonthPicker/MonthPickerVisual/useMonthGrid.ts +45 -0
  153. package/src/components/MonthPicker/MonthPickerVisual/useYearGrid.ts +45 -0
  154. package/src/components/MonthPicker/MonthPickerVisual/utils.ts +17 -0
  155. package/src/components/MonthPicker/accessibilite/Accessibility.mdx +59 -0
  156. package/src/components/MonthPicker/locales.ts +12 -0
  157. package/src/components/MonthPicker/tests/MonthPicker.a11y.spec.ts +71 -0
  158. package/src/components/MonthPicker/tests/MonthPicker.spec.ts +1248 -0
  159. package/src/components/MonthPicker/tests/__snapshots__/MonthPicker.spec.ts.snap +2545 -0
  160. package/src/components/MonthPicker/useMonthPickerValidation.ts +30 -0
  161. package/src/components/NirField/NirField.mdx +1 -2
  162. package/src/components/NirField/NirField.stories.ts +66 -6
  163. package/src/components/NotFoundPage/tests/NotFoundPage.spec.ts +2 -3
  164. package/src/components/NotFoundPage/tests/__snapshots__/NotFoundPage.spec.ts.snap +22 -14
  165. package/src/components/NotificationBar/Notification/Notification.vue +3 -1
  166. package/src/components/NotificationBar/NotificationBar.stories.ts +154 -0
  167. package/src/components/NotificationBar/tests/NotificationBar.a11y.spec.ts +26 -0
  168. package/src/components/NotificationBar/tests/NotificationBar.spec.ts +60 -0
  169. package/src/components/RangeField/accessibilite/Accessibility.mdx +79 -11
  170. package/src/components/SkipLink/tests/SkipLink.a11y.spec.ts +23 -0
  171. package/src/components/StatusPage/StatusPage.stories.ts +118 -0
  172. package/src/components/StatusPage/StatusPage.vue +5 -3
  173. package/src/components/StatusPage/tests/StatusPage.a11y.spec.ts +22 -0
  174. package/src/components/StatusPage/tests/StatusPage.spec.ts +22 -0
  175. package/src/components/StatusPage/tests/__snapshots__/StatusPage.spec.ts.snap +22 -14
  176. package/src/components/SubHeader/tests/SubHeader.a11y.spec.ts +20 -0
  177. package/src/components/SyAlert/SyAlert.vue +1 -0
  178. package/src/components/SyAlert/accessibilite/Accessibility.mdx +79 -9
  179. package/src/components/SyAlert/tests/SyAlert.a11y.spec.ts +23 -0
  180. package/src/components/SyHeading/SyHeading.a11y.test.ts +149 -0
  181. package/src/components/SyHeading/SyHeading.test.ts +115 -0
  182. package/src/components/SyHeading/SyHeading.vue +5 -3
  183. package/src/components/SyTextArea/accessibilite/Accessibility.mdx +80 -8
  184. package/src/components/SyTextArea/tests/SyTextArea.a11y.spec.ts +151 -0
  185. package/src/components/ToolbarContainer/tests/ToolbarContainer.a11y.spec.ts +126 -0
  186. package/src/components/UploadWorkflow/tests/__snapshots__/UploadWorkflow.spec.ts.snap +2 -2
  187. package/src/components/index.ts +1 -0
  188. package/src/composables/useFormFieldErrorHandling.ts +11 -2
  189. package/src/designTokens/tokens/cnam/cnamContextual.ts +6 -1
  190. package/src/main.ts +2 -0
@@ -0,0 +1,27 @@
1
+ import { computed } from 'vue'
2
+
3
+ export type TextFieldProps = {
4
+ label: string
5
+ density?: 'default' | 'comfortable' | 'compact'
6
+ hint?: string | false
7
+ placeholder?: string
8
+ disabled?: boolean
9
+ readonly?: boolean
10
+ }
11
+
12
+ export const defaultTextFieldProps = {
13
+ density: 'default',
14
+ disabled: false,
15
+ readonly: false,
16
+ } as const satisfies Partial<TextFieldProps>
17
+
18
+ export function useTextField(props: TextFieldProps) {
19
+ return computed(() => ({
20
+ label: props.label,
21
+ density: props.density,
22
+ hint: props.hint || undefined,
23
+ placeholder: props.placeholder,
24
+ disabled: props.disabled ? true : undefined,
25
+ readonly: props.readonly ? true : undefined,
26
+ }))
27
+ }
@@ -0,0 +1,154 @@
1
+ <script setup lang="ts">
2
+ import { computed, inject, ref, useId, watch, type ComponentPublicInstance } from 'vue'
3
+ import MonthSelector from './MonthSelector.vue'
4
+ import YearSelector from './YearSelector.vue'
5
+ import VisualpickerHeader from './VisualPickerHeader.vue'
6
+ import VisualPickerFooter from './VisualPickerFooter.vue'
7
+ import { locales as defaultLocales, localesKey } from '../locales'
8
+ import { parseMonthYearString } from './utils'
9
+ import type { MonthPickerVisualProps } from './MonthPickerVisualProps'
10
+
11
+ const props = defineProps<{
12
+ textInput: ComponentPublicInstance | null
13
+ toggleBtn: HTMLElement | null
14
+ modelValue: string | undefined
15
+ readonly: boolean
16
+ disabled: boolean
17
+ } & MonthPickerVisualProps>()
18
+
19
+ const emits = defineEmits<{
20
+ (e: 'update:modelValue', value: string | undefined): void
21
+ (e: 'update:year', value: number | undefined): void
22
+ (e: 'update:month', value: number | undefined): void
23
+ (e: 'update:open', value: boolean): void
24
+ }>()
25
+
26
+ const locales = inject<typeof defaultLocales>(localesKey)!
27
+
28
+ const view = ref<'months' | 'years'>(props.initialView)
29
+ const open = ref(false)
30
+ watch(open, (newValue) => {
31
+ if (newValue) {
32
+ view.value = props.initialView
33
+ draftMonth.value = undefined
34
+ draftYear.value = undefined
35
+ }
36
+ else {
37
+ props.toggleBtn!.focus()
38
+ }
39
+ emits('update:open', newValue)
40
+ })
41
+
42
+ const initValue = computed(() => parseMonthYearString(props.modelValue))
43
+ const draftMonth = ref<number | undefined>(undefined)
44
+ const draftYear = ref<number | undefined>(undefined)
45
+
46
+ function setYear(value: number | undefined) {
47
+ draftYear.value = value
48
+ emits('update:year', draftYear.value)
49
+ if (draftMonth.value === undefined) {
50
+ view.value = 'months'
51
+ }
52
+ else {
53
+ open.value = false
54
+ }
55
+ }
56
+
57
+ function setMonth(value: number | undefined) {
58
+ draftMonth.value = value
59
+ emits('update:month', draftMonth.value)
60
+ if (draftYear.value === undefined) {
61
+ view.value = 'years'
62
+ }
63
+ else {
64
+ open.value = false
65
+ }
66
+ }
67
+
68
+ function emitModelValue(value: string) {
69
+ if (!props.readonly && !props.disabled) {
70
+ emits('update:modelValue', value)
71
+ }
72
+ }
73
+
74
+ function setDate(value: string) {
75
+ emitModelValue(value)
76
+ open.value = false
77
+ }
78
+
79
+ watch(
80
+ [draftMonth, draftYear],
81
+ () => {
82
+ const oldValue = parseMonthYearString(props.modelValue)
83
+ if (draftMonth.value !== undefined && draftYear.value !== undefined && (draftMonth.value !== oldValue[0] || draftYear.value !== oldValue[1])) {
84
+ emitModelValue(`${String(draftMonth.value).padStart(2, '0')}/${draftYear.value}`)
85
+ }
86
+ },
87
+ { immediate: true },
88
+ )
89
+
90
+ const id = useId()
91
+
92
+ </script>
93
+ <template>
94
+ <VMenu
95
+ v-model="open"
96
+ :target="(textInput as ComponentPublicInstance)"
97
+ :activator="(toggleBtn as HTMLElement)"
98
+ :close-on-content-click="false"
99
+ :max-width="328"
100
+ :min-width="328"
101
+ :min-height="455"
102
+ disable-initial-focus
103
+ :retain-focus="false"
104
+ :disabled="props.disabled"
105
+ transition="fade-transition"
106
+ :activator-props="{
107
+ 'aria-haspopup': 'dialog',
108
+ 'disabled': props.disabled ? 'true' : undefined,
109
+ }"
110
+ role="dialog"
111
+ :aria-labelledby="`${id}-title`"
112
+ >
113
+ <div
114
+ class="month-picker-menu"
115
+ :class="{ 'month-picker-menu--readonly': props.readonly }"
116
+ >
117
+ <VisualpickerHeader
118
+ :id
119
+ v-model:view="view"
120
+ :title="view === 'months' ? locales.headerSelectMonth : locales.headerSelectYear"
121
+ :model-value="modelValue"
122
+ :min-year
123
+ :max-year
124
+ />
125
+ <YearSelector
126
+ v-if="view === 'years'"
127
+ :model-value="draftYear || initValue[1]"
128
+ :min="minYear"
129
+ :max="maxYear"
130
+ :order="yearsOrder"
131
+ @update:model-value="setYear"
132
+ />
133
+ <MonthSelector
134
+ v-else-if="view === 'months'"
135
+ :model-value="draftMonth || initValue[0]"
136
+ @update:model-value="setMonth"
137
+ />
138
+ <VisualPickerFooter
139
+ @update:model-value="setDate"
140
+ />
141
+ </div>
142
+ </VMenu>
143
+ </template>
144
+ <style scoped lang="scss">
145
+ .month-picker-menu {
146
+ overflow-y: auto;
147
+ background-color: white;
148
+ border-radius: 4px;
149
+ box-shadow:
150
+ 0 1px 5px 0 #0000001f,
151
+ 0 2px 2px 0 #00000024,
152
+ 0 3px 1px -2px #0003;
153
+ }
154
+ </style>
@@ -0,0 +1,13 @@
1
+ export type MonthPickerVisualProps = {
2
+ minYear: number
3
+ maxYear: number
4
+ yearsOrder: 'asc' | 'desc'
5
+ initialView: 'months' | 'years'
6
+ }
7
+
8
+ export const defaultMonthPickerVisualProps = {
9
+ minYear: 1900,
10
+ maxYear: 2100,
11
+ yearsOrder: 'asc',
12
+ initialView: 'months',
13
+ } as const satisfies Partial<MonthPickerVisualProps>
@@ -0,0 +1,137 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, onUnmounted, useTemplateRef, inject } from 'vue'
3
+ import { useMonthGrid } from './useMonthGrid'
4
+ import { localesKey, type locales as defaultLocales } from '../locales'
5
+
6
+ const props = defineProps<{
7
+ modelValue: number | undefined
8
+ }>()
9
+
10
+ const emits = defineEmits<{
11
+ (event: 'update:modelValue', value: number): void
12
+ }>()
13
+
14
+ function getMonthName(monthIndex: number): string {
15
+ return Intl.DateTimeFormat(navigator.language, { month: 'long' }).format(
16
+ new Date(0, monthIndex - 1),
17
+ )
18
+ }
19
+
20
+ function getMonthShortName(monthIndex: number): string {
21
+ return Intl.DateTimeFormat(navigator.language, { month: 'short' }).format(
22
+ new Date(0, monthIndex - 1),
23
+ )
24
+ }
25
+
26
+ const monthSelector = useTemplateRef<HTMLElement>('monthSelector')
27
+
28
+ const initialFocusedMonth = (props.modelValue && props.modelValue >= 1 && props.modelValue <= 12)
29
+ ? props.modelValue
30
+ : new Date().getMonth() + 1
31
+
32
+ const {
33
+ activeMonth,
34
+ selectNextMonth,
35
+ selectPreviousMonth,
36
+ selectNextRow,
37
+ selectPreviousRow,
38
+ } = useMonthGrid(
39
+ monthSelector,
40
+ initialFocusedMonth,
41
+ )
42
+
43
+ let focusTimeout: ReturnType<typeof setTimeout> | undefined
44
+
45
+ onMounted(() => {
46
+ const selectedMonthElement = monthSelector.value!.querySelector<HTMLElement>(`.month-${initialFocusedMonth}`)
47
+ selectedMonthElement!.focus()
48
+
49
+ // When the menu opens, the transition can cause the focus to be lost, so we ensure it is set after the transition begins.
50
+ focusTimeout = setTimeout(() => {
51
+ selectedMonthElement!.focus()
52
+ }, 0)
53
+ })
54
+
55
+ onUnmounted(() => {
56
+ clearTimeout(focusTimeout)
57
+ })
58
+
59
+ const locales = inject<typeof defaultLocales>(localesKey)!
60
+
61
+ </script>
62
+
63
+ <template>
64
+ <div
65
+ ref="monthSelector"
66
+ class="month-selector"
67
+ role="group"
68
+ :aria-label="locales.monthSelectorLabel"
69
+ >
70
+ <button
71
+ v-for="monthIndex in 12"
72
+ :key="monthIndex"
73
+ :tabindex="monthIndex === activeMonth ? 0 : -1"
74
+ class="month-selector__month"
75
+ :class="[`month-${monthIndex}`, {
76
+ 'month-selector__month--active': monthIndex === activeMonth,
77
+ 'month-selector__month--selected': monthIndex === props.modelValue,
78
+ }]"
79
+ :aria-label="getMonthName(monthIndex)"
80
+ :aria-pressed="monthIndex === props.modelValue"
81
+ @click="() => emits('update:modelValue', monthIndex)"
82
+ @keydown.left.prevent="selectPreviousMonth"
83
+ @keydown.right.prevent="selectNextMonth"
84
+ @keydown.up.prevent="selectPreviousRow"
85
+ @keydown.down.prevent="selectNextRow"
86
+ @keydown.enter.stop
87
+ @keydown.space.stop
88
+ >
89
+ {{ getMonthShortName(monthIndex) }}
90
+ </button>
91
+ </div>
92
+ </template>
93
+
94
+ <style lang="scss" scoped>
95
+ .month-selector {
96
+ height: 288px;
97
+ width: 100%;
98
+ display: grid;
99
+ grid-template-columns: repeat(2, 1fr);
100
+ align-items: center;
101
+ grid-gap: 0 24px;
102
+ padding-inline: 32px;
103
+ }
104
+
105
+ .month-selector__month {
106
+ height: 40px;
107
+ padding-inline: 16px;
108
+ border: 2px solid #fff;
109
+ cursor: pointer;
110
+ text-align: center;
111
+ min-width: 33px;
112
+ font-size: var(--v-typography-body2-font-size, 1rem);
113
+ border-radius: 99px;
114
+ font-weight: bold;
115
+
116
+ &:hover {
117
+ /* stylelint-disable-next-line custom-property-pattern */
118
+ background-color: rgb(var(--v-theme-interactiveHover, 227, 234, 252));
119
+ }
120
+ }
121
+
122
+ .month-selector__month--selected {
123
+ /* stylelint-disable-next-line custom-property-pattern */
124
+ background-color: rgb(var(--v-theme-accentPrimary, 12, 65, 154));
125
+ color: white;
126
+
127
+ &:hover {
128
+ /* stylelint-disable-next-line custom-property-pattern */
129
+ background-color: rgb(var(--v-theme-accentPrimaryContrasted, 7, 39, 92));
130
+ }
131
+ }
132
+
133
+ .month-selector__month--active:focus {
134
+ /* stylelint-disable-next-line custom-property-pattern */
135
+ outline: 2px solid rgb(var(--v-theme-accentPrimary, 12, 65, 154));
136
+ }
137
+ </style>
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import { mdiCalendarMonthOutline } from '@mdi/js'
3
+ import SyIcon from '@/components/Customs/SyIcon/SyIcon.vue'
4
+
5
+ const emits = defineEmits<{
6
+ (e: 'update:modelValue', value: string): void
7
+ }>()
8
+
9
+ function selectCurrentMonth() {
10
+ const currentMonth = new Date().getMonth() + 1
11
+ const currentYear = new Date().getFullYear()
12
+ emits('update:modelValue', `${String(currentMonth).padStart(2, '0')}/${currentYear}`)
13
+ }
14
+
15
+ const btnText = 'Mois actuel'
16
+ const btnLabel = `Sélectionner le mois en cours`
17
+ </script>
18
+
19
+ <template>
20
+ <div class="month-picker-footer">
21
+ <button
22
+ class="month-picker-footer__current-month-btn"
23
+ type="button"
24
+ :aria-label="btnLabel"
25
+ :title="btnLabel"
26
+ @click="selectCurrentMonth"
27
+ >
28
+ <SyIcon
29
+ :icon="mdiCalendarMonthOutline"
30
+ decorative
31
+ />
32
+ {{ btnText }}
33
+ </button>
34
+ </div>
35
+ </template>
36
+
37
+ <style scoped lang="scss">
38
+ .month-picker-footer {
39
+ padding: 0 12px 12px;
40
+ }
41
+
42
+ .month-picker-footer__current-month-btn {
43
+ display: flex;
44
+ gap: 8px;
45
+ margin: auto;
46
+ margin-block: 8px;
47
+ padding: 8px;
48
+ text-align: center;
49
+ font-weight: bold;
50
+ font-size: var(--v-typography-body2-font-size, 1rem);
51
+ border-radius: 99px;
52
+ color: rgb(var(--v-theme-primary, 12, 65, 154));
53
+ margin-bottom: 2px;
54
+
55
+ &:focus-visible {
56
+ /* stylelint-disable-next-line custom-property-pattern */
57
+ outline: 2px solid rgb(var(--v-theme-accentPrimary, 12, 65, 154));
58
+ }
59
+ }
60
+ </style>
@@ -0,0 +1,149 @@
1
+ <script setup lang="ts">
2
+ import SyIcon from '@/components/Customs/SyIcon/SyIcon.vue'
3
+ import { mdiChevronDown } from '@mdi/js'
4
+ import { computed, inject, type ComputedRef } from 'vue'
5
+ import { locales as defaultLocales, localesKey } from '../locales'
6
+ import { dateToString } from './utils'
7
+
8
+ const props = defineProps<{
9
+ modelValue: string | undefined
10
+ view: 'months' | 'years'
11
+ title: string
12
+ minYear: number
13
+ maxYear: number
14
+ id: string
15
+ }>()
16
+
17
+ const emits = defineEmits<{
18
+ (e: 'update:view', value: 'months' | 'years'): void
19
+ }>()
20
+
21
+ const localeDate = computed(() => {
22
+ const [month, year] = (props.modelValue || '').split('/').map(Number)
23
+ const isYearValid = year && !isNaN(year)
24
+ const isMonthValid = month && !isNaN(month) && month >= 1 && month <= 12
25
+ if (isMonthValid && isYearValid) {
26
+ return dateToString(new Date(year, month - 1))
27
+ }
28
+ else return dateToString(new Date())
29
+ })
30
+
31
+ const formatter = Intl.DateTimeFormat(navigator.language, { month: 'long' })
32
+
33
+ const locales = inject<ComputedRef<typeof defaultLocales>>(localesKey)!
34
+
35
+ const btnLabel = computed(() => {
36
+ if (props.view === 'months') {
37
+ return props.modelValue ? parseInt(props.modelValue.split('/')[1] || '', 10) : (new Date().getFullYear())
38
+ }
39
+ else {
40
+ const month = props.modelValue ? parseInt(props.modelValue.split('/')[0] || '', 10) : (new Date().getMonth() + 1)
41
+ return formatter.format(new Date(2000, month - 1))
42
+ }
43
+ })
44
+
45
+ const btnAriaLabel = computed(() => {
46
+ const labels = locales.value
47
+ if (props.view === 'months') {
48
+ const selectedYear = props.modelValue ? parseInt(props.modelValue.split('/')[1] || '', 10) : undefined
49
+ if (selectedYear && !isNaN(selectedYear) && selectedYear >= props.minYear && selectedYear <= props.maxYear) {
50
+ return labels.yearBtnLabelSelected(String(selectedYear))
51
+ }
52
+ else {
53
+ return labels.yearBtnLabelUnselected(String(new Date().getFullYear()))
54
+ }
55
+ }
56
+ else {
57
+ const selectedMonth = props.modelValue ? parseInt(props.modelValue.split('/')[0] || '', 10) : undefined
58
+ if (selectedMonth && !isNaN(selectedMonth)) {
59
+ const monthName = formatter.format(new Date(2000, selectedMonth - 1))
60
+ return labels.monthBtnLabelSelected(monthName)
61
+ }
62
+ else {
63
+ const currentMonthName = formatter.format(new Date())
64
+ return labels.monthBtnLabelUnselected(currentMonthName)
65
+ }
66
+ }
67
+ })
68
+
69
+ </script>
70
+
71
+ <template>
72
+ <div class="visual-picker-header">
73
+ <div
74
+ :id="`${props.id}-title`"
75
+ class="visual-picker-header__title"
76
+ >
77
+ {{ title }}
78
+ </div>
79
+ <div class="visual-picker-header__date">
80
+ {{ localeDate }}
81
+ </div>
82
+ </div>
83
+ <div
84
+ class="visual-picker-subheader"
85
+ >
86
+ <button
87
+ type="button"
88
+ class="visual-picker-year-btn"
89
+ :title="btnAriaLabel"
90
+ :aria-label="btnAriaLabel"
91
+ @click="emits('update:view', props.view === 'months' ? 'years' : 'months')"
92
+ >
93
+ {{ btnLabel }}
94
+ <SyIcon
95
+ :icon="mdiChevronDown"
96
+ decorative
97
+ />
98
+ </button>
99
+ </div>
100
+ </template>
101
+
102
+ <style scoped lang="scss">
103
+ .visual-picker-header {
104
+ padding-block: 16px;
105
+ background-color: rgb(var(--v-theme-primary, '12, 65, 154'));
106
+ color: #fff;
107
+ }
108
+
109
+ .visual-picker-header__title {
110
+ padding-inline: 24px 12px;
111
+ padding-bottom: 16px;
112
+ font-size: var(--v-typography-caption-font-size, 0.875rem);
113
+ font-weight: 400;
114
+ letter-spacing: 0.125rem;
115
+ line-height: 150%;
116
+ }
117
+
118
+ .visual-picker-header__date {
119
+ font-size: var(--v-typography-h3-font-size, 1.5rem);
120
+ font-weight: var(--v-typography-h3-font-weight, 700);
121
+ line-height: var(--v-typography-h3-line-height, 130%);
122
+ letter-spacing: var(--v-typography-h3-letter-spacing, 0%);
123
+ text-transform: capitalize;
124
+ margin-left: 20px;
125
+ }
126
+
127
+ .visual-picker-subheader {
128
+ height: 56px;
129
+ padding: 4px;
130
+ display: flex;
131
+ justify-content: center;
132
+ }
133
+
134
+ .visual-picker-year-btn {
135
+ display: flex;
136
+ gap: 4px;
137
+ margin: auto;
138
+ padding: 0.2rem 0.5rem;
139
+ border-radius: 99px;
140
+ font-size: var(--v-typography-body1-font-size, 1.125rem);
141
+ font-weight: bold;
142
+ cursor: pointer;
143
+
144
+ &:focus-visible {
145
+ /* stylelint-disable-next-line custom-property-pattern */
146
+ outline: 2px solid rgb(var(--v-theme-accentPrimary, 12, 65, 154));
147
+ }
148
+ }
149
+ </style>
@@ -0,0 +1,143 @@
1
+ <script setup lang="ts">
2
+ import { computed, inject, onMounted, onUnmounted, ref, watch } from 'vue'
3
+ import { useYearGrid } from './useYearGrid'
4
+ import { localesKey, type locales as defaultLocales } from '../locales'
5
+
6
+ const props = defineProps<{
7
+ min: number
8
+ max: number
9
+ order: 'asc' | 'desc'
10
+ modelValue?: number
11
+ }>()
12
+
13
+ const yearSelector = ref<HTMLElement | null>(null)
14
+
15
+ const emits = defineEmits<{
16
+ (event: 'update:modelValue', value: number): void
17
+ }>()
18
+
19
+ function getFocusedYear() {
20
+ if (props.modelValue && props.modelValue >= props.min && props.modelValue <= props.max) {
21
+ return props.modelValue
22
+ }
23
+ const currentYear = new Date().getFullYear()
24
+ if (currentYear >= props.min && currentYear <= props.max) {
25
+ return currentYear
26
+ }
27
+ else {
28
+ return props.order === 'asc' ? props.min : props.max
29
+ }
30
+ }
31
+
32
+ const {
33
+ activeYear,
34
+ selectNextYear,
35
+ selectPreviousYear,
36
+ selectNextRow,
37
+ selectPreviousRow,
38
+ } = useYearGrid(
39
+ yearSelector,
40
+ computed(() => props.min),
41
+ computed(() => props.max),
42
+ getFocusedYear(),
43
+ )
44
+
45
+ let focusTimeout: ReturnType<typeof setTimeout> | undefined
46
+
47
+ onMounted(() => {
48
+ const selectedYearElement = yearSelector.value!.querySelector<HTMLElement>(`.year-${getFocusedYear()}`)
49
+ selectedYearElement!.focus()
50
+
51
+ // When the menu opens, the transition can cause the focus to be lost, so we ensure it is set after the transition begins.
52
+ focusTimeout = setTimeout(() => {
53
+ selectedYearElement!.focus()
54
+ }, 0)
55
+ })
56
+
57
+ watch(() => props.order, () => {
58
+ const selectedYearElement = yearSelector.value!.querySelector<HTMLElement>(`.year-${getFocusedYear()}`)
59
+ selectedYearElement!.focus()
60
+ })
61
+
62
+ onUnmounted(() => {
63
+ clearTimeout(focusTimeout)
64
+ })
65
+
66
+ const locales = inject<typeof defaultLocales>(localesKey)!
67
+
68
+ </script>
69
+
70
+ <template>
71
+ <div
72
+ ref="yearSelector"
73
+ class="year-selector"
74
+ role="group"
75
+ :aria-label="locales.yearSelectorLabel"
76
+ >
77
+ <button
78
+ v-for="year of Array.from({ length: props.max - props.min + 1 }, (_, i) => props.order === 'asc' ? props.min + i : props.max - i)"
79
+ :key="year"
80
+ :tabindex="year === activeYear ? 0 : -1"
81
+ class="year-selector__year"
82
+ :class="[`year-${year}`, {
83
+ 'year-selector__year--active': year === activeYear,
84
+ 'year-selector__year--selected': year === props.modelValue,
85
+ }]"
86
+ :aria-pressed="year === props.modelValue"
87
+ @click="() => emits('update:modelValue', year)"
88
+ @keydown.left.prevent="props.order === 'asc' ? selectPreviousYear() : selectNextYear()"
89
+ @keydown.right.prevent="props.order === 'asc' ? selectNextYear() : selectPreviousYear()"
90
+ @keydown.up.prevent="props.order === 'asc' ? selectPreviousRow() : selectNextRow()"
91
+ @keydown.down.prevent="props.order === 'asc' ? selectNextRow() : selectPreviousRow()"
92
+ @keydown.enter.stop
93
+ @keydown.space.stop
94
+ >
95
+ {{ year }}
96
+ </button>
97
+ </div>
98
+ </template>
99
+
100
+ <style lang="scss" scoped>
101
+ .year-selector {
102
+ width: 100%;
103
+ display: grid;
104
+ grid-template-columns: repeat(3, 1fr);
105
+ max-height: 288px;
106
+ grid-gap: 0 24px;
107
+ padding-inline: 32px;
108
+ overflow: auto;
109
+ }
110
+
111
+ .year-selector__year {
112
+ height: 40px;
113
+ margin-block: 2px;
114
+ border: 2px solid #fff;
115
+ cursor: pointer;
116
+ text-align: center;
117
+ min-width: 33px;
118
+ font-size: var(--v-typography-body2-font-size, 1rem);
119
+ border-radius: 99px;
120
+ font-weight: bold;
121
+
122
+ &:hover {
123
+ /* stylelint-disable-next-line custom-property-pattern */
124
+ background-color: rgb(var(--v-theme-interactiveHover, 227, 234, 252));
125
+ }
126
+ }
127
+
128
+ .year-selector__year--selected {
129
+ /* stylelint-disable-next-line custom-property-pattern */
130
+ background-color: rgb(var(--v-theme-accentPrimary, 12, 65, 154));
131
+ color: white;
132
+
133
+ &:hover {
134
+ /* stylelint-disable-next-line custom-property-pattern */
135
+ background-color: rgb(var(--v-theme-accentPrimaryContrasted, 7, 39, 92));
136
+ }
137
+ }
138
+
139
+ .year-selector__year--active:focus {
140
+ /* stylelint-disable-next-line custom-property-pattern */
141
+ outline: 2px solid rgb(var(--v-theme-accentPrimary, 12, 65, 154));
142
+ }
143
+ </style>