@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,270 @@
1
+ import FForm from './FForm.vue';
2
+ import FFormField from '../../molecules/FFormField/FFormField.vue';
3
+ import FButton from '../../atoms/FButton/FButton.vue';
4
+ import FCheckbox from '../../atoms/FCheckbox/FCheckbox.vue';
5
+
6
+ export default {
7
+ title: 'Organisms/FForm',
8
+ component: FForm,
9
+ tags: ['autodocs'],
10
+ argTypes: {}
11
+ };
12
+
13
+ export const Default = () => ({
14
+ components: { FForm, FFormField, FButton },
15
+ data() {
16
+ return {
17
+ form: {
18
+ name: '',
19
+ email: ''
20
+ }
21
+ };
22
+ },
23
+ methods: {
24
+ handleSubmit() {
25
+ alert(
26
+ `Formulaire soumis:\nNom: ${this.form.name}\nEmail: ${this.form.email}`
27
+ );
28
+ }
29
+ },
30
+ template: `
31
+ <FForm @submit="handleSubmit">
32
+ <FFormField
33
+ v-model="form.name"
34
+ label="Nom"
35
+ placeholder="Votre nom"
36
+ required
37
+ />
38
+ <FFormField
39
+ v-model="form.email"
40
+ label="Email"
41
+ type="email"
42
+ placeholder="exemple@email.com"
43
+ required
44
+ />
45
+ <template #actions>
46
+ <FButton type="submit" variant="primary">Envoyer</FButton>
47
+ </template>
48
+ </FForm>
49
+ `
50
+ });
51
+
52
+ export const LoginForm = () => ({
53
+ components: { FForm, FFormField, FButton, FCheckbox },
54
+ data() {
55
+ return {
56
+ form: {
57
+ email: '',
58
+ password: '',
59
+ remember: false
60
+ }
61
+ };
62
+ },
63
+ methods: {
64
+ handleSubmit() {
65
+ alert('Connexion en cours...');
66
+ }
67
+ },
68
+ template: `
69
+ <FForm @submit="handleSubmit" class="max-w-sm">
70
+ <FFormField
71
+ v-model="form.email"
72
+ label="Email"
73
+ type="email"
74
+ placeholder="exemple@email.com"
75
+ required
76
+ />
77
+ <FFormField
78
+ v-model="form.password"
79
+ label="Mot de passe"
80
+ type="password"
81
+ required
82
+ />
83
+ <FCheckbox v-model="form.remember" label="Se souvenir de moi" />
84
+ <template #actions>
85
+ <FButton type="submit" variant="primary" block>Se connecter</FButton>
86
+ </template>
87
+ </FForm>
88
+ `
89
+ });
90
+
91
+ export const RegistrationForm = () => ({
92
+ components: { FForm, FFormField, FButton, FCheckbox },
93
+ data() {
94
+ return {
95
+ form: {
96
+ firstName: '',
97
+ lastName: '',
98
+ email: '',
99
+ password: '',
100
+ confirmPassword: '',
101
+ terms: false
102
+ }
103
+ };
104
+ },
105
+ methods: {
106
+ handleSubmit() {
107
+ if (this.form.password !== this.form.confirmPassword) {
108
+ alert('Les mots de passe ne correspondent pas');
109
+ return;
110
+ }
111
+ alert('Inscription réussie !');
112
+ }
113
+ },
114
+ template: `
115
+ <FForm @submit="handleSubmit" class="max-w-md">
116
+ <div class="grid grid-cols-2 gap-4">
117
+ <FFormField
118
+ v-model="form.firstName"
119
+ label="Prénom"
120
+ required
121
+ />
122
+ <FFormField
123
+ v-model="form.lastName"
124
+ label="Nom"
125
+ required
126
+ />
127
+ </div>
128
+ <FFormField
129
+ v-model="form.email"
130
+ label="Email"
131
+ type="email"
132
+ required
133
+ />
134
+ <FFormField
135
+ v-model="form.password"
136
+ label="Mot de passe"
137
+ type="password"
138
+ hint="8 caractères minimum"
139
+ required
140
+ />
141
+ <FFormField
142
+ v-model="form.confirmPassword"
143
+ label="Confirmer le mot de passe"
144
+ type="password"
145
+ required
146
+ />
147
+ <FCheckbox
148
+ v-model="form.terms"
149
+ label="J'accepte les conditions d'utilisation"
150
+ />
151
+ <template #actions>
152
+ <FButton type="submit" variant="primary" block>S'inscrire</FButton>
153
+ </template>
154
+ </FForm>
155
+ `
156
+ });
157
+
158
+ export const ContactForm = () => ({
159
+ components: { FForm, FFormField, FButton },
160
+ data() {
161
+ return {
162
+ form: {
163
+ name: '',
164
+ email: '',
165
+ subject: '',
166
+ message: ''
167
+ },
168
+ isLoading: false
169
+ };
170
+ },
171
+ methods: {
172
+ async handleSubmit() {
173
+ this.isLoading = true;
174
+ await new Promise((resolve) => setTimeout(resolve, 1500));
175
+ this.isLoading = false;
176
+ alert('Message envoyé !');
177
+ this.form = { name: '', email: '', subject: '', message: '' };
178
+ }
179
+ },
180
+ template: `
181
+ <FForm @submit="handleSubmit" class="max-w-lg">
182
+ <FFormField
183
+ v-model="form.name"
184
+ label="Nom complet"
185
+ required
186
+ />
187
+ <FFormField
188
+ v-model="form.email"
189
+ label="Email"
190
+ type="email"
191
+ required
192
+ />
193
+ <FFormField
194
+ v-model="form.subject"
195
+ label="Sujet"
196
+ required
197
+ />
198
+ <div class="flex flex-col gap-1.5">
199
+ <label class="text-sm font-medium text-neutral-700">Message</label>
200
+ <textarea
201
+ v-model="form.message"
202
+ rows="5"
203
+ class="block w-full font-sans border rounded py-2.5 px-3.5 text-sm border-neutral-300 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20"
204
+ placeholder="Votre message..."
205
+ ></textarea>
206
+ </div>
207
+ <template #actions>
208
+ <FButton type="button" variant="outline">Annuler</FButton>
209
+ <FButton type="submit" variant="primary" :loading="isLoading">Envoyer</FButton>
210
+ </template>
211
+ </FForm>
212
+ `
213
+ });
214
+
215
+ export const WithValidation = () => ({
216
+ components: { FForm, FFormField, FButton },
217
+ data() {
218
+ return {
219
+ form: {
220
+ email: '',
221
+ password: ''
222
+ },
223
+ errors: {}
224
+ };
225
+ },
226
+ methods: {
227
+ validate() {
228
+ this.errors = {};
229
+ if (!this.form.email) {
230
+ this.errors.email = "L'email est requis";
231
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.form.email)) {
232
+ this.errors.email = 'Email invalide';
233
+ }
234
+ if (!this.form.password) {
235
+ this.errors.password = 'Le mot de passe est requis';
236
+ } else if (this.form.password.length < 8) {
237
+ this.errors.password =
238
+ 'Le mot de passe doit faire au moins 8 caractères';
239
+ }
240
+ return Object.keys(this.errors).length === 0;
241
+ },
242
+ handleSubmit() {
243
+ if (this.validate()) {
244
+ alert('Formulaire valide !');
245
+ }
246
+ }
247
+ },
248
+ template: `
249
+ <FForm @submit="handleSubmit" class="max-w-sm">
250
+ <FFormField
251
+ v-model="form.email"
252
+ label="Email"
253
+ type="email"
254
+ :errorMessage="errors.email"
255
+ required
256
+ />
257
+ <FFormField
258
+ v-model="form.password"
259
+ label="Mot de passe"
260
+ type="password"
261
+ :errorMessage="errors.password"
262
+ hint="8 caractères minimum"
263
+ required
264
+ />
265
+ <template #actions>
266
+ <FButton type="submit" variant="primary">Valider</FButton>
267
+ </template>
268
+ </FForm>
269
+ `
270
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FForm from './FForm.vue';
4
+
5
+ describe('FForm', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FForm);
8
+ expect(wrapper.find('form').exists()).toBe(true);
9
+ });
10
+
11
+ it('renders slot content', () => {
12
+ const wrapper = mount(FForm, {
13
+ slots: {
14
+ default: '<input type="text" name="test" />'
15
+ }
16
+ });
17
+ expect(wrapper.find('input[name="test"]').exists()).toBe(true);
18
+ });
19
+
20
+ it('renders actions slot', () => {
21
+ const wrapper = mount(FForm, {
22
+ slots: {
23
+ actions: '<button type="submit">Submit</button>'
24
+ }
25
+ });
26
+ expect(wrapper.html()).toContain('Submit');
27
+ });
28
+
29
+ it('emits submit event on form submission', async () => {
30
+ const wrapper = mount(FForm);
31
+ await wrapper.find('form').trigger('submit.prevent');
32
+ expect(wrapper.emitted('submit')).toBeTruthy();
33
+ });
34
+
35
+ it('prevents default form submission', async () => {
36
+ const wrapper = mount(FForm);
37
+ const form = wrapper.find('form');
38
+ let defaultPrevented = false;
39
+
40
+ form.element.addEventListener(
41
+ 'submit',
42
+ (e) => {
43
+ defaultPrevented = e.defaultPrevented;
44
+ },
45
+ { once: true }
46
+ );
47
+
48
+ await form.trigger('submit');
49
+ // The form component handles prevention internally
50
+ expect(wrapper.emitted('submit')).toBeTruthy();
51
+ });
52
+
53
+ it('has flex layout', () => {
54
+ const wrapper = mount(FForm);
55
+ expect(wrapper.find('form').classes()).toContain('flex');
56
+ expect(wrapper.find('form').classes()).toContain('flex-col');
57
+ });
58
+
59
+ it('has gap between elements', () => {
60
+ const wrapper = mount(FForm);
61
+ expect(wrapper.find('form').classes()).toContain('gap-4');
62
+ });
63
+ });
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <form class="flex flex-col gap-4" @submit.prevent="handleSubmit">
3
+ <slot />
4
+ <div v-if="$slots.actions" class="flex gap-2 mt-2">
5
+ <slot name="actions" />
6
+ </div>
7
+ </form>
8
+ </template>
9
+
10
+ <script>
11
+ export default {
12
+ name: 'FForm',
13
+ methods: {
14
+ handleSubmit(event) {
15
+ this.$emit('submit', event);
16
+ }
17
+ }
18
+ };
19
+ </script>
@@ -0,0 +1,227 @@
1
+ import FModal from './FModal.vue';
2
+ import FButton from '../../atoms/FButton/FButton.vue';
3
+ import FFormField from '../../molecules/FFormField/FFormField.vue';
4
+
5
+ export default {
6
+ title: 'Organisms/FModal',
7
+ component: FModal,
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ value: {
11
+ control: 'boolean',
12
+ description: 'État ouvert/fermé'
13
+ },
14
+ title: {
15
+ control: 'text',
16
+ description: 'Titre de la modale'
17
+ },
18
+ subtitle: {
19
+ control: 'text',
20
+ description: 'Sous-titre'
21
+ },
22
+ size: {
23
+ control: { type: 'select' },
24
+ options: ['small', 'medium', 'large', 'full'],
25
+ description: 'Taille de la modale'
26
+ },
27
+ closable: {
28
+ control: 'boolean',
29
+ description: 'Afficher le bouton de fermeture'
30
+ },
31
+ closeOnOverlay: {
32
+ control: 'boolean',
33
+ description: "Fermer au clic sur l'overlay"
34
+ },
35
+ closeOnEscape: {
36
+ control: 'boolean',
37
+ description: 'Fermer avec la touche Échap'
38
+ },
39
+ bordered: {
40
+ control: 'boolean',
41
+ description: 'Afficher une bordure'
42
+ }
43
+ }
44
+ };
45
+
46
+ export const Default = () => ({
47
+ components: { FModal, FButton },
48
+ data() {
49
+ return { isOpen: false };
50
+ },
51
+ template: `
52
+ <div>
53
+ <FButton @click="isOpen = true">Ouvrir la modale</FButton>
54
+ <FModal v-model="isOpen" title="Titre de la modale">
55
+ <template #body>
56
+ <p>Contenu de la modale.</p>
57
+ </template>
58
+ <template #actions>
59
+ <FButton variant="outline" @click="isOpen = false">Annuler</FButton>
60
+ <FButton variant="primary" @click="isOpen = false">Confirmer</FButton>
61
+ </template>
62
+ </FModal>
63
+ </div>
64
+ `
65
+ });
66
+
67
+ export const WithSubtitle = () => ({
68
+ components: { FModal, FButton },
69
+ data() {
70
+ return { isOpen: false };
71
+ },
72
+ template: `
73
+ <div>
74
+ <FButton @click="isOpen = true">Ouvrir</FButton>
75
+ <FModal v-model="isOpen" title="Confirmation" subtitle="Action requise">
76
+ <template #body>
77
+ <p>Êtes-vous sûr de vouloir continuer cette action ?</p>
78
+ </template>
79
+ <template #actions>
80
+ <FButton variant="outline" @click="isOpen = false">Annuler</FButton>
81
+ <FButton variant="primary" @click="isOpen = false">Confirmer</FButton>
82
+ </template>
83
+ </FModal>
84
+ </div>
85
+ `
86
+ });
87
+
88
+ export const Sizes = () => ({
89
+ components: { FModal, FButton },
90
+ data() {
91
+ return {
92
+ smallOpen: false,
93
+ mediumOpen: false,
94
+ largeOpen: false,
95
+ fullOpen: false
96
+ };
97
+ },
98
+ template: `
99
+ <div class="flex gap-2">
100
+ <FButton @click="smallOpen = true">Petite</FButton>
101
+ <FButton @click="mediumOpen = true">Moyenne</FButton>
102
+ <FButton @click="largeOpen = true">Grande</FButton>
103
+ <FButton @click="fullOpen = true">Plein écran</FButton>
104
+
105
+ <FModal v-model="smallOpen" title="Petite modale" size="small">
106
+ <template #body><p>Contenu compact.</p></template>
107
+ </FModal>
108
+ <FModal v-model="mediumOpen" title="Modale moyenne" size="medium">
109
+ <template #body><p>Taille par défaut.</p></template>
110
+ </FModal>
111
+ <FModal v-model="largeOpen" title="Grande modale" size="large">
112
+ <template #body><p>Plus d'espace pour le contenu.</p></template>
113
+ </FModal>
114
+ <FModal v-model="fullOpen" title="Modale plein écran" size="full">
115
+ <template #body><p>Contenu en pleine largeur.</p></template>
116
+ </FModal>
117
+ </div>
118
+ `
119
+ });
120
+
121
+ export const NotClosable = () => ({
122
+ components: { FModal, FButton },
123
+ data() {
124
+ return { isOpen: false };
125
+ },
126
+ template: `
127
+ <div>
128
+ <FButton @click="isOpen = true">Ouvrir (non fermable)</FButton>
129
+ <FModal
130
+ v-model="isOpen"
131
+ title="Action obligatoire"
132
+ :closable="false"
133
+ :closeOnOverlay="false"
134
+ :closeOnEscape="false"
135
+ >
136
+ <template #body>
137
+ <p>Vous devez confirmer cette action.</p>
138
+ </template>
139
+ <template #actions>
140
+ <FButton variant="primary" @click="isOpen = false">J'ai compris</FButton>
141
+ </template>
142
+ </FModal>
143
+ </div>
144
+ `
145
+ });
146
+
147
+ export const Danger = () => ({
148
+ components: { FModal, FButton },
149
+ data() {
150
+ return { isOpen: false };
151
+ },
152
+ template: `
153
+ <div>
154
+ <FButton variant="danger" @click="isOpen = true">Supprimer</FButton>
155
+ <FModal v-model="isOpen" title="Confirmer la suppression">
156
+ <template #body>
157
+ <p>Cette action est irréversible. Êtes-vous sûr de vouloir supprimer cet élément ?</p>
158
+ </template>
159
+ <template #actions>
160
+ <FButton variant="outline" @click="isOpen = false">Annuler</FButton>
161
+ <FButton variant="danger" @click="isOpen = false">Supprimer</FButton>
162
+ </template>
163
+ </FModal>
164
+ </div>
165
+ `
166
+ });
167
+
168
+ export const WithForm = () => ({
169
+ components: { FModal, FButton, FFormField },
170
+ data() {
171
+ return {
172
+ isOpen: false,
173
+ form: { name: '', email: '' }
174
+ };
175
+ },
176
+ methods: {
177
+ handleSubmit() {
178
+ alert(`Nom: ${this.form.name}, Email: ${this.form.email}`);
179
+ this.isOpen = false;
180
+ }
181
+ },
182
+ template: `
183
+ <div>
184
+ <FButton @click="isOpen = true">Ajouter un contact</FButton>
185
+ <FModal v-model="isOpen" title="Nouveau contact" subtitle="Remplissez les informations">
186
+ <template #body>
187
+ <div class="flex flex-col gap-4">
188
+ <FFormField v-model="form.name" label="Nom" required />
189
+ <FFormField v-model="form.email" label="Email" type="email" required />
190
+ </div>
191
+ </template>
192
+ <template #actions>
193
+ <FButton variant="outline" @click="isOpen = false">Annuler</FButton>
194
+ <FButton variant="primary" @click="handleSubmit">Ajouter</FButton>
195
+ </template>
196
+ </FModal>
197
+ </div>
198
+ `
199
+ });
200
+
201
+ export const CustomHeader = () => ({
202
+ components: { FModal, FButton },
203
+ data() {
204
+ return { isOpen: false };
205
+ },
206
+ template: `
207
+ <div>
208
+ <FButton @click="isOpen = true">Ouvrir</FButton>
209
+ <FModal v-model="isOpen">
210
+ <template #header>
211
+ <div class="flex items-center gap-3">
212
+ <div class="w-10 h-10 rounded-full bg-primary-100 flex items-center justify-center">
213
+ <span class="text-primary-600 font-bold">!</span>
214
+ </div>
215
+ <div>
216
+ <h3 class="font-semibold">En-tête personnalisé</h3>
217
+ <p class="text-sm text-neutral-500">Avec icône et mise en forme</p>
218
+ </div>
219
+ </div>
220
+ </template>
221
+ <template #body>
222
+ <p>Contenu de la modale avec un en-tête personnalisé.</p>
223
+ </template>
224
+ </FModal>
225
+ </div>
226
+ `
227
+ });