@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,370 @@
1
+ import FDataTable from './FDataTable.vue';
2
+ import FButton from '../../atoms/FButton/FButton.vue';
3
+ import FBadge from '../../atoms/FBadge/FBadge.vue';
4
+
5
+ export default {
6
+ title: 'Organisms/FDataTable',
7
+ component: FDataTable,
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ data: {
11
+ control: 'object',
12
+ description: 'Données à afficher'
13
+ },
14
+ columns: {
15
+ control: 'object',
16
+ description: 'Définition des colonnes'
17
+ },
18
+ loading: {
19
+ control: 'boolean',
20
+ description: 'État de chargement'
21
+ },
22
+ searchable: {
23
+ control: 'boolean',
24
+ description: 'Activer la recherche'
25
+ },
26
+ paginated: {
27
+ control: 'boolean',
28
+ description: 'Activer la pagination'
29
+ },
30
+ selectable: {
31
+ control: 'boolean',
32
+ description: 'Activer la sélection'
33
+ },
34
+ striped: {
35
+ control: 'boolean',
36
+ description: 'Lignes alternées'
37
+ },
38
+ hoverable: {
39
+ control: 'boolean',
40
+ description: 'Effet au survol'
41
+ },
42
+ bordered: {
43
+ control: 'boolean',
44
+ description: 'Bordure'
45
+ }
46
+ }
47
+ };
48
+
49
+ const sampleData = [
50
+ {
51
+ id: 1,
52
+ name: 'Alice Martin',
53
+ email: 'alice@example.com',
54
+ role: 'Admin',
55
+ status: 'active'
56
+ },
57
+ {
58
+ id: 2,
59
+ name: 'Bob Dupont',
60
+ email: 'bob@example.com',
61
+ role: 'User',
62
+ status: 'active'
63
+ },
64
+ {
65
+ id: 3,
66
+ name: 'Claire Durand',
67
+ email: 'claire@example.com',
68
+ role: 'User',
69
+ status: 'inactive'
70
+ },
71
+ {
72
+ id: 4,
73
+ name: 'David Petit',
74
+ email: 'david@example.com',
75
+ role: 'Editor',
76
+ status: 'active'
77
+ },
78
+ {
79
+ id: 5,
80
+ name: 'Emma Bernard',
81
+ email: 'emma@example.com',
82
+ role: 'User',
83
+ status: 'pending'
84
+ }
85
+ ];
86
+
87
+ const columns = [
88
+ { key: 'name', label: 'Nom' },
89
+ { key: 'email', label: 'Email' },
90
+ { key: 'role', label: 'Rôle' },
91
+ { key: 'status', label: 'Statut' }
92
+ ];
93
+
94
+ const Template = (args, { argTypes }) => ({
95
+ components: { FDataTable },
96
+ props: Object.keys(argTypes),
97
+ template: '<FDataTable v-bind="$props" />'
98
+ });
99
+
100
+ export const Default = Template.bind({});
101
+ Default.args = {
102
+ data: sampleData,
103
+ columns
104
+ };
105
+
106
+ export const WithSearch = Template.bind({});
107
+ WithSearch.args = {
108
+ data: sampleData,
109
+ columns,
110
+ searchable: true
111
+ };
112
+
113
+ export const WithPagination = () => ({
114
+ components: { FDataTable },
115
+ data() {
116
+ return {
117
+ columns,
118
+ data: Array.from({ length: 50 }, (_, i) => ({
119
+ id: i + 1,
120
+ name: `Utilisateur ${i + 1}`,
121
+ email: `user${i + 1}@example.com`,
122
+ role: ['Admin', 'User', 'Editor'][i % 3],
123
+ status: ['active', 'inactive', 'pending'][i % 3]
124
+ }))
125
+ };
126
+ },
127
+ template:
128
+ '<FDataTable :data="data" :columns="columns" paginated :perPage="10" />'
129
+ });
130
+
131
+ export const WithSelection = () => ({
132
+ components: { FDataTable },
133
+ data() {
134
+ return {
135
+ columns,
136
+ data: sampleData,
137
+ selected: []
138
+ };
139
+ },
140
+ template: `
141
+ <div>
142
+ <FDataTable
143
+ :data="data"
144
+ :columns="columns"
145
+ selectable
146
+ :selected.sync="selected"
147
+ />
148
+ <p class="mt-4 text-sm text-neutral-600">
149
+ Sélectionnés: {{ selected.join(', ') || 'Aucun' }}
150
+ </p>
151
+ </div>
152
+ `
153
+ });
154
+
155
+ export const Loading = Template.bind({});
156
+ Loading.args = {
157
+ data: sampleData,
158
+ columns,
159
+ loading: true
160
+ };
161
+
162
+ export const Empty = Template.bind({});
163
+ Empty.args = {
164
+ data: [],
165
+ columns
166
+ };
167
+
168
+ export const Striped = Template.bind({});
169
+ Striped.args = {
170
+ data: sampleData,
171
+ columns,
172
+ striped: true
173
+ };
174
+
175
+ export const Bordered = Template.bind({});
176
+ Bordered.args = {
177
+ data: sampleData,
178
+ columns,
179
+ bordered: true
180
+ };
181
+
182
+ export const CustomCells = () => ({
183
+ components: { FDataTable, FBadge },
184
+ data() {
185
+ return {
186
+ columns: [
187
+ { key: 'name', label: 'Nom' },
188
+ { key: 'email', label: 'Email' },
189
+ { key: 'status', label: 'Statut' }
190
+ ],
191
+ data: sampleData
192
+ };
193
+ },
194
+ template: `
195
+ <FDataTable :data="data" :columns="columns">
196
+ <template #cell-status="{ value }">
197
+ <FBadge
198
+ :content="value"
199
+ :variant="value === 'active' ? 'success' : value === 'pending' ? 'warning' : 'neutral'"
200
+ />
201
+ </template>
202
+ </FDataTable>
203
+ `
204
+ });
205
+
206
+ export const WithActions = () => ({
207
+ components: { FDataTable, FButton },
208
+ data() {
209
+ return {
210
+ columns,
211
+ data: sampleData
212
+ };
213
+ },
214
+ methods: {
215
+ handleAction(action, item) {
216
+ alert(`${action}: ${item.name}`);
217
+ }
218
+ },
219
+ template: `
220
+ <FDataTable :data="data" :columns="columns" selectable searchable>
221
+ <template #actions="{ selectedItems }">
222
+ <FButton
223
+ v-if="selectedItems.length > 0"
224
+ variant="danger"
225
+ size="small"
226
+ @click="handleAction('delete', selectedItems[0])"
227
+ >
228
+ Supprimer ({{ selectedItems.length }})
229
+ </FButton>
230
+ </template>
231
+ </FDataTable>
232
+ `
233
+ });
234
+
235
+ export const FullFeatures = () => ({
236
+ components: { FDataTable, FButton, FBadge },
237
+ data() {
238
+ return {
239
+ columns: [
240
+ { key: 'name', label: 'Nom' },
241
+ { key: 'email', label: 'Email' },
242
+ { key: 'role', label: 'Rôle' },
243
+ { key: 'status', label: 'Statut' }
244
+ ],
245
+ data: Array.from({ length: 30 }, (_, i) => ({
246
+ id: i + 1,
247
+ name: `Utilisateur ${i + 1}`,
248
+ email: `user${i + 1}@example.com`,
249
+ role: ['Admin', 'User', 'Editor'][i % 3],
250
+ status: ['active', 'inactive', 'pending'][i % 3]
251
+ })),
252
+ selected: []
253
+ };
254
+ },
255
+ template: `
256
+ <FDataTable
257
+ :data="data"
258
+ :columns="columns"
259
+ :selected.sync="selected"
260
+ selectable
261
+ searchable
262
+ paginated
263
+ striped
264
+ :perPage="10"
265
+ >
266
+ <template #cell-status="{ value }">
267
+ <FBadge
268
+ :content="value"
269
+ :variant="value === 'active' ? 'success' : value === 'pending' ? 'warning' : 'neutral'"
270
+ />
271
+ </template>
272
+ <template #actions="{ selectedItems }">
273
+ <FButton v-if="selectedItems.length > 0" variant="outline" size="small">
274
+ Exporter ({{ selectedItems.length }})
275
+ </FButton>
276
+ </template>
277
+ </FDataTable>
278
+ `
279
+ });
280
+
281
+ export const MobileCardView = () => ({
282
+ components: { FDataTable, FBadge },
283
+ data() {
284
+ return {
285
+ columns: [
286
+ { key: 'name', label: 'Nom' },
287
+ { key: 'email', label: 'Email' },
288
+ { key: 'role', label: 'Rôle' },
289
+ { key: 'status', label: 'Statut' }
290
+ ],
291
+ data: sampleData
292
+ };
293
+ },
294
+ template: `
295
+ <div style="max-width: 400px; margin: 0 auto;">
296
+ <p class="text-sm text-neutral-500 mb-4">
297
+ Ce tableau utilise le mode "Card View" sur mobile (écrans &lt; 640px).
298
+ Redimensionnez la fenêtre ou utilisez les outils de développement pour voir le mode mobile.
299
+ </p>
300
+ <FDataTable :data="data" :columns="columns">
301
+ <template #cell-status="{ value }">
302
+ <FBadge
303
+ :content="value"
304
+ :variant="value === 'active' ? 'success' : value === 'pending' ? 'warning' : 'neutral'"
305
+ />
306
+ </template>
307
+ </FDataTable>
308
+ </div>
309
+ `
310
+ });
311
+ MobileCardView.parameters = {
312
+ docs: {
313
+ description: {
314
+ story:
315
+ 'Sur les écrans mobiles (< 640px), le tableau se transforme automatiquement en mode "Card View" où chaque ligne devient une carte avec les labels de colonnes affichés comme étiquettes.'
316
+ }
317
+ }
318
+ };
319
+
320
+ export const VirtualizedLargeDataset = () => ({
321
+ components: { FDataTable, FBadge },
322
+ data() {
323
+ return {
324
+ columns: [
325
+ { key: 'id', label: 'ID' },
326
+ { key: 'name', label: 'Nom' },
327
+ { key: 'email', label: 'Email' },
328
+ { key: 'role', label: 'Rôle' },
329
+ { key: 'status', label: 'Statut' }
330
+ ],
331
+ data: Array.from({ length: 10000 }, (_, i) => ({
332
+ id: i + 1,
333
+ name: `Utilisateur ${i + 1}`,
334
+ email: `user${i + 1}@example.com`,
335
+ role: ['Admin', 'User', 'Editor', 'Viewer'][i % 4],
336
+ status: ['active', 'inactive', 'pending'][i % 3]
337
+ }))
338
+ };
339
+ },
340
+ template: `
341
+ <div>
342
+ <p class="text-sm text-neutral-600 mb-4">
343
+ Ce tableau utilise la virtualisation pour afficher 10 000 lignes de manière fluide.
344
+ Seuls les éléments visibles sont rendus dans le DOM, ce qui améliore drastiquement les performances.
345
+ </p>
346
+ <FDataTable
347
+ :data="data"
348
+ :columns="columns"
349
+ virtual
350
+ searchable
351
+ selectable
352
+ >
353
+ <template #cell-status="{ value }">
354
+ <FBadge
355
+ :content="value"
356
+ :variant="value === 'active' ? 'success' : value === 'pending' ? 'warning' : 'neutral'"
357
+ />
358
+ </template>
359
+ </FDataTable>
360
+ </div>
361
+ `
362
+ });
363
+ VirtualizedLargeDataset.parameters = {
364
+ docs: {
365
+ description: {
366
+ story:
367
+ "La virtualisation permet d'afficher des milliers d'enregistrements sans ralentissement. Le tableau ne rend que les lignes visibles à l'écran, ce qui réduit la charge du DOM et améliore les performances de défilement."
368
+ }
369
+ }
370
+ };
@@ -0,0 +1,248 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FDataTable from './FDataTable.vue';
4
+
5
+ describe('FDataTable', () => {
6
+ const columns = [
7
+ { key: 'name', label: 'Name' },
8
+ { key: 'email', label: 'Email' },
9
+ { key: 'role', label: 'Role' }
10
+ ];
11
+
12
+ const data = [
13
+ { id: 1, name: 'Alice', email: 'alice@test.com', role: 'Admin' },
14
+ { id: 2, name: 'Bob', email: 'bob@test.com', role: 'User' },
15
+ { id: 3, name: 'Charlie', email: 'charlie@test.com', role: 'User' }
16
+ ];
17
+
18
+ it('renders correctly with required props', () => {
19
+ const wrapper = mount(FDataTable, {
20
+ propsData: { columns, data }
21
+ });
22
+ expect(wrapper.find('table').exists()).toBe(true);
23
+ });
24
+
25
+ it('displays column headers', () => {
26
+ const wrapper = mount(FDataTable, {
27
+ propsData: { columns, data }
28
+ });
29
+ expect(wrapper.text()).toContain('Name');
30
+ expect(wrapper.text()).toContain('Email');
31
+ expect(wrapper.text()).toContain('Role');
32
+ });
33
+
34
+ it('displays data rows', () => {
35
+ const wrapper = mount(FDataTable, {
36
+ propsData: { columns, data }
37
+ });
38
+ expect(wrapper.text()).toContain('Alice');
39
+ expect(wrapper.text()).toContain('bob@test.com');
40
+ });
41
+
42
+ it('shows empty state when no data', () => {
43
+ const wrapper = mount(FDataTable, {
44
+ propsData: { columns, data: [] }
45
+ });
46
+ expect(wrapper.findComponent({ name: 'FEmptyState' }).exists()).toBe(true);
47
+ });
48
+
49
+ it('shows loader when loading', () => {
50
+ const wrapper = mount(FDataTable, {
51
+ propsData: { columns, data, loading: true }
52
+ });
53
+ expect(wrapper.findComponent({ name: 'FLoader' }).exists()).toBe(true);
54
+ });
55
+
56
+ it('renders pagination when paginated', () => {
57
+ const manyRows = Array.from({ length: 20 }, (_, i) => ({
58
+ id: i + 1,
59
+ name: `User ${i + 1}`,
60
+ email: `user${i + 1}@test.com`,
61
+ role: 'User'
62
+ }));
63
+ const wrapper = mount(FDataTable, {
64
+ propsData: { columns, data: manyRows, paginated: true, perPage: 5 }
65
+ });
66
+ expect(wrapper.findComponent({ name: 'FPagination' }).exists()).toBe(true);
67
+ });
68
+
69
+ it('renders search bar when searchable', () => {
70
+ const wrapper = mount(FDataTable, {
71
+ propsData: { columns, data, searchable: true }
72
+ });
73
+ expect(wrapper.findComponent({ name: 'FSearchBar' }).exists()).toBe(true);
74
+ });
75
+
76
+ it('renders checkboxes when selectable', () => {
77
+ const wrapper = mount(FDataTable, {
78
+ propsData: { columns, data, selectable: true }
79
+ });
80
+ expect(wrapper.findComponent({ name: 'FCheckbox' }).exists()).toBe(true);
81
+ });
82
+
83
+ it('emits row-click when row is clicked', async () => {
84
+ const wrapper = mount(FDataTable, {
85
+ propsData: { columns, data }
86
+ });
87
+ const rows = wrapper.findAll('tbody tr');
88
+ if (rows.length > 0) {
89
+ await rows[0].trigger('click');
90
+ expect(wrapper.emitted('row-click')).toBeTruthy();
91
+ }
92
+ });
93
+
94
+ it('emits sort event when header is clicked', async () => {
95
+ const wrapper = mount(FDataTable, {
96
+ propsData: { columns, data }
97
+ });
98
+ const headers = wrapper.findAll('th');
99
+ if (headers.length > 0) {
100
+ await headers[0].trigger('click');
101
+ expect(wrapper.emitted('sort')).toBeTruthy();
102
+ }
103
+ });
104
+
105
+ it('applies striped style when striped is true', () => {
106
+ const wrapper = mount(FDataTable, {
107
+ propsData: { columns, data, striped: true }
108
+ });
109
+ expect(wrapper.exists()).toBe(true);
110
+ });
111
+
112
+ it('renders data-label attributes on cells for mobile card view', () => {
113
+ const wrapper = mount(FDataTable, {
114
+ propsData: { columns, data }
115
+ });
116
+ const cells = wrapper.findAll('td[data-label]');
117
+ // Each row should have cells with data-label attributes matching column labels
118
+ expect(cells.length).toBeGreaterThan(0);
119
+ // Check that at least one cell has the correct data-label
120
+ const hasNameLabel = cells.wrappers.some(
121
+ (cell) => cell.attributes('data-label') === 'Name'
122
+ );
123
+ const hasEmailLabel = cells.wrappers.some(
124
+ (cell) => cell.attributes('data-label') === 'Email'
125
+ );
126
+ const hasRoleLabel = cells.wrappers.some(
127
+ (cell) => cell.attributes('data-label') === 'Role'
128
+ );
129
+ expect(hasNameLabel).toBe(true);
130
+ expect(hasEmailLabel).toBe(true);
131
+ expect(hasRoleLabel).toBe(true);
132
+ });
133
+
134
+ it('renders empty data-label for checkbox column when selectable', () => {
135
+ const wrapper = mount(FDataTable, {
136
+ propsData: { columns, data, selectable: true }
137
+ });
138
+ // The checkbox cells should have empty data-label
139
+ const checkboxCells = wrapper.findAll('td[data-label=""]');
140
+ expect(checkboxCells.length).toBe(data.length);
141
+ });
142
+
143
+ it('renders RecycleScroller when virtual is enabled', () => {
144
+ const wrapper = mount(FDataTable, {
145
+ propsData: { columns, data, virtual: true }
146
+ });
147
+ expect(wrapper.findComponent({ name: 'RecycleScroller' }).exists()).toBe(
148
+ true
149
+ );
150
+ });
151
+
152
+ it('does not render pagination when virtual is enabled', () => {
153
+ const manyRows = Array.from({ length: 100 }, (_, i) => ({
154
+ id: i + 1,
155
+ name: `User ${i + 1}`,
156
+ email: `user${i + 1}@test.com`,
157
+ role: 'User'
158
+ }));
159
+ const wrapper = mount(FDataTable, {
160
+ propsData: {
161
+ columns,
162
+ data: manyRows,
163
+ virtual: true,
164
+ paginated: true,
165
+ perPage: 10
166
+ }
167
+ });
168
+ // Pagination should be disabled when virtual is true
169
+ expect(wrapper.findComponent({ name: 'FPagination' }).exists()).toBe(false);
170
+ });
171
+
172
+ it('uses custom virtualItemHeight when provided', () => {
173
+ const wrapper = mount(FDataTable, {
174
+ propsData: { columns, data, virtual: true, virtualItemHeight: 60 }
175
+ });
176
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
177
+ expect((wrapper.vm as any).computedVirtualItemHeight).toBe(60);
178
+ });
179
+
180
+ it('calculates virtualItemHeight based on size when not provided', () => {
181
+ const wrapper = mount(FDataTable, {
182
+ propsData: { columns, data, virtual: true, size: 'large' }
183
+ });
184
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
185
+ expect((wrapper.vm as any).computedVirtualItemHeight).toBe(64);
186
+ });
187
+
188
+ it('renders regular tbody when virtual is disabled', () => {
189
+ const wrapper = mount(FDataTable, {
190
+ propsData: { columns, data, virtual: false }
191
+ });
192
+ expect(wrapper.find('tbody').exists()).toBe(true);
193
+ expect(wrapper.findComponent({ name: 'RecycleScroller' }).exists()).toBe(
194
+ false
195
+ );
196
+ });
197
+
198
+ it('adds aria-sort="none" to sortable headers when not sorted', () => {
199
+ const wrapper = mount(FDataTable, {
200
+ propsData: { columns, data }
201
+ });
202
+ const headers = wrapper.findAll('th');
203
+ // Skip the first header if selectable (it's the checkbox column)
204
+ const nameHeader = headers.wrappers.find((h) => h.text().includes('Name'));
205
+ expect(nameHeader?.attributes('aria-sort')).toBe('none');
206
+ });
207
+
208
+ it('adds aria-sort="ascending" when column is sorted ascending', async () => {
209
+ const wrapper = mount(FDataTable, {
210
+ propsData: {
211
+ columns,
212
+ data,
213
+ defaultSortKey: 'name',
214
+ defaultSortDirection: 'asc'
215
+ }
216
+ });
217
+ const headers = wrapper.findAll('th');
218
+ const nameHeader = headers.wrappers.find((h) => h.text().includes('Name'));
219
+ expect(nameHeader?.attributes('aria-sort')).toBe('ascending');
220
+ });
221
+
222
+ it('adds aria-sort="descending" when column is sorted descending', async () => {
223
+ const wrapper = mount(FDataTable, {
224
+ propsData: {
225
+ columns,
226
+ data,
227
+ defaultSortKey: 'name',
228
+ defaultSortDirection: 'desc'
229
+ }
230
+ });
231
+ const headers = wrapper.findAll('th');
232
+ const nameHeader = headers.wrappers.find((h) => h.text().includes('Name'));
233
+ expect(nameHeader?.attributes('aria-sort')).toBe('descending');
234
+ });
235
+
236
+ it('does not add aria-sort to non-sortable columns', () => {
237
+ const nonSortableColumns = [
238
+ { key: 'name', label: 'Name', sortable: false },
239
+ { key: 'email', label: 'Email' }
240
+ ];
241
+ const wrapper = mount(FDataTable, {
242
+ propsData: { columns: nonSortableColumns, data }
243
+ });
244
+ const headers = wrapper.findAll('th');
245
+ const nameHeader = headers.wrappers.find((h) => h.text().includes('Name'));
246
+ expect(nameHeader?.attributes('aria-sort')).toBeUndefined();
247
+ });
248
+ });