@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,303 @@
1
+ <template>
2
+ <div v-if="isOpen" class="fixed inset-0 z-50 overflow-hidden">
3
+ <!-- Overlay -->
4
+ <div
5
+ class="fixed inset-0 bg-black opacity-50 transition-opacity duration-[var(--transition-duration-slow)] ease-[var(--transition-easing-standard)]"
6
+ @click="handleOverlayClick"
7
+ ></div>
8
+
9
+ <!-- Drawer Container -->
10
+ <div :class="containerClasses">
11
+ <div
12
+ :class="drawerClasses"
13
+ role="dialog"
14
+ aria-modal="true"
15
+ :aria-labelledby="titleId"
16
+ >
17
+ <!-- Header -->
18
+ <div
19
+ v-if="$slots.header || title"
20
+ class="flex items-center justify-between px-4 pt-4 flex-shrink-0"
21
+ >
22
+ <div class="flex-1 min-w-0">
23
+ <slot name="header">
24
+ <f-typography :id="titleId" variant="h5">{{
25
+ title
26
+ }}</f-typography>
27
+ <f-typography
28
+ v-if="subtitle"
29
+ variant="caption"
30
+ class="text-neutral-500"
31
+ >
32
+ {{ subtitle }}
33
+ </f-typography>
34
+ </slot>
35
+ </div>
36
+ <f-button
37
+ v-if="closable"
38
+ variant="ghost"
39
+ size="small"
40
+ class="flex-shrink-0 -mr-2"
41
+ @click="handleClose"
42
+ >
43
+ <f-icon name="close" size="sm" />
44
+ <span class="sr-only">Fermer le tiroir</span>
45
+ </f-button>
46
+ </div>
47
+
48
+ <!-- Body -->
49
+ <div class="p-4 flex-1 overflow-y-auto">
50
+ <slot name="body">
51
+ <slot />
52
+ </slot>
53
+ </div>
54
+
55
+ <!-- Actions -->
56
+ <div
57
+ v-if="$slots.actions"
58
+ class="px-4 pb-4 flex gap-2 justify-end flex-shrink-0"
59
+ >
60
+ <slot name="actions" />
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </template>
66
+
67
+ <script>
68
+ import FTypography from '../../atoms/FTypography/FTypography.vue';
69
+ import FButton from '../../atoms/FButton/FButton.vue';
70
+ import FIcon from '../../atoms/FIcon/FIcon.vue';
71
+
72
+ let idCounter = 0;
73
+
74
+ export default {
75
+ name: 'FDrawer',
76
+ components: {
77
+ FTypography,
78
+ FButton,
79
+ FIcon
80
+ },
81
+ props: {
82
+ /**
83
+ * Controls the visibility of the drawer.
84
+ * Use v-model for two-way binding.
85
+ */
86
+ value: {
87
+ type: Boolean,
88
+ default: false
89
+ },
90
+ /**
91
+ * Drawer title displayed in the header
92
+ */
93
+ title: {
94
+ type: String,
95
+ default: ''
96
+ },
97
+ /**
98
+ * Optional subtitle displayed below the title
99
+ */
100
+ subtitle: {
101
+ type: String,
102
+ default: ''
103
+ },
104
+ /**
105
+ * Show the close button in the header
106
+ */
107
+ closable: {
108
+ type: Boolean,
109
+ default: true
110
+ },
111
+ /**
112
+ * Close the drawer when clicking the overlay
113
+ */
114
+ closeOnOverlay: {
115
+ type: Boolean,
116
+ default: true
117
+ },
118
+ /**
119
+ * Close the drawer when pressing Escape key
120
+ */
121
+ closeOnEscape: {
122
+ type: Boolean,
123
+ default: true
124
+ },
125
+ /**
126
+ * Position of the drawer (left, right, top, bottom)
127
+ */
128
+ position: {
129
+ type: String,
130
+ default: 'right',
131
+ validator: (value) => ['left', 'right', 'top', 'bottom'].includes(value)
132
+ },
133
+ /**
134
+ * Size of the drawer
135
+ */
136
+ size: {
137
+ type: String,
138
+ default: 'medium',
139
+ validator: (value) => ['small', 'medium', 'large'].includes(value)
140
+ },
141
+ /**
142
+ * Whether the drawer has a border
143
+ */
144
+ bordered: {
145
+ type: Boolean,
146
+ default: true
147
+ }
148
+ },
149
+ data() {
150
+ return {
151
+ uid: idCounter++
152
+ };
153
+ },
154
+ computed: {
155
+ /**
156
+ * Computed property for v-model support
157
+ */
158
+ isOpen: {
159
+ get() {
160
+ return this.value;
161
+ },
162
+ set(val) {
163
+ this.$emit('input', val);
164
+ }
165
+ },
166
+ /**
167
+ * Unique ID for the drawer title (accessibility)
168
+ */
169
+ titleId() {
170
+ return `f-drawer-title-${this.uid}`;
171
+ },
172
+ /**
173
+ * Container classes for positioning
174
+ */
175
+ containerClasses() {
176
+ const positionClasses = {
177
+ left: 'flex items-stretch justify-start',
178
+ right: 'flex items-stretch justify-end',
179
+ top: 'flex flex-col items-stretch justify-start',
180
+ bottom: 'flex flex-col items-stretch justify-end'
181
+ };
182
+
183
+ return [
184
+ 'fixed inset-0 pointer-events-none',
185
+ positionClasses[this.position]
186
+ ]
187
+ .filter(Boolean)
188
+ .join(' ');
189
+ },
190
+ /**
191
+ * Drawer panel classes
192
+ */
193
+ drawerClasses() {
194
+ const baseClasses =
195
+ 'relative bg-white shadow-xl pointer-events-auto flex flex-col';
196
+ const transitionClasses =
197
+ 'transition-all duration-[var(--transition-duration-slow)] ease-[var(--transition-easing-emphasized)]';
198
+ const borderedClasses = this.bordered ? 'border border-neutral-200' : '';
199
+
200
+ const sizeClasses = {
201
+ left: {
202
+ small: 'w-64',
203
+ medium: 'w-80',
204
+ large: 'w-96'
205
+ },
206
+ right: {
207
+ small: 'w-64',
208
+ medium: 'w-80',
209
+ large: 'w-96'
210
+ },
211
+ top: {
212
+ small: 'h-64',
213
+ medium: 'h-80',
214
+ large: 'h-96'
215
+ },
216
+ bottom: {
217
+ small: 'h-64',
218
+ medium: 'h-80',
219
+ large: 'h-96'
220
+ }
221
+ };
222
+
223
+ const heightWidthClass = sizeClasses[this.position][this.size];
224
+ const fullHeightWidth =
225
+ this.position === 'left' || this.position === 'right'
226
+ ? 'h-full'
227
+ : 'w-full';
228
+
229
+ return [
230
+ baseClasses,
231
+ transitionClasses,
232
+ borderedClasses,
233
+ heightWidthClass,
234
+ fullHeightWidth
235
+ ]
236
+ .filter(Boolean)
237
+ .join(' ');
238
+ }
239
+ },
240
+ watch: {
241
+ /**
242
+ * Watch for drawer open/close to manage body scroll
243
+ */
244
+ isOpen: {
245
+ immediate: true,
246
+ handler(newValue) {
247
+ if (newValue) {
248
+ this.lockBodyScroll();
249
+ this.$nextTick(() => {
250
+ if (this.closeOnEscape) {
251
+ document.addEventListener('keydown', this.handleKeydown);
252
+ }
253
+ });
254
+ } else {
255
+ this.unlockBodyScroll();
256
+ document.removeEventListener('keydown', this.handleKeydown);
257
+ }
258
+ }
259
+ }
260
+ },
261
+ beforeDestroy() {
262
+ this.unlockBodyScroll();
263
+ document.removeEventListener('keydown', this.handleKeydown);
264
+ },
265
+ methods: {
266
+ /**
267
+ * Handle overlay click
268
+ */
269
+ handleOverlayClick() {
270
+ if (this.closeOnOverlay) {
271
+ this.handleClose();
272
+ }
273
+ },
274
+ /**
275
+ * Handle close action
276
+ */
277
+ handleClose() {
278
+ this.isOpen = false;
279
+ this.$emit('close');
280
+ },
281
+ /**
282
+ * Handle keyboard events
283
+ */
284
+ handleKeydown(event) {
285
+ if (event.key === 'Escape' && this.closeOnEscape) {
286
+ this.handleClose();
287
+ }
288
+ },
289
+ /**
290
+ * Lock body scroll when drawer is open
291
+ */
292
+ lockBodyScroll() {
293
+ document.body.style.overflow = 'hidden';
294
+ },
295
+ /**
296
+ * Unlock body scroll when drawer is closed
297
+ */
298
+ unlockBodyScroll() {
299
+ document.body.style.overflow = '';
300
+ }
301
+ }
302
+ };
303
+ </script>
@@ -0,0 +1,162 @@
1
+ import FFileUpload from './FFileUpload.vue';
2
+
3
+ export default {
4
+ title: 'Organisms/FFileUpload',
5
+ component: FFileUpload,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ accept: {
9
+ control: 'text',
10
+ description: 'Types de fichiers acceptés'
11
+ },
12
+ multiple: {
13
+ control: 'boolean',
14
+ description: 'Autoriser plusieurs fichiers'
15
+ },
16
+ maxSize: {
17
+ control: 'number',
18
+ description: 'Taille maximale en octets'
19
+ },
20
+ maxFiles: {
21
+ control: 'number',
22
+ description: 'Nombre maximum de fichiers'
23
+ },
24
+ showButton: {
25
+ control: 'boolean',
26
+ description: "Afficher le bouton d'upload"
27
+ },
28
+ showProgress: {
29
+ control: 'boolean',
30
+ description: 'Afficher la barre de progression'
31
+ },
32
+ disabled: {
33
+ control: 'boolean',
34
+ description: 'État désactivé'
35
+ },
36
+ hint: {
37
+ control: 'text',
38
+ description: "Texte d'aide"
39
+ }
40
+ }
41
+ };
42
+
43
+ const Template = (args, { argTypes }) => ({
44
+ components: { FFileUpload },
45
+ props: Object.keys(argTypes),
46
+ data() {
47
+ return { files: [] };
48
+ },
49
+ template: '<FFileUpload v-bind="$props" v-model="files" />'
50
+ });
51
+
52
+ export const Default = Template.bind({});
53
+ Default.args = {};
54
+
55
+ export const WithButton = Template.bind({});
56
+ WithButton.args = {
57
+ showButton: true,
58
+ buttonLabel: 'Parcourir'
59
+ };
60
+
61
+ export const WithHint = Template.bind({});
62
+ WithHint.args = {
63
+ hint: 'Formats acceptés: PDF, DOC, DOCX. Taille max: 10 Mo'
64
+ };
65
+
66
+ export const ImagesOnly = Template.bind({});
67
+ ImagesOnly.args = {
68
+ accept: 'image/*',
69
+ hint: 'Images uniquement (JPG, PNG, GIF)'
70
+ };
71
+
72
+ export const MultipleFiles = Template.bind({});
73
+ MultipleFiles.args = {
74
+ multiple: true,
75
+ hint: 'Vous pouvez sélectionner plusieurs fichiers'
76
+ };
77
+
78
+ export const Disabled = Template.bind({});
79
+ Disabled.args = {
80
+ disabled: true
81
+ };
82
+
83
+ export const WithMaxSize = Template.bind({});
84
+ WithMaxSize.args = {
85
+ maxSize: 5 * 1024 * 1024, // 5 MB
86
+ hint: 'Taille maximale: 5 Mo'
87
+ };
88
+
89
+ export const WithMaxFiles = Template.bind({});
90
+ WithMaxFiles.args = {
91
+ multiple: true,
92
+ maxFiles: 3,
93
+ hint: 'Maximum 3 fichiers'
94
+ };
95
+
96
+ export const WithPreloadedFiles = () => ({
97
+ components: { FFileUpload },
98
+ data() {
99
+ return {
100
+ files: [
101
+ { id: 1, name: 'document.pdf', status: 'success' },
102
+ { id: 2, name: 'image.png', status: 'success' }
103
+ ]
104
+ };
105
+ },
106
+ template: '<FFileUpload v-model="files" multiple />'
107
+ });
108
+
109
+ export const WithUploadingFile = () => ({
110
+ components: { FFileUpload },
111
+ data() {
112
+ return {
113
+ files: [
114
+ { id: 1, name: 'rapport.pdf', status: 'success' },
115
+ { id: 2, name: 'video.mp4', status: 'uploading', progress: 45 },
116
+ { id: 3, name: 'archive.zip', status: 'pending' }
117
+ ]
118
+ };
119
+ },
120
+ template: '<FFileUpload v-model="files" multiple showProgress />'
121
+ });
122
+
123
+ export const Interactive = () => ({
124
+ components: { FFileUpload },
125
+ data() {
126
+ return {
127
+ files: []
128
+ };
129
+ },
130
+ methods: {
131
+ handleChange(files) {
132
+ console.log('Files changed:', files);
133
+ },
134
+ handleError(error) {
135
+ console.error('Upload error:', error);
136
+ }
137
+ },
138
+ template: `
139
+ <div class="max-w-lg">
140
+ <FFileUpload
141
+ v-model="files"
142
+ multiple
143
+ showButton
144
+ showProgress
145
+ accept=".pdf,.doc,.docx,.jpg,.png"
146
+ :maxSize="10 * 1024 * 1024"
147
+ hint="PDF, DOC, images. Max 10 Mo par fichier."
148
+ @change="handleChange"
149
+ @error="handleError"
150
+ />
151
+ <div v-if="files.length" class="mt-4 text-sm text-neutral-600">
152
+ <p>{{ files.length }} fichier(s) sélectionné(s)</p>
153
+ </div>
154
+ </div>
155
+ `
156
+ });
157
+
158
+ export const PDFOnly = Template.bind({});
159
+ PDFOnly.args = {
160
+ accept: '.pdf',
161
+ hint: 'Fichiers PDF uniquement'
162
+ };
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FFileUpload from './FFileUpload.vue';
4
+
5
+ describe('FFileUpload', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FFileUpload);
8
+ expect(wrapper.find('input[type="file"]').exists()).toBe(true);
9
+ });
10
+
11
+ it('has hidden file input', () => {
12
+ const wrapper = mount(FFileUpload);
13
+ const input = wrapper.find('input[type="file"]');
14
+ expect(input.classes()).toContain('sr-only');
15
+ });
16
+
17
+ it('displays drop zone', () => {
18
+ const wrapper = mount(FFileUpload);
19
+ expect(wrapper.text()).toContain('Glissez-déposez');
20
+ });
21
+
22
+ it('accepts custom accept types', () => {
23
+ const wrapper = mount(FFileUpload, {
24
+ propsData: { accept: '.pdf,.doc' }
25
+ });
26
+ expect(wrapper.find('input[type="file"]').attributes('accept')).toBe(
27
+ '.pdf,.doc'
28
+ );
29
+ });
30
+
31
+ it('allows multiple files when multiple is true', () => {
32
+ const wrapper = mount(FFileUpload, {
33
+ propsData: { multiple: true }
34
+ });
35
+ expect(
36
+ wrapper.find('input[type="file"]').attributes('multiple')
37
+ ).toBeDefined();
38
+ });
39
+
40
+ it('disables input when disabled', () => {
41
+ const wrapper = mount(FFileUpload, {
42
+ propsData: { disabled: true }
43
+ });
44
+ expect(
45
+ wrapper.find('input[type="file"]').attributes('disabled')
46
+ ).toBeDefined();
47
+ });
48
+
49
+ it('shows upload button when showButton is true', () => {
50
+ const wrapper = mount(FFileUpload, {
51
+ propsData: { showButton: true }
52
+ });
53
+ expect(wrapper.findComponent({ name: 'FButton' }).exists()).toBe(true);
54
+ });
55
+
56
+ it('shows hint when provided', () => {
57
+ const wrapper = mount(FFileUpload, {
58
+ propsData: { hint: 'Max 10MB' }
59
+ });
60
+ expect(wrapper.text()).toContain('Max 10MB');
61
+ });
62
+
63
+ it('displays file previews when files are present', () => {
64
+ const wrapper = mount(FFileUpload, {
65
+ propsData: {
66
+ value: [{ id: 1, name: 'test.pdf', status: 'success' }]
67
+ }
68
+ });
69
+ expect(wrapper.findComponent({ name: 'FFilePreview' }).exists()).toBe(true);
70
+ });
71
+
72
+ it('emits input event when files are added', async () => {
73
+ const wrapper = mount(FFileUpload);
74
+ const input = wrapper.find('input[type="file"]');
75
+
76
+ // Create a mock file
77
+ const file = new File(['content'], 'test.txt', { type: 'text/plain' });
78
+ const mockFileList = {
79
+ 0: file,
80
+ length: 1,
81
+ item: (index: number) => file
82
+ };
83
+
84
+ // Trigger change
85
+ Object.defineProperty(input.element, 'files', {
86
+ value: mockFileList,
87
+ writable: false
88
+ });
89
+ await input.trigger('change');
90
+
91
+ expect(wrapper.emitted('input')).toBeTruthy();
92
+ });
93
+
94
+ it('shows progress bar when uploading', () => {
95
+ const wrapper = mount(FFileUpload, {
96
+ propsData: {
97
+ showProgress: true,
98
+ value: [{ id: 1, name: 'test.pdf', status: 'uploading', progress: 50 }]
99
+ }
100
+ });
101
+ expect(wrapper.exists()).toBe(true);
102
+ });
103
+ });