@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,233 @@
1
+ # Composables Fabric
2
+
3
+ Les composables Fabric exposent la logique métier des composants "organismes" sous forme de fonctions réutilisables basées sur l'API de Composition de Vue.
4
+
5
+ ## Vue d'ensemble
6
+
7
+ Les composables permettent de:
8
+ - **Séparer les préoccupations**: Isoler la logique d'état de la logique de présentation
9
+ - **Améliorer la réutilisabilité**: Réutiliser la logique dans des composants personnalisés
10
+ - **Faciliter les tests**: Tester la logique métier de manière isolée
11
+
12
+ ## Composables disponibles
13
+
14
+ ### `useDataTableState`
15
+
16
+ Gère l'état et la logique d'un tableau de données avec recherche, tri, pagination et sélection.
17
+
18
+ #### Exemple d'utilisation
19
+
20
+ ```typescript
21
+ import { useDataTableState } from '@pyreweb/fabric';
22
+
23
+ export default {
24
+ name: 'MyCustomTable',
25
+ setup(props, { emit }) {
26
+ const tableState = useDataTableState(
27
+ {
28
+ data: props.data,
29
+ columns: props.columns,
30
+ perPage: 10,
31
+ paginated: true,
32
+ defaultSortKey: 'name'
33
+ },
34
+ emit
35
+ );
36
+
37
+ return {
38
+ ...tableState
39
+ };
40
+ }
41
+ };
42
+ ```
43
+
44
+ #### Options
45
+
46
+ - `data` (Array, required): Tableau de données à afficher
47
+ - `columns` (Array, required): Définitions des colonnes
48
+ - `rowKey` (String, default: 'id'): Propriété unique dans les objets de données
49
+ - `initialPage` (Number, default: 1): Numéro de page initial
50
+ - `perPage` (Number, default: 10): Éléments par page
51
+ - `paginated` (Boolean, default: true): Activer la pagination
52
+ - `virtual` (Boolean, default: false): Activer la virtualisation (désactive la pagination)
53
+ - `defaultSortKey` (String, default: null): Clé de tri par défaut
54
+ - `defaultSortDirection` (String, default: 'asc'): Direction de tri par défaut ('asc' | 'desc')
55
+ - `serverMode` (Boolean, default: false): Mode serveur (récupération externe des données)
56
+ - `totalItems` (Number, default: null): Total d'éléments pour la pagination côté serveur
57
+ - `selected` (Array, default: []): Clés de lignes initialement sélectionnées
58
+
59
+ #### Valeurs retournées
60
+
61
+ **État:**
62
+ - `searchQuery`: Query de recherche (Ref<string>)
63
+ - `sortKey`: Clé de tri actuelle (Ref<string | null>)
64
+ - `sortDirection`: Direction de tri (Ref<'asc' | 'desc'>)
65
+ - `internalPage`: Page actuelle (Ref<number>)
66
+ - `selectedKeys`: Clés des lignes sélectionnées (Ref<any[]>)
67
+ - `selectedItems`: Items sélectionnés (ComputedRef<any[]>)
68
+ - `isAllSelected`: Toutes les lignes sont-elles sélectionnées (ComputedRef<boolean>)
69
+
70
+ **Données traitées:**
71
+ - `filteredData`: Données filtrées (ComputedRef<any[]>)
72
+ - `sortedData`: Données triées (ComputedRef<any[]>)
73
+ - `processedData`: Données traitées (ComputedRef<any[]>)
74
+ - `paginatedData`: Données paginées (ComputedRef<any[]>)
75
+ - `totalPages`: Nombre total de pages (ComputedRef<number>)
76
+ - `paginationInfo`: Texte d'info de pagination (ComputedRef<string>)
77
+
78
+ **Méthodes:**
79
+ - `getCellValue(row, key)`: Obtenir la valeur d'une cellule
80
+ - `getRowKey(row, index?)`: Obtenir la clé d'une ligne
81
+ - `handleSort(key)`: Gérer le tri
82
+ - `isRowSelected(row)`: Vérifier si une ligne est sélectionnée
83
+ - `handleRowSelect(row, checked)`: Gérer la sélection d'une ligne
84
+ - `handleSelectAll(checked)`: Sélectionner/désélectionner toutes les lignes
85
+ - `clearSelection()`: Effacer la sélection
86
+
87
+ ---
88
+
89
+ ### `useSidebarState`
90
+
91
+ Gère l'état et la logique d'une barre latérale de navigation avec sous-menus.
92
+
93
+ #### Exemple d'utilisation
94
+
95
+ ```typescript
96
+ import { useSidebarState } from '@pyreweb/fabric';
97
+
98
+ export default {
99
+ name: 'MyCustomSidebar',
100
+ setup(props, { emit }) {
101
+ const sidebarState = useSidebarState(
102
+ {
103
+ items: props.items,
104
+ activeRoute: '/dashboard',
105
+ initialCollapsed: false
106
+ },
107
+ emit
108
+ );
109
+
110
+ // Initialiser les sous-menus ouverts basés sur la route active
111
+ sidebarState.initializeOpenSubmenus();
112
+
113
+ return {
114
+ ...sidebarState
115
+ };
116
+ }
117
+ };
118
+ ```
119
+
120
+ #### Options
121
+
122
+ - `items` (Array, required): Configuration des éléments de navigation
123
+ - `initialCollapsed` (Boolean, default: false): État initial réduit
124
+ - `activeRoute` (String, default: ''): Chemin de route actif actuel
125
+
126
+ #### Valeurs retournées
127
+
128
+ **État:**
129
+ - `collapsed`: État réduit de la sidebar (Ref<boolean>)
130
+ - `openSubmenus`: Liste des sous-menus ouverts (Ref<string[]>)
131
+ - `navigationItems`: Éléments de navigation filtrés (ComputedRef<NavigationItem[]>)
132
+
133
+ **Méthodes:**
134
+ - `toggleCollapsed()`: Basculer l'état réduit
135
+ - `isSubmenuOpen(item)`: Vérifier si un sous-menu est ouvert
136
+ - `toggleSubmenu(item)`: Basculer l'état d'un sous-menu
137
+ - `isItemActive(item)`: Vérifier si un élément est actif
138
+ - `hasActiveChild(item)`: Vérifier si un élément a un enfant actif
139
+ - `initializeOpenSubmenus()`: Initialiser les sous-menus ouverts basés sur la route active
140
+
141
+ ---
142
+
143
+ ### `useFormValidation`
144
+
145
+ Gère la validation et la soumission de formulaires avec gestion d'état.
146
+
147
+ #### Exemple d'utilisation
148
+
149
+ ```typescript
150
+ import { useFormValidation } from '@pyreweb/fabric';
151
+
152
+ export default {
153
+ name: 'MyCustomForm',
154
+ setup(props, { emit }) {
155
+ const formState = useFormValidation(
156
+ {
157
+ initialData: {
158
+ name: '',
159
+ email: ''
160
+ }
161
+ },
162
+ emit
163
+ );
164
+
165
+ const onSubmit = async (event) => {
166
+ await formState.handleSubmit(event, async (data) => {
167
+ // Validation personnalisée
168
+ if (!data.email.includes('@')) {
169
+ formState.setFieldError('email', 'Email invalide');
170
+ return;
171
+ }
172
+
173
+ // Soumettre au serveur
174
+ await api.submitForm(data);
175
+ });
176
+ };
177
+
178
+ return {
179
+ ...formState,
180
+ onSubmit
181
+ };
182
+ }
183
+ };
184
+ ```
185
+
186
+ #### Options
187
+
188
+ - `initialData` (Object, default: {}): Données initiales du formulaire
189
+
190
+ #### Valeurs retournées
191
+
192
+ **État:**
193
+ - `formData`: Données du formulaire (Ref<Record<string, any>>)
194
+ - `errors`: Erreurs de validation (Ref<Record<string, string>>)
195
+ - `isValid`: Le formulaire est-il valide (Ref<boolean>)
196
+ - `isSubmitting`: Le formulaire est-il en cours de soumission (Ref<boolean>)
197
+
198
+ **Méthodes:**
199
+ - `handleSubmit(event, callback?)`: Gérer la soumission du formulaire
200
+ - `setFieldValue(field, value)`: Définir la valeur d'un champ
201
+ - `setFieldError(field, error)`: Définir l'erreur d'un champ
202
+ - `clearFieldError(field)`: Effacer l'erreur d'un champ
203
+ - `clearErrors()`: Effacer toutes les erreurs
204
+ - `reset()`: Réinitialiser le formulaire à l'état initial
205
+ - `validate()`: Valider le formulaire (peut être étendu avec une logique personnalisée)
206
+
207
+ ## Compatibilité
208
+
209
+ Les composables sont compatibles avec:
210
+ - Vue 2.7 (avec API de Composition)
211
+ - Vue 3.x
212
+
213
+ ## Tests
214
+
215
+ Tous les composables sont testés de manière exhaustive avec 73 tests unitaires:
216
+ - `useDataTableState`: 33 tests
217
+ - `useSidebarState`: 23 tests
218
+ - `useFormValidation`: 17 tests
219
+
220
+ Pour exécuter les tests:
221
+ ```bash
222
+ npm test -- src/composables
223
+ ```
224
+
225
+ ## Notes d'implémentation
226
+
227
+ Les composables sont des fonctions pures qui:
228
+ 1. Acceptent des options et une fonction `emit` comme paramètres
229
+ 2. Retournent un objet contenant l'état réactif et les méthodes
230
+ 3. Utilisent l'API de Composition de Vue (`ref`, `computed`, `watch`)
231
+ 4. Sont complètement typés avec TypeScript
232
+
233
+ Les composants existants de la librairie (FForm, FDataTable, FNavigationSidebar) continuent d'utiliser l'API Options pour la compatibilité avec le build toolchain.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Composables for Fabric component library
3
+ *
4
+ * These composables extract the core logic from organism components
5
+ * and make it reusable across applications and custom components.
6
+ */
7
+
8
+ export { useDataTableState } from './useDataTableState';
9
+ export type {
10
+ DataTableStateOptions,
11
+ DataTableState
12
+ } from './useDataTableState';
13
+
14
+ export { useSidebarState } from './useSidebarState';
15
+ export type {
16
+ NavigationItem,
17
+ SidebarStateOptions,
18
+ SidebarState
19
+ } from './useSidebarState';
20
+
21
+ export { useFormValidation } from './useFormValidation';
22
+ export type {
23
+ FormValidationOptions,
24
+ FormValidationState
25
+ } from './useFormValidation';
@@ -0,0 +1,378 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { useDataTableState } from './useDataTableState';
3
+
4
+ describe('useDataTableState', () => {
5
+ const columns = [
6
+ { key: 'name', label: 'Name' },
7
+ { key: 'email', label: 'Email' },
8
+ { key: 'role', label: 'Role' }
9
+ ];
10
+
11
+ const data = [
12
+ { id: 1, name: 'Alice', email: 'alice@test.com', role: 'Admin' },
13
+ { id: 2, name: 'Bob', email: 'bob@test.com', role: 'User' },
14
+ { id: 3, name: 'Charlie', email: 'charlie@test.com', role: 'User' },
15
+ { id: 4, name: 'David', email: 'david@test.com', role: 'Manager' }
16
+ ];
17
+
18
+ it('initializes with default state', () => {
19
+ const emit = vi.fn();
20
+ const state = useDataTableState({ data, columns }, emit);
21
+
22
+ expect(state.searchQuery.value).toBe('');
23
+ expect(state.sortKey.value).toBe(null);
24
+ expect(state.sortDirection.value).toBe('asc');
25
+ expect(state.internalPage.value).toBe(1);
26
+ expect(state.selectedKeys.value).toEqual([]);
27
+ });
28
+
29
+ it('initializes with custom options', () => {
30
+ const emit = vi.fn();
31
+ const state = useDataTableState(
32
+ {
33
+ data,
34
+ columns,
35
+ initialPage: 2,
36
+ defaultSortKey: 'name',
37
+ defaultSortDirection: 'desc',
38
+ selected: [1, 2]
39
+ },
40
+ emit
41
+ );
42
+
43
+ expect(state.internalPage.value).toBe(2);
44
+ expect(state.sortKey.value).toBe('name');
45
+ expect(state.sortDirection.value).toBe('desc');
46
+ expect(state.selectedKeys.value).toEqual([1, 2]);
47
+ });
48
+
49
+ describe('getCellValue', () => {
50
+ it('gets direct property value', () => {
51
+ const emit = vi.fn();
52
+ const state = useDataTableState({ data, columns }, emit);
53
+ const value = state.getCellValue(data[0], 'name');
54
+ expect(value).toBe('Alice');
55
+ });
56
+
57
+ it('gets nested property value', () => {
58
+ const emit = vi.fn();
59
+ const nestedData = [{ user: { profile: { name: 'Test' } } }];
60
+ const state = useDataTableState({ data: nestedData, columns }, emit);
61
+ const value = state.getCellValue(nestedData[0], 'user.profile.name');
62
+ expect(value).toBe('Test');
63
+ });
64
+ });
65
+
66
+ describe('getRowKey', () => {
67
+ it('gets row key using default rowKey prop', () => {
68
+ const emit = vi.fn();
69
+ const state = useDataTableState({ data, columns }, emit);
70
+ const key = state.getRowKey(data[0]);
71
+ expect(key).toBe(1);
72
+ });
73
+
74
+ it('uses index when row key is missing', () => {
75
+ const emit = vi.fn();
76
+ const state = useDataTableState({ data, columns }, emit);
77
+ const key = state.getRowKey({}, 5);
78
+ expect(key).toBe(5);
79
+ });
80
+
81
+ it('uses custom rowKey', () => {
82
+ const emit = vi.fn();
83
+ const customData = [{ customId: 'abc', name: 'Test' }];
84
+ const state = useDataTableState(
85
+ { data: customData, columns, rowKey: 'customId' },
86
+ emit
87
+ );
88
+ const key = state.getRowKey(customData[0]);
89
+ expect(key).toBe('abc');
90
+ });
91
+ });
92
+
93
+ describe('filtering', () => {
94
+ it('filters data based on search query', () => {
95
+ const emit = vi.fn();
96
+ const state = useDataTableState({ data, columns }, emit);
97
+
98
+ expect(state.filteredData.value.length).toBe(4);
99
+
100
+ state.searchQuery.value = 'alice';
101
+ expect(state.filteredData.value.length).toBe(1);
102
+ expect(state.filteredData.value[0].name).toBe('Alice');
103
+ });
104
+
105
+ it('filters across multiple columns', () => {
106
+ const emit = vi.fn();
107
+ const state = useDataTableState({ data, columns }, emit);
108
+
109
+ state.searchQuery.value = 'test.com';
110
+ expect(state.filteredData.value.length).toBe(4);
111
+ });
112
+
113
+ it('is case insensitive', () => {
114
+ const emit = vi.fn();
115
+ const state = useDataTableState({ data, columns }, emit);
116
+
117
+ state.searchQuery.value = 'ALICE';
118
+ expect(state.filteredData.value.length).toBe(1);
119
+ });
120
+
121
+ it('does not filter in server mode', () => {
122
+ const emit = vi.fn();
123
+ const state = useDataTableState(
124
+ { data, columns, serverMode: true },
125
+ emit
126
+ );
127
+
128
+ state.searchQuery.value = 'alice';
129
+ expect(state.filteredData.value.length).toBe(4);
130
+ });
131
+
132
+ it('resets to first page when search changes', async () => {
133
+ const emit = vi.fn();
134
+ const state = useDataTableState({ data, columns }, emit);
135
+
136
+ state.internalPage.value = 2;
137
+ state.searchQuery.value = 'alice';
138
+ // Wait for watcher to trigger
139
+ await new Promise((resolve) => setTimeout(resolve, 0));
140
+ expect(state.internalPage.value).toBe(1);
141
+ });
142
+ });
143
+
144
+ describe('sorting', () => {
145
+ it('sorts data in ascending order', () => {
146
+ const emit = vi.fn();
147
+ const state = useDataTableState({ data, columns }, emit);
148
+
149
+ state.handleSort('name');
150
+ expect(state.sortedData.value[0].name).toBe('Alice');
151
+ expect(state.sortedData.value[3].name).toBe('David');
152
+ });
153
+
154
+ it('sorts data in descending order', () => {
155
+ const emit = vi.fn();
156
+ const state = useDataTableState({ data, columns }, emit);
157
+
158
+ state.handleSort('name');
159
+ state.handleSort('name');
160
+ expect(state.sortedData.value[0].name).toBe('David');
161
+ expect(state.sortedData.value[3].name).toBe('Alice');
162
+ });
163
+
164
+ it('emits sort event', () => {
165
+ const emit = vi.fn();
166
+ const state = useDataTableState({ data, columns }, emit);
167
+
168
+ state.handleSort('name');
169
+ expect(emit).toHaveBeenCalledWith('sort', {
170
+ key: 'name',
171
+ direction: 'asc'
172
+ });
173
+ });
174
+
175
+ it('does not sort in server mode', () => {
176
+ const emit = vi.fn();
177
+ const state = useDataTableState(
178
+ { data, columns, serverMode: true },
179
+ emit
180
+ );
181
+
182
+ state.handleSort('name');
183
+ expect(state.sortedData.value).toEqual(data);
184
+ });
185
+
186
+ it('handles null and undefined values', () => {
187
+ const emit = vi.fn();
188
+ const dataWithNulls = [
189
+ { id: 1, name: 'Alice' },
190
+ { id: 2, name: null },
191
+ { id: 3, name: 'Bob' }
192
+ ];
193
+ const state = useDataTableState({ data: dataWithNulls, columns }, emit);
194
+
195
+ state.handleSort('name');
196
+ // Nulls should be sorted to the end
197
+ expect(state.sortedData.value[2].name).toBe(null);
198
+ });
199
+ });
200
+
201
+ describe('pagination', () => {
202
+ it('calculates total pages correctly', () => {
203
+ const emit = vi.fn();
204
+ const state = useDataTableState({ data, columns, perPage: 2 }, emit);
205
+
206
+ expect(state.totalPages.value).toBe(2);
207
+ });
208
+
209
+ it('paginates data correctly', () => {
210
+ const emit = vi.fn();
211
+ const state = useDataTableState({ data, columns, perPage: 2 }, emit);
212
+
213
+ expect(state.paginatedData.value.length).toBe(2);
214
+ expect(state.paginatedData.value[0].name).toBe('Alice');
215
+
216
+ state.internalPage.value = 2;
217
+ expect(state.paginatedData.value.length).toBe(2);
218
+ expect(state.paginatedData.value[0].name).toBe('Charlie');
219
+ });
220
+
221
+ it('generates pagination info text', () => {
222
+ const emit = vi.fn();
223
+ const state = useDataTableState({ data, columns, perPage: 2 }, emit);
224
+
225
+ expect(state.paginationInfo.value).toBe('1 - 2 sur 4');
226
+
227
+ state.internalPage.value = 2;
228
+ expect(state.paginationInfo.value).toBe('3 - 4 sur 4');
229
+ });
230
+
231
+ it('disables pagination when virtual mode is enabled', () => {
232
+ const emit = vi.fn();
233
+ const state = useDataTableState({ data, columns, virtual: true }, emit);
234
+
235
+ expect(state.effectivePaginated.value).toBe(false);
236
+ expect(state.paginatedData.value.length).toBe(4);
237
+ });
238
+
239
+ it('emits page change events', async () => {
240
+ const emit = vi.fn();
241
+ const state = useDataTableState({ data, columns }, emit);
242
+
243
+ state.internalPage.value = 2;
244
+ // Wait for watcher to trigger
245
+ await new Promise((resolve) => setTimeout(resolve, 0));
246
+ expect(emit).toHaveBeenCalledWith('update:page', 2);
247
+ });
248
+ });
249
+
250
+ describe('selection', () => {
251
+ it('selects a single row', () => {
252
+ const emit = vi.fn();
253
+ const state = useDataTableState({ data, columns }, emit);
254
+
255
+ state.handleRowSelect(data[0], true);
256
+ expect(state.selectedKeys.value).toContain(1);
257
+ expect(emit).toHaveBeenCalledWith('select', {
258
+ row: data[0],
259
+ selected: true
260
+ });
261
+ });
262
+
263
+ it('deselects a row', () => {
264
+ const emit = vi.fn();
265
+ const state = useDataTableState({ data, columns, selected: [1] }, emit);
266
+
267
+ state.handleRowSelect(data[0], false);
268
+ expect(state.selectedKeys.value).not.toContain(1);
269
+ });
270
+
271
+ it('checks if row is selected', () => {
272
+ const emit = vi.fn();
273
+ const state = useDataTableState(
274
+ { data, columns, selected: [1, 2] },
275
+ emit
276
+ );
277
+
278
+ expect(state.isRowSelected(data[0])).toBe(true);
279
+ expect(state.isRowSelected(data[2])).toBe(false);
280
+ });
281
+
282
+ it('selects all rows on current page', () => {
283
+ const emit = vi.fn();
284
+ const state = useDataTableState({ data, columns, perPage: 2 }, emit);
285
+
286
+ state.handleSelectAll(true);
287
+ expect(state.selectedKeys.value).toContain(1);
288
+ expect(state.selectedKeys.value).toContain(2);
289
+ expect(state.selectedKeys.value).not.toContain(3);
290
+ });
291
+
292
+ it('deselects all rows on current page', () => {
293
+ const emit = vi.fn();
294
+ const state = useDataTableState(
295
+ { data, columns, perPage: 2, selected: [1, 2] },
296
+ emit
297
+ );
298
+
299
+ state.handleSelectAll(false);
300
+ expect(state.selectedKeys.value).toEqual([]);
301
+ });
302
+
303
+ it('determines if all rows are selected', () => {
304
+ const emit = vi.fn();
305
+ const state = useDataTableState({ data, columns, perPage: 2 }, emit);
306
+
307
+ expect(state.isAllSelected.value).toBe(false);
308
+
309
+ state.handleSelectAll(true);
310
+ expect(state.isAllSelected.value).toBe(true);
311
+ });
312
+
313
+ it('clears selection', () => {
314
+ const emit = vi.fn();
315
+ const state = useDataTableState(
316
+ { data, columns, selected: [1, 2, 3] },
317
+ emit
318
+ );
319
+
320
+ state.clearSelection();
321
+ expect(state.selectedKeys.value).toEqual([]);
322
+ });
323
+
324
+ it('emits selection change events', async () => {
325
+ const emit = vi.fn();
326
+ const state = useDataTableState({ data, columns }, emit);
327
+
328
+ state.selectedKeys.value = [1];
329
+ // Wait for next tick for watcher to trigger
330
+ await new Promise((resolve) => setTimeout(resolve, 0));
331
+ expect(emit).toHaveBeenCalledWith('update:selected', [1]);
332
+ });
333
+
334
+ it('provides selected items', () => {
335
+ const emit = vi.fn();
336
+ const state = useDataTableState(
337
+ { data, columns, selected: [1, 3] },
338
+ emit
339
+ );
340
+
341
+ expect(state.selectedItems.value.length).toBe(2);
342
+ expect(state.selectedItems.value[0].name).toBe('Alice');
343
+ expect(state.selectedItems.value[1].name).toBe('Charlie');
344
+ });
345
+ });
346
+
347
+ describe('server mode', () => {
348
+ it('uses external total items for pagination', () => {
349
+ const emit = vi.fn();
350
+ const state = useDataTableState(
351
+ {
352
+ data: data.slice(0, 2),
353
+ columns,
354
+ serverMode: true,
355
+ totalItems: 100,
356
+ perPage: 2
357
+ },
358
+ emit
359
+ );
360
+
361
+ expect(state.computedTotalItems.value).toBe(100);
362
+ expect(state.totalPages.value).toBe(50);
363
+ });
364
+
365
+ it('does not filter or sort in server mode', () => {
366
+ const emit = vi.fn();
367
+ const state = useDataTableState(
368
+ { data, columns, serverMode: true },
369
+ emit
370
+ );
371
+
372
+ state.searchQuery.value = 'alice';
373
+ state.handleSort('name');
374
+
375
+ expect(state.processedData.value).toEqual(data);
376
+ });
377
+ });
378
+ });