@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,130 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FStepperProgress from './FStepperProgress.vue';
4
+
5
+ describe('FStepperProgress', () => {
6
+ const steps = ['Étape 1', 'Étape 2', 'Étape 3'];
7
+
8
+ it('renders correctly with required props', () => {
9
+ const wrapper = mount(FStepperProgress, {
10
+ propsData: { steps }
11
+ });
12
+ expect(wrapper.find('nav').exists()).toBe(true);
13
+ expect(wrapper.find('ol').exists()).toBe(true);
14
+ });
15
+
16
+ it('displays all step titles', () => {
17
+ const wrapper = mount(FStepperProgress, {
18
+ propsData: { steps }
19
+ });
20
+ expect(wrapper.text()).toContain('Étape 1');
21
+ expect(wrapper.text()).toContain('Étape 2');
22
+ expect(wrapper.text()).toContain('Étape 3');
23
+ });
24
+
25
+ it('renders correct number of steps', () => {
26
+ const wrapper = mount(FStepperProgress, {
27
+ propsData: { steps }
28
+ });
29
+ const stepItems = wrapper.findAll('li');
30
+ expect(stepItems).toHaveLength(3);
31
+ });
32
+
33
+ it('starts at step 0 by default', () => {
34
+ const wrapper = mount(FStepperProgress, {
35
+ propsData: { steps }
36
+ });
37
+ expect(wrapper.vm.currentStep).toBe(0);
38
+ });
39
+
40
+ it('respects currentStep prop', () => {
41
+ const wrapper = mount(FStepperProgress, {
42
+ propsData: { steps, currentStep: 1 }
43
+ });
44
+ expect(wrapper.vm.currentStep).toBe(1);
45
+ });
46
+
47
+ it('displays step numbers for future steps', () => {
48
+ const wrapper = mount(FStepperProgress, {
49
+ propsData: { steps, currentStep: 0 }
50
+ });
51
+ // Step 2 and 3 should show numbers
52
+ expect(wrapper.text()).toContain('2');
53
+ expect(wrapper.text()).toContain('3');
54
+ });
55
+
56
+ it('displays checkmark for completed steps', () => {
57
+ const wrapper = mount(FStepperProgress, {
58
+ propsData: { steps, currentStep: 2 }
59
+ });
60
+ // Completed steps should have SVG checkmarks
61
+ const svgs = wrapper.findAll('svg');
62
+ expect(svgs.length).toBeGreaterThanOrEqual(2);
63
+ });
64
+
65
+ it('applies correct classes for current step', () => {
66
+ const wrapper = mount(FStepperProgress, {
67
+ propsData: { steps, currentStep: 1 }
68
+ });
69
+ const html = wrapper.html();
70
+ expect(html).toContain('bg-primary-600');
71
+ });
72
+
73
+ it('applies correct classes for completed steps', () => {
74
+ const wrapper = mount(FStepperProgress, {
75
+ propsData: { steps, currentStep: 2 }
76
+ });
77
+ const html = wrapper.html();
78
+ expect(html).toContain('bg-success-600');
79
+ });
80
+
81
+ it('applies correct classes for future steps', () => {
82
+ const wrapper = mount(FStepperProgress, {
83
+ propsData: { steps, currentStep: 0 }
84
+ });
85
+ const html = wrapper.html();
86
+ expect(html).toContain('bg-neutral-200');
87
+ });
88
+
89
+ it('renders connector lines between steps', () => {
90
+ const wrapper = mount(FStepperProgress, {
91
+ propsData: { steps }
92
+ });
93
+ // Should have 2 connector lines for 3 steps
94
+ const connectors = wrapper.findAll('.h-0\\.5');
95
+ expect(connectors.length).toBe(2);
96
+ });
97
+
98
+ it('has proper accessibility attributes', () => {
99
+ const wrapper = mount(FStepperProgress, {
100
+ propsData: { steps }
101
+ });
102
+ const nav = wrapper.find('nav');
103
+ expect(nav.attributes('aria-label')).toBe('Progression des étapes');
104
+ });
105
+
106
+ it('sets aria-current on current step', () => {
107
+ const wrapper = mount(FStepperProgress, {
108
+ propsData: { steps, currentStep: 1 }
109
+ });
110
+ const currentStepIndicator = wrapper.find('[aria-current="step"]');
111
+ expect(currentStepIndicator.exists()).toBe(true);
112
+ });
113
+
114
+ it('handles single step', () => {
115
+ const wrapper = mount(FStepperProgress, {
116
+ propsData: { steps: ['Seule étape'], currentStep: 0 }
117
+ });
118
+ expect(wrapper.findAll('li')).toHaveLength(1);
119
+ // No connectors for single step
120
+ expect(wrapper.findAll('.h-0\\.5').length).toBe(0);
121
+ });
122
+
123
+ it('handles many steps', () => {
124
+ const manySteps = ['A', 'B', 'C', 'D', 'E'];
125
+ const wrapper = mount(FStepperProgress, {
126
+ propsData: { steps: manySteps, currentStep: 2 }
127
+ });
128
+ expect(wrapper.findAll('li')).toHaveLength(5);
129
+ });
130
+ });
@@ -0,0 +1,146 @@
1
+ <template>
2
+ <nav aria-label="Progression des étapes" class="w-full">
3
+ <ol class="flex items-center w-full">
4
+ <li
5
+ v-for="(step, index) in steps"
6
+ :key="index"
7
+ :class="stepContainerClasses(index)"
8
+ >
9
+ <!-- Step indicator circle -->
10
+ <div class="flex items-center">
11
+ <span
12
+ :class="stepCircleClasses(index)"
13
+ :aria-current="index === currentStep ? 'step' : null"
14
+ >
15
+ <!-- Completed step: checkmark -->
16
+ <svg
17
+ v-if="index < currentStep"
18
+ class="w-4 h-4"
19
+ fill="currentColor"
20
+ viewBox="0 0 20 20"
21
+ >
22
+ <path
23
+ fill-rule="evenodd"
24
+ d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
25
+ clip-rule="evenodd"
26
+ />
27
+ </svg>
28
+ <!-- Current or future step: number -->
29
+ <span v-else class="text-sm font-medium">{{ index + 1 }}</span>
30
+ </span>
31
+ </div>
32
+
33
+ <!-- Step title -->
34
+ <span :class="stepTitleClasses(index)">
35
+ {{ step }}
36
+ </span>
37
+
38
+ <!-- Connector line (not for last step) -->
39
+ <div
40
+ v-if="index < steps.length - 1"
41
+ :class="connectorClasses(index)"
42
+ ></div>
43
+ </li>
44
+ </ol>
45
+ </nav>
46
+ </template>
47
+
48
+ <script>
49
+ export default {
50
+ name: 'FStepperProgress',
51
+ props: {
52
+ /**
53
+ * Array of step titles
54
+ */
55
+ steps: {
56
+ type: Array,
57
+ required: true
58
+ },
59
+ /**
60
+ * Current active step index (0-based)
61
+ */
62
+ currentStep: {
63
+ type: Number,
64
+ default: 0
65
+ }
66
+ },
67
+ methods: {
68
+ /**
69
+ * Get container classes for each step
70
+ */
71
+ stepContainerClasses(index) {
72
+ const isLast = index === this.steps.length - 1;
73
+ return ['flex', 'items-center', isLast ? '' : 'flex-1']
74
+ .filter(Boolean)
75
+ .join(' ');
76
+ },
77
+ /**
78
+ * Get classes for step circle indicator
79
+ */
80
+ stepCircleClasses(index) {
81
+ const baseClasses =
82
+ 'flex items-center justify-center w-8 h-8 rounded-full ring-4 ring-white';
83
+ const transitionClasses =
84
+ 'transition-colors duration-[var(--transition-duration-base)] ease-[var(--transition-easing-standard)]';
85
+
86
+ if (index < this.currentStep) {
87
+ // Completed step
88
+ return [
89
+ baseClasses,
90
+ transitionClasses,
91
+ 'bg-success-600 text-white'
92
+ ].join(' ');
93
+ } else if (index === this.currentStep) {
94
+ // Current step
95
+ return [
96
+ baseClasses,
97
+ transitionClasses,
98
+ 'bg-primary-600 text-white'
99
+ ].join(' ');
100
+ } else {
101
+ // Future step
102
+ return [
103
+ baseClasses,
104
+ transitionClasses,
105
+ 'bg-neutral-200 text-neutral-500'
106
+ ].join(' ');
107
+ }
108
+ },
109
+ /**
110
+ * Get classes for step title
111
+ */
112
+ stepTitleClasses(index) {
113
+ const baseClasses = 'ml-2 text-sm font-medium';
114
+ const transitionClasses =
115
+ 'transition-colors duration-[var(--transition-duration-base)] ease-[var(--transition-easing-standard)]';
116
+
117
+ if (index < this.currentStep) {
118
+ // Completed step
119
+ return [baseClasses, transitionClasses, 'text-success-600'].join(' ');
120
+ } else if (index === this.currentStep) {
121
+ // Current step
122
+ return [baseClasses, transitionClasses, 'text-primary-600'].join(' ');
123
+ } else {
124
+ // Future step
125
+ return [baseClasses, transitionClasses, 'text-neutral-500'].join(' ');
126
+ }
127
+ },
128
+ /**
129
+ * Get classes for connector line between steps
130
+ */
131
+ connectorClasses(index) {
132
+ const baseClasses = 'flex-1 h-0.5 mx-4';
133
+ const transitionClasses =
134
+ 'transition-colors duration-[var(--transition-duration-base)] ease-[var(--transition-easing-standard)]';
135
+
136
+ if (index < this.currentStep) {
137
+ // Connector before current step (completed)
138
+ return [baseClasses, transitionClasses, 'bg-success-600'].join(' ');
139
+ } else {
140
+ // Connector at or after current step
141
+ return [baseClasses, transitionClasses, 'bg-neutral-200'].join(' ');
142
+ }
143
+ }
144
+ }
145
+ };
146
+ </script>
@@ -0,0 +1,142 @@
1
+ import FPageHeader from './FPageHeader.vue';
2
+ import FButton from '../../atoms/FButton/FButton.vue';
3
+ import FBreadcrumb from '../../molecules/FBreadcrumb/FBreadcrumb.vue';
4
+
5
+ export default {
6
+ title: 'Organisms/FPageHeader',
7
+ component: FPageHeader,
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ title: {
11
+ control: 'text',
12
+ description: 'Titre de la page'
13
+ },
14
+ subtitle: {
15
+ control: 'text',
16
+ description: 'Sous-titre'
17
+ },
18
+ showBack: {
19
+ control: 'boolean',
20
+ description: 'Afficher le bouton retour'
21
+ },
22
+ bordered: {
23
+ control: 'boolean',
24
+ description: 'Bordure en bas'
25
+ }
26
+ }
27
+ };
28
+
29
+ const Template = (args, { argTypes }) => ({
30
+ components: { FPageHeader },
31
+ props: Object.keys(argTypes),
32
+ template: '<FPageHeader v-bind="$props" />'
33
+ });
34
+
35
+ export const Default = Template.bind({});
36
+ Default.args = {
37
+ title: 'Tableau de bord'
38
+ };
39
+
40
+ export const WithSubtitle = Template.bind({});
41
+ WithSubtitle.args = {
42
+ title: 'Utilisateurs',
43
+ subtitle: 'Gérez les comptes utilisateurs'
44
+ };
45
+
46
+ export const WithBackButton = Template.bind({});
47
+ WithBackButton.args = {
48
+ title: 'Détail du projet',
49
+ showBack: true
50
+ };
51
+
52
+ export const WithActions = () => ({
53
+ components: { FPageHeader, FButton },
54
+ template: `
55
+ <FPageHeader title="Projets" subtitle="Liste de tous vos projets">
56
+ <template #actions>
57
+ <FButton variant="outline">Exporter</FButton>
58
+ <FButton variant="primary">Nouveau projet</FButton>
59
+ </template>
60
+ </FPageHeader>
61
+ `
62
+ });
63
+
64
+ export const WithBreadcrumb = () => ({
65
+ components: { FPageHeader, FBreadcrumb, FButton },
66
+ data() {
67
+ return {
68
+ breadcrumbItems: [
69
+ { label: 'Accueil', href: '/' },
70
+ { label: 'Projets', href: '/projects' },
71
+ { label: 'Mon projet' }
72
+ ]
73
+ };
74
+ },
75
+ template: `
76
+ <FPageHeader title="Mon projet" subtitle="Créé le 12 janvier 2024">
77
+ <template #breadcrumb>
78
+ <FBreadcrumb :items="breadcrumbItems" />
79
+ </template>
80
+ <template #actions>
81
+ <FButton variant="outline">Paramètres</FButton>
82
+ <FButton variant="primary">Modifier</FButton>
83
+ </template>
84
+ </FPageHeader>
85
+ `
86
+ });
87
+
88
+ export const Bordered = Template.bind({});
89
+ Bordered.args = {
90
+ title: 'Paramètres',
91
+ subtitle: 'Configurez votre application',
92
+ bordered: true
93
+ };
94
+
95
+ export const Complex = () => ({
96
+ components: { FPageHeader, FBreadcrumb, FButton },
97
+ data() {
98
+ return {
99
+ breadcrumbItems: [
100
+ { label: 'Dashboard', href: '/' },
101
+ { label: 'Équipe', href: '/team' },
102
+ { label: 'Jean Dupont' }
103
+ ]
104
+ };
105
+ },
106
+ methods: {
107
+ handleBack() {
108
+ alert('Retour cliqué');
109
+ }
110
+ },
111
+ template: `
112
+ <FPageHeader
113
+ title="Jean Dupont"
114
+ subtitle="Développeur Senior"
115
+ showBack
116
+ bordered
117
+ @back="handleBack"
118
+ >
119
+ <template #breadcrumb>
120
+ <FBreadcrumb :items="breadcrumbItems" />
121
+ </template>
122
+ <template #actions>
123
+ <FButton variant="ghost">Archiver</FButton>
124
+ <FButton variant="outline">Message</FButton>
125
+ <FButton variant="primary">Modifier</FButton>
126
+ </template>
127
+ </FPageHeader>
128
+ `
129
+ });
130
+
131
+ export const MinimalWithSlot = () => ({
132
+ components: { FPageHeader },
133
+ template: `
134
+ <FPageHeader title="Statistiques">
135
+ <div class="flex gap-4 text-sm text-neutral-600">
136
+ <span>Dernière mise à jour: il y a 5 min</span>
137
+ <span>•</span>
138
+ <span>Données en temps réel</span>
139
+ </div>
140
+ </FPageHeader>
141
+ `
142
+ });
@@ -0,0 +1,83 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FPageHeader from './FPageHeader.vue';
4
+
5
+ describe('FPageHeader', () => {
6
+ it('renders correctly with required props', () => {
7
+ const wrapper = mount(FPageHeader, {
8
+ propsData: { title: 'Page Title' }
9
+ });
10
+ expect(wrapper.text()).toContain('Page Title');
11
+ });
12
+
13
+ it('displays title', () => {
14
+ const wrapper = mount(FPageHeader, {
15
+ propsData: { title: 'Dashboard' }
16
+ });
17
+ expect(wrapper.text()).toContain('Dashboard');
18
+ });
19
+
20
+ it('displays subtitle when provided', () => {
21
+ const wrapper = mount(FPageHeader, {
22
+ propsData: { title: 'Title', subtitle: 'Subtitle text' }
23
+ });
24
+ expect(wrapper.text()).toContain('Subtitle text');
25
+ });
26
+
27
+ it('renders actions slot', () => {
28
+ const wrapper = mount(FPageHeader, {
29
+ propsData: { title: 'Title' },
30
+ slots: { actions: '<button>Action</button>' }
31
+ });
32
+ expect(wrapper.html()).toContain('Action');
33
+ });
34
+
35
+ it('renders default slot', () => {
36
+ const wrapper = mount(FPageHeader, {
37
+ propsData: { title: 'Title' },
38
+ slots: { default: '<span>Extra content</span>' }
39
+ });
40
+ expect(wrapper.html()).toContain('Extra content');
41
+ });
42
+
43
+ it('shows back button when showBack is true', () => {
44
+ const wrapper = mount(FPageHeader, {
45
+ propsData: { title: 'Title', showBack: true }
46
+ });
47
+ expect(wrapper.findComponent({ name: 'FButton' }).exists()).toBe(true);
48
+ });
49
+
50
+ it('hides back button by default', () => {
51
+ const wrapper = mount(FPageHeader, {
52
+ propsData: { title: 'Title' }
53
+ });
54
+ // No back button by default
55
+ expect(wrapper.html()).not.toContain('Retour');
56
+ });
57
+
58
+ it('emits back event when back button is clicked', async () => {
59
+ const wrapper = mount(FPageHeader, {
60
+ propsData: { title: 'Title', showBack: true }
61
+ });
62
+ const backButton = wrapper.findComponent({ name: 'FButton' });
63
+ if (backButton.exists()) {
64
+ await backButton.trigger('click');
65
+ expect(wrapper.emitted('back')).toBeTruthy();
66
+ }
67
+ });
68
+
69
+ it('renders breadcrumb slot', () => {
70
+ const wrapper = mount(FPageHeader, {
71
+ propsData: { title: 'Title' },
72
+ slots: { breadcrumb: '<nav>Breadcrumb</nav>' }
73
+ });
74
+ expect(wrapper.html()).toContain('Breadcrumb');
75
+ });
76
+
77
+ it('applies bordered class when bordered is true', () => {
78
+ const wrapper = mount(FPageHeader, {
79
+ propsData: { title: 'Title', bordered: true }
80
+ });
81
+ expect(wrapper.classes().join(' ')).toContain('border');
82
+ });
83
+ });