@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,132 @@
1
+ import FPagination from './FPagination.vue';
2
+
3
+ export default {
4
+ title: 'Molecules/FPagination',
5
+ component: FPagination,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ value: {
9
+ control: { type: 'number', min: 1 },
10
+ description: 'Page actuelle'
11
+ },
12
+ totalPages: {
13
+ control: { type: 'number', min: 1 },
14
+ description: 'Nombre total de pages'
15
+ },
16
+ maxVisiblePages: {
17
+ control: { type: 'number', min: 3 },
18
+ description: 'Nombre maximum de pages visibles'
19
+ },
20
+ size: {
21
+ control: { type: 'select' },
22
+ options: ['small', 'medium', 'large'],
23
+ description: 'Taille des boutons'
24
+ },
25
+ variant: {
26
+ control: { type: 'select' },
27
+ options: ['outline', 'ghost'],
28
+ description: 'Variante des boutons'
29
+ },
30
+ showLabels: {
31
+ control: 'boolean',
32
+ description: 'Afficher les labels précédent/suivant'
33
+ }
34
+ }
35
+ };
36
+
37
+ const Template = (args, { argTypes }) => ({
38
+ components: { FPagination },
39
+ props: Object.keys(argTypes),
40
+ data() {
41
+ return { currentPage: args.value || 1 };
42
+ },
43
+ template: '<FPagination v-bind="$props" v-model="currentPage" />'
44
+ });
45
+
46
+ export const Default = Template.bind({});
47
+ Default.args = {
48
+ totalPages: 10,
49
+ value: 1
50
+ };
51
+
52
+ export const MiddlePage = Template.bind({});
53
+ MiddlePage.args = {
54
+ totalPages: 20,
55
+ value: 10
56
+ };
57
+
58
+ export const FewPages = Template.bind({});
59
+ FewPages.args = {
60
+ totalPages: 3,
61
+ value: 1
62
+ };
63
+
64
+ export const ManyPages = Template.bind({});
65
+ ManyPages.args = {
66
+ totalPages: 50,
67
+ value: 25
68
+ };
69
+
70
+ export const NoLabels = Template.bind({});
71
+ NoLabels.args = {
72
+ totalPages: 10,
73
+ value: 5,
74
+ showLabels: false
75
+ };
76
+
77
+ export const Sizes = () => ({
78
+ components: { FPagination },
79
+ data() {
80
+ return { page1: 1, page2: 1, page3: 1 };
81
+ },
82
+ template: `
83
+ <div class="flex flex-col gap-6">
84
+ <div>
85
+ <p class="text-sm text-neutral-500 mb-2">Petit</p>
86
+ <FPagination v-model="page1" :totalPages="10" size="small" />
87
+ </div>
88
+ <div>
89
+ <p class="text-sm text-neutral-500 mb-2">Moyen</p>
90
+ <FPagination v-model="page2" :totalPages="10" size="medium" />
91
+ </div>
92
+ <div>
93
+ <p class="text-sm text-neutral-500 mb-2">Grand</p>
94
+ <FPagination v-model="page3" :totalPages="10" size="large" />
95
+ </div>
96
+ </div>
97
+ `
98
+ });
99
+
100
+ export const Variants = () => ({
101
+ components: { FPagination },
102
+ data() {
103
+ return { page1: 3, page2: 3 };
104
+ },
105
+ template: `
106
+ <div class="flex flex-col gap-6">
107
+ <div>
108
+ <p class="text-sm text-neutral-500 mb-2">Outline</p>
109
+ <FPagination v-model="page1" :totalPages="10" variant="outline" />
110
+ </div>
111
+ <div>
112
+ <p class="text-sm text-neutral-500 mb-2">Ghost</p>
113
+ <FPagination v-model="page2" :totalPages="10" variant="ghost" />
114
+ </div>
115
+ </div>
116
+ `
117
+ });
118
+
119
+ export const Interactive = () => ({
120
+ components: { FPagination },
121
+ data() {
122
+ return { currentPage: 1, totalPages: 20 };
123
+ },
124
+ template: `
125
+ <div class="flex flex-col gap-4">
126
+ <FPagination v-model="currentPage" :totalPages="totalPages" />
127
+ <p class="text-sm text-neutral-600">
128
+ Page {{ currentPage }} sur {{ totalPages }}
129
+ </p>
130
+ </div>
131
+ `
132
+ });
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FPagination from './FPagination.vue';
4
+
5
+ describe('FPagination', () => {
6
+ it('renders correctly with required props', () => {
7
+ const wrapper = mount(FPagination, {
8
+ propsData: { totalPages: 10 }
9
+ });
10
+ expect(wrapper.find('nav').exists()).toBe(true);
11
+ });
12
+
13
+ it('shows correct number of page buttons', () => {
14
+ const wrapper = mount(FPagination, {
15
+ propsData: { totalPages: 5, maxVisiblePages: 5 }
16
+ });
17
+ // Should show all 5 pages plus prev/next
18
+ const buttons = wrapper.findAllComponents({ name: 'FButton' });
19
+ expect(buttons.length).toBeGreaterThan(0);
20
+ });
21
+
22
+ it('highlights current page', () => {
23
+ const wrapper = mount(FPagination, {
24
+ propsData: { totalPages: 10, value: 5 }
25
+ });
26
+ expect(wrapper.find('[aria-current="page"]').exists()).toBe(true);
27
+ });
28
+
29
+ it('emits input event when page is clicked', async () => {
30
+ const wrapper = mount(FPagination, {
31
+ propsData: { totalPages: 10, value: 1 }
32
+ });
33
+ // Find a page button (not prev/next) and click it
34
+ const pageButtons = wrapper.findAllComponents({ name: 'FButton' });
35
+ // Click on one of the middle buttons
36
+ await pageButtons[2].trigger('click');
37
+ expect(wrapper.emitted('input') || wrapper.emitted('change')).toBeTruthy();
38
+ });
39
+
40
+ it('disables previous button on first page', () => {
41
+ const wrapper = mount(FPagination, {
42
+ propsData: { totalPages: 10, value: 1 }
43
+ });
44
+ const prevButton = wrapper.findAllComponents({ name: 'FButton' })[0];
45
+ expect(prevButton.props('disabled')).toBe(true);
46
+ });
47
+
48
+ it('disables next button on last page', () => {
49
+ const wrapper = mount(FPagination, {
50
+ propsData: { totalPages: 10, value: 10 }
51
+ });
52
+ const buttons = wrapper.findAllComponents({ name: 'FButton' });
53
+ const nextButton = buttons[buttons.length - 1];
54
+ expect(nextButton.props('disabled')).toBe(true);
55
+ });
56
+
57
+ it('shows ellipsis for many pages', () => {
58
+ const wrapper = mount(FPagination, {
59
+ propsData: { totalPages: 20, value: 10 }
60
+ });
61
+ expect(wrapper.text()).toContain('...');
62
+ });
63
+
64
+ it('emits change event when navigating', async () => {
65
+ const wrapper = mount(FPagination, {
66
+ propsData: { totalPages: 10, value: 5 }
67
+ });
68
+ const nextButton = wrapper.findAllComponents({ name: 'FButton' }).at(-1);
69
+ await nextButton.trigger('click');
70
+ expect(wrapper.emitted('change')).toBeTruthy();
71
+ });
72
+
73
+ it('has correct aria-label', () => {
74
+ const wrapper = mount(FPagination, {
75
+ propsData: { totalPages: 10 }
76
+ });
77
+ expect(wrapper.find('nav').attributes('aria-label')).toBe('Pagination');
78
+ });
79
+ });
@@ -0,0 +1,206 @@
1
+ <template>
2
+ <nav :class="containerClasses" role="navigation" aria-label="Pagination">
3
+ <f-button
4
+ :variant="buttonVariant"
5
+ :size="size"
6
+ :disabled="currentPage <= 1"
7
+ @click="goToPreviousPage"
8
+ >
9
+ <template #iconLeft>
10
+ <f-icon name="chevron-left" :size="iconSize" />
11
+ </template>
12
+ <span :class="{ 'sr-only': !showLabels }">{{ previousLabel }}</span>
13
+ </f-button>
14
+
15
+ <div class="flex items-center gap-1">
16
+ <template v-for="(page, index) in visiblePages">
17
+ <span
18
+ v-if="page === '...'"
19
+ :key="'ellipsis-' + index"
20
+ class="px-2 text-neutral-400"
21
+ >
22
+ ...
23
+ </span>
24
+ <template v-else>
25
+ <span v-if="page === currentPage" :key="page" aria-current="page">
26
+ <f-button
27
+ :variant="activeVariant"
28
+ :size="size"
29
+ @click="goToPage(page)"
30
+ >
31
+ {{ page }}
32
+ </f-button>
33
+ </span>
34
+ <f-button
35
+ v-else
36
+ :key="page"
37
+ :variant="buttonVariant"
38
+ :size="size"
39
+ @click="goToPage(page)"
40
+ >
41
+ {{ page }}
42
+ </f-button>
43
+ </template>
44
+ </template>
45
+ </div>
46
+
47
+ <f-button
48
+ :variant="buttonVariant"
49
+ :size="size"
50
+ :disabled="currentPage >= totalPages"
51
+ @click="goToNextPage"
52
+ >
53
+ <span :class="{ 'sr-only': !showLabels }">{{ nextLabel }}</span>
54
+ <template #iconRight>
55
+ <f-icon name="chevron-right" :size="iconSize" />
56
+ </template>
57
+ </f-button>
58
+ </nav>
59
+ </template>
60
+
61
+ <script>
62
+ import FButton from '../../atoms/FButton/FButton.vue';
63
+ import FIcon from '../../atoms/FIcon/FIcon.vue';
64
+
65
+ export default {
66
+ name: 'FPagination',
67
+ components: {
68
+ FButton,
69
+ FIcon
70
+ },
71
+ props: {
72
+ value: {
73
+ type: Number,
74
+ default: 1
75
+ },
76
+ totalPages: {
77
+ type: Number,
78
+ required: true,
79
+ validator: (value) => value >= 1
80
+ },
81
+ maxVisiblePages: {
82
+ type: Number,
83
+ default: 5,
84
+ validator: (value) => value >= 3
85
+ },
86
+ size: {
87
+ type: String,
88
+ default: 'medium',
89
+ validator: (value) => ['small', 'medium', 'large'].includes(value)
90
+ },
91
+ variant: {
92
+ type: String,
93
+ default: 'outline',
94
+ validator: (value) => ['outline', 'ghost'].includes(value)
95
+ },
96
+ previousLabel: {
97
+ type: String,
98
+ default: 'Précédent'
99
+ },
100
+ nextLabel: {
101
+ type: String,
102
+ default: 'Suivant'
103
+ },
104
+ showLabels: {
105
+ type: Boolean,
106
+ default: true
107
+ }
108
+ },
109
+ computed: {
110
+ currentPage() {
111
+ return this.value;
112
+ },
113
+ buttonVariant() {
114
+ return this.variant;
115
+ },
116
+ activeVariant() {
117
+ return 'primary';
118
+ },
119
+ iconSize() {
120
+ const sizeMap = {
121
+ small: 'sm',
122
+ medium: 'sm',
123
+ large: 'md'
124
+ };
125
+ return sizeMap[this.size];
126
+ },
127
+ containerClasses() {
128
+ return 'flex items-center gap-2';
129
+ },
130
+ visiblePages() {
131
+ const total = this.totalPages;
132
+ const current = this.currentPage;
133
+ const max = this.maxVisiblePages;
134
+
135
+ if (total <= max) {
136
+ return this.range(1, total);
137
+ }
138
+
139
+ const half = Math.floor((max - 1) / 2);
140
+ let start = current - half;
141
+ let end = current + (max - 1 - half);
142
+
143
+ if (start < 1) {
144
+ start = 1;
145
+ end = max;
146
+ }
147
+
148
+ if (end > total) {
149
+ end = total;
150
+ start = total - max + 1;
151
+ }
152
+
153
+ const pages = [];
154
+
155
+ if (start > 1) {
156
+ pages.push(1);
157
+ if (start > 2) {
158
+ pages.push('...');
159
+ }
160
+ }
161
+
162
+ for (let i = start; i <= end; i++) {
163
+ if (i >= 1 && i <= total) {
164
+ // Skip page 1 if already added in the first block
165
+ // (removed unreachable condition)
166
+ pages.push(i);
167
+ }
168
+ }
169
+
170
+ if (end < total) {
171
+ if (end < total - 1) {
172
+ pages.push('...');
173
+ }
174
+ pages.push(total);
175
+ }
176
+
177
+ return pages;
178
+ }
179
+ },
180
+ methods: {
181
+ range(start, end) {
182
+ const result = [];
183
+ for (let i = start; i <= end; i++) {
184
+ result.push(i);
185
+ }
186
+ return result;
187
+ },
188
+ goToPage(page) {
189
+ if (page !== this.currentPage && page >= 1 && page <= this.totalPages) {
190
+ this.$emit('input', page);
191
+ this.$emit('change', page);
192
+ }
193
+ },
194
+ goToPreviousPage() {
195
+ if (this.currentPage > 1) {
196
+ this.goToPage(this.currentPage - 1);
197
+ }
198
+ },
199
+ goToNextPage() {
200
+ if (this.currentPage < this.totalPages) {
201
+ this.goToPage(this.currentPage + 1);
202
+ }
203
+ }
204
+ }
205
+ };
206
+ </script>
@@ -0,0 +1,129 @@
1
+ import FSearchBar from './FSearchBar.vue';
2
+
3
+ export default {
4
+ title: 'Molecules/FSearchBar',
5
+ component: FSearchBar,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ value: {
9
+ control: 'text',
10
+ description: 'Valeur de recherche'
11
+ },
12
+ placeholder: {
13
+ control: 'text',
14
+ description: 'Placeholder'
15
+ },
16
+ size: {
17
+ control: { type: 'select' },
18
+ options: ['small', 'medium', 'large'],
19
+ description: 'Taille de la barre'
20
+ },
21
+ iconPosition: {
22
+ control: { type: 'select' },
23
+ options: ['inside', 'outside'],
24
+ description: "Position de l'icône"
25
+ },
26
+ buttonMode: {
27
+ control: 'boolean',
28
+ description: 'Afficher un bouton de recherche'
29
+ },
30
+ buttonLabel: {
31
+ control: 'text',
32
+ description: 'Libellé du bouton'
33
+ },
34
+ disabled: {
35
+ control: 'boolean',
36
+ description: 'État désactivé'
37
+ }
38
+ }
39
+ };
40
+
41
+ const Template = (args, { argTypes }) => ({
42
+ components: { FSearchBar },
43
+ props: Object.keys(argTypes),
44
+ data() {
45
+ return { searchValue: args.value || '' };
46
+ },
47
+ template: '<FSearchBar v-bind="$props" v-model="searchValue" />'
48
+ });
49
+
50
+ export const Default = Template.bind({});
51
+ Default.args = {
52
+ placeholder: 'Rechercher...'
53
+ };
54
+
55
+ export const WithValue = Template.bind({});
56
+ WithValue.args = {
57
+ value: 'Terme de recherche'
58
+ };
59
+
60
+ export const IconOutside = Template.bind({});
61
+ IconOutside.args = {
62
+ placeholder: 'Rechercher...',
63
+ iconPosition: 'outside'
64
+ };
65
+
66
+ export const WithButton = Template.bind({});
67
+ WithButton.args = {
68
+ placeholder: 'Rechercher...',
69
+ buttonMode: true,
70
+ buttonLabel: 'Rechercher'
71
+ };
72
+
73
+ export const Disabled = Template.bind({});
74
+ Disabled.args = {
75
+ placeholder: 'Recherche désactivée',
76
+ disabled: true
77
+ };
78
+
79
+ export const Sizes = () => ({
80
+ components: { FSearchBar },
81
+ template: `
82
+ <div class="flex flex-col gap-4">
83
+ <FSearchBar size="small" placeholder="Petit" />
84
+ <FSearchBar size="medium" placeholder="Moyen" />
85
+ <FSearchBar size="large" placeholder="Grand" />
86
+ </div>
87
+ `
88
+ });
89
+
90
+ export const Interactive = () => ({
91
+ components: { FSearchBar },
92
+ data() {
93
+ return {
94
+ query: '',
95
+ results: []
96
+ };
97
+ },
98
+ methods: {
99
+ handleSearch(q) {
100
+ this.results = [
101
+ `Résultat 1 pour "${q}"`,
102
+ `Résultat 2 pour "${q}"`,
103
+ `Résultat 3 pour "${q}"`
104
+ ];
105
+ }
106
+ },
107
+ template: `
108
+ <div class="flex flex-col gap-4">
109
+ <FSearchBar
110
+ v-model="query"
111
+ placeholder="Tapez et appuyez sur Entrée..."
112
+ @search="handleSearch"
113
+ />
114
+ <div v-if="results.length" class="text-sm text-neutral-600">
115
+ <p v-for="result in results" :key="result">{{ result }}</p>
116
+ </div>
117
+ </div>
118
+ `
119
+ });
120
+
121
+ export const InHeader = () => ({
122
+ components: { FSearchBar },
123
+ template: `
124
+ <div class="bg-neutral-100 p-4 rounded-lg flex items-center justify-between gap-4">
125
+ <span class="font-semibold">Mon Application</span>
126
+ <FSearchBar placeholder="Rechercher..." size="small" class="w-64" />
127
+ </div>
128
+ `
129
+ });
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FSearchBar from './FSearchBar.vue';
4
+
5
+ describe('FSearchBar', () => {
6
+ it('renders correctly with default props', () => {
7
+ const wrapper = mount(FSearchBar);
8
+ expect(wrapper.find('input').exists()).toBe(true);
9
+ });
10
+
11
+ it('displays placeholder', () => {
12
+ const wrapper = mount(FSearchBar, {
13
+ propsData: { placeholder: 'Search...' }
14
+ });
15
+ expect(wrapper.find('input').attributes('placeholder')).toBe('Search...');
16
+ });
17
+
18
+ it('displays value', () => {
19
+ const wrapper = mount(FSearchBar, {
20
+ propsData: { value: 'test query' }
21
+ });
22
+ expect((wrapper.find('input').element as HTMLInputElement).value).toBe(
23
+ 'test query'
24
+ );
25
+ });
26
+
27
+ it('emits input event when typing', async () => {
28
+ const wrapper = mount(FSearchBar);
29
+ await wrapper.find('input').setValue('new query');
30
+ expect(wrapper.emitted('input')).toBeTruthy();
31
+ });
32
+
33
+ it('emits search event on enter', async () => {
34
+ const wrapper = mount(FSearchBar, {
35
+ propsData: { value: 'query' }
36
+ });
37
+ await wrapper.find('input').trigger('keydown.enter');
38
+ expect(wrapper.emitted('search')).toBeTruthy();
39
+ });
40
+
41
+ it('shows inside icon by default', () => {
42
+ const wrapper = mount(FSearchBar);
43
+ expect(wrapper.findComponent({ name: 'FIcon' }).exists()).toBe(true);
44
+ });
45
+
46
+ it('shows button when buttonMode is true', () => {
47
+ const wrapper = mount(FSearchBar, {
48
+ propsData: { buttonMode: true }
49
+ });
50
+ expect(wrapper.findComponent({ name: 'FButton' }).exists()).toBe(true);
51
+ });
52
+
53
+ it('shows custom button label', () => {
54
+ const wrapper = mount(FSearchBar, {
55
+ propsData: { buttonMode: true, buttonLabel: 'Find' }
56
+ });
57
+ expect(wrapper.text()).toContain('Find');
58
+ });
59
+
60
+ it('applies disabled state', () => {
61
+ const wrapper = mount(FSearchBar, {
62
+ propsData: { disabled: true }
63
+ });
64
+ expect(wrapper.find('input').attributes('disabled')).toBeDefined();
65
+ });
66
+
67
+ it('applies correct size classes', () => {
68
+ const sizes = ['small', 'medium', 'large'] as const;
69
+ sizes.forEach((size) => {
70
+ const wrapper = mount(FSearchBar, {
71
+ propsData: { size }
72
+ });
73
+ expect(wrapper.find('input').exists()).toBe(true);
74
+ });
75
+ });
76
+
77
+ it('has focus method', () => {
78
+ const wrapper = mount(FSearchBar);
79
+ expect(typeof wrapper.vm.focus).toBe('function');
80
+ });
81
+ });