@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.
- package/README.md +119 -0
- package/dist/fabric.cjs.js +18109 -0
- package/dist/fabric.css +2180 -0
- package/dist/fabric.esm.js +18062 -0
- package/dist/fabric.min.js +18112 -0
- package/dist/types/components/atoms/FAvatar/FAvatar.test.d.ts +1 -0
- package/dist/types/components/atoms/FBadge/FBadge.test.d.ts +1 -0
- package/dist/types/components/atoms/FButton/FButton.test.d.ts +1 -0
- package/dist/types/components/atoms/FCheckbox/FCheckbox.test.d.ts +1 -0
- package/dist/types/components/atoms/FDivider/FDivider.test.d.ts +1 -0
- package/dist/types/components/atoms/FIcon/FIcon.test.d.ts +1 -0
- package/dist/types/components/atoms/FInput/FInput.test.d.ts +1 -0
- package/dist/types/components/atoms/FLoader/FLoader.test.d.ts +1 -0
- package/dist/types/components/atoms/FRadio/FRadio.test.d.ts +1 -0
- package/dist/types/components/atoms/FTextarea/FTextarea.test.d.ts +1 -0
- package/dist/types/components/atoms/FToggle/FToggle.test.d.ts +1 -0
- package/dist/types/components/atoms/FTypography/FTypography.test.d.ts +1 -0
- package/dist/types/components/atoms/index.d.ts +13 -0
- package/dist/types/components/molecules/FAccordionItem/FAccordionItem.test.d.ts +1 -0
- package/dist/types/components/molecules/FAlert/FAlert.test.d.ts +1 -0
- package/dist/types/components/molecules/FBreadcrumb/FBreadcrumb.test.d.ts +1 -0
- package/dist/types/components/molecules/FButtonGroup/FButtonGroup.test.d.ts +1 -0
- package/dist/types/components/molecules/FCard/FCard.test.d.ts +1 -0
- package/dist/types/components/molecules/FDatePicker/FDatePicker.test.d.ts +1 -0
- package/dist/types/components/molecules/FEmptyState/FEmptyState.test.d.ts +1 -0
- package/dist/types/components/molecules/FFilePreview/FFilePreview.test.d.ts +1 -0
- package/dist/types/components/molecules/FFormField/FFormField.test.d.ts +1 -0
- package/dist/types/components/molecules/FListItem/FListItem.test.d.ts +1 -0
- package/dist/types/components/molecules/FPagination/FPagination.test.d.ts +1 -0
- package/dist/types/components/molecules/FSearchBar/FSearchBar.test.d.ts +1 -0
- package/dist/types/components/molecules/FSelect/FSelect.test.d.ts +1 -0
- package/dist/types/components/molecules/FStatCard/FStatCard.test.d.ts +1 -0
- package/dist/types/components/molecules/FTabs/FTabs.test.d.ts +1 -0
- package/dist/types/components/molecules/FToast/FToast.test.d.ts +1 -0
- package/dist/types/components/molecules/index.d.ts +18 -0
- package/dist/types/components/organisms/FActivityFeed/FActivityFeed.test.d.ts +1 -0
- package/dist/types/components/organisms/FDataTable/FDataTable.test.d.ts +1 -0
- package/dist/types/components/organisms/FDrawer/FDrawer.test.d.ts +1 -0
- package/dist/types/components/organisms/FFileUpload/FFileUpload.test.d.ts +1 -0
- package/dist/types/components/organisms/FFilterSidebar/FFilterSidebar.test.d.ts +1 -0
- package/dist/types/components/organisms/FForm/FForm.test.d.ts +1 -0
- package/dist/types/components/organisms/FModal/FModal.test.d.ts +1 -0
- package/dist/types/components/organisms/FNavigationSidebar/FNavigationSidebar.test.d.ts +1 -0
- package/dist/types/components/organisms/FOnboardingStepper/FOnboardingStepper.test.d.ts +1 -0
- package/dist/types/components/organisms/FOnboardingStepper/FStepperProgress.test.d.ts +1 -0
- package/dist/types/components/organisms/FPageHeader/FPageHeader.test.d.ts +1 -0
- package/dist/types/components/organisms/FProfileSection/FProfileSection.test.d.ts +1 -0
- package/dist/types/components/organisms/FToastProvider/FToastProvider.test.d.ts +1 -0
- package/dist/types/components/organisms/FUserMenu/FUserMenu.test.d.ts +1 -0
- package/dist/types/components/organisms/index.d.ts +14 -0
- package/dist/types/components/utils/FThemeProvider.test.d.ts +1 -0
- package/dist/types/components/utils/index.d.ts +2 -0
- package/dist/types/components.d.ts +602 -0
- package/dist/types/composables/index.d.ts +12 -0
- package/dist/types/composables/useDataTableState.d.ts +106 -0
- package/dist/types/composables/useDataTableState.test.d.ts +1 -0
- package/dist/types/composables/useFormValidation.d.ts +49 -0
- package/dist/types/composables/useFormValidation.test.d.ts +1 -0
- package/dist/types/composables/useSidebarState.d.ts +65 -0
- package/dist/types/composables/useSidebarState.test.d.ts +1 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/types.d.ts +529 -0
- package/package.json +100 -0
- package/src/components/atoms/FAvatar/FAvatar.stories.js +100 -0
- package/src/components/atoms/FAvatar/FAvatar.test.ts +95 -0
- package/src/components/atoms/FAvatar/FAvatar.vue +190 -0
- package/src/components/atoms/FBadge/FBadge.stories.js +129 -0
- package/src/components/atoms/FBadge/FBadge.test.ts +93 -0
- package/src/components/atoms/FBadge/FBadge.vue +103 -0
- package/src/components/atoms/FButton/FButton.stories.js +122 -0
- package/src/components/atoms/FButton/FButton.test.ts +98 -0
- package/src/components/atoms/FButton/FButton.vue +147 -0
- package/src/components/atoms/FCheckbox/FCheckbox.stories.js +96 -0
- package/src/components/atoms/FCheckbox/FCheckbox.test.ts +64 -0
- package/src/components/atoms/FCheckbox/FCheckbox.vue +76 -0
- package/src/components/atoms/FDivider/FDivider.stories.js +104 -0
- package/src/components/atoms/FDivider/FDivider.test.ts +80 -0
- package/src/components/atoms/FDivider/FDivider.vue +117 -0
- package/src/components/atoms/FIcon/FIcon.stories.js +189 -0
- package/src/components/atoms/FIcon/FIcon.test.ts +99 -0
- package/src/components/atoms/FIcon/FIcon.vue +192 -0
- package/src/components/atoms/FInput/FInput.stories.js +119 -0
- package/src/components/atoms/FInput/FInput.test.ts +79 -0
- package/src/components/atoms/FInput/FInput.vue +88 -0
- package/src/components/atoms/FLoader/FLoader.stories.js +109 -0
- package/src/components/atoms/FLoader/FLoader.test.ts +66 -0
- package/src/components/atoms/FLoader/FLoader.vue +97 -0
- package/src/components/atoms/FRadio/FRadio.stories.js +105 -0
- package/src/components/atoms/FRadio/FRadio.test.ts +75 -0
- package/src/components/atoms/FRadio/FRadio.vue +119 -0
- package/src/components/atoms/FTextarea/FTextarea.stories.js +126 -0
- package/src/components/atoms/FTextarea/FTextarea.test.ts +94 -0
- package/src/components/atoms/FTextarea/FTextarea.vue +156 -0
- package/src/components/atoms/FToggle/FToggle.stories.js +108 -0
- package/src/components/atoms/FToggle/FToggle.test.ts +96 -0
- package/src/components/atoms/FToggle/FToggle.vue +123 -0
- package/src/components/atoms/FTypography/FTypography.stories.js +127 -0
- package/src/components/atoms/FTypography/FTypography.test.ts +93 -0
- package/src/components/atoms/FTypography/FTypography.vue +78 -0
- package/src/components/atoms/index.ts +27 -0
- package/src/components/molecules/FAccordionItem/FAccordionItem.stories.js +71 -0
- package/src/components/molecules/FAccordionItem/FAccordionItem.test.ts +61 -0
- package/src/components/molecules/FAccordionItem/FAccordionItem.vue +105 -0
- package/src/components/molecules/FAlert/FAlert.stories.js +87 -0
- package/src/components/molecules/FAlert/FAlert.test.ts +59 -0
- package/src/components/molecules/FAlert/FAlert.vue +108 -0
- package/src/components/molecules/FBreadcrumb/FBreadcrumb.stories.js +90 -0
- package/src/components/molecules/FBreadcrumb/FBreadcrumb.test.ts +76 -0
- package/src/components/molecules/FBreadcrumb/FBreadcrumb.vue +117 -0
- package/src/components/molecules/FButtonGroup/FButtonGroup.stories.js +82 -0
- package/src/components/molecules/FButtonGroup/FButtonGroup.test.ts +44 -0
- package/src/components/molecules/FButtonGroup/FButtonGroup.vue +31 -0
- package/src/components/molecules/FCard/FCard.stories.js +136 -0
- package/src/components/molecules/FCard/FCard.test.ts +87 -0
- package/src/components/molecules/FCard/FCard.vue +75 -0
- package/src/components/molecules/FDatePicker/FDatePicker.stories.js +305 -0
- package/src/components/molecules/FDatePicker/FDatePicker.test.ts +282 -0
- package/src/components/molecules/FDatePicker/FDatePicker.vue +750 -0
- package/src/components/molecules/FEmptyState/FEmptyState.stories.js +98 -0
- package/src/components/molecules/FEmptyState/FEmptyState.test.ts +82 -0
- package/src/components/molecules/FEmptyState/FEmptyState.vue +89 -0
- package/src/components/molecules/FFilePreview/FFilePreview.stories.js +130 -0
- package/src/components/molecules/FFilePreview/FFilePreview.test.ts +70 -0
- package/src/components/molecules/FFilePreview/FFilePreview.vue +125 -0
- package/src/components/molecules/FFormField/FFormField.stories.js +149 -0
- package/src/components/molecules/FFormField/FFormField.test.ts +85 -0
- package/src/components/molecules/FFormField/FFormField.vue +107 -0
- package/src/components/molecules/FListItem/FListItem.stories.js +158 -0
- package/src/components/molecules/FListItem/FListItem.test.ts +93 -0
- package/src/components/molecules/FListItem/FListItem.vue +113 -0
- package/src/components/molecules/FPagination/FPagination.stories.js +132 -0
- package/src/components/molecules/FPagination/FPagination.test.ts +79 -0
- package/src/components/molecules/FPagination/FPagination.vue +206 -0
- package/src/components/molecules/FSearchBar/FSearchBar.stories.js +129 -0
- package/src/components/molecules/FSearchBar/FSearchBar.test.ts +81 -0
- package/src/components/molecules/FSearchBar/FSearchBar.vue +180 -0
- package/src/components/molecules/FSelect/FSelect.stories.js +333 -0
- package/src/components/molecules/FSelect/FSelect.test.ts +478 -0
- package/src/components/molecules/FSelect/FSelect.vue +551 -0
- package/src/components/molecules/FStatCard/FStatCard.stories.js +144 -0
- package/src/components/molecules/FStatCard/FStatCard.test.ts +78 -0
- package/src/components/molecules/FStatCard/FStatCard.vue +106 -0
- package/src/components/molecules/FTabs/FTab.vue +63 -0
- package/src/components/molecules/FTabs/FTabs.stories.js +277 -0
- package/src/components/molecules/FTabs/FTabs.test.ts +264 -0
- package/src/components/molecules/FTabs/FTabs.vue +273 -0
- package/src/components/molecules/FToast/FToast.stories.js +150 -0
- package/src/components/molecules/FToast/FToast.test.ts +157 -0
- package/src/components/molecules/FToast/FToast.vue +283 -0
- package/src/components/molecules/index.ts +37 -0
- package/src/components/organisms/FActivityFeed/FActivityFeed.stories.js +217 -0
- package/src/components/organisms/FActivityFeed/FActivityFeed.test.ts +134 -0
- package/src/components/organisms/FActivityFeed/FActivityFeed.vue +589 -0
- package/src/components/organisms/FDataTable/FDataTable.stories.js +370 -0
- package/src/components/organisms/FDataTable/FDataTable.test.ts +248 -0
- package/src/components/organisms/FDataTable/FDataTable.vue +808 -0
- package/src/components/organisms/FDrawer/FDrawer.stories.js +296 -0
- package/src/components/organisms/FDrawer/FDrawer.test.ts +142 -0
- package/src/components/organisms/FDrawer/FDrawer.vue +303 -0
- package/src/components/organisms/FFileUpload/FFileUpload.stories.js +162 -0
- package/src/components/organisms/FFileUpload/FFileUpload.test.ts +103 -0
- package/src/components/organisms/FFileUpload/FFileUpload.vue +616 -0
- package/src/components/organisms/FFilterSidebar/FFilterSidebar.stories.js +161 -0
- package/src/components/organisms/FFilterSidebar/FFilterSidebar.test.ts +92 -0
- package/src/components/organisms/FFilterSidebar/FFilterSidebar.vue +458 -0
- package/src/components/organisms/FForm/FForm.stories.js +270 -0
- package/src/components/organisms/FForm/FForm.test.ts +63 -0
- package/src/components/organisms/FForm/FForm.vue +19 -0
- package/src/components/organisms/FModal/FModal.stories.js +227 -0
- package/src/components/organisms/FModal/FModal.test.ts +181 -0
- package/src/components/organisms/FModal/FModal.vue +319 -0
- package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.stories.js +176 -0
- package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.test.ts +95 -0
- package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.vue +577 -0
- package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.stories.js +197 -0
- package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.test.ts +114 -0
- package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.vue +212 -0
- package/src/components/organisms/FOnboardingStepper/FStepperProgress.stories.js +122 -0
- package/src/components/organisms/FOnboardingStepper/FStepperProgress.test.ts +130 -0
- package/src/components/organisms/FOnboardingStepper/FStepperProgress.vue +146 -0
- package/src/components/organisms/FPageHeader/FPageHeader.stories.js +142 -0
- package/src/components/organisms/FPageHeader/FPageHeader.test.ts +83 -0
- package/src/components/organisms/FPageHeader/FPageHeader.vue +241 -0
- package/src/components/organisms/FProfileSection/FProfileSection.stories.js +190 -0
- package/src/components/organisms/FProfileSection/FProfileSection.test.ts +85 -0
- package/src/components/organisms/FProfileSection/FProfileSection.vue +562 -0
- package/src/components/organisms/FToastProvider/FToastProvider.stories.js +290 -0
- package/src/components/organisms/FToastProvider/FToastProvider.test.ts +215 -0
- package/src/components/organisms/FToastProvider/FToastProvider.vue +214 -0
- package/src/components/organisms/FUserMenu/FUserMenu.stories.js +170 -0
- package/src/components/organisms/FUserMenu/FUserMenu.test.ts +102 -0
- package/src/components/organisms/FUserMenu/FUserMenu.vue +407 -0
- package/src/components/organisms/index.ts +29 -0
- package/src/components/utils/FThemeProvider.stories.js +236 -0
- package/src/components/utils/FThemeProvider.test.ts +244 -0
- package/src/components/utils/FThemeProvider.vue +191 -0
- package/src/components/utils/index.ts +3 -0
- package/src/components.d.ts +602 -0
- package/src/composables/README.md +233 -0
- package/src/composables/index.ts +25 -0
- package/src/composables/useDataTableState.test.ts +378 -0
- package/src/composables/useDataTableState.ts +361 -0
- package/src/composables/useFormValidation.test.ts +198 -0
- package/src/composables/useFormValidation.ts +178 -0
- package/src/composables/useSidebarState.test.ts +307 -0
- package/src/composables/useSidebarState.ts +201 -0
- package/src/env.d.ts +14 -0
- package/src/index.ts +167 -0
- package/src/styles/tailwind.css +173 -0
- 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
|
+
});
|