@pyreweb/fabric 1.2.6

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 (210) hide show
  1. package/README.md +119 -0
  2. package/dist/fabric.cjs.js +18109 -0
  3. package/dist/fabric.css +2180 -0
  4. package/dist/fabric.esm.js +18062 -0
  5. package/dist/fabric.min.js +18112 -0
  6. package/dist/types/components/atoms/FAvatar/FAvatar.test.d.ts +1 -0
  7. package/dist/types/components/atoms/FBadge/FBadge.test.d.ts +1 -0
  8. package/dist/types/components/atoms/FButton/FButton.test.d.ts +1 -0
  9. package/dist/types/components/atoms/FCheckbox/FCheckbox.test.d.ts +1 -0
  10. package/dist/types/components/atoms/FDivider/FDivider.test.d.ts +1 -0
  11. package/dist/types/components/atoms/FIcon/FIcon.test.d.ts +1 -0
  12. package/dist/types/components/atoms/FInput/FInput.test.d.ts +1 -0
  13. package/dist/types/components/atoms/FLoader/FLoader.test.d.ts +1 -0
  14. package/dist/types/components/atoms/FRadio/FRadio.test.d.ts +1 -0
  15. package/dist/types/components/atoms/FTextarea/FTextarea.test.d.ts +1 -0
  16. package/dist/types/components/atoms/FToggle/FToggle.test.d.ts +1 -0
  17. package/dist/types/components/atoms/FTypography/FTypography.test.d.ts +1 -0
  18. package/dist/types/components/atoms/index.d.ts +13 -0
  19. package/dist/types/components/molecules/FAccordionItem/FAccordionItem.test.d.ts +1 -0
  20. package/dist/types/components/molecules/FAlert/FAlert.test.d.ts +1 -0
  21. package/dist/types/components/molecules/FBreadcrumb/FBreadcrumb.test.d.ts +1 -0
  22. package/dist/types/components/molecules/FButtonGroup/FButtonGroup.test.d.ts +1 -0
  23. package/dist/types/components/molecules/FCard/FCard.test.d.ts +1 -0
  24. package/dist/types/components/molecules/FDatePicker/FDatePicker.test.d.ts +1 -0
  25. package/dist/types/components/molecules/FEmptyState/FEmptyState.test.d.ts +1 -0
  26. package/dist/types/components/molecules/FFilePreview/FFilePreview.test.d.ts +1 -0
  27. package/dist/types/components/molecules/FFormField/FFormField.test.d.ts +1 -0
  28. package/dist/types/components/molecules/FListItem/FListItem.test.d.ts +1 -0
  29. package/dist/types/components/molecules/FPagination/FPagination.test.d.ts +1 -0
  30. package/dist/types/components/molecules/FSearchBar/FSearchBar.test.d.ts +1 -0
  31. package/dist/types/components/molecules/FSelect/FSelect.test.d.ts +1 -0
  32. package/dist/types/components/molecules/FStatCard/FStatCard.test.d.ts +1 -0
  33. package/dist/types/components/molecules/FTabs/FTabs.test.d.ts +1 -0
  34. package/dist/types/components/molecules/FToast/FToast.test.d.ts +1 -0
  35. package/dist/types/components/molecules/index.d.ts +18 -0
  36. package/dist/types/components/organisms/FActivityFeed/FActivityFeed.test.d.ts +1 -0
  37. package/dist/types/components/organisms/FDataTable/FDataTable.test.d.ts +1 -0
  38. package/dist/types/components/organisms/FDrawer/FDrawer.test.d.ts +1 -0
  39. package/dist/types/components/organisms/FFileUpload/FFileUpload.test.d.ts +1 -0
  40. package/dist/types/components/organisms/FFilterSidebar/FFilterSidebar.test.d.ts +1 -0
  41. package/dist/types/components/organisms/FForm/FForm.test.d.ts +1 -0
  42. package/dist/types/components/organisms/FModal/FModal.test.d.ts +1 -0
  43. package/dist/types/components/organisms/FNavigationSidebar/FNavigationSidebar.test.d.ts +1 -0
  44. package/dist/types/components/organisms/FOnboardingStepper/FOnboardingStepper.test.d.ts +1 -0
  45. package/dist/types/components/organisms/FOnboardingStepper/FStepperProgress.test.d.ts +1 -0
  46. package/dist/types/components/organisms/FPageHeader/FPageHeader.test.d.ts +1 -0
  47. package/dist/types/components/organisms/FProfileSection/FProfileSection.test.d.ts +1 -0
  48. package/dist/types/components/organisms/FToastProvider/FToastProvider.test.d.ts +1 -0
  49. package/dist/types/components/organisms/FUserMenu/FUserMenu.test.d.ts +1 -0
  50. package/dist/types/components/organisms/index.d.ts +14 -0
  51. package/dist/types/components/utils/FThemeProvider.test.d.ts +1 -0
  52. package/dist/types/components/utils/index.d.ts +2 -0
  53. package/dist/types/components.d.ts +602 -0
  54. package/dist/types/composables/index.d.ts +12 -0
  55. package/dist/types/composables/useDataTableState.d.ts +106 -0
  56. package/dist/types/composables/useDataTableState.test.d.ts +1 -0
  57. package/dist/types/composables/useFormValidation.d.ts +49 -0
  58. package/dist/types/composables/useFormValidation.test.d.ts +1 -0
  59. package/dist/types/composables/useSidebarState.d.ts +65 -0
  60. package/dist/types/composables/useSidebarState.test.d.ts +1 -0
  61. package/dist/types/index.d.ts +19 -0
  62. package/dist/types/types.d.ts +529 -0
  63. package/package.json +100 -0
  64. package/src/components/atoms/FAvatar/FAvatar.stories.js +100 -0
  65. package/src/components/atoms/FAvatar/FAvatar.test.ts +95 -0
  66. package/src/components/atoms/FAvatar/FAvatar.vue +190 -0
  67. package/src/components/atoms/FBadge/FBadge.stories.js +129 -0
  68. package/src/components/atoms/FBadge/FBadge.test.ts +93 -0
  69. package/src/components/atoms/FBadge/FBadge.vue +103 -0
  70. package/src/components/atoms/FButton/FButton.stories.js +122 -0
  71. package/src/components/atoms/FButton/FButton.test.ts +98 -0
  72. package/src/components/atoms/FButton/FButton.vue +147 -0
  73. package/src/components/atoms/FCheckbox/FCheckbox.stories.js +96 -0
  74. package/src/components/atoms/FCheckbox/FCheckbox.test.ts +64 -0
  75. package/src/components/atoms/FCheckbox/FCheckbox.vue +76 -0
  76. package/src/components/atoms/FDivider/FDivider.stories.js +104 -0
  77. package/src/components/atoms/FDivider/FDivider.test.ts +80 -0
  78. package/src/components/atoms/FDivider/FDivider.vue +117 -0
  79. package/src/components/atoms/FIcon/FIcon.stories.js +189 -0
  80. package/src/components/atoms/FIcon/FIcon.test.ts +99 -0
  81. package/src/components/atoms/FIcon/FIcon.vue +192 -0
  82. package/src/components/atoms/FInput/FInput.stories.js +119 -0
  83. package/src/components/atoms/FInput/FInput.test.ts +79 -0
  84. package/src/components/atoms/FInput/FInput.vue +88 -0
  85. package/src/components/atoms/FLoader/FLoader.stories.js +109 -0
  86. package/src/components/atoms/FLoader/FLoader.test.ts +66 -0
  87. package/src/components/atoms/FLoader/FLoader.vue +97 -0
  88. package/src/components/atoms/FRadio/FRadio.stories.js +105 -0
  89. package/src/components/atoms/FRadio/FRadio.test.ts +75 -0
  90. package/src/components/atoms/FRadio/FRadio.vue +119 -0
  91. package/src/components/atoms/FTextarea/FTextarea.stories.js +126 -0
  92. package/src/components/atoms/FTextarea/FTextarea.test.ts +94 -0
  93. package/src/components/atoms/FTextarea/FTextarea.vue +156 -0
  94. package/src/components/atoms/FToggle/FToggle.stories.js +108 -0
  95. package/src/components/atoms/FToggle/FToggle.test.ts +96 -0
  96. package/src/components/atoms/FToggle/FToggle.vue +123 -0
  97. package/src/components/atoms/FTypography/FTypography.stories.js +127 -0
  98. package/src/components/atoms/FTypography/FTypography.test.ts +93 -0
  99. package/src/components/atoms/FTypography/FTypography.vue +78 -0
  100. package/src/components/atoms/index.ts +27 -0
  101. package/src/components/molecules/FAccordionItem/FAccordionItem.stories.js +71 -0
  102. package/src/components/molecules/FAccordionItem/FAccordionItem.test.ts +61 -0
  103. package/src/components/molecules/FAccordionItem/FAccordionItem.vue +105 -0
  104. package/src/components/molecules/FAlert/FAlert.stories.js +87 -0
  105. package/src/components/molecules/FAlert/FAlert.test.ts +59 -0
  106. package/src/components/molecules/FAlert/FAlert.vue +108 -0
  107. package/src/components/molecules/FBreadcrumb/FBreadcrumb.stories.js +90 -0
  108. package/src/components/molecules/FBreadcrumb/FBreadcrumb.test.ts +76 -0
  109. package/src/components/molecules/FBreadcrumb/FBreadcrumb.vue +117 -0
  110. package/src/components/molecules/FButtonGroup/FButtonGroup.stories.js +82 -0
  111. package/src/components/molecules/FButtonGroup/FButtonGroup.test.ts +44 -0
  112. package/src/components/molecules/FButtonGroup/FButtonGroup.vue +31 -0
  113. package/src/components/molecules/FCard/FCard.stories.js +136 -0
  114. package/src/components/molecules/FCard/FCard.test.ts +87 -0
  115. package/src/components/molecules/FCard/FCard.vue +75 -0
  116. package/src/components/molecules/FDatePicker/FDatePicker.stories.js +305 -0
  117. package/src/components/molecules/FDatePicker/FDatePicker.test.ts +282 -0
  118. package/src/components/molecules/FDatePicker/FDatePicker.vue +750 -0
  119. package/src/components/molecules/FEmptyState/FEmptyState.stories.js +98 -0
  120. package/src/components/molecules/FEmptyState/FEmptyState.test.ts +82 -0
  121. package/src/components/molecules/FEmptyState/FEmptyState.vue +89 -0
  122. package/src/components/molecules/FFilePreview/FFilePreview.stories.js +130 -0
  123. package/src/components/molecules/FFilePreview/FFilePreview.test.ts +70 -0
  124. package/src/components/molecules/FFilePreview/FFilePreview.vue +125 -0
  125. package/src/components/molecules/FFormField/FFormField.stories.js +149 -0
  126. package/src/components/molecules/FFormField/FFormField.test.ts +85 -0
  127. package/src/components/molecules/FFormField/FFormField.vue +107 -0
  128. package/src/components/molecules/FListItem/FListItem.stories.js +158 -0
  129. package/src/components/molecules/FListItem/FListItem.test.ts +93 -0
  130. package/src/components/molecules/FListItem/FListItem.vue +113 -0
  131. package/src/components/molecules/FPagination/FPagination.stories.js +132 -0
  132. package/src/components/molecules/FPagination/FPagination.test.ts +79 -0
  133. package/src/components/molecules/FPagination/FPagination.vue +206 -0
  134. package/src/components/molecules/FSearchBar/FSearchBar.stories.js +129 -0
  135. package/src/components/molecules/FSearchBar/FSearchBar.test.ts +81 -0
  136. package/src/components/molecules/FSearchBar/FSearchBar.vue +180 -0
  137. package/src/components/molecules/FSelect/FSelect.stories.js +333 -0
  138. package/src/components/molecules/FSelect/FSelect.test.ts +478 -0
  139. package/src/components/molecules/FSelect/FSelect.vue +551 -0
  140. package/src/components/molecules/FStatCard/FStatCard.stories.js +144 -0
  141. package/src/components/molecules/FStatCard/FStatCard.test.ts +78 -0
  142. package/src/components/molecules/FStatCard/FStatCard.vue +106 -0
  143. package/src/components/molecules/FTabs/FTab.vue +63 -0
  144. package/src/components/molecules/FTabs/FTabs.stories.js +277 -0
  145. package/src/components/molecules/FTabs/FTabs.test.ts +264 -0
  146. package/src/components/molecules/FTabs/FTabs.vue +273 -0
  147. package/src/components/molecules/FToast/FToast.stories.js +150 -0
  148. package/src/components/molecules/FToast/FToast.test.ts +157 -0
  149. package/src/components/molecules/FToast/FToast.vue +283 -0
  150. package/src/components/molecules/index.ts +37 -0
  151. package/src/components/organisms/FActivityFeed/FActivityFeed.stories.js +217 -0
  152. package/src/components/organisms/FActivityFeed/FActivityFeed.test.ts +134 -0
  153. package/src/components/organisms/FActivityFeed/FActivityFeed.vue +589 -0
  154. package/src/components/organisms/FDataTable/FDataTable.stories.js +370 -0
  155. package/src/components/organisms/FDataTable/FDataTable.test.ts +248 -0
  156. package/src/components/organisms/FDataTable/FDataTable.vue +808 -0
  157. package/src/components/organisms/FDrawer/FDrawer.stories.js +296 -0
  158. package/src/components/organisms/FDrawer/FDrawer.test.ts +142 -0
  159. package/src/components/organisms/FDrawer/FDrawer.vue +303 -0
  160. package/src/components/organisms/FFileUpload/FFileUpload.stories.js +162 -0
  161. package/src/components/organisms/FFileUpload/FFileUpload.test.ts +103 -0
  162. package/src/components/organisms/FFileUpload/FFileUpload.vue +616 -0
  163. package/src/components/organisms/FFilterSidebar/FFilterSidebar.stories.js +161 -0
  164. package/src/components/organisms/FFilterSidebar/FFilterSidebar.test.ts +92 -0
  165. package/src/components/organisms/FFilterSidebar/FFilterSidebar.vue +458 -0
  166. package/src/components/organisms/FForm/FForm.stories.js +270 -0
  167. package/src/components/organisms/FForm/FForm.test.ts +63 -0
  168. package/src/components/organisms/FForm/FForm.vue +19 -0
  169. package/src/components/organisms/FModal/FModal.stories.js +227 -0
  170. package/src/components/organisms/FModal/FModal.test.ts +181 -0
  171. package/src/components/organisms/FModal/FModal.vue +319 -0
  172. package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.stories.js +176 -0
  173. package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.test.ts +95 -0
  174. package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.vue +577 -0
  175. package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.stories.js +197 -0
  176. package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.test.ts +114 -0
  177. package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.vue +212 -0
  178. package/src/components/organisms/FOnboardingStepper/FStepperProgress.stories.js +122 -0
  179. package/src/components/organisms/FOnboardingStepper/FStepperProgress.test.ts +130 -0
  180. package/src/components/organisms/FOnboardingStepper/FStepperProgress.vue +146 -0
  181. package/src/components/organisms/FPageHeader/FPageHeader.stories.js +142 -0
  182. package/src/components/organisms/FPageHeader/FPageHeader.test.ts +83 -0
  183. package/src/components/organisms/FPageHeader/FPageHeader.vue +241 -0
  184. package/src/components/organisms/FProfileSection/FProfileSection.stories.js +190 -0
  185. package/src/components/organisms/FProfileSection/FProfileSection.test.ts +85 -0
  186. package/src/components/organisms/FProfileSection/FProfileSection.vue +562 -0
  187. package/src/components/organisms/FToastProvider/FToastProvider.stories.js +290 -0
  188. package/src/components/organisms/FToastProvider/FToastProvider.test.ts +215 -0
  189. package/src/components/organisms/FToastProvider/FToastProvider.vue +214 -0
  190. package/src/components/organisms/FUserMenu/FUserMenu.stories.js +170 -0
  191. package/src/components/organisms/FUserMenu/FUserMenu.test.ts +102 -0
  192. package/src/components/organisms/FUserMenu/FUserMenu.vue +407 -0
  193. package/src/components/organisms/index.ts +29 -0
  194. package/src/components/utils/FThemeProvider.stories.js +236 -0
  195. package/src/components/utils/FThemeProvider.test.ts +244 -0
  196. package/src/components/utils/FThemeProvider.vue +191 -0
  197. package/src/components/utils/index.ts +3 -0
  198. package/src/components.d.ts +602 -0
  199. package/src/composables/README.md +233 -0
  200. package/src/composables/index.ts +25 -0
  201. package/src/composables/useDataTableState.test.ts +378 -0
  202. package/src/composables/useDataTableState.ts +361 -0
  203. package/src/composables/useFormValidation.test.ts +198 -0
  204. package/src/composables/useFormValidation.ts +178 -0
  205. package/src/composables/useSidebarState.test.ts +307 -0
  206. package/src/composables/useSidebarState.ts +201 -0
  207. package/src/env.d.ts +14 -0
  208. package/src/index.ts +167 -0
  209. package/src/styles/tailwind.css +173 -0
  210. package/src/types.ts +740 -0
@@ -0,0 +1,750 @@
1
+ <template>
2
+ <div :class="containerClasses" @keydown.escape="closeCalendar">
3
+ <!-- Input Field -->
4
+ <div class="relative">
5
+ <input
6
+ ref="input"
7
+ type="text"
8
+ :class="inputClasses"
9
+ :value="displayValue"
10
+ :placeholder="placeholder"
11
+ :disabled="disabled"
12
+ :readonly="readonly"
13
+ :aria-expanded="String(isOpen)"
14
+ :aria-haspopup="'dialog'"
15
+ @click="toggleCalendar"
16
+ @focus="handleFocus"
17
+ @blur="handleBlur"
18
+ @keydown.down.prevent="openCalendar"
19
+ />
20
+ <f-icon
21
+ name="calendar"
22
+ size="sm"
23
+ :class="iconClasses"
24
+ @click.native="!disabled && toggleCalendar()"
25
+ />
26
+ </div>
27
+
28
+ <!-- Calendar Dropdown -->
29
+ <div
30
+ v-show="isOpen"
31
+ ref="calendar"
32
+ :class="calendarClasses"
33
+ role="dialog"
34
+ aria-label="Sélecteur de date"
35
+ >
36
+ <!-- Header with Month/Year Navigation -->
37
+ <div class="p-3 border-b border-neutral-200">
38
+ <div class="flex items-center justify-between mb-2">
39
+ <button
40
+ type="button"
41
+ :class="navButtonClasses"
42
+ aria-label="Mois précédent"
43
+ @click="previousMonth"
44
+ >
45
+ <f-icon name="chevron-left" size="sm" />
46
+ </button>
47
+ <div class="flex items-center gap-2">
48
+ <select
49
+ v-model="displayMonth"
50
+ :class="selectClasses"
51
+ @change="updateCalendar"
52
+ >
53
+ <option
54
+ v-for="(month, index) in monthNames"
55
+ :key="index"
56
+ :value="index"
57
+ >
58
+ {{ month }}
59
+ </option>
60
+ </select>
61
+ <select
62
+ v-model="displayYear"
63
+ :class="selectClasses"
64
+ @change="updateCalendar"
65
+ >
66
+ <option v-for="year in yearRange" :key="year" :value="year">
67
+ {{ year }}
68
+ </option>
69
+ </select>
70
+ </div>
71
+ <button
72
+ type="button"
73
+ :class="navButtonClasses"
74
+ aria-label="Mois suivant"
75
+ @click="nextMonth"
76
+ >
77
+ <f-icon name="chevron-right" size="sm" />
78
+ </button>
79
+ </div>
80
+ </div>
81
+
82
+ <!-- Calendar Grid -->
83
+ <div class="p-3">
84
+ <!-- Day Names -->
85
+ <div class="grid grid-cols-7 gap-1 mb-2">
86
+ <div
87
+ v-for="day in dayNames"
88
+ :key="day"
89
+ class="text-xs font-medium text-neutral-600 text-center py-1"
90
+ >
91
+ {{ day }}
92
+ </div>
93
+ </div>
94
+
95
+ <!-- Days Grid -->
96
+ <div class="grid grid-cols-7 gap-1">
97
+ <button
98
+ v-for="(day, index) in calendarDays"
99
+ :key="index"
100
+ type="button"
101
+ :class="getDayClasses(day)"
102
+ :disabled="isDayDisabled(day)"
103
+ @click="selectDate(day)"
104
+ @mouseenter="handleDayHover(day)"
105
+ >
106
+ {{ day.date }}
107
+ </button>
108
+ </div>
109
+ </div>
110
+
111
+ <!-- Time Picker (if enabled) -->
112
+ <div v-if="showTimePicker" class="p-3 border-t border-neutral-200">
113
+ <div class="flex items-center justify-center gap-2">
114
+ <div class="flex flex-col items-center">
115
+ <label class="text-xs font-medium text-neutral-600 mb-1"
116
+ >Heures</label
117
+ >
118
+ <div class="flex items-center gap-1">
119
+ <button
120
+ type="button"
121
+ :class="timeButtonClasses"
122
+ aria-label="Augmenter les heures"
123
+ @click="incrementHour"
124
+ >
125
+ <f-icon name="chevron-up" size="sm" />
126
+ </button>
127
+ <input
128
+ v-model.number="selectedHour"
129
+ type="number"
130
+ min="0"
131
+ max="23"
132
+ :class="timeInputClasses"
133
+ @change="updateTime"
134
+ />
135
+ <button
136
+ type="button"
137
+ :class="timeButtonClasses"
138
+ aria-label="Diminuer les heures"
139
+ @click="decrementHour"
140
+ >
141
+ <f-icon name="chevron-down" size="sm" />
142
+ </button>
143
+ </div>
144
+ </div>
145
+ <span class="text-lg font-bold text-neutral-400 mt-6">:</span>
146
+ <div class="flex flex-col items-center">
147
+ <label class="text-xs font-medium text-neutral-600 mb-1"
148
+ >Minutes</label
149
+ >
150
+ <div class="flex items-center gap-1">
151
+ <button
152
+ type="button"
153
+ :class="timeButtonClasses"
154
+ aria-label="Augmenter les minutes"
155
+ @click="incrementMinute"
156
+ >
157
+ <f-icon name="chevron-up" size="sm" />
158
+ </button>
159
+ <input
160
+ v-model.number="selectedMinute"
161
+ type="number"
162
+ min="0"
163
+ max="59"
164
+ :class="timeInputClasses"
165
+ @change="updateTime"
166
+ />
167
+ <button
168
+ type="button"
169
+ :class="timeButtonClasses"
170
+ aria-label="Diminuer les minutes"
171
+ @click="decrementMinute"
172
+ >
173
+ <f-icon name="chevron-down" size="sm" />
174
+ </button>
175
+ </div>
176
+ </div>
177
+ </div>
178
+ </div>
179
+
180
+ <!-- Action Buttons -->
181
+ <div
182
+ v-if="mode === 'range' || showTimePicker"
183
+ class="p-3 border-t border-neutral-200 flex justify-end gap-2"
184
+ >
185
+ <button
186
+ type="button"
187
+ :class="cancelButtonClasses"
188
+ @click="closeCalendar"
189
+ >
190
+ Annuler
191
+ </button>
192
+ <button
193
+ type="button"
194
+ :class="applyButtonClasses"
195
+ :disabled="!canApply"
196
+ @click="applySelection"
197
+ >
198
+ Appliquer
199
+ </button>
200
+ </div>
201
+ </div>
202
+ </div>
203
+ </template>
204
+
205
+ <script>
206
+ import FIcon from '../../atoms/FIcon/FIcon.vue';
207
+
208
+ let idCounter = 0;
209
+
210
+ export default {
211
+ name: 'FDatePicker',
212
+ components: {
213
+ FIcon
214
+ },
215
+ props: {
216
+ value: {
217
+ type: [String, Date, Array],
218
+ default: null
219
+ },
220
+ mode: {
221
+ type: String,
222
+ default: 'single',
223
+ validator: (value) => ['single', 'range'].includes(value)
224
+ },
225
+ placeholder: {
226
+ type: String,
227
+ default: 'Sélectionner une date'
228
+ },
229
+ format: {
230
+ type: String,
231
+ default: 'DD/MM/YYYY'
232
+ },
233
+ size: {
234
+ type: String,
235
+ default: 'medium',
236
+ validator: (value) => ['small', 'medium', 'large'].includes(value)
237
+ },
238
+ disabled: {
239
+ type: Boolean,
240
+ default: false
241
+ },
242
+ readonly: {
243
+ type: Boolean,
244
+ default: false
245
+ },
246
+ error: {
247
+ type: Boolean,
248
+ default: false
249
+ },
250
+ showTimePicker: {
251
+ type: Boolean,
252
+ default: false
253
+ },
254
+ minDate: {
255
+ type: [String, Date],
256
+ default: null
257
+ },
258
+ maxDate: {
259
+ type: [String, Date],
260
+ default: null
261
+ },
262
+ disabledDates: {
263
+ type: Array,
264
+ default: () => []
265
+ },
266
+ monthNames: {
267
+ type: Array,
268
+ default: () => [
269
+ 'Janvier',
270
+ 'Février',
271
+ 'Mars',
272
+ 'Avril',
273
+ 'Mai',
274
+ 'Juin',
275
+ 'Juillet',
276
+ 'Août',
277
+ 'Septembre',
278
+ 'Octobre',
279
+ 'Novembre',
280
+ 'Décembre'
281
+ ]
282
+ },
283
+ dayNames: {
284
+ type: Array,
285
+ default: () => ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']
286
+ },
287
+ firstDayOfWeek: {
288
+ type: Number,
289
+ default: 1,
290
+ validator: (value) => value >= 0 && value <= 6
291
+ }
292
+ },
293
+ data() {
294
+ return {
295
+ isOpen: false,
296
+ displayMonth: new Date().getMonth(),
297
+ displayYear: new Date().getFullYear(),
298
+ selectedDate: null,
299
+ selectedRangeStart: null,
300
+ selectedRangeEnd: null,
301
+ hoverDate: null,
302
+ selectedHour: 0,
303
+ selectedMinute: 0,
304
+ calendarDays: [],
305
+ generatedId: `f-datepicker-${++idCounter}`
306
+ };
307
+ },
308
+ computed: {
309
+ containerClasses() {
310
+ return 'relative w-full';
311
+ },
312
+ inputClasses() {
313
+ const baseClasses =
314
+ 'block w-full font-sans border rounded transition-all duration-200 box-border focus:outline-none focus:ring-2 pr-10';
315
+
316
+ const sizeClasses = {
317
+ small: 'py-1.5 px-2.5 text-xs',
318
+ medium: 'py-2.5 px-3.5 text-sm',
319
+ large: 'py-3.5 px-4.5 text-base'
320
+ };
321
+
322
+ const stateClasses = this.error
323
+ ? 'border-danger-500 focus:border-danger-500 focus:ring-danger-500/20'
324
+ : 'border-neutral-300 focus:border-primary-500 focus:ring-primary-500/20';
325
+
326
+ const disabledClasses = this.disabled
327
+ ? 'bg-neutral-100 cursor-not-allowed opacity-70'
328
+ : 'cursor-pointer';
329
+
330
+ return [
331
+ baseClasses,
332
+ sizeClasses[this.size],
333
+ stateClasses,
334
+ disabledClasses
335
+ ]
336
+ .filter(Boolean)
337
+ .join(' ');
338
+ },
339
+ iconClasses() {
340
+ const baseClasses =
341
+ 'absolute right-3 top-1/2 -translate-y-1/2 text-neutral-400';
342
+ const cursorClass = this.disabled
343
+ ? 'cursor-not-allowed'
344
+ : 'cursor-pointer';
345
+ return `${baseClasses} ${cursorClass}`;
346
+ },
347
+ calendarClasses() {
348
+ return 'absolute z-50 mt-2 bg-white border border-neutral-200 rounded-lg shadow-lg min-w-[320px]';
349
+ },
350
+ navButtonClasses() {
351
+ return 'p-1 rounded hover:bg-neutral-100 transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500/20';
352
+ },
353
+ selectClasses() {
354
+ return 'px-2 py-1 text-sm border border-neutral-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500/20';
355
+ },
356
+ timeButtonClasses() {
357
+ return 'p-1 rounded hover:bg-neutral-100 transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500/20';
358
+ },
359
+ timeInputClasses() {
360
+ return 'w-12 px-2 py-1 text-center text-sm border border-neutral-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500/20';
361
+ },
362
+ cancelButtonClasses() {
363
+ return 'px-3 py-1.5 text-sm font-medium text-neutral-700 bg-white border border-neutral-300 rounded hover:bg-neutral-50 transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500/20';
364
+ },
365
+ applyButtonClasses() {
366
+ return 'px-3 py-1.5 text-sm font-medium text-white bg-primary-500 rounded hover:bg-primary-600 transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500/20 disabled:opacity-50 disabled:cursor-not-allowed';
367
+ },
368
+ displayValue() {
369
+ if (this.mode === 'single' && this.selectedDate) {
370
+ return this.formatDate(this.selectedDate);
371
+ } else if (
372
+ this.mode === 'range' &&
373
+ this.selectedRangeStart &&
374
+ this.selectedRangeEnd
375
+ ) {
376
+ return `${this.formatDate(this.selectedRangeStart)} - ${this.formatDate(
377
+ this.selectedRangeEnd
378
+ )}`;
379
+ } else if (this.mode === 'range' && this.selectedRangeStart) {
380
+ return this.formatDate(this.selectedRangeStart);
381
+ }
382
+ return '';
383
+ },
384
+ yearRange() {
385
+ const currentYear = new Date().getFullYear();
386
+ const years = [];
387
+ for (let i = currentYear - 100; i <= currentYear + 10; i++) {
388
+ years.push(i);
389
+ }
390
+ return years;
391
+ },
392
+ canApply() {
393
+ if (this.mode === 'range') {
394
+ return this.selectedRangeStart && this.selectedRangeEnd;
395
+ }
396
+ return this.selectedDate !== null;
397
+ }
398
+ },
399
+ watch: {
400
+ value: {
401
+ immediate: true,
402
+ handler(newValue) {
403
+ this.initializeFromValue(newValue);
404
+ }
405
+ },
406
+ isOpen(newValue) {
407
+ if (newValue) {
408
+ this.$nextTick(() => {
409
+ this.updateCalendar();
410
+ document.addEventListener('click', this.handleClickOutside);
411
+ });
412
+ } else {
413
+ document.removeEventListener('click', this.handleClickOutside);
414
+ }
415
+ }
416
+ },
417
+ beforeDestroy() {
418
+ document.removeEventListener('click', this.handleClickOutside);
419
+ },
420
+ methods: {
421
+ initializeFromValue(value) {
422
+ if (!value) {
423
+ this.selectedDate = null;
424
+ this.selectedRangeStart = null;
425
+ this.selectedRangeEnd = null;
426
+ return;
427
+ }
428
+
429
+ if (this.mode === 'single') {
430
+ this.selectedDate = this.parseDate(value);
431
+ if (this.selectedDate && this.showTimePicker) {
432
+ this.selectedHour = this.selectedDate.getHours();
433
+ this.selectedMinute = this.selectedDate.getMinutes();
434
+ }
435
+ } else if (
436
+ this.mode === 'range' &&
437
+ Array.isArray(value) &&
438
+ value.length === 2
439
+ ) {
440
+ this.selectedRangeStart = this.parseDate(value[0]);
441
+ this.selectedRangeEnd = this.parseDate(value[1]);
442
+ }
443
+ },
444
+ parseDate(value) {
445
+ if (value instanceof Date) {
446
+ return value;
447
+ }
448
+ if (typeof value === 'string') {
449
+ return new Date(value);
450
+ }
451
+ return null;
452
+ },
453
+ formatDate(date) {
454
+ if (!date) return '';
455
+
456
+ const day = String(date.getDate()).padStart(2, '0');
457
+ const month = String(date.getMonth() + 1).padStart(2, '0');
458
+ const year = date.getFullYear();
459
+ const hours = String(date.getHours()).padStart(2, '0');
460
+ const minutes = String(date.getMinutes()).padStart(2, '0');
461
+
462
+ let formatted = this.format
463
+ .replace('DD', day)
464
+ .replace('MM', month)
465
+ .replace('YYYY', year);
466
+
467
+ if (this.showTimePicker) {
468
+ formatted += ` ${hours}:${minutes}`;
469
+ }
470
+
471
+ return formatted;
472
+ },
473
+ toggleCalendar() {
474
+ if (this.disabled || this.readonly) return;
475
+ this.isOpen = !this.isOpen;
476
+ },
477
+ openCalendar() {
478
+ if (this.disabled || this.readonly) return;
479
+ this.isOpen = true;
480
+ },
481
+ closeCalendar() {
482
+ this.isOpen = false;
483
+ this.hoverDate = null;
484
+ },
485
+ handleFocus(event) {
486
+ this.$emit('focus', event);
487
+ },
488
+ handleBlur(event) {
489
+ this.$emit('blur', event);
490
+ },
491
+ handleClickOutside(event) {
492
+ if (
493
+ this.$el &&
494
+ !this.$el.contains(event.target) &&
495
+ event.target instanceof Node
496
+ ) {
497
+ this.closeCalendar();
498
+ }
499
+ },
500
+ updateCalendar() {
501
+ const year = this.displayYear;
502
+ const month = this.displayMonth;
503
+ const firstDay = new Date(year, month, 1);
504
+ const lastDay = new Date(year, month + 1, 0);
505
+ const daysInMonth = lastDay.getDate();
506
+
507
+ // Adjust first day based on firstDayOfWeek prop
508
+ let firstDayOfWeek = firstDay.getDay();
509
+ firstDayOfWeek = (firstDayOfWeek - this.firstDayOfWeek + 7) % 7;
510
+
511
+ const days = [];
512
+
513
+ // Add previous month days
514
+ const prevMonthLastDay = new Date(year, month, 0).getDate();
515
+ for (let i = firstDayOfWeek - 1; i >= 0; i--) {
516
+ days.push({
517
+ date: prevMonthLastDay - i,
518
+ month: month - 1,
519
+ year: month === 0 ? year - 1 : year,
520
+ isCurrentMonth: false
521
+ });
522
+ }
523
+
524
+ // Add current month days
525
+ for (let i = 1; i <= daysInMonth; i++) {
526
+ days.push({
527
+ date: i,
528
+ month: month,
529
+ year: year,
530
+ isCurrentMonth: true
531
+ });
532
+ }
533
+
534
+ // Add next month days to complete the grid
535
+ const remainingDays = 42 - days.length;
536
+ for (let i = 1; i <= remainingDays; i++) {
537
+ days.push({
538
+ date: i,
539
+ month: month + 1,
540
+ year: month === 11 ? year + 1 : year,
541
+ isCurrentMonth: false
542
+ });
543
+ }
544
+
545
+ this.calendarDays = days;
546
+ },
547
+ previousMonth() {
548
+ if (this.displayMonth === 0) {
549
+ this.displayMonth = 11;
550
+ this.displayYear--;
551
+ } else {
552
+ this.displayMonth--;
553
+ }
554
+ this.updateCalendar();
555
+ },
556
+ nextMonth() {
557
+ if (this.displayMonth === 11) {
558
+ this.displayMonth = 0;
559
+ this.displayYear++;
560
+ } else {
561
+ this.displayMonth++;
562
+ }
563
+ this.updateCalendar();
564
+ },
565
+ getDayClasses(day) {
566
+ const baseClasses =
567
+ 'h-8 w-8 text-sm rounded-full transition-all duration-150 focus:outline-none focus:ring-2 focus:ring-primary-500/20';
568
+
569
+ const classes = [baseClasses];
570
+
571
+ if (!day.isCurrentMonth) {
572
+ classes.push('text-neutral-300');
573
+ }
574
+
575
+ if (this.isDateSelected(day)) {
576
+ classes.push(
577
+ 'bg-primary-500 text-white font-semibold hover:bg-primary-600'
578
+ );
579
+ } else if (this.isDateInRange(day)) {
580
+ classes.push('bg-primary-100 text-primary-700');
581
+ } else if (this.isToday(day)) {
582
+ classes.push(
583
+ 'border-2 border-primary-500 text-primary-600 font-medium'
584
+ );
585
+ } else if (day.isCurrentMonth) {
586
+ classes.push('hover:bg-neutral-100 text-neutral-700');
587
+ }
588
+
589
+ if (this.isDayDisabled(day)) {
590
+ classes.push('cursor-not-allowed opacity-40');
591
+ } else {
592
+ classes.push('cursor-pointer');
593
+ }
594
+
595
+ return classes.join(' ');
596
+ },
597
+ isToday(day) {
598
+ const today = new Date();
599
+ return (
600
+ day.date === today.getDate() &&
601
+ day.month === today.getMonth() &&
602
+ day.year === today.getFullYear()
603
+ );
604
+ },
605
+ isDateSelected(day) {
606
+ if (this.mode === 'single' && this.selectedDate) {
607
+ return (
608
+ day.date === this.selectedDate.getDate() &&
609
+ day.month === this.selectedDate.getMonth() &&
610
+ day.year === this.selectedDate.getFullYear()
611
+ );
612
+ } else if (this.mode === 'range') {
613
+ const isStart =
614
+ this.selectedRangeStart &&
615
+ day.date === this.selectedRangeStart.getDate() &&
616
+ day.month === this.selectedRangeStart.getMonth() &&
617
+ day.year === this.selectedRangeStart.getFullYear();
618
+
619
+ const isEnd =
620
+ this.selectedRangeEnd &&
621
+ day.date === this.selectedRangeEnd.getDate() &&
622
+ day.month === this.selectedRangeEnd.getMonth() &&
623
+ day.year === this.selectedRangeEnd.getFullYear();
624
+
625
+ return isStart || isEnd;
626
+ }
627
+ return false;
628
+ },
629
+ isDateInRange(day) {
630
+ if (this.mode !== 'range') return false;
631
+
632
+ const date = new Date(day.year, day.month, day.date);
633
+
634
+ if (this.selectedRangeStart && this.selectedRangeEnd) {
635
+ return date > this.selectedRangeStart && date < this.selectedRangeEnd;
636
+ } else if (this.selectedRangeStart && this.hoverDate) {
637
+ const start = this.selectedRangeStart;
638
+ const end = this.hoverDate;
639
+ return (date > start && date < end) || (date < start && date > end);
640
+ }
641
+
642
+ return false;
643
+ },
644
+ isDayDisabled(day) {
645
+ const date = new Date(day.year, day.month, day.date);
646
+
647
+ if (this.minDate) {
648
+ const min = this.parseDate(this.minDate);
649
+ if (min && date < min) return true;
650
+ }
651
+
652
+ if (this.maxDate) {
653
+ const max = this.parseDate(this.maxDate);
654
+ if (max && date > max) return true;
655
+ }
656
+
657
+ if (this.disabledDates.length > 0) {
658
+ return this.disabledDates.some((disabledDate) => {
659
+ const disabled = this.parseDate(disabledDate);
660
+ return (
661
+ disabled &&
662
+ date.getDate() === disabled.getDate() &&
663
+ date.getMonth() === disabled.getMonth() &&
664
+ date.getFullYear() === disabled.getFullYear()
665
+ );
666
+ });
667
+ }
668
+
669
+ return false;
670
+ },
671
+ selectDate(day) {
672
+ if (this.isDayDisabled(day)) return;
673
+
674
+ const selectedDate = new Date(day.year, day.month, day.date);
675
+
676
+ if (this.showTimePicker) {
677
+ selectedDate.setHours(this.selectedHour);
678
+ selectedDate.setMinutes(this.selectedMinute);
679
+ }
680
+
681
+ if (this.mode === 'single') {
682
+ this.selectedDate = selectedDate;
683
+ if (!this.showTimePicker) {
684
+ this.emitValue();
685
+ this.closeCalendar();
686
+ }
687
+ } else if (this.mode === 'range') {
688
+ if (
689
+ !this.selectedRangeStart ||
690
+ (this.selectedRangeStart && this.selectedRangeEnd)
691
+ ) {
692
+ this.selectedRangeStart = selectedDate;
693
+ this.selectedRangeEnd = null;
694
+ } else {
695
+ if (selectedDate < this.selectedRangeStart) {
696
+ this.selectedRangeEnd = this.selectedRangeStart;
697
+ this.selectedRangeStart = selectedDate;
698
+ } else {
699
+ this.selectedRangeEnd = selectedDate;
700
+ }
701
+ }
702
+ }
703
+ },
704
+ handleDayHover(day) {
705
+ if (
706
+ this.mode === 'range' &&
707
+ this.selectedRangeStart &&
708
+ !this.selectedRangeEnd
709
+ ) {
710
+ this.hoverDate = new Date(day.year, day.month, day.date);
711
+ }
712
+ },
713
+ incrementHour() {
714
+ this.selectedHour = (this.selectedHour + 1) % 24;
715
+ },
716
+ decrementHour() {
717
+ this.selectedHour = (this.selectedHour - 1 + 24) % 24;
718
+ },
719
+ incrementMinute() {
720
+ this.selectedMinute = (this.selectedMinute + 1) % 60;
721
+ },
722
+ decrementMinute() {
723
+ this.selectedMinute = (this.selectedMinute - 1 + 60) % 60;
724
+ },
725
+ updateTime() {
726
+ if (this.selectedDate && this.mode === 'single') {
727
+ this.selectedDate.setHours(this.selectedHour);
728
+ this.selectedDate.setMinutes(this.selectedMinute);
729
+ }
730
+ },
731
+ applySelection() {
732
+ this.emitValue();
733
+ this.closeCalendar();
734
+ },
735
+ emitValue() {
736
+ if (this.mode === 'single') {
737
+ this.$emit('input', this.selectedDate);
738
+ this.$emit('change', this.selectedDate);
739
+ } else if (
740
+ this.mode === 'range' &&
741
+ this.selectedRangeStart &&
742
+ this.selectedRangeEnd
743
+ ) {
744
+ this.$emit('input', [this.selectedRangeStart, this.selectedRangeEnd]);
745
+ this.$emit('change', [this.selectedRangeStart, this.selectedRangeEnd]);
746
+ }
747
+ }
748
+ }
749
+ };
750
+ </script>