@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,149 @@
1
+ import FFormField from './FFormField.vue';
2
+
3
+ export default {
4
+ title: 'Molecules/FFormField',
5
+ component: FFormField,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ label: {
9
+ control: 'text',
10
+ description: 'Libellé du champ'
11
+ },
12
+ value: {
13
+ control: 'text',
14
+ description: 'Valeur du champ'
15
+ },
16
+ type: {
17
+ control: { type: 'select' },
18
+ options: ['text', 'email', 'password', 'number'],
19
+ description: 'Type du champ'
20
+ },
21
+ placeholder: {
22
+ control: 'text',
23
+ description: 'Placeholder'
24
+ },
25
+ size: {
26
+ control: { type: 'select' },
27
+ options: ['small', 'medium', 'large'],
28
+ description: 'Taille du champ'
29
+ },
30
+ disabled: {
31
+ control: 'boolean',
32
+ description: 'État désactivé'
33
+ },
34
+ readonly: {
35
+ control: 'boolean',
36
+ description: 'Lecture seule'
37
+ },
38
+ required: {
39
+ control: 'boolean',
40
+ description: 'Champ obligatoire'
41
+ },
42
+ hint: {
43
+ control: 'text',
44
+ description: "Texte d'aide"
45
+ },
46
+ errorMessage: {
47
+ control: 'text',
48
+ description: "Message d'erreur"
49
+ }
50
+ }
51
+ };
52
+
53
+ const Template = (args, { argTypes }) => ({
54
+ components: { FFormField },
55
+ props: Object.keys(argTypes),
56
+ data() {
57
+ return { inputValue: args.value || '' };
58
+ },
59
+ template: '<FFormField v-bind="$props" v-model="inputValue" />'
60
+ });
61
+
62
+ export const Default = Template.bind({});
63
+ Default.args = {
64
+ label: 'Adresse email',
65
+ placeholder: 'exemple@email.com'
66
+ };
67
+
68
+ export const WithHint = Template.bind({});
69
+ WithHint.args = {
70
+ label: 'Mot de passe',
71
+ type: 'password',
72
+ hint: 'Le mot de passe doit contenir au moins 8 caractères'
73
+ };
74
+
75
+ export const Required = Template.bind({});
76
+ Required.args = {
77
+ label: 'Nom complet',
78
+ placeholder: 'Jean Dupont',
79
+ required: true
80
+ };
81
+
82
+ export const WithError = Template.bind({});
83
+ WithError.args = {
84
+ label: 'Email',
85
+ value: 'email-invalide',
86
+ errorMessage: 'Veuillez entrer une adresse email valide'
87
+ };
88
+
89
+ export const Disabled = Template.bind({});
90
+ Disabled.args = {
91
+ label: 'Champ désactivé',
92
+ value: 'Valeur non modifiable',
93
+ disabled: true
94
+ };
95
+
96
+ export const Readonly = Template.bind({});
97
+ Readonly.args = {
98
+ label: 'Référence',
99
+ value: 'REF-2024-001',
100
+ readonly: true
101
+ };
102
+
103
+ export const Sizes = () => ({
104
+ components: { FFormField },
105
+ template: `
106
+ <div class="flex flex-col gap-4">
107
+ <FFormField label="Petit" size="small" placeholder="Petit champ" />
108
+ <FFormField label="Moyen" size="medium" placeholder="Champ moyen" />
109
+ <FFormField label="Grand" size="large" placeholder="Grand champ" />
110
+ </div>
111
+ `
112
+ });
113
+
114
+ export const CompleteForm = () => ({
115
+ components: { FFormField },
116
+ data() {
117
+ return {
118
+ form: {
119
+ name: '',
120
+ email: '',
121
+ password: ''
122
+ }
123
+ };
124
+ },
125
+ template: `
126
+ <div class="flex flex-col gap-4 max-w-md">
127
+ <FFormField
128
+ v-model="form.name"
129
+ label="Nom complet"
130
+ placeholder="Votre nom"
131
+ required
132
+ />
133
+ <FFormField
134
+ v-model="form.email"
135
+ label="Email"
136
+ type="email"
137
+ placeholder="exemple@email.com"
138
+ required
139
+ />
140
+ <FFormField
141
+ v-model="form.password"
142
+ label="Mot de passe"
143
+ type="password"
144
+ hint="8 caractères minimum"
145
+ required
146
+ />
147
+ </div>
148
+ `
149
+ });
@@ -0,0 +1,85 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FFormField from './FFormField.vue';
4
+
5
+ describe('FFormField', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FFormField);
8
+ expect(wrapper.findComponent({ name: 'FInput' }).exists()).toBe(true);
9
+ });
10
+
11
+ it('displays label when provided', () => {
12
+ const wrapper = mount(FFormField, {
13
+ propsData: { label: 'Email Address' }
14
+ });
15
+ expect(wrapper.text()).toContain('Email Address');
16
+ });
17
+
18
+ it('displays hint when provided', () => {
19
+ const wrapper = mount(FFormField, {
20
+ propsData: { hint: 'Enter a valid email' }
21
+ });
22
+ expect(wrapper.text()).toContain('Enter a valid email');
23
+ });
24
+
25
+ it('displays error message when provided', () => {
26
+ const wrapper = mount(FFormField, {
27
+ propsData: { errorMessage: 'This field is required' }
28
+ });
29
+ expect(wrapper.text()).toContain('This field is required');
30
+ });
31
+
32
+ it('shows required indicator when required', () => {
33
+ const wrapper = mount(FFormField, {
34
+ propsData: { label: 'Name', required: true }
35
+ });
36
+ expect(wrapper.html()).toContain('*');
37
+ });
38
+
39
+ it('passes value to input', () => {
40
+ const wrapper = mount(FFormField, {
41
+ propsData: { value: 'test@example.com' }
42
+ });
43
+ const input = wrapper.findComponent({ name: 'FInput' });
44
+ expect(input.props('value')).toBe('test@example.com');
45
+ });
46
+
47
+ it('emits input event', async () => {
48
+ const wrapper = mount(FFormField);
49
+ const input = wrapper.findComponent({ name: 'FInput' });
50
+ await input.vm.$emit('input', 'new value');
51
+ expect(wrapper.emitted('input')).toBeTruthy();
52
+ });
53
+
54
+ it('passes placeholder to input', () => {
55
+ const wrapper = mount(FFormField, {
56
+ propsData: { placeholder: 'Enter your email' }
57
+ });
58
+ const input = wrapper.findComponent({ name: 'FInput' });
59
+ expect(input.props('placeholder')).toBe('Enter your email');
60
+ });
61
+
62
+ it('passes size to input', () => {
63
+ const wrapper = mount(FFormField, {
64
+ propsData: { size: 'large' }
65
+ });
66
+ const input = wrapper.findComponent({ name: 'FInput' });
67
+ expect(input.props('size')).toBe('large');
68
+ });
69
+
70
+ it('passes disabled state to input', () => {
71
+ const wrapper = mount(FFormField, {
72
+ propsData: { disabled: true }
73
+ });
74
+ const input = wrapper.findComponent({ name: 'FInput' });
75
+ expect(input.props('disabled')).toBe(true);
76
+ });
77
+
78
+ it('passes error state based on errorMessage', () => {
79
+ const wrapper = mount(FFormField, {
80
+ propsData: { errorMessage: 'Error!' }
81
+ });
82
+ const input = wrapper.findComponent({ name: 'FInput' });
83
+ expect(input.props('error')).toBe(true);
84
+ });
85
+ });
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <div class="flex flex-col gap-1.5">
3
+ <label
4
+ v-if="label"
5
+ :for="inputId"
6
+ :class="[
7
+ 'text-sm font-medium text-neutral-700',
8
+ { 'after:content-[\'_*\'] after:text-danger-500': required }
9
+ ]"
10
+ >
11
+ {{ label }}
12
+ </label>
13
+ <f-input
14
+ :id="inputId"
15
+ :value="value"
16
+ :type="type"
17
+ :placeholder="placeholder"
18
+ :size="size"
19
+ :disabled="disabled"
20
+ :readonly="readonly"
21
+ :error="!!errorMessage"
22
+ @input="$emit('input', $event)"
23
+ @focus="$emit('focus', $event)"
24
+ @blur="$emit('blur', $event)"
25
+ />
26
+ <span v-if="errorMessage" class="text-xs text-danger-500">
27
+ {{ errorMessage }}
28
+ </span>
29
+ <span v-else-if="hint" class="text-xs text-neutral-500">
30
+ {{ hint }}
31
+ </span>
32
+ </div>
33
+ </template>
34
+
35
+ <script>
36
+ import FInput from '../../atoms/FInput/FInput.vue';
37
+
38
+ let idCounter = 0;
39
+
40
+ export default {
41
+ name: 'FFormField',
42
+ components: {
43
+ FInput
44
+ },
45
+ props: {
46
+ id: {
47
+ type: String,
48
+ default: ''
49
+ },
50
+ label: {
51
+ type: String,
52
+ default: ''
53
+ },
54
+ value: {
55
+ type: [String, Number],
56
+ default: ''
57
+ },
58
+ type: {
59
+ type: String,
60
+ default: 'text'
61
+ },
62
+ placeholder: {
63
+ type: String,
64
+ default: ''
65
+ },
66
+ size: {
67
+ type: String,
68
+ default: 'medium'
69
+ },
70
+ disabled: {
71
+ type: Boolean,
72
+ default: false
73
+ },
74
+ readonly: {
75
+ type: Boolean,
76
+ default: false
77
+ },
78
+ required: {
79
+ type: Boolean,
80
+ default: false
81
+ },
82
+ hint: {
83
+ type: String,
84
+ default: ''
85
+ },
86
+ errorMessage: {
87
+ type: String,
88
+ default: ''
89
+ }
90
+ },
91
+ data() {
92
+ return {
93
+ generatedId: ''
94
+ };
95
+ },
96
+ computed: {
97
+ inputId() {
98
+ return this.id || this.generatedId;
99
+ }
100
+ },
101
+ created() {
102
+ if (!this.id) {
103
+ this.generatedId = `f-form-field-${++idCounter}`;
104
+ }
105
+ }
106
+ };
107
+ </script>
@@ -0,0 +1,158 @@
1
+ import FListItem from './FListItem.vue';
2
+ import FAvatar from '../../atoms/FAvatar/FAvatar.vue';
3
+ import FBadge from '../../atoms/FBadge/FBadge.vue';
4
+ import FIcon from '../../atoms/FIcon/FIcon.vue';
5
+
6
+ export default {
7
+ title: 'Molecules/FListItem',
8
+ component: FListItem,
9
+ tags: ['autodocs'],
10
+ argTypes: {
11
+ title: {
12
+ control: 'text',
13
+ description: 'Titre principal'
14
+ },
15
+ subtitle: {
16
+ control: 'text',
17
+ description: 'Sous-titre'
18
+ },
19
+ clickable: {
20
+ control: 'boolean',
21
+ description: 'Élément cliquable'
22
+ },
23
+ selected: {
24
+ control: 'boolean',
25
+ description: 'Élément sélectionné'
26
+ },
27
+ disabled: {
28
+ control: 'boolean',
29
+ description: 'État désactivé'
30
+ },
31
+ truncate: {
32
+ control: 'boolean',
33
+ description: 'Tronquer le texte'
34
+ }
35
+ }
36
+ };
37
+
38
+ const Template = (args, { argTypes }) => ({
39
+ components: { FListItem },
40
+ props: Object.keys(argTypes),
41
+ template: '<FListItem v-bind="$props" />'
42
+ });
43
+
44
+ export const Default = Template.bind({});
45
+ Default.args = {
46
+ title: "Titre de l'élément",
47
+ subtitle: 'Description secondaire'
48
+ };
49
+
50
+ export const Clickable = Template.bind({});
51
+ Clickable.args = {
52
+ title: 'Élément cliquable',
53
+ subtitle: 'Cliquez pour interagir',
54
+ clickable: true
55
+ };
56
+
57
+ export const Selected = Template.bind({});
58
+ Selected.args = {
59
+ title: 'Élément sélectionné',
60
+ subtitle: 'Cet élément est actif',
61
+ selected: true
62
+ };
63
+
64
+ export const Disabled = Template.bind({});
65
+ Disabled.args = {
66
+ title: 'Élément désactivé',
67
+ subtitle: 'Non interactif',
68
+ disabled: true
69
+ };
70
+
71
+ export const WithAvatar = () => ({
72
+ components: { FListItem, FAvatar },
73
+ template: `
74
+ <FListItem title="Jean Dupont" subtitle="Développeur" clickable>
75
+ <template #left>
76
+ <FAvatar name="Jean Dupont" />
77
+ </template>
78
+ </FListItem>
79
+ `
80
+ });
81
+
82
+ export const WithIcon = () => ({
83
+ components: { FListItem, FIcon },
84
+ template: `
85
+ <FListItem title="Paramètres" subtitle="Gérer vos préférences" clickable>
86
+ <template #left>
87
+ <FIcon name="cog" size="md" class="text-neutral-500" />
88
+ </template>
89
+ <template #right>
90
+ <FIcon name="chevron-right" size="sm" class="text-neutral-400" />
91
+ </template>
92
+ </FListItem>
93
+ `
94
+ });
95
+
96
+ export const WithBadge = () => ({
97
+ components: { FListItem, FIcon, FBadge },
98
+ template: `
99
+ <FListItem title="Messages" subtitle="Consultez vos messages" clickable>
100
+ <template #left>
101
+ <FIcon name="mail" size="md" class="text-neutral-500" />
102
+ </template>
103
+ <template #right>
104
+ <FBadge content="3" variant="error" shape="circle" />
105
+ </template>
106
+ </FListItem>
107
+ `
108
+ });
109
+
110
+ export const List = () => ({
111
+ components: { FListItem, FAvatar },
112
+ data() {
113
+ return {
114
+ selectedId: 2,
115
+ users: [
116
+ { id: 1, name: 'Alice Martin', role: 'Designer' },
117
+ { id: 2, name: 'Bob Dupont', role: 'Développeur' },
118
+ { id: 3, name: 'Claire Durand', role: 'Product Manager' },
119
+ { id: 4, name: 'David Petit', role: 'DevOps' }
120
+ ]
121
+ };
122
+ },
123
+ methods: {
124
+ selectUser(id) {
125
+ this.selectedId = id;
126
+ }
127
+ },
128
+ template: `
129
+ <div class="divide-y divide-neutral-100">
130
+ <FListItem
131
+ v-for="user in users"
132
+ :key="user.id"
133
+ :title="user.name"
134
+ :subtitle="user.role"
135
+ :selected="selectedId === user.id"
136
+ clickable
137
+ @click="selectUser(user.id)"
138
+ >
139
+ <template #left>
140
+ <FAvatar :name="user.name" size="sm" />
141
+ </template>
142
+ </FListItem>
143
+ </div>
144
+ `
145
+ });
146
+
147
+ export const LongContent = () => ({
148
+ components: { FListItem },
149
+ template: `
150
+ <div class="w-64">
151
+ <FListItem
152
+ title="Un titre très long qui devrait être tronqué"
153
+ subtitle="Une description également très longue qui sera tronquée"
154
+ truncate
155
+ />
156
+ </div>
157
+ `
158
+ });
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FListItem from './FListItem.vue';
4
+
5
+ describe('FListItem', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FListItem);
8
+ expect(wrapper.exists()).toBe(true);
9
+ });
10
+
11
+ it('displays title when provided', () => {
12
+ const wrapper = mount(FListItem, {
13
+ propsData: { title: 'Item Title' }
14
+ });
15
+ expect(wrapper.text()).toContain('Item Title');
16
+ });
17
+
18
+ it('displays subtitle when provided', () => {
19
+ const wrapper = mount(FListItem, {
20
+ propsData: { title: 'Title', subtitle: 'Subtitle text' }
21
+ });
22
+ expect(wrapper.text()).toContain('Subtitle text');
23
+ });
24
+
25
+ it('is not clickable by default', () => {
26
+ const wrapper = mount(FListItem);
27
+ expect(wrapper.attributes('role')).toBeUndefined();
28
+ });
29
+
30
+ it('is clickable when clickable prop is true', () => {
31
+ const wrapper = mount(FListItem, {
32
+ propsData: { clickable: true }
33
+ });
34
+ expect(wrapper.attributes('role')).toBe('button');
35
+ });
36
+
37
+ it('emits click event when clickable and clicked', async () => {
38
+ const wrapper = mount(FListItem, {
39
+ propsData: { clickable: true }
40
+ });
41
+ await wrapper.trigger('click');
42
+ expect(wrapper.emitted('click')).toBeTruthy();
43
+ });
44
+
45
+ it('does not emit click when not clickable', async () => {
46
+ const wrapper = mount(FListItem);
47
+ await wrapper.trigger('click');
48
+ expect(wrapper.emitted('click')).toBeFalsy();
49
+ });
50
+
51
+ it('does not emit click when disabled', async () => {
52
+ const wrapper = mount(FListItem, {
53
+ propsData: { clickable: true, disabled: true }
54
+ });
55
+ await wrapper.trigger('click');
56
+ expect(wrapper.emitted('click')).toBeFalsy();
57
+ });
58
+
59
+ it('applies selected styles when selected', () => {
60
+ const wrapper = mount(FListItem, {
61
+ propsData: { selected: true }
62
+ });
63
+ expect(wrapper.classes().join(' ')).toContain('bg-primary-50');
64
+ });
65
+
66
+ it('renders left slot', () => {
67
+ const wrapper = mount(FListItem, {
68
+ slots: { left: '<span>Left content</span>' }
69
+ });
70
+ expect(wrapper.html()).toContain('Left content');
71
+ });
72
+
73
+ it('renders right slot', () => {
74
+ const wrapper = mount(FListItem, {
75
+ slots: { right: '<span>Right content</span>' }
76
+ });
77
+ expect(wrapper.html()).toContain('Right content');
78
+ });
79
+
80
+ it('renders content slot', () => {
81
+ const wrapper = mount(FListItem, {
82
+ slots: { content: '<div>Custom content</div>' }
83
+ });
84
+ expect(wrapper.html()).toContain('Custom content');
85
+ });
86
+
87
+ it('applies truncate to text when truncate is true', () => {
88
+ const wrapper = mount(FListItem, {
89
+ propsData: { title: 'Long title', truncate: true }
90
+ });
91
+ expect(wrapper.exists()).toBe(true);
92
+ });
93
+ });
@@ -0,0 +1,113 @@
1
+ <template>
2
+ <div
3
+ :class="listItemClasses"
4
+ :tabindex="clickable ? 0 : undefined"
5
+ :role="clickable ? 'button' : undefined"
6
+ @click="handleClick"
7
+ @keydown.enter="handleClick"
8
+ @keydown.space.prevent="handleClick"
9
+ >
10
+ <div v-if="$slots.left" class="flex-shrink-0">
11
+ <slot name="left" />
12
+ </div>
13
+
14
+ <div class="flex-1 min-w-0">
15
+ <f-typography
16
+ v-if="title"
17
+ variant="body"
18
+ :truncate="truncate"
19
+ :class="titleClasses"
20
+ >
21
+ {{ title }}
22
+ </f-typography>
23
+ <f-typography
24
+ v-if="subtitle"
25
+ variant="caption"
26
+ :truncate="truncate"
27
+ :class="subtitleClasses"
28
+ >
29
+ {{ subtitle }}
30
+ </f-typography>
31
+ <slot name="content" />
32
+ </div>
33
+
34
+ <div v-if="$slots.right" class="flex-shrink-0">
35
+ <slot name="right" />
36
+ </div>
37
+ </div>
38
+ </template>
39
+
40
+ <script>
41
+ import FTypography from '../../atoms/FTypography/FTypography.vue';
42
+
43
+ export default {
44
+ name: 'FListItem',
45
+ components: {
46
+ FTypography
47
+ },
48
+ props: {
49
+ title: {
50
+ type: String,
51
+ default: ''
52
+ },
53
+ subtitle: {
54
+ type: String,
55
+ default: ''
56
+ },
57
+ clickable: {
58
+ type: Boolean,
59
+ default: false
60
+ },
61
+ selected: {
62
+ type: Boolean,
63
+ default: false
64
+ },
65
+ disabled: {
66
+ type: Boolean,
67
+ default: false
68
+ },
69
+ truncate: {
70
+ type: Boolean,
71
+ default: true
72
+ }
73
+ },
74
+ computed: {
75
+ listItemClasses() {
76
+ const baseClasses = 'flex items-center gap-3 px-4 py-3';
77
+ const transitionClasses =
78
+ 'transition-all duration-[var(--transition-duration-base)] ease-[var(--transition-easing-standard)]';
79
+ const clickableClasses =
80
+ this.clickable && !this.disabled
81
+ ? 'cursor-pointer hover:bg-neutral-50 focus:outline-none focus:ring-2 focus:ring-primary-500/20'
82
+ : '';
83
+ const selectedClasses = this.selected ? 'bg-primary-50' : '';
84
+ const disabledClasses = this.disabled
85
+ ? 'opacity-50 cursor-not-allowed'
86
+ : '';
87
+
88
+ return [
89
+ baseClasses,
90
+ transitionClasses,
91
+ clickableClasses,
92
+ selectedClasses,
93
+ disabledClasses
94
+ ]
95
+ .filter(Boolean)
96
+ .join(' ');
97
+ },
98
+ titleClasses() {
99
+ return this.disabled ? 'text-neutral-400' : '';
100
+ },
101
+ subtitleClasses() {
102
+ return this.disabled ? 'text-neutral-300' : '';
103
+ }
104
+ },
105
+ methods: {
106
+ handleClick(event) {
107
+ if (!this.disabled && this.clickable) {
108
+ this.$emit('click', event);
109
+ }
110
+ }
111
+ }
112
+ };
113
+ </script>