@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,78 @@
1
+ <template>
2
+ <component :is="computedTag" :class="typographyClasses">
3
+ <slot />
4
+ </component>
5
+ </template>
6
+
7
+ <script>
8
+ export default {
9
+ name: 'FTypography',
10
+ props: {
11
+ variant: {
12
+ type: String,
13
+ default: 'body',
14
+ validator: (value) =>
15
+ [
16
+ 'h1',
17
+ 'h2',
18
+ 'h3',
19
+ 'h4',
20
+ 'h5',
21
+ 'h6',
22
+ 'body',
23
+ 'caption',
24
+ 'overline'
25
+ ].includes(value)
26
+ },
27
+ tag: {
28
+ type: String,
29
+ default: null
30
+ },
31
+ truncate: {
32
+ type: Boolean,
33
+ default: false
34
+ }
35
+ },
36
+ computed: {
37
+ computedTag() {
38
+ if (this.tag) return this.tag;
39
+ const tagMap = {
40
+ h1: 'h1',
41
+ h2: 'h2',
42
+ h3: 'h3',
43
+ h4: 'h4',
44
+ h5: 'h5',
45
+ h6: 'h6',
46
+ body: 'p',
47
+ caption: 'span',
48
+ overline: 'span'
49
+ };
50
+ return tagMap[this.variant] || 'p';
51
+ },
52
+ typographyClasses() {
53
+ const baseClasses = 'm-0 font-sans text-neutral-800';
54
+
55
+ const variantClasses = {
56
+ h1: 'text-4xl font-bold leading-tight',
57
+ h2: 'text-3xl font-bold leading-snug',
58
+ h3: 'text-2xl font-semibold leading-normal',
59
+ h4: 'text-xl font-semibold leading-normal',
60
+ h5: 'text-lg font-medium leading-relaxed',
61
+ h6: 'text-base font-medium leading-normal',
62
+ body: 'text-base font-normal leading-relaxed',
63
+ caption: 'text-sm font-normal leading-normal text-neutral-500',
64
+ overline:
65
+ 'text-xs font-semibold leading-normal uppercase tracking-wider text-neutral-500'
66
+ };
67
+
68
+ const truncateClasses = this.truncate
69
+ ? 'overflow-hidden text-ellipsis whitespace-nowrap'
70
+ : '';
71
+
72
+ return [baseClasses, variantClasses[this.variant], truncateClasses]
73
+ .filter(Boolean)
74
+ .join(' ');
75
+ }
76
+ }
77
+ };
78
+ </script>
@@ -0,0 +1,27 @@
1
+ import FAvatar from './FAvatar/FAvatar.vue';
2
+ import FBadge from './FBadge/FBadge.vue';
3
+ import FButton from './FButton/FButton.vue';
4
+ import FCheckbox from './FCheckbox/FCheckbox.vue';
5
+ import FDivider from './FDivider/FDivider.vue';
6
+ import FIcon from './FIcon/FIcon.vue';
7
+ import FInput from './FInput/FInput.vue';
8
+ import FLoader from './FLoader/FLoader.vue';
9
+ import FRadio from './FRadio/FRadio.vue';
10
+ import FTextarea from './FTextarea/FTextarea.vue';
11
+ import FToggle from './FToggle/FToggle.vue';
12
+ import FTypography from './FTypography/FTypography.vue';
13
+
14
+ export {
15
+ FAvatar,
16
+ FBadge,
17
+ FButton,
18
+ FCheckbox,
19
+ FDivider,
20
+ FIcon,
21
+ FInput,
22
+ FLoader,
23
+ FRadio,
24
+ FTextarea,
25
+ FToggle,
26
+ FTypography
27
+ };
@@ -0,0 +1,71 @@
1
+ import FAccordionItem from './FAccordionItem.vue';
2
+
3
+ export default {
4
+ title: 'Molecules/FAccordionItem',
5
+ component: FAccordionItem,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ title: {
9
+ control: 'text',
10
+ description: "Titre de l'accordéon"
11
+ },
12
+ defaultOpen: {
13
+ control: 'boolean',
14
+ description: 'Ouvert par défaut'
15
+ }
16
+ }
17
+ };
18
+
19
+ const Template = (args, { argTypes }) => ({
20
+ components: { FAccordionItem },
21
+ props: Object.keys(argTypes),
22
+ template: `
23
+ <FAccordionItem v-bind="$props">
24
+ <p>Contenu de l'accordéon. Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
25
+ </FAccordionItem>
26
+ `
27
+ });
28
+
29
+ export const Default = Template.bind({});
30
+ Default.args = {
31
+ title: 'Section 1'
32
+ };
33
+
34
+ export const OpenByDefault = Template.bind({});
35
+ OpenByDefault.args = {
36
+ title: 'Section ouverte',
37
+ defaultOpen: true
38
+ };
39
+
40
+ export const Multiple = () => ({
41
+ components: { FAccordionItem },
42
+ template: `
43
+ <div class="flex flex-col gap-2">
44
+ <FAccordionItem title="Première section">
45
+ <p>Contenu de la première section.</p>
46
+ </FAccordionItem>
47
+ <FAccordionItem title="Deuxième section">
48
+ <p>Contenu de la deuxième section.</p>
49
+ </FAccordionItem>
50
+ <FAccordionItem title="Troisième section">
51
+ <p>Contenu de la troisième section.</p>
52
+ </FAccordionItem>
53
+ </div>
54
+ `
55
+ });
56
+
57
+ export const WithRichContent = () => ({
58
+ components: { FAccordionItem },
59
+ template: `
60
+ <FAccordionItem title="FAQ - Question importante">
61
+ <div class="space-y-2">
62
+ <p>Voici une réponse détaillée avec du contenu riche.</p>
63
+ <ul class="list-disc pl-4">
64
+ <li>Premier point</li>
65
+ <li>Deuxième point</li>
66
+ <li>Troisième point</li>
67
+ </ul>
68
+ </div>
69
+ </FAccordionItem>
70
+ `
71
+ });
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FAccordionItem from './FAccordionItem.vue';
4
+
5
+ describe('FAccordionItem', () => {
6
+ it('renders correctly with required props', () => {
7
+ const wrapper = mount(FAccordionItem, {
8
+ propsData: { title: 'Section Title' },
9
+ slots: { default: 'Content' }
10
+ });
11
+ expect(wrapper.text()).toContain('Section Title');
12
+ });
13
+
14
+ it('is closed by default', () => {
15
+ const wrapper = mount(FAccordionItem, {
16
+ propsData: { title: 'Title' }
17
+ });
18
+ expect(wrapper.find('[aria-expanded="false"]').exists()).toBe(true);
19
+ });
20
+
21
+ it('can be open by default', () => {
22
+ const wrapper = mount(FAccordionItem, {
23
+ propsData: { title: 'Title', defaultOpen: true }
24
+ });
25
+ expect(wrapper.find('[aria-expanded="true"]').exists()).toBe(true);
26
+ });
27
+
28
+ it('toggles when header is clicked', async () => {
29
+ const wrapper = mount(FAccordionItem, {
30
+ propsData: { title: 'Title' }
31
+ });
32
+ await wrapper.find('button').trigger('click');
33
+ expect(wrapper.find('[aria-expanded="true"]').exists()).toBe(true);
34
+ });
35
+
36
+ it('emits toggle event when clicked', async () => {
37
+ const wrapper = mount(FAccordionItem, {
38
+ propsData: { title: 'Title' }
39
+ });
40
+ await wrapper.find('button').trigger('click');
41
+ expect(wrapper.emitted('toggle')).toBeTruthy();
42
+ });
43
+
44
+ it('displays slot content', () => {
45
+ const wrapper = mount(FAccordionItem, {
46
+ propsData: { title: 'Title' },
47
+ slots: { default: '<p>Accordion content</p>' }
48
+ });
49
+ expect(wrapper.html()).toContain('Accordion content');
50
+ });
51
+
52
+ it('has accessible structure', () => {
53
+ const wrapper = mount(FAccordionItem, {
54
+ propsData: { title: 'Title' }
55
+ });
56
+ const button = wrapper.find('button');
57
+ const contentId = button.attributes('aria-controls');
58
+ expect(contentId).toBeDefined();
59
+ expect(wrapper.find(`#${contentId}`).exists()).toBe(true);
60
+ });
61
+ });
@@ -0,0 +1,105 @@
1
+ <template>
2
+ <div class="border border-neutral-200 rounded-lg overflow-hidden">
3
+ <button
4
+ :id="headerId"
5
+ type="button"
6
+ :class="headerClasses"
7
+ :aria-expanded="String(isOpen)"
8
+ :aria-controls="contentId"
9
+ @click="toggle"
10
+ >
11
+ <f-typography variant="h6" class="flex-1 text-left">
12
+ {{ title }}
13
+ </f-typography>
14
+ <f-icon name="chevron-down" size="md" :class="iconClasses" />
15
+ </button>
16
+ <div
17
+ :id="contentId"
18
+ ref="content"
19
+ :class="contentWrapperClasses"
20
+ :style="contentStyle"
21
+ :aria-labelledby="headerId"
22
+ role="region"
23
+ >
24
+ <div ref="contentInner" class="p-4">
25
+ <slot />
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+ <script>
32
+ import FIcon from '../../atoms/FIcon/FIcon.vue';
33
+ import FTypography from '../../atoms/FTypography/FTypography.vue';
34
+
35
+ let accordionItemId = 0;
36
+
37
+ export default {
38
+ name: 'FAccordionItem',
39
+ components: {
40
+ FIcon,
41
+ FTypography
42
+ },
43
+ props: {
44
+ title: {
45
+ type: String,
46
+ required: true
47
+ },
48
+ defaultOpen: {
49
+ type: Boolean,
50
+ default: false
51
+ }
52
+ },
53
+ data() {
54
+ const id = ++accordionItemId;
55
+ return {
56
+ isOpen: this.defaultOpen,
57
+ contentHeight: 0,
58
+ headerId: `accordion-header-${id}`,
59
+ contentId: `accordion-content-${id}`
60
+ };
61
+ },
62
+ computed: {
63
+ headerClasses() {
64
+ return 'w-full flex items-center justify-between gap-3 p-4 bg-neutral-50 hover:bg-neutral-100 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-inset cursor-pointer transition-colors duration-[var(--transition-duration-base)] ease-[var(--transition-easing-standard)]';
65
+ },
66
+ iconClasses() {
67
+ const baseClasses = 'transform text-neutral-500';
68
+ const transitionClasses =
69
+ 'transition-transform duration-[var(--transition-duration-slow)] ease-[var(--transition-easing-emphasized)]';
70
+ const rotateClasses = this.isOpen ? 'rotate-180' : 'rotate-0';
71
+ return `${baseClasses} ${transitionClasses} ${rotateClasses}`;
72
+ },
73
+ contentWrapperClasses() {
74
+ return 'overflow-hidden transition-all duration-[var(--transition-duration-slow)] ease-[var(--transition-easing-emphasized)]';
75
+ },
76
+ contentStyle() {
77
+ return {
78
+ maxHeight: this.isOpen ? `${this.contentHeight}px` : '0px'
79
+ };
80
+ }
81
+ },
82
+ watch: {
83
+ isOpen() {
84
+ this.$nextTick(() => {
85
+ this.updateContentHeight();
86
+ });
87
+ }
88
+ },
89
+ mounted() {
90
+ this.updateContentHeight();
91
+ },
92
+ methods: {
93
+ toggle() {
94
+ this.isOpen = !this.isOpen;
95
+ this.$emit('toggle', this.isOpen);
96
+ this.$emit('input', this.isOpen);
97
+ },
98
+ updateContentHeight() {
99
+ if (this.$refs.contentInner) {
100
+ this.contentHeight = this.$refs.contentInner.scrollHeight;
101
+ }
102
+ }
103
+ }
104
+ };
105
+ </script>
@@ -0,0 +1,87 @@
1
+ import FAlert from './FAlert.vue';
2
+
3
+ export default {
4
+ title: 'Molecules/FAlert',
5
+ component: FAlert,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ variant: {
9
+ control: { type: 'select' },
10
+ options: ['success', 'error', 'info'],
11
+ description: "Type d'alerte"
12
+ },
13
+ title: {
14
+ control: 'text',
15
+ description: "Titre de l'alerte"
16
+ },
17
+ message: {
18
+ control: 'text',
19
+ description: "Message de l'alerte"
20
+ },
21
+ closable: {
22
+ control: 'boolean',
23
+ description: 'Afficher le bouton de fermeture'
24
+ }
25
+ }
26
+ };
27
+
28
+ const Template = (args, { argTypes }) => ({
29
+ components: { FAlert },
30
+ props: Object.keys(argTypes),
31
+ template: '<FAlert v-bind="$props" />'
32
+ });
33
+
34
+ export const Success = Template.bind({});
35
+ Success.args = {
36
+ variant: 'success',
37
+ title: 'Succès',
38
+ message: 'Votre action a été effectuée avec succès.'
39
+ };
40
+
41
+ export const Error = Template.bind({});
42
+ Error.args = {
43
+ variant: 'error',
44
+ title: 'Erreur',
45
+ message: "Une erreur s'est produite. Veuillez réessayer."
46
+ };
47
+
48
+ export const Info = Template.bind({});
49
+ Info.args = {
50
+ variant: 'info',
51
+ title: 'Information',
52
+ message: 'Voici une information importante à prendre en compte.'
53
+ };
54
+
55
+ export const WithoutTitle = Template.bind({});
56
+ WithoutTitle.args = {
57
+ variant: 'info',
58
+ message: 'Alerte simple sans titre.'
59
+ };
60
+
61
+ export const NotClosable = Template.bind({});
62
+ NotClosable.args = {
63
+ variant: 'info',
64
+ title: 'Notification permanente',
65
+ message: 'Cette alerte ne peut pas être fermée.',
66
+ closable: false
67
+ };
68
+
69
+ export const WithSlotContent = () => ({
70
+ components: { FAlert },
71
+ template: `
72
+ <FAlert variant="info" title="Information">
73
+ <p>Contenu personnalisé avec <a href="#" class="underline">un lien</a> cliquable.</p>
74
+ </FAlert>
75
+ `
76
+ });
77
+
78
+ export const AllVariants = () => ({
79
+ components: { FAlert },
80
+ template: `
81
+ <div class="flex flex-col gap-4">
82
+ <FAlert variant="success" title="Succès" message="Opération réussie." />
83
+ <FAlert variant="error" title="Erreur" message="Une erreur s'est produite." />
84
+ <FAlert variant="info" title="Information" message="Information importante." />
85
+ </div>
86
+ `
87
+ });
@@ -0,0 +1,59 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FAlert from './FAlert.vue';
4
+
5
+ describe('FAlert', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FAlert);
8
+ expect(wrapper.find('[role="alert"]').exists()).toBe(true);
9
+ });
10
+
11
+ it('displays title when provided', () => {
12
+ const wrapper = mount(FAlert, {
13
+ propsData: { title: 'Alert Title' }
14
+ });
15
+ expect(wrapper.text()).toContain('Alert Title');
16
+ });
17
+
18
+ it('displays message when provided', () => {
19
+ const wrapper = mount(FAlert, {
20
+ propsData: { message: 'Alert message content' }
21
+ });
22
+ expect(wrapper.text()).toContain('Alert message content');
23
+ });
24
+
25
+ it('applies correct variant classes', () => {
26
+ const variants = ['success', 'error', 'info'] as const;
27
+ variants.forEach((variant) => {
28
+ const wrapper = mount(FAlert, {
29
+ propsData: { variant }
30
+ });
31
+ expect(wrapper.find('[role="alert"]').exists()).toBe(true);
32
+ });
33
+ });
34
+
35
+ it('shows close button by default', () => {
36
+ const wrapper = mount(FAlert);
37
+ expect(wrapper.findComponent({ name: 'FButton' }).exists()).toBe(true);
38
+ });
39
+
40
+ it('hides close button when closable is false', () => {
41
+ const wrapper = mount(FAlert, {
42
+ propsData: { closable: false }
43
+ });
44
+ expect(wrapper.findComponent({ name: 'FButton' }).exists()).toBe(false);
45
+ });
46
+
47
+ it('emits close event and hides when close button is clicked', async () => {
48
+ const wrapper = mount(FAlert);
49
+ await wrapper.findComponent({ name: 'FButton' }).trigger('click');
50
+ expect(wrapper.emitted('close')).toBeTruthy();
51
+ });
52
+
53
+ it('displays slot content', () => {
54
+ const wrapper = mount(FAlert, {
55
+ slots: { default: '<p>Custom content</p>' }
56
+ });
57
+ expect(wrapper.html()).toContain('Custom content');
58
+ });
59
+ });
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <div v-if="isVisible" :class="alertClasses" role="alert">
3
+ <f-icon :name="variant" size="md" />
4
+ <div class="flex-1 min-w-0">
5
+ <f-typography v-if="title" variant="h6" :class="titleClasses">
6
+ {{ title }}
7
+ </f-typography>
8
+ <f-typography v-if="message" variant="body" :class="messageClasses">
9
+ {{ message }}
10
+ </f-typography>
11
+ <slot />
12
+ </div>
13
+ <f-button
14
+ v-if="closable"
15
+ variant="text"
16
+ size="small"
17
+ :class="closeButtonClasses"
18
+ @click="handleClose"
19
+ >
20
+ <f-icon name="close" size="sm" />
21
+ <span class="sr-only">Fermer l'alerte</span>
22
+ </f-button>
23
+ </div>
24
+ </template>
25
+
26
+ <script>
27
+ import FIcon from '../../atoms/FIcon/FIcon.vue';
28
+ import FTypography from '../../atoms/FTypography/FTypography.vue';
29
+ import FButton from '../../atoms/FButton/FButton.vue';
30
+
31
+ const VARIANT_COLORS = {
32
+ success: {
33
+ container: 'bg-success-50 border-success-200 text-success-800',
34
+ title: 'text-success-800',
35
+ message: 'text-success-700',
36
+ closeButton: 'text-success-600 hover:text-success-800'
37
+ },
38
+ error: {
39
+ container: 'bg-danger-50 border-danger-200 text-danger-800',
40
+ title: 'text-danger-800',
41
+ message: 'text-danger-700',
42
+ closeButton: 'text-danger-600 hover:text-danger-800'
43
+ },
44
+ info: {
45
+ container: 'bg-primary-50 border-primary-200 text-primary-800',
46
+ title: 'text-primary-800',
47
+ message: 'text-primary-700',
48
+ closeButton: 'text-primary-600 hover:text-primary-800'
49
+ }
50
+ };
51
+
52
+ export default {
53
+ name: 'FAlert',
54
+ components: {
55
+ FIcon,
56
+ FTypography,
57
+ FButton
58
+ },
59
+ props: {
60
+ variant: {
61
+ type: String,
62
+ default: 'info',
63
+ validator: (value) => ['success', 'error', 'info'].includes(value)
64
+ },
65
+ title: {
66
+ type: String,
67
+ default: ''
68
+ },
69
+ message: {
70
+ type: String,
71
+ default: ''
72
+ },
73
+ closable: {
74
+ type: Boolean,
75
+ default: true
76
+ }
77
+ },
78
+ data() {
79
+ return {
80
+ isVisible: true
81
+ };
82
+ },
83
+ computed: {
84
+ variantColors() {
85
+ return VARIANT_COLORS[this.variant];
86
+ },
87
+ alertClasses() {
88
+ const baseClasses = 'flex items-start gap-3 p-4 rounded-lg border';
89
+ return `${baseClasses} ${this.variantColors.container}`;
90
+ },
91
+ titleClasses() {
92
+ return this.variantColors.title;
93
+ },
94
+ messageClasses() {
95
+ return this.variantColors.message;
96
+ },
97
+ closeButtonClasses() {
98
+ return `flex-shrink-0 ${this.variantColors.closeButton}`;
99
+ }
100
+ },
101
+ methods: {
102
+ handleClose() {
103
+ this.isVisible = false;
104
+ this.$emit('close');
105
+ }
106
+ }
107
+ };
108
+ </script>
@@ -0,0 +1,90 @@
1
+ import FBreadcrumb from './FBreadcrumb.vue';
2
+
3
+ export default {
4
+ title: 'Molecules/FBreadcrumb',
5
+ component: FBreadcrumb,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ items: {
9
+ control: 'object',
10
+ description: "Liste des éléments du fil d'Ariane"
11
+ },
12
+ separatorIcon: {
13
+ control: 'text',
14
+ description: 'Icône de séparation'
15
+ },
16
+ ariaLabel: {
17
+ control: 'text',
18
+ description: "Label pour l'accessibilité"
19
+ }
20
+ }
21
+ };
22
+
23
+ const Template = (args, { argTypes }) => ({
24
+ components: { FBreadcrumb },
25
+ props: Object.keys(argTypes),
26
+ template: '<FBreadcrumb v-bind="$props" />'
27
+ });
28
+
29
+ export const Default = Template.bind({});
30
+ Default.args = {
31
+ items: [
32
+ { label: 'Accueil', href: '/' },
33
+ { label: 'Produits', href: '/products' },
34
+ { label: 'Détail du produit' }
35
+ ]
36
+ };
37
+
38
+ export const WithLinks = Template.bind({});
39
+ WithLinks.args = {
40
+ items: [
41
+ { label: 'Accueil', href: '/' },
42
+ { label: 'Catégorie', href: '/category' },
43
+ { label: 'Sous-catégorie', href: '/category/sub' },
44
+ { label: 'Article' }
45
+ ]
46
+ };
47
+
48
+ export const WithIcons = Template.bind({});
49
+ WithIcons.args = {
50
+ items: [
51
+ { label: 'Accueil', href: '/', icon: 'home' },
52
+ { label: 'Documents', href: '/docs', icon: 'folder' },
53
+ { label: 'Fichier', icon: 'document' }
54
+ ]
55
+ };
56
+
57
+ export const CustomSeparator = Template.bind({});
58
+ CustomSeparator.args = {
59
+ items: [
60
+ { label: 'Accueil', href: '/' },
61
+ { label: 'Section', href: '/section' },
62
+ { label: 'Page actuelle' }
63
+ ],
64
+ separatorIcon: 'arrow-right'
65
+ };
66
+
67
+ export const TwoLevels = Template.bind({});
68
+ TwoLevels.args = {
69
+ items: [{ label: 'Tableau de bord', href: '/' }, { label: 'Paramètres' }]
70
+ };
71
+
72
+ export const Interactive = () => ({
73
+ components: { FBreadcrumb },
74
+ data() {
75
+ return {
76
+ items: [
77
+ { label: 'Accueil', href: '/' },
78
+ { label: 'Produits', href: '/products' },
79
+ { label: 'Électronique', href: '/products/electronics' },
80
+ { label: 'Smartphone' }
81
+ ]
82
+ };
83
+ },
84
+ methods: {
85
+ handleNavigate(data) {
86
+ alert(`Navigation vers: ${data.item.label}`);
87
+ }
88
+ },
89
+ template: '<FBreadcrumb :items="items" @navigate="handleNavigate" />'
90
+ });