@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,264 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mount, createLocalVue } from '@vue/test-utils';
|
|
3
|
+
import FTabs from './FTabs.vue';
|
|
4
|
+
import FTab from './FTab.vue';
|
|
5
|
+
|
|
6
|
+
const localVue = createLocalVue();
|
|
7
|
+
localVue.component('FTab', FTab);
|
|
8
|
+
|
|
9
|
+
describe('FTabs', () => {
|
|
10
|
+
it('renders correctly with default props', () => {
|
|
11
|
+
const wrapper = mount(FTabs, {
|
|
12
|
+
localVue,
|
|
13
|
+
slots: {
|
|
14
|
+
default: `
|
|
15
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
16
|
+
<FTab name="tab2" label="Tab 2">Content 2</FTab>
|
|
17
|
+
`
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
expect(wrapper.find('[role="tablist"]').exists()).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('renders all tab buttons', () => {
|
|
24
|
+
const wrapper = mount(FTabs, {
|
|
25
|
+
localVue,
|
|
26
|
+
slots: {
|
|
27
|
+
default: `
|
|
28
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
29
|
+
<FTab name="tab2" label="Tab 2">Content 2</FTab>
|
|
30
|
+
<FTab name="tab3" label="Tab 3" disabled>Content 3</FTab>
|
|
31
|
+
`
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
wrapper.vm.$nextTick(() => {
|
|
35
|
+
const tabs = wrapper.findAll('[role="tab"]');
|
|
36
|
+
expect(tabs.length).toBe(3);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('activates first enabled tab by default', async () => {
|
|
41
|
+
const wrapper = mount(FTabs, {
|
|
42
|
+
localVue,
|
|
43
|
+
slots: {
|
|
44
|
+
default: `
|
|
45
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
46
|
+
<FTab name="tab2" label="Tab 2">Content 2</FTab>
|
|
47
|
+
`
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
await wrapper.vm.$nextTick();
|
|
51
|
+
await wrapper.vm.$nextTick();
|
|
52
|
+
await wrapper.vm.$nextTick();
|
|
53
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
54
|
+
await wrapper.vm.$nextTick();
|
|
55
|
+
|
|
56
|
+
// Verify tabs are registered
|
|
57
|
+
expect(wrapper.vm.tabItems.length).toBeGreaterThan(0);
|
|
58
|
+
|
|
59
|
+
const tabs = wrapper.findAll('[role="tab"]');
|
|
60
|
+
expect(tabs.length).toBeGreaterThan(0);
|
|
61
|
+
|
|
62
|
+
// Check that at least one tab is selected
|
|
63
|
+
const selectedTabs = tabs.wrappers.filter(
|
|
64
|
+
(tab) => tab.attributes('aria-selected') === 'true'
|
|
65
|
+
);
|
|
66
|
+
expect(selectedTabs.length).toBeGreaterThan(0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('changes active tab on click', async () => {
|
|
70
|
+
const wrapper = mount(FTabs, {
|
|
71
|
+
localVue,
|
|
72
|
+
slots: {
|
|
73
|
+
default: `
|
|
74
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
75
|
+
<FTab name="tab2" label="Tab 2">Content 2</FTab>
|
|
76
|
+
`
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
await wrapper.vm.$nextTick();
|
|
80
|
+
await wrapper.vm.$nextTick();
|
|
81
|
+
const tabs = wrapper.findAll('[role="tab"]');
|
|
82
|
+
if (tabs.length > 1) {
|
|
83
|
+
await tabs.at(1).trigger('click');
|
|
84
|
+
expect(wrapper.emitted('input')).toBeTruthy();
|
|
85
|
+
expect(wrapper.emitted('change')).toBeTruthy();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('does not activate disabled tab on click', async () => {
|
|
90
|
+
const wrapper = mount(FTabs, {
|
|
91
|
+
localVue,
|
|
92
|
+
slots: {
|
|
93
|
+
default: `
|
|
94
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
95
|
+
<FTab name="tab2" label="Tab 2" disabled>Content 2</FTab>
|
|
96
|
+
`
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
await wrapper.vm.$nextTick();
|
|
100
|
+
await wrapper.vm.$nextTick();
|
|
101
|
+
const tabs = wrapper.findAll('[role="tab"]');
|
|
102
|
+
if (tabs.length > 1) {
|
|
103
|
+
const initialEmitCount = wrapper.emitted('input')
|
|
104
|
+
? wrapper.emitted('input').length
|
|
105
|
+
: 0;
|
|
106
|
+
await tabs.at(1).trigger('click');
|
|
107
|
+
const finalEmitCount = wrapper.emitted('input')
|
|
108
|
+
? wrapper.emitted('input').length
|
|
109
|
+
: 0;
|
|
110
|
+
expect(finalEmitCount).toBe(initialEmitCount);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('supports v-model binding', async () => {
|
|
115
|
+
const wrapperWithModel = mount(FTabs, {
|
|
116
|
+
localVue,
|
|
117
|
+
propsData: {
|
|
118
|
+
value: 'tab2'
|
|
119
|
+
},
|
|
120
|
+
slots: {
|
|
121
|
+
default: `
|
|
122
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
123
|
+
<FTab name="tab2" label="Tab 2">Content 2</FTab>
|
|
124
|
+
`
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
await wrapperWithModel.vm.$nextTick();
|
|
128
|
+
const tabs = wrapperWithModel.findAll('[role="tab"]');
|
|
129
|
+
expect(tabs.at(1).attributes('aria-selected')).toBe('true');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('navigates with arrow keys', async () => {
|
|
133
|
+
const wrapper = mount(FTabs, {
|
|
134
|
+
localVue,
|
|
135
|
+
slots: {
|
|
136
|
+
default: `
|
|
137
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
138
|
+
<FTab name="tab2" label="Tab 2">Content 2</FTab>
|
|
139
|
+
`
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
await wrapper.vm.$nextTick();
|
|
143
|
+
await wrapper.vm.$nextTick();
|
|
144
|
+
const tabs = wrapper.findAll('[role="tab"]');
|
|
145
|
+
if (tabs.length > 0) {
|
|
146
|
+
await tabs.at(0).trigger('keydown', { key: 'ArrowRight' });
|
|
147
|
+
expect(wrapper.emitted('change')).toBeTruthy();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('navigates to first tab with Home key', async () => {
|
|
152
|
+
const wrapper = mount(FTabs, {
|
|
153
|
+
localVue,
|
|
154
|
+
propsData: {
|
|
155
|
+
value: 'tab2'
|
|
156
|
+
},
|
|
157
|
+
slots: {
|
|
158
|
+
default: `
|
|
159
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
160
|
+
<FTab name="tab2" label="Tab 2">Content 2</FTab>
|
|
161
|
+
`
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
await wrapper.vm.$nextTick();
|
|
165
|
+
await wrapper.vm.$nextTick();
|
|
166
|
+
const tabs = wrapper.findAll('[role="tab"]');
|
|
167
|
+
await tabs.at(1).trigger('keydown', { key: 'Home' });
|
|
168
|
+
expect(wrapper.emitted('change')[0][0]).toBe('tab1');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('navigates to last tab with End key', async () => {
|
|
172
|
+
const wrapper = mount(FTabs, {
|
|
173
|
+
localVue,
|
|
174
|
+
slots: {
|
|
175
|
+
default: `
|
|
176
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
177
|
+
<FTab name="tab2" label="Tab 2">Content 2</FTab>
|
|
178
|
+
`
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
await wrapper.vm.$nextTick();
|
|
182
|
+
await wrapper.vm.$nextTick();
|
|
183
|
+
const tabs = wrapper.findAll('[role="tab"]');
|
|
184
|
+
if (tabs.length > 0) {
|
|
185
|
+
await tabs.at(0).trigger('keydown', { key: 'End' });
|
|
186
|
+
expect(wrapper.emitted('change')).toBeTruthy();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('applies correct variant classes', () => {
|
|
191
|
+
const variants = ['default', 'pills', 'underline'];
|
|
192
|
+
variants.forEach((variant) => {
|
|
193
|
+
const variantWrapper = mount(FTabs, {
|
|
194
|
+
localVue,
|
|
195
|
+
propsData: { variant },
|
|
196
|
+
slots: {
|
|
197
|
+
default: '<FTab name="tab1" label="Tab 1">Content</FTab>'
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
expect(variantWrapper.find('[role="tablist"]').exists()).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('has correct ARIA attributes', async () => {
|
|
205
|
+
const wrapper = mount(FTabs, {
|
|
206
|
+
localVue,
|
|
207
|
+
slots: {
|
|
208
|
+
default: `
|
|
209
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
210
|
+
<FTab name="tab2" label="Tab 2">Content 2</FTab>
|
|
211
|
+
`
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
await wrapper.vm.$nextTick();
|
|
215
|
+
const tabList = wrapper.find('[role="tablist"]');
|
|
216
|
+
expect(tabList.attributes('aria-label')).toBeDefined();
|
|
217
|
+
|
|
218
|
+
const tabs = wrapper.findAll('[role="tab"]');
|
|
219
|
+
tabs.wrappers.forEach((tab) => {
|
|
220
|
+
expect(tab.attributes('aria-controls')).toBeDefined();
|
|
221
|
+
expect(tab.attributes('aria-selected')).toBeDefined();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('sets correct tabindex for active and inactive tabs', async () => {
|
|
226
|
+
const wrapper = mount(FTabs, {
|
|
227
|
+
localVue,
|
|
228
|
+
propsData: {
|
|
229
|
+
value: 'tab1'
|
|
230
|
+
},
|
|
231
|
+
slots: {
|
|
232
|
+
default: `
|
|
233
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
234
|
+
<FTab name="tab2" label="Tab 2">Content 2</FTab>
|
|
235
|
+
`
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
await wrapper.vm.$nextTick();
|
|
239
|
+
await wrapper.vm.$nextTick(); // Wait for mounted nextTick to complete
|
|
240
|
+
const tabs = wrapper.findAll('[role="tab"]');
|
|
241
|
+
expect(tabs.at(0).attributes('tabindex')).toBe('0');
|
|
242
|
+
expect(tabs.at(1).attributes('tabindex')).toBe('-1');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('displays tab panel content when tab is active', async () => {
|
|
246
|
+
const wrapper = mount(FTabs, {
|
|
247
|
+
localVue,
|
|
248
|
+
propsData: {
|
|
249
|
+
value: 'tab1'
|
|
250
|
+
},
|
|
251
|
+
slots: {
|
|
252
|
+
default: `
|
|
253
|
+
<FTab name="tab1" label="Tab 1">Content 1</FTab>
|
|
254
|
+
<FTab name="tab2" label="Tab 2">Content 2</FTab>
|
|
255
|
+
`
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
await wrapper.vm.$nextTick();
|
|
259
|
+
await wrapper.vm.$nextTick(); // Wait for mounted nextTick to complete
|
|
260
|
+
const panels = wrapper.findAll('[role="tabpanel"]');
|
|
261
|
+
expect(panels.at(0).isVisible()).toBe(true);
|
|
262
|
+
expect(panels.at(1).isVisible()).toBe(false);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="f-tabs">
|
|
3
|
+
<!-- Tab buttons -->
|
|
4
|
+
<div :class="tabListClasses" role="tablist" :aria-label="ariaLabel">
|
|
5
|
+
<button
|
|
6
|
+
v-for="tab in tabItems"
|
|
7
|
+
:id="getTabId(tab.name)"
|
|
8
|
+
:key="tab.name"
|
|
9
|
+
:ref="`tab-${tab.name}`"
|
|
10
|
+
role="tab"
|
|
11
|
+
:aria-selected="activeTabName === tab.name ? 'true' : 'false'"
|
|
12
|
+
:aria-controls="getPanelId(tab.name)"
|
|
13
|
+
:disabled="tab.disabled"
|
|
14
|
+
:class="getTabButtonClasses(tab)"
|
|
15
|
+
:tabindex="activeTabName === tab.name ? 0 : -1"
|
|
16
|
+
@click="handleTabClick(tab.name)"
|
|
17
|
+
@keydown="handleKeydown($event, tab.name)"
|
|
18
|
+
>
|
|
19
|
+
{{ tab.label }}
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<!-- Tab panels (content) -->
|
|
24
|
+
<div class="mt-4">
|
|
25
|
+
<slot />
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script>
|
|
31
|
+
let idCounter = 0;
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
name: 'FTabs',
|
|
35
|
+
provide() {
|
|
36
|
+
return {
|
|
37
|
+
tabsProvider: {
|
|
38
|
+
registerTab: this.registerTab,
|
|
39
|
+
unregisterTab: this.unregisterTab,
|
|
40
|
+
isActive: this.isTabActive,
|
|
41
|
+
getTabId: this.getTabId,
|
|
42
|
+
getPanelId: this.getPanelId
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
props: {
|
|
47
|
+
/**
|
|
48
|
+
* Currently active tab name (v-model support)
|
|
49
|
+
*/
|
|
50
|
+
value: {
|
|
51
|
+
type: String,
|
|
52
|
+
default: ''
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* Visual variant of the tabs
|
|
56
|
+
*/
|
|
57
|
+
variant: {
|
|
58
|
+
type: String,
|
|
59
|
+
default: 'default',
|
|
60
|
+
validator: (value) => ['default', 'pills', 'underline'].includes(value)
|
|
61
|
+
},
|
|
62
|
+
/**
|
|
63
|
+
* Position of the tab buttons
|
|
64
|
+
*/
|
|
65
|
+
position: {
|
|
66
|
+
type: String,
|
|
67
|
+
default: 'top',
|
|
68
|
+
validator: (value) => ['top', 'bottom'].includes(value)
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* Accessible label for the tab list
|
|
72
|
+
*/
|
|
73
|
+
ariaLabel: {
|
|
74
|
+
type: String,
|
|
75
|
+
default: 'Onglets'
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
data() {
|
|
79
|
+
return {
|
|
80
|
+
tabItems: [],
|
|
81
|
+
uid: idCounter++,
|
|
82
|
+
initialTabSet: false,
|
|
83
|
+
internalActiveTab: ''
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
computed: {
|
|
87
|
+
activeTabName: {
|
|
88
|
+
get() {
|
|
89
|
+
// Use value prop if provided, otherwise use internal state
|
|
90
|
+
return this.value !== '' ? this.value : this.internalActiveTab;
|
|
91
|
+
},
|
|
92
|
+
set(val) {
|
|
93
|
+
this.internalActiveTab = val;
|
|
94
|
+
this.$emit('input', val);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
tabListClasses() {
|
|
98
|
+
const baseClasses = 'flex gap-1';
|
|
99
|
+
const variantClasses = {
|
|
100
|
+
default: 'border-b border-neutral-200',
|
|
101
|
+
pills: '',
|
|
102
|
+
underline: 'border-b border-neutral-200'
|
|
103
|
+
};
|
|
104
|
+
const positionClasses = {
|
|
105
|
+
top: '',
|
|
106
|
+
bottom: 'order-2 mt-4'
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return [
|
|
110
|
+
baseClasses,
|
|
111
|
+
variantClasses[this.variant],
|
|
112
|
+
positionClasses[this.position]
|
|
113
|
+
]
|
|
114
|
+
.filter(Boolean)
|
|
115
|
+
.join(' ');
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
watch: {
|
|
119
|
+
tabItems: {
|
|
120
|
+
handler() {
|
|
121
|
+
// Set first tab as active if no value provided and not yet set
|
|
122
|
+
if (
|
|
123
|
+
!this.initialTabSet &&
|
|
124
|
+
!this.activeTabName &&
|
|
125
|
+
this.tabItems.length > 0
|
|
126
|
+
) {
|
|
127
|
+
const firstEnabledTab = this.tabItems.find((tab) => !tab.disabled);
|
|
128
|
+
if (firstEnabledTab) {
|
|
129
|
+
this.activeTabName = firstEnabledTab.name;
|
|
130
|
+
this.initialTabSet = true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
immediate: true
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
methods: {
|
|
138
|
+
/**
|
|
139
|
+
* Register a tab from FTab component
|
|
140
|
+
*/
|
|
141
|
+
registerTab(tab) {
|
|
142
|
+
const exists = this.tabItems.find((t) => t.name === tab.name);
|
|
143
|
+
if (!exists) {
|
|
144
|
+
this.tabItems.push(tab);
|
|
145
|
+
}
|
|
146
|
+
// Set first non-disabled tab as active if no value provided
|
|
147
|
+
if (!this.activeTabName) {
|
|
148
|
+
const firstEnabledTab = this.tabItems.find((t) => !t.disabled);
|
|
149
|
+
if (firstEnabledTab) {
|
|
150
|
+
this.activeTabName = firstEnabledTab.name;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
/**
|
|
155
|
+
* Unregister a tab from FTab component
|
|
156
|
+
*/
|
|
157
|
+
unregisterTab(name) {
|
|
158
|
+
const index = this.tabItems.findIndex((t) => t.name === name);
|
|
159
|
+
if (index > -1) {
|
|
160
|
+
this.tabItems.splice(index, 1);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
/**
|
|
164
|
+
* Check if a tab is active
|
|
165
|
+
*/
|
|
166
|
+
isTabActive(name) {
|
|
167
|
+
return this.activeTabName === name;
|
|
168
|
+
},
|
|
169
|
+
/**
|
|
170
|
+
* Generate unique tab button ID
|
|
171
|
+
*/
|
|
172
|
+
getTabId(name) {
|
|
173
|
+
return `f-tab-${this.uid}-${name}`;
|
|
174
|
+
},
|
|
175
|
+
/**
|
|
176
|
+
* Generate unique tab panel ID
|
|
177
|
+
*/
|
|
178
|
+
getPanelId(name) {
|
|
179
|
+
return `f-tabpanel-${this.uid}-${name}`;
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* Get classes for tab button based on state
|
|
183
|
+
*/
|
|
184
|
+
getTabButtonClasses(tab) {
|
|
185
|
+
const isActive = this.activeTabName === tab.name;
|
|
186
|
+
const baseClasses =
|
|
187
|
+
'px-4 py-2 font-medium text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2';
|
|
188
|
+
|
|
189
|
+
const roundingClasses = this.variant === 'pills' ? '' : 'rounded-t-md';
|
|
190
|
+
|
|
191
|
+
const variantClasses = {
|
|
192
|
+
default: isActive
|
|
193
|
+
? 'text-primary-600 border-b-2 border-primary-600 -mb-px'
|
|
194
|
+
: 'text-neutral-600 hover:text-neutral-800 hover:border-neutral-300 border-b-2 border-transparent -mb-px',
|
|
195
|
+
pills: isActive
|
|
196
|
+
? 'bg-primary-100 text-primary-700 rounded-lg'
|
|
197
|
+
: 'text-neutral-600 hover:bg-neutral-100 hover:text-neutral-800 rounded-lg',
|
|
198
|
+
underline: isActive
|
|
199
|
+
? 'text-primary-600 border-b-2 border-primary-600 -mb-px'
|
|
200
|
+
: 'text-neutral-600 hover:text-neutral-800 border-b-2 border-transparent -mb-px'
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const disabledClasses = tab.disabled
|
|
204
|
+
? 'opacity-50 cursor-not-allowed'
|
|
205
|
+
: 'cursor-pointer';
|
|
206
|
+
|
|
207
|
+
return [
|
|
208
|
+
baseClasses,
|
|
209
|
+
roundingClasses,
|
|
210
|
+
variantClasses[this.variant],
|
|
211
|
+
disabledClasses
|
|
212
|
+
]
|
|
213
|
+
.filter(Boolean)
|
|
214
|
+
.join(' ');
|
|
215
|
+
},
|
|
216
|
+
/**
|
|
217
|
+
* Handle tab click
|
|
218
|
+
*/
|
|
219
|
+
handleTabClick(name) {
|
|
220
|
+
const tab = this.tabItems.find((t) => t.name === name);
|
|
221
|
+
if (tab && !tab.disabled) {
|
|
222
|
+
this.activeTabName = name;
|
|
223
|
+
this.$emit('change', name);
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
/**
|
|
227
|
+
* Handle keyboard navigation
|
|
228
|
+
*/
|
|
229
|
+
handleKeydown(event, currentName) {
|
|
230
|
+
const enabledTabs = this.tabItems.filter((tab) => !tab.disabled);
|
|
231
|
+
const currentIndex = enabledTabs.findIndex(
|
|
232
|
+
(tab) => tab.name === currentName
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
let nextIndex = currentIndex;
|
|
236
|
+
|
|
237
|
+
switch (event.key) {
|
|
238
|
+
case 'ArrowRight':
|
|
239
|
+
event.preventDefault();
|
|
240
|
+
nextIndex = (currentIndex + 1) % enabledTabs.length;
|
|
241
|
+
break;
|
|
242
|
+
case 'ArrowLeft':
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
nextIndex =
|
|
245
|
+
currentIndex === 0 ? enabledTabs.length - 1 : currentIndex - 1;
|
|
246
|
+
break;
|
|
247
|
+
case 'Home':
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
nextIndex = 0;
|
|
250
|
+
break;
|
|
251
|
+
case 'End':
|
|
252
|
+
event.preventDefault();
|
|
253
|
+
nextIndex = enabledTabs.length - 1;
|
|
254
|
+
break;
|
|
255
|
+
default:
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const nextTab = enabledTabs[nextIndex];
|
|
260
|
+
if (nextTab) {
|
|
261
|
+
this.activeTabName = nextTab.name;
|
|
262
|
+
this.$emit('change', nextTab.name);
|
|
263
|
+
this.$nextTick(() => {
|
|
264
|
+
const tabButton = this.$refs[`tab-${nextTab.name}`];
|
|
265
|
+
if (tabButton && tabButton[0]) {
|
|
266
|
+
tabButton[0].focus();
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
</script>
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import FToast from './FToast.vue';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Molecules/FToast',
|
|
5
|
+
component: FToast,
|
|
6
|
+
tags: ['autodocs'],
|
|
7
|
+
argTypes: {
|
|
8
|
+
variant: {
|
|
9
|
+
control: { type: 'select' },
|
|
10
|
+
options: ['success', 'error', 'info', 'warning'],
|
|
11
|
+
description: 'Type de notification'
|
|
12
|
+
},
|
|
13
|
+
title: {
|
|
14
|
+
control: 'text',
|
|
15
|
+
description: 'Titre de la notification'
|
|
16
|
+
},
|
|
17
|
+
message: {
|
|
18
|
+
control: 'text',
|
|
19
|
+
description: 'Message de la notification'
|
|
20
|
+
},
|
|
21
|
+
closable: {
|
|
22
|
+
control: 'boolean',
|
|
23
|
+
description: 'Afficher le bouton de fermeture'
|
|
24
|
+
},
|
|
25
|
+
duration: {
|
|
26
|
+
control: 'number',
|
|
27
|
+
description: "Durée d'affichage en millisecondes (0 = permanent)"
|
|
28
|
+
},
|
|
29
|
+
position: {
|
|
30
|
+
control: { type: 'select' },
|
|
31
|
+
options: [
|
|
32
|
+
'top-left',
|
|
33
|
+
'top-center',
|
|
34
|
+
'top-right',
|
|
35
|
+
'bottom-left',
|
|
36
|
+
'bottom-center',
|
|
37
|
+
'bottom-right'
|
|
38
|
+
],
|
|
39
|
+
description: 'Position du toast'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const Template = (args, { argTypes }) => ({
|
|
45
|
+
components: { FToast },
|
|
46
|
+
props: Object.keys(argTypes),
|
|
47
|
+
template: '<FToast v-bind="$props" @close="onClose" />',
|
|
48
|
+
methods: {
|
|
49
|
+
onClose() {
|
|
50
|
+
console.log('Toast fermé');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const Success = Template.bind({});
|
|
56
|
+
Success.args = {
|
|
57
|
+
variant: 'success',
|
|
58
|
+
title: 'Succès',
|
|
59
|
+
message: 'Votre action a été effectuée avec succès.',
|
|
60
|
+
duration: 0
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Error = Template.bind({});
|
|
64
|
+
Error.args = {
|
|
65
|
+
variant: 'error',
|
|
66
|
+
title: 'Erreur',
|
|
67
|
+
message: "Une erreur s'est produite. Veuillez réessayer.",
|
|
68
|
+
duration: 0
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const Info = Template.bind({});
|
|
72
|
+
Info.args = {
|
|
73
|
+
variant: 'info',
|
|
74
|
+
title: 'Information',
|
|
75
|
+
message: 'Voici une information importante à prendre en compte.',
|
|
76
|
+
duration: 0
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const Warning = Template.bind({});
|
|
80
|
+
Warning.args = {
|
|
81
|
+
variant: 'warning',
|
|
82
|
+
title: 'Avertissement',
|
|
83
|
+
message: 'Veuillez vérifier vos informations avant de continuer.',
|
|
84
|
+
duration: 0
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const WithoutTitle = Template.bind({});
|
|
88
|
+
WithoutTitle.args = {
|
|
89
|
+
variant: 'info',
|
|
90
|
+
message: 'Notification simple sans titre.',
|
|
91
|
+
duration: 0
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const NotClosable = Template.bind({});
|
|
95
|
+
NotClosable.args = {
|
|
96
|
+
variant: 'info',
|
|
97
|
+
title: 'Notification permanente',
|
|
98
|
+
message: 'Cette notification ne peut pas être fermée.',
|
|
99
|
+
closable: false,
|
|
100
|
+
duration: 0
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const WithSlotContent = () => ({
|
|
104
|
+
components: { FToast },
|
|
105
|
+
template: `
|
|
106
|
+
<FToast variant="info" title="Information" :duration="0">
|
|
107
|
+
<p>Contenu personnalisé avec <a href="#" class="underline">un lien</a> cliquable.</p>
|
|
108
|
+
</FToast>
|
|
109
|
+
`
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export const AllVariants = () => ({
|
|
113
|
+
components: { FToast },
|
|
114
|
+
template: `
|
|
115
|
+
<div class="flex flex-col gap-4 p-4">
|
|
116
|
+
<FToast variant="success" title="Succès" message="Opération réussie." :duration="0" />
|
|
117
|
+
<FToast variant="error" title="Erreur" message="Une erreur s'est produite." :duration="0" />
|
|
118
|
+
<FToast variant="info" title="Information" message="Information importante." :duration="0" />
|
|
119
|
+
<FToast variant="warning" title="Avertissement" message="Veuillez vérifier." :duration="0" />
|
|
120
|
+
</div>
|
|
121
|
+
`
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
export const WithAutoClose = () => ({
|
|
125
|
+
components: { FToast },
|
|
126
|
+
data() {
|
|
127
|
+
return {
|
|
128
|
+
showToast: true
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
template: `
|
|
132
|
+
<div class="p-4">
|
|
133
|
+
<FToast
|
|
134
|
+
v-if="showToast"
|
|
135
|
+
variant="success"
|
|
136
|
+
title="Auto-fermeture"
|
|
137
|
+
message="Ce toast se fermera automatiquement dans 5 secondes."
|
|
138
|
+
:duration="5000"
|
|
139
|
+
@close="showToast = false"
|
|
140
|
+
/>
|
|
141
|
+
<button
|
|
142
|
+
v-if="!showToast"
|
|
143
|
+
@click="showToast = true"
|
|
144
|
+
class="px-4 py-2 bg-primary-500 text-white rounded"
|
|
145
|
+
>
|
|
146
|
+
Afficher à nouveau
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
`
|
|
150
|
+
});
|