@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,98 @@
|
|
|
1
|
+
import FEmptyState from './FEmptyState.vue';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Molecules/FEmptyState',
|
|
5
|
+
component: FEmptyState,
|
|
6
|
+
tags: ['autodocs'],
|
|
7
|
+
argTypes: {
|
|
8
|
+
icon: {
|
|
9
|
+
control: 'text',
|
|
10
|
+
description: 'Icône à afficher'
|
|
11
|
+
},
|
|
12
|
+
title: {
|
|
13
|
+
control: 'text',
|
|
14
|
+
description: 'Titre principal'
|
|
15
|
+
},
|
|
16
|
+
description: {
|
|
17
|
+
control: 'text',
|
|
18
|
+
description: 'Description détaillée'
|
|
19
|
+
},
|
|
20
|
+
actionLabel: {
|
|
21
|
+
control: 'text',
|
|
22
|
+
description: "Libellé du bouton d'action"
|
|
23
|
+
},
|
|
24
|
+
actionVariant: {
|
|
25
|
+
control: { type: 'select' },
|
|
26
|
+
options: ['primary', 'secondary', 'outline', 'ghost', 'link'],
|
|
27
|
+
description: "Variante du bouton d'action"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const Template = (args, { argTypes }) => ({
|
|
33
|
+
components: { FEmptyState },
|
|
34
|
+
props: Object.keys(argTypes),
|
|
35
|
+
template: '<FEmptyState v-bind="$props" />'
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const Default = Template.bind({});
|
|
39
|
+
Default.args = {
|
|
40
|
+
title: 'Aucune donnée',
|
|
41
|
+
description: "Il n'y a aucun élément à afficher pour le moment."
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const WithAction = Template.bind({});
|
|
45
|
+
WithAction.args = {
|
|
46
|
+
title: 'Aucun projet',
|
|
47
|
+
description: "Vous n'avez pas encore créé de projet.",
|
|
48
|
+
actionLabel: 'Créer un projet',
|
|
49
|
+
actionVariant: 'primary'
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const NoResults = Template.bind({});
|
|
53
|
+
NoResults.args = {
|
|
54
|
+
icon: 'search',
|
|
55
|
+
title: 'Aucun résultat',
|
|
56
|
+
description: 'Aucun élément ne correspond à votre recherche.',
|
|
57
|
+
actionLabel: 'Effacer les filtres'
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const NoDocuments = Template.bind({});
|
|
61
|
+
NoDocuments.args = {
|
|
62
|
+
icon: 'document',
|
|
63
|
+
title: 'Aucun document',
|
|
64
|
+
description: 'Téléversez un document pour commencer.',
|
|
65
|
+
actionLabel: 'Téléverser un fichier'
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const NoNotifications = Template.bind({});
|
|
69
|
+
NoNotifications.args = {
|
|
70
|
+
icon: 'bell',
|
|
71
|
+
title: 'Pas de notifications',
|
|
72
|
+
description: 'Vous êtes à jour ! Aucune nouvelle notification.'
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const WithSlot = () => ({
|
|
76
|
+
components: { FEmptyState },
|
|
77
|
+
template: `
|
|
78
|
+
<FEmptyState icon="folder" title="Dossier vide">
|
|
79
|
+
<p class="text-sm text-neutral-500 mt-2">
|
|
80
|
+
Faites glisser des fichiers ici ou <a href="#" class="text-primary-600 underline">parcourez</a> votre ordinateur.
|
|
81
|
+
</p>
|
|
82
|
+
</FEmptyState>
|
|
83
|
+
`
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const AllIcons = () => ({
|
|
87
|
+
components: { FEmptyState },
|
|
88
|
+
template: `
|
|
89
|
+
<div class="grid grid-cols-3 gap-8">
|
|
90
|
+
<FEmptyState icon="folder" title="Dossier vide" />
|
|
91
|
+
<FEmptyState icon="mail" title="Pas de messages" />
|
|
92
|
+
<FEmptyState icon="user" title="Aucun utilisateur" />
|
|
93
|
+
<FEmptyState icon="calendar" title="Aucun événement" />
|
|
94
|
+
<FEmptyState icon="bell" title="Pas de notifications" />
|
|
95
|
+
<FEmptyState icon="heart" title="Aucun favori" />
|
|
96
|
+
</div>
|
|
97
|
+
`
|
|
98
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import FEmptyState from './FEmptyState.vue';
|
|
4
|
+
|
|
5
|
+
describe('FEmptyState', () => {
|
|
6
|
+
it('renders correctly with required props', () => {
|
|
7
|
+
const wrapper = mount(FEmptyState, {
|
|
8
|
+
propsData: { title: 'No data' }
|
|
9
|
+
});
|
|
10
|
+
expect(wrapper.text()).toContain('No data');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('displays title', () => {
|
|
14
|
+
const wrapper = mount(FEmptyState, {
|
|
15
|
+
propsData: { title: 'Empty State Title' }
|
|
16
|
+
});
|
|
17
|
+
expect(wrapper.text()).toContain('Empty State Title');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('displays description when provided', () => {
|
|
21
|
+
const wrapper = mount(FEmptyState, {
|
|
22
|
+
propsData: { title: 'Title', description: 'Description text' }
|
|
23
|
+
});
|
|
24
|
+
expect(wrapper.text()).toContain('Description text');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('displays custom icon', () => {
|
|
28
|
+
const wrapper = mount(FEmptyState, {
|
|
29
|
+
propsData: { title: 'Title', icon: 'search' }
|
|
30
|
+
});
|
|
31
|
+
expect(wrapper.findComponent({ name: 'FIcon' }).exists()).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('shows action button when actionLabel is provided', () => {
|
|
35
|
+
const wrapper = mount(FEmptyState, {
|
|
36
|
+
propsData: { title: 'Title', actionLabel: 'Create New' }
|
|
37
|
+
});
|
|
38
|
+
expect(wrapper.findComponent({ name: 'FButton' }).exists()).toBe(true);
|
|
39
|
+
expect(wrapper.text()).toContain('Create New');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('hides action button when actionLabel is empty', () => {
|
|
43
|
+
const wrapper = mount(FEmptyState, {
|
|
44
|
+
propsData: { title: 'Title' }
|
|
45
|
+
});
|
|
46
|
+
expect(wrapper.findComponent({ name: 'FButton' }).exists()).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('emits action event when button is clicked', async () => {
|
|
50
|
+
const wrapper = mount(FEmptyState, {
|
|
51
|
+
propsData: { title: 'Title', actionLabel: 'Click me' }
|
|
52
|
+
});
|
|
53
|
+
await wrapper.findComponent({ name: 'FButton' }).trigger('click');
|
|
54
|
+
expect(wrapper.emitted('action')).toBeTruthy();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('applies correct action variant', () => {
|
|
58
|
+
const wrapper = mount(FEmptyState, {
|
|
59
|
+
propsData: {
|
|
60
|
+
title: 'Title',
|
|
61
|
+
actionLabel: 'Action',
|
|
62
|
+
actionVariant: 'secondary'
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
expect(wrapper.findComponent({ name: 'FButton' }).exists()).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('renders slot content', () => {
|
|
69
|
+
const wrapper = mount(FEmptyState, {
|
|
70
|
+
propsData: { title: 'Title' },
|
|
71
|
+
slots: { default: '<p>Custom content</p>' }
|
|
72
|
+
});
|
|
73
|
+
expect(wrapper.html()).toContain('Custom content');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('has correct role for accessibility', () => {
|
|
77
|
+
const wrapper = mount(FEmptyState, {
|
|
78
|
+
propsData: { title: 'Title' }
|
|
79
|
+
});
|
|
80
|
+
expect(wrapper.find('[role="status"]').exists()).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="containerClasses" role="status" aria-live="polite">
|
|
3
|
+
<f-icon :name="icon" size="xl" :decorative="true" :class="iconClasses" />
|
|
4
|
+
<f-typography variant="h5" :class="titleClasses">
|
|
5
|
+
{{ title }}
|
|
6
|
+
</f-typography>
|
|
7
|
+
<f-typography v-if="description" variant="body" :class="descriptionClasses">
|
|
8
|
+
{{ description }}
|
|
9
|
+
</f-typography>
|
|
10
|
+
<slot />
|
|
11
|
+
<f-button
|
|
12
|
+
v-if="actionLabel"
|
|
13
|
+
:variant="actionVariant"
|
|
14
|
+
:class="actionClasses"
|
|
15
|
+
@click="handleAction"
|
|
16
|
+
>
|
|
17
|
+
{{ actionLabel }}
|
|
18
|
+
</f-button>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
import FIcon from '../../atoms/FIcon/FIcon.vue';
|
|
24
|
+
import FTypography from '../../atoms/FTypography/FTypography.vue';
|
|
25
|
+
import FButton from '../../atoms/FButton/FButton.vue';
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
name: 'FEmptyState',
|
|
29
|
+
components: {
|
|
30
|
+
FIcon,
|
|
31
|
+
FTypography,
|
|
32
|
+
FButton
|
|
33
|
+
},
|
|
34
|
+
props: {
|
|
35
|
+
icon: {
|
|
36
|
+
type: String,
|
|
37
|
+
default: 'folder'
|
|
38
|
+
},
|
|
39
|
+
title: {
|
|
40
|
+
type: String,
|
|
41
|
+
required: true
|
|
42
|
+
},
|
|
43
|
+
description: {
|
|
44
|
+
type: String,
|
|
45
|
+
default: ''
|
|
46
|
+
},
|
|
47
|
+
actionLabel: {
|
|
48
|
+
type: String,
|
|
49
|
+
default: ''
|
|
50
|
+
},
|
|
51
|
+
actionVariant: {
|
|
52
|
+
type: String,
|
|
53
|
+
default: 'primary',
|
|
54
|
+
validator: (value) =>
|
|
55
|
+
[
|
|
56
|
+
'primary',
|
|
57
|
+
'secondary',
|
|
58
|
+
'danger',
|
|
59
|
+
'success',
|
|
60
|
+
'outline',
|
|
61
|
+
'ghost',
|
|
62
|
+
'link'
|
|
63
|
+
].includes(value)
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
computed: {
|
|
67
|
+
containerClasses() {
|
|
68
|
+
return 'flex flex-col items-center justify-center text-center py-12 px-4';
|
|
69
|
+
},
|
|
70
|
+
iconClasses() {
|
|
71
|
+
return 'text-neutral-400 mb-4';
|
|
72
|
+
},
|
|
73
|
+
titleClasses() {
|
|
74
|
+
return 'text-neutral-700 mb-2';
|
|
75
|
+
},
|
|
76
|
+
descriptionClasses() {
|
|
77
|
+
return 'text-neutral-500 max-w-md mb-6';
|
|
78
|
+
},
|
|
79
|
+
actionClasses() {
|
|
80
|
+
return 'mt-4';
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
methods: {
|
|
84
|
+
handleAction(event) {
|
|
85
|
+
this.$emit('action', event);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
</script>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import FFilePreview from './FFilePreview.vue';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Molecules/FFilePreview',
|
|
5
|
+
component: FFilePreview,
|
|
6
|
+
tags: ['autodocs'],
|
|
7
|
+
argTypes: {
|
|
8
|
+
fileName: {
|
|
9
|
+
control: 'text',
|
|
10
|
+
description: 'Nom du fichier'
|
|
11
|
+
},
|
|
12
|
+
fileType: {
|
|
13
|
+
control: 'text',
|
|
14
|
+
description: 'Type de fichier (extension)'
|
|
15
|
+
},
|
|
16
|
+
loading: {
|
|
17
|
+
control: 'boolean',
|
|
18
|
+
description: 'État de chargement'
|
|
19
|
+
},
|
|
20
|
+
disabled: {
|
|
21
|
+
control: 'boolean',
|
|
22
|
+
description: 'État désactivé'
|
|
23
|
+
},
|
|
24
|
+
loadingLabel: {
|
|
25
|
+
control: 'text',
|
|
26
|
+
description: 'Label de chargement'
|
|
27
|
+
},
|
|
28
|
+
removeLabel: {
|
|
29
|
+
control: 'text',
|
|
30
|
+
description: 'Label du bouton supprimer'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const Template = (args, { argTypes }) => ({
|
|
36
|
+
components: { FFilePreview },
|
|
37
|
+
props: Object.keys(argTypes),
|
|
38
|
+
template: '<FFilePreview v-bind="$props" />'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const Default = Template.bind({});
|
|
42
|
+
Default.args = {
|
|
43
|
+
fileName: 'document.pdf'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Loading = Template.bind({});
|
|
47
|
+
Loading.args = {
|
|
48
|
+
fileName: 'rapport-2024.pdf',
|
|
49
|
+
loading: true
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const Disabled = Template.bind({});
|
|
53
|
+
Disabled.args = {
|
|
54
|
+
fileName: 'fichier-verrouillé.docx',
|
|
55
|
+
disabled: true
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const DifferentFileTypes = () => ({
|
|
59
|
+
components: { FFilePreview },
|
|
60
|
+
template: `
|
|
61
|
+
<div class="flex flex-col gap-2">
|
|
62
|
+
<FFilePreview fileName="document.pdf" />
|
|
63
|
+
<FFilePreview fileName="image.png" />
|
|
64
|
+
<FFilePreview fileName="presentation.pptx" />
|
|
65
|
+
<FFilePreview fileName="spreadsheet.xlsx" />
|
|
66
|
+
<FFilePreview fileName="archive.zip" />
|
|
67
|
+
<FFilePreview fileName="texte.txt" />
|
|
68
|
+
</div>
|
|
69
|
+
`
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export const LongFileName = Template.bind({});
|
|
73
|
+
LongFileName.args = {
|
|
74
|
+
fileName:
|
|
75
|
+
'un-nom-de-fichier-tres-long-qui-devrait-etre-tronque-dans-linterface.pdf'
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const Interactive = () => ({
|
|
79
|
+
components: { FFilePreview },
|
|
80
|
+
data() {
|
|
81
|
+
return {
|
|
82
|
+
files: [
|
|
83
|
+
{ id: 1, name: 'rapport-q1.pdf' },
|
|
84
|
+
{ id: 2, name: 'presentation.pptx' },
|
|
85
|
+
{ id: 3, name: 'donnees.xlsx' }
|
|
86
|
+
]
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
methods: {
|
|
90
|
+
removeFile(id) {
|
|
91
|
+
this.files = this.files.filter((f) => f.id !== id);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
template: `
|
|
95
|
+
<div class="flex flex-col gap-2">
|
|
96
|
+
<FFilePreview
|
|
97
|
+
v-for="file in files"
|
|
98
|
+
:key="file.id"
|
|
99
|
+
:fileName="file.name"
|
|
100
|
+
@remove="removeFile(file.id)"
|
|
101
|
+
/>
|
|
102
|
+
<p v-if="files.length === 0" class="text-neutral-500 text-sm">
|
|
103
|
+
Aucun fichier restant
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
`
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export const WithUploadProgress = () => ({
|
|
110
|
+
components: { FFilePreview },
|
|
111
|
+
data() {
|
|
112
|
+
return {
|
|
113
|
+
files: [
|
|
114
|
+
{ id: 1, name: 'image.jpg', loading: false },
|
|
115
|
+
{ id: 2, name: 'video.mp4', loading: true },
|
|
116
|
+
{ id: 3, name: 'document.pdf', loading: false }
|
|
117
|
+
]
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
template: `
|
|
121
|
+
<div class="flex flex-col gap-2">
|
|
122
|
+
<FFilePreview
|
|
123
|
+
v-for="file in files"
|
|
124
|
+
:key="file.id"
|
|
125
|
+
:fileName="file.name"
|
|
126
|
+
:loading="file.loading"
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
`
|
|
130
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import FFilePreview from './FFilePreview.vue';
|
|
4
|
+
|
|
5
|
+
describe('FFilePreview', () => {
|
|
6
|
+
it('renders correctly with required props', () => {
|
|
7
|
+
const wrapper = mount(FFilePreview, {
|
|
8
|
+
propsData: { fileName: 'document.pdf' }
|
|
9
|
+
});
|
|
10
|
+
expect(wrapper.text()).toContain('document.pdf');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('displays file name', () => {
|
|
14
|
+
const wrapper = mount(FFilePreview, {
|
|
15
|
+
propsData: { fileName: 'test-file.docx' }
|
|
16
|
+
});
|
|
17
|
+
expect(wrapper.text()).toContain('test-file.docx');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('shows loader when loading', () => {
|
|
21
|
+
const wrapper = mount(FFilePreview, {
|
|
22
|
+
propsData: { fileName: 'file.pdf', loading: true }
|
|
23
|
+
});
|
|
24
|
+
expect(wrapper.findComponent({ name: 'FLoader' }).exists()).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('shows remove button when not loading', () => {
|
|
28
|
+
const wrapper = mount(FFilePreview, {
|
|
29
|
+
propsData: { fileName: 'file.pdf', loading: false }
|
|
30
|
+
});
|
|
31
|
+
expect(wrapper.findComponent({ name: 'FButton' }).exists()).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('emits remove event when remove button is clicked', async () => {
|
|
35
|
+
const wrapper = mount(FFilePreview, {
|
|
36
|
+
propsData: { fileName: 'file.pdf' }
|
|
37
|
+
});
|
|
38
|
+
await wrapper.findComponent({ name: 'FButton' }).trigger('click');
|
|
39
|
+
expect(wrapper.emitted('remove')).toBeTruthy();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('does not emit remove when disabled', async () => {
|
|
43
|
+
const wrapper = mount(FFilePreview, {
|
|
44
|
+
propsData: { fileName: 'file.pdf', disabled: true }
|
|
45
|
+
});
|
|
46
|
+
// Button is disabled so we can just verify the disabled state
|
|
47
|
+
expect(wrapper.html()).toContain('disabled');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('detects file extension from fileName', () => {
|
|
51
|
+
const wrapper = mount(FFilePreview, {
|
|
52
|
+
propsData: { fileName: 'image.png' }
|
|
53
|
+
});
|
|
54
|
+
expect(wrapper.findComponent({ name: 'FIcon' }).exists()).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('uses fileType prop when provided', () => {
|
|
58
|
+
const wrapper = mount(FFilePreview, {
|
|
59
|
+
propsData: { fileName: 'file', fileType: 'pdf' }
|
|
60
|
+
});
|
|
61
|
+
expect(wrapper.exists()).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('applies disabled styles', () => {
|
|
65
|
+
const wrapper = mount(FFilePreview, {
|
|
66
|
+
propsData: { fileName: 'file.pdf', disabled: true }
|
|
67
|
+
});
|
|
68
|
+
expect(wrapper.classes().join(' ')).toContain('opacity-50');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="containerClasses">
|
|
3
|
+
<f-icon :name="fileIcon" size="md" :class="iconClasses" />
|
|
4
|
+
|
|
5
|
+
<div class="flex-1 min-w-0">
|
|
6
|
+
<f-typography variant="body" :truncate="true" :class="fileNameClasses">
|
|
7
|
+
{{ fileName }}
|
|
8
|
+
</f-typography>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="flex-shrink-0">
|
|
12
|
+
<f-loader v-if="loading" size="sm" :label="loadingLabel" />
|
|
13
|
+
<f-button
|
|
14
|
+
v-else
|
|
15
|
+
variant="ghost"
|
|
16
|
+
size="small"
|
|
17
|
+
:disabled="disabled"
|
|
18
|
+
@click="handleRemove"
|
|
19
|
+
>
|
|
20
|
+
<f-icon name="trash" size="sm" />
|
|
21
|
+
<span class="sr-only">{{ removeLabel }}</span>
|
|
22
|
+
</f-button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script>
|
|
28
|
+
import FIcon from '../../atoms/FIcon/FIcon.vue';
|
|
29
|
+
import FTypography from '../../atoms/FTypography/FTypography.vue';
|
|
30
|
+
import FLoader from '../../atoms/FLoader/FLoader.vue';
|
|
31
|
+
import FButton from '../../atoms/FButton/FButton.vue';
|
|
32
|
+
|
|
33
|
+
const FILE_TYPE_ICONS = {
|
|
34
|
+
pdf: 'document',
|
|
35
|
+
doc: 'document',
|
|
36
|
+
docx: 'document',
|
|
37
|
+
xls: 'document',
|
|
38
|
+
xlsx: 'document',
|
|
39
|
+
ppt: 'document',
|
|
40
|
+
pptx: 'document',
|
|
41
|
+
txt: 'document',
|
|
42
|
+
csv: 'document',
|
|
43
|
+
jpg: 'image',
|
|
44
|
+
jpeg: 'image',
|
|
45
|
+
png: 'image',
|
|
46
|
+
gif: 'image',
|
|
47
|
+
svg: 'image',
|
|
48
|
+
webp: 'image',
|
|
49
|
+
bmp: 'image',
|
|
50
|
+
zip: 'document',
|
|
51
|
+
rar: 'document',
|
|
52
|
+
'7z': 'document',
|
|
53
|
+
tar: 'document',
|
|
54
|
+
gz: 'document',
|
|
55
|
+
default: 'document'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default {
|
|
59
|
+
name: 'FFilePreview',
|
|
60
|
+
components: {
|
|
61
|
+
FIcon,
|
|
62
|
+
FTypography,
|
|
63
|
+
FLoader,
|
|
64
|
+
FButton
|
|
65
|
+
},
|
|
66
|
+
props: {
|
|
67
|
+
fileName: {
|
|
68
|
+
type: String,
|
|
69
|
+
required: true
|
|
70
|
+
},
|
|
71
|
+
fileType: {
|
|
72
|
+
type: String,
|
|
73
|
+
default: ''
|
|
74
|
+
},
|
|
75
|
+
loading: {
|
|
76
|
+
type: Boolean,
|
|
77
|
+
default: false
|
|
78
|
+
},
|
|
79
|
+
disabled: {
|
|
80
|
+
type: Boolean,
|
|
81
|
+
default: false
|
|
82
|
+
},
|
|
83
|
+
loadingLabel: {
|
|
84
|
+
type: String,
|
|
85
|
+
default: 'Téléversement en cours'
|
|
86
|
+
},
|
|
87
|
+
removeLabel: {
|
|
88
|
+
type: String,
|
|
89
|
+
default: 'Supprimer le fichier'
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
computed: {
|
|
93
|
+
fileExtension() {
|
|
94
|
+
if (this.fileType) {
|
|
95
|
+
return this.fileType.toLowerCase();
|
|
96
|
+
}
|
|
97
|
+
const parts = this.fileName.split('.');
|
|
98
|
+
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : '';
|
|
99
|
+
},
|
|
100
|
+
fileIcon() {
|
|
101
|
+
return FILE_TYPE_ICONS[this.fileExtension] || FILE_TYPE_ICONS.default;
|
|
102
|
+
},
|
|
103
|
+
containerClasses() {
|
|
104
|
+
const baseClasses =
|
|
105
|
+
'flex items-center gap-3 px-4 py-3 bg-neutral-50 rounded-lg border border-neutral-200';
|
|
106
|
+
const disabledClasses = this.disabled ? 'opacity-50' : '';
|
|
107
|
+
|
|
108
|
+
return [baseClasses, disabledClasses].filter(Boolean).join(' ');
|
|
109
|
+
},
|
|
110
|
+
iconClasses() {
|
|
111
|
+
return 'text-neutral-500 flex-shrink-0';
|
|
112
|
+
},
|
|
113
|
+
fileNameClasses() {
|
|
114
|
+
return this.disabled ? 'text-neutral-400' : '';
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
methods: {
|
|
118
|
+
handleRemove() {
|
|
119
|
+
if (!this.disabled && !this.loading) {
|
|
120
|
+
this.$emit('remove');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
</script>
|