@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,178 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { ref, Ref } from 'vue';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for useFormValidation
|
|
6
|
+
*/
|
|
7
|
+
export interface FormValidationOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Initial form data
|
|
10
|
+
*/
|
|
11
|
+
initialData?: Record<string, any>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Return type for useFormValidation
|
|
16
|
+
*/
|
|
17
|
+
export interface FormValidationState {
|
|
18
|
+
// Form data
|
|
19
|
+
formData: Ref<Record<string, any>>;
|
|
20
|
+
|
|
21
|
+
// Validation state
|
|
22
|
+
errors: Ref<Record<string, string>>;
|
|
23
|
+
isValid: Ref<boolean>;
|
|
24
|
+
isSubmitting: Ref<boolean>;
|
|
25
|
+
|
|
26
|
+
// Methods
|
|
27
|
+
handleSubmit: (
|
|
28
|
+
event: Event,
|
|
29
|
+
callback?: (data: Record<string, any>) => void | Promise<void>
|
|
30
|
+
) => Promise<void>;
|
|
31
|
+
setFieldValue: (field: string, value: any) => void;
|
|
32
|
+
setFieldError: (field: string, error: string) => void;
|
|
33
|
+
clearFieldError: (field: string) => void;
|
|
34
|
+
clearErrors: () => void;
|
|
35
|
+
reset: () => void;
|
|
36
|
+
validate: () => boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Composable for managing form validation and submission
|
|
41
|
+
*
|
|
42
|
+
* Provides a simple interface for form handling with built-in validation state.
|
|
43
|
+
* Can be extended with custom validation logic.
|
|
44
|
+
*
|
|
45
|
+
* @param options - Configuration options for the form validation
|
|
46
|
+
* @param emit - Emit function from the component setup
|
|
47
|
+
* @returns Object containing reactive state and methods for form operations
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const formState = useFormValidation({
|
|
52
|
+
* initialData: { name: '', email: '' }
|
|
53
|
+
* }, emit);
|
|
54
|
+
*
|
|
55
|
+
* // In submit handler
|
|
56
|
+
* await formState.handleSubmit(event, async (data) => {
|
|
57
|
+
* await api.submitForm(data);
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function useFormValidation(
|
|
62
|
+
options: FormValidationOptions = {},
|
|
63
|
+
emit: (event: string, ...args: any[]) => void
|
|
64
|
+
): FormValidationState {
|
|
65
|
+
const { initialData = {} } = options;
|
|
66
|
+
|
|
67
|
+
// Reactive state
|
|
68
|
+
const formData = ref<Record<string, any>>({ ...initialData });
|
|
69
|
+
const errors = ref<Record<string, string>>({});
|
|
70
|
+
const isValid = ref(true);
|
|
71
|
+
const isSubmitting = ref(false);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Set a field value
|
|
75
|
+
*/
|
|
76
|
+
const setFieldValue = (field: string, value: any): void => {
|
|
77
|
+
formData.value[field] = value;
|
|
78
|
+
// Clear error when field is updated
|
|
79
|
+
clearFieldError(field);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Set a field error
|
|
84
|
+
*/
|
|
85
|
+
const setFieldError = (field: string, error: string): void => {
|
|
86
|
+
errors.value[field] = error;
|
|
87
|
+
isValid.value = false;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Clear a specific field error
|
|
92
|
+
*/
|
|
93
|
+
const clearFieldError = (field: string): void => {
|
|
94
|
+
if (errors.value[field]) {
|
|
95
|
+
delete errors.value[field];
|
|
96
|
+
// Recalculate overall validity
|
|
97
|
+
isValid.value = Object.keys(errors.value).length === 0;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Clear all errors
|
|
103
|
+
*/
|
|
104
|
+
const clearErrors = (): void => {
|
|
105
|
+
errors.value = {};
|
|
106
|
+
isValid.value = true;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate form (can be overridden with custom validation logic)
|
|
111
|
+
* Returns true if valid, false otherwise
|
|
112
|
+
*/
|
|
113
|
+
const validate = (): boolean => {
|
|
114
|
+
clearErrors();
|
|
115
|
+
// Basic validation - can be extended with custom logic
|
|
116
|
+
// For now, just check if there are no errors
|
|
117
|
+
return isValid.value;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Handle form submission
|
|
122
|
+
*/
|
|
123
|
+
const handleSubmit = async (
|
|
124
|
+
event: Event,
|
|
125
|
+
callback?: (data: Record<string, any>) => void | Promise<void>
|
|
126
|
+
): Promise<void> => {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
|
|
129
|
+
// Emit submit event with the event object
|
|
130
|
+
emit('submit', event);
|
|
131
|
+
|
|
132
|
+
// If callback is provided, validate and execute it
|
|
133
|
+
if (callback) {
|
|
134
|
+
if (!validate()) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
isSubmitting.value = true;
|
|
139
|
+
try {
|
|
140
|
+
await callback(formData.value);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
// Emit error event if submission fails
|
|
143
|
+
emit('submit-error', error);
|
|
144
|
+
throw error;
|
|
145
|
+
} finally {
|
|
146
|
+
isSubmitting.value = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Reset form to initial state
|
|
153
|
+
*/
|
|
154
|
+
const reset = (): void => {
|
|
155
|
+
formData.value = { ...initialData };
|
|
156
|
+
clearErrors();
|
|
157
|
+
isSubmitting.value = false;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
// Form data
|
|
162
|
+
formData,
|
|
163
|
+
|
|
164
|
+
// Validation state
|
|
165
|
+
errors,
|
|
166
|
+
isValid,
|
|
167
|
+
isSubmitting,
|
|
168
|
+
|
|
169
|
+
// Methods
|
|
170
|
+
handleSubmit,
|
|
171
|
+
setFieldValue,
|
|
172
|
+
setFieldError,
|
|
173
|
+
clearFieldError,
|
|
174
|
+
clearErrors,
|
|
175
|
+
reset,
|
|
176
|
+
validate
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { useSidebarState } from './useSidebarState';
|
|
4
|
+
|
|
5
|
+
describe('useSidebarState', () => {
|
|
6
|
+
const items = [
|
|
7
|
+
{ id: 'dashboard', label: 'Dashboard', href: '/dashboard', icon: 'home' },
|
|
8
|
+
{ id: 'projects', label: 'Projects', href: '/projects', icon: 'folder' },
|
|
9
|
+
{ id: 'settings', label: 'Settings', href: '/settings', icon: 'cog' }
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
it('initializes with default state', () => {
|
|
13
|
+
const emit = vi.fn();
|
|
14
|
+
const state = useSidebarState({ items }, emit);
|
|
15
|
+
|
|
16
|
+
expect(state.collapsed.value).toBe(false);
|
|
17
|
+
expect(state.openSubmenus.value).toEqual([]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('initializes with custom collapsed state', () => {
|
|
21
|
+
const emit = vi.fn();
|
|
22
|
+
const state = useSidebarState({ items, initialCollapsed: true }, emit);
|
|
23
|
+
|
|
24
|
+
expect(state.collapsed.value).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('navigationItems', () => {
|
|
28
|
+
it('filters out invalid items', () => {
|
|
29
|
+
const emit = vi.fn();
|
|
30
|
+
const itemsWithInvalid = [
|
|
31
|
+
{ id: 'dashboard', label: 'Dashboard', href: '/dashboard' },
|
|
32
|
+
null,
|
|
33
|
+
undefined,
|
|
34
|
+
{ id: 'projects', label: 'Projects', href: '/projects' }
|
|
35
|
+
];
|
|
36
|
+
const state = useSidebarState({ items: itemsWithInvalid as any }, emit);
|
|
37
|
+
|
|
38
|
+
expect(state.navigationItems.value.length).toBe(2);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('includes divider items without labels', () => {
|
|
42
|
+
const emit = vi.fn();
|
|
43
|
+
const itemsWithDivider = [
|
|
44
|
+
{ id: 'dashboard', label: 'Dashboard', href: '/dashboard' },
|
|
45
|
+
{ type: 'divider' as const },
|
|
46
|
+
{ id: 'projects', label: 'Projects', href: '/projects' }
|
|
47
|
+
];
|
|
48
|
+
const state = useSidebarState({ items: itemsWithDivider }, emit);
|
|
49
|
+
|
|
50
|
+
expect(state.navigationItems.value.length).toBe(3);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('isItemActive', () => {
|
|
55
|
+
it('detects exact route match', () => {
|
|
56
|
+
const emit = vi.fn();
|
|
57
|
+
const state = useSidebarState({ items, activeRoute: '/dashboard' }, emit);
|
|
58
|
+
|
|
59
|
+
expect(state.isItemActive(items[0])).toBe(true);
|
|
60
|
+
expect(state.isItemActive(items[1])).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('detects nested route match', () => {
|
|
64
|
+
const emit = vi.fn();
|
|
65
|
+
const state = useSidebarState(
|
|
66
|
+
{ items, activeRoute: '/projects/123' },
|
|
67
|
+
emit
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(state.isItemActive(items[1])).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('does not match partial route names', () => {
|
|
74
|
+
const emit = vi.fn();
|
|
75
|
+
const itemsWithSimilarRoutes = [
|
|
76
|
+
{ id: 'user', label: 'User', href: '/user' },
|
|
77
|
+
{ id: 'users', label: 'Users', href: '/users' }
|
|
78
|
+
];
|
|
79
|
+
const state = useSidebarState(
|
|
80
|
+
{ items: itemsWithSimilarRoutes, activeRoute: '/users' },
|
|
81
|
+
emit
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(state.isItemActive(itemsWithSimilarRoutes[0])).toBe(false);
|
|
85
|
+
expect(state.isItemActive(itemsWithSimilarRoutes[1])).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('handles root path specially', () => {
|
|
89
|
+
const emit = vi.fn();
|
|
90
|
+
const itemsWithRoot = [
|
|
91
|
+
{ id: 'home', label: 'Home', href: '/' },
|
|
92
|
+
{ id: 'dashboard', label: 'Dashboard', href: '/dashboard' }
|
|
93
|
+
];
|
|
94
|
+
const state = useSidebarState(
|
|
95
|
+
{ items: itemsWithRoot, activeRoute: '/dashboard' },
|
|
96
|
+
emit
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(state.isItemActive(itemsWithRoot[0])).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('returns false when no active route is set', () => {
|
|
103
|
+
const emit = vi.fn();
|
|
104
|
+
const state = useSidebarState({ items }, emit);
|
|
105
|
+
|
|
106
|
+
expect(state.isItemActive(items[0])).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('works with router-link "to" property', () => {
|
|
110
|
+
const emit = vi.fn();
|
|
111
|
+
const itemsWithTo = [
|
|
112
|
+
{ id: 'dashboard', label: 'Dashboard', to: '/dashboard' }
|
|
113
|
+
];
|
|
114
|
+
const state = useSidebarState(
|
|
115
|
+
{ items: itemsWithTo, activeRoute: '/dashboard' },
|
|
116
|
+
emit
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(state.isItemActive(itemsWithTo[0])).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('hasActiveChild', () => {
|
|
124
|
+
const nestedItems = [
|
|
125
|
+
{
|
|
126
|
+
id: 'section',
|
|
127
|
+
label: 'Section',
|
|
128
|
+
icon: 'folder',
|
|
129
|
+
children: [
|
|
130
|
+
{ id: 'child1', label: 'Child 1', href: '/section/child1' },
|
|
131
|
+
{ id: 'child2', label: 'Child 2', href: '/section/child2' }
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
it('detects active child', () => {
|
|
137
|
+
const emit = vi.fn();
|
|
138
|
+
const state = useSidebarState(
|
|
139
|
+
{ items: nestedItems, activeRoute: '/section/child1' },
|
|
140
|
+
emit
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(state.hasActiveChild(nestedItems[0])).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('returns false when no child is active', () => {
|
|
147
|
+
const emit = vi.fn();
|
|
148
|
+
const state = useSidebarState(
|
|
149
|
+
{ items: nestedItems, activeRoute: '/other' },
|
|
150
|
+
emit
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(state.hasActiveChild(nestedItems[0])).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('returns false when item has no children', () => {
|
|
157
|
+
const emit = vi.fn();
|
|
158
|
+
const state = useSidebarState({ items }, emit);
|
|
159
|
+
|
|
160
|
+
expect(state.hasActiveChild(items[0])).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('submenu management', () => {
|
|
165
|
+
const nestedItems = [
|
|
166
|
+
{
|
|
167
|
+
id: 'section',
|
|
168
|
+
label: 'Section',
|
|
169
|
+
children: [{ id: 'child1', label: 'Child 1', href: '/child1' }]
|
|
170
|
+
}
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
it('checks if submenu is open', () => {
|
|
174
|
+
const emit = vi.fn();
|
|
175
|
+
const state = useSidebarState({ items: nestedItems }, emit);
|
|
176
|
+
|
|
177
|
+
expect(state.isSubmenuOpen(nestedItems[0])).toBe(false);
|
|
178
|
+
|
|
179
|
+
state.openSubmenus.value = ['section'];
|
|
180
|
+
expect(state.isSubmenuOpen(nestedItems[0])).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('toggles submenu open', () => {
|
|
184
|
+
const emit = vi.fn();
|
|
185
|
+
const state = useSidebarState({ items: nestedItems }, emit);
|
|
186
|
+
|
|
187
|
+
state.toggleSubmenu(nestedItems[0]);
|
|
188
|
+
expect(state.isSubmenuOpen(nestedItems[0])).toBe(true);
|
|
189
|
+
expect(emit).toHaveBeenCalledWith('submenu-toggle', {
|
|
190
|
+
item: nestedItems[0],
|
|
191
|
+
open: true
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('toggles submenu closed', () => {
|
|
196
|
+
const emit = vi.fn();
|
|
197
|
+
const state = useSidebarState({ items: nestedItems }, emit);
|
|
198
|
+
|
|
199
|
+
state.openSubmenus.value = ['section'];
|
|
200
|
+
state.toggleSubmenu(nestedItems[0]);
|
|
201
|
+
expect(state.isSubmenuOpen(nestedItems[0])).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('uses label as key when id is not present', () => {
|
|
205
|
+
const emit = vi.fn();
|
|
206
|
+
const itemsWithoutId = [
|
|
207
|
+
{
|
|
208
|
+
label: 'Section',
|
|
209
|
+
children: [{ label: 'Child', href: '/child' }]
|
|
210
|
+
}
|
|
211
|
+
];
|
|
212
|
+
const state = useSidebarState({ items: itemsWithoutId }, emit);
|
|
213
|
+
|
|
214
|
+
state.toggleSubmenu(itemsWithoutId[0]);
|
|
215
|
+
expect(state.openSubmenus.value).toContain('Section');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('initializeOpenSubmenus', () => {
|
|
220
|
+
const nestedItems = [
|
|
221
|
+
{
|
|
222
|
+
id: 'section1',
|
|
223
|
+
label: 'Section 1',
|
|
224
|
+
children: [{ id: 'child1', label: 'Child 1', href: '/section1/child1' }]
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: 'section2',
|
|
228
|
+
label: 'Section 2',
|
|
229
|
+
children: [{ id: 'child2', label: 'Child 2', href: '/section2/child2' }]
|
|
230
|
+
}
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
it('opens parent submenu when child is active', () => {
|
|
234
|
+
const emit = vi.fn();
|
|
235
|
+
const state = useSidebarState(
|
|
236
|
+
{ items: nestedItems, activeRoute: '/section1/child1' },
|
|
237
|
+
emit
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
state.initializeOpenSubmenus();
|
|
241
|
+
expect(state.openSubmenus.value).toContain('section1');
|
|
242
|
+
expect(state.openSubmenus.value).not.toContain('section2');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('does nothing when no active route', () => {
|
|
246
|
+
const emit = vi.fn();
|
|
247
|
+
const state = useSidebarState({ items: nestedItems }, emit);
|
|
248
|
+
|
|
249
|
+
state.initializeOpenSubmenus();
|
|
250
|
+
expect(state.openSubmenus.value).toEqual([]);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('does not duplicate entries', () => {
|
|
254
|
+
const emit = vi.fn();
|
|
255
|
+
const state = useSidebarState(
|
|
256
|
+
{ items: nestedItems, activeRoute: '/section1/child1' },
|
|
257
|
+
emit
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
state.initializeOpenSubmenus();
|
|
261
|
+
state.initializeOpenSubmenus();
|
|
262
|
+
expect(
|
|
263
|
+
state.openSubmenus.value.filter((k) => k === 'section1').length
|
|
264
|
+
).toBe(1);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe('toggleCollapsed', () => {
|
|
269
|
+
it('toggles collapsed state', () => {
|
|
270
|
+
const emit = vi.fn();
|
|
271
|
+
const state = useSidebarState({ items }, emit);
|
|
272
|
+
|
|
273
|
+
expect(state.collapsed.value).toBe(false);
|
|
274
|
+
state.toggleCollapsed();
|
|
275
|
+
expect(state.collapsed.value).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('emits events on toggle', () => {
|
|
279
|
+
const emit = vi.fn();
|
|
280
|
+
const state = useSidebarState({ items }, emit);
|
|
281
|
+
|
|
282
|
+
state.toggleCollapsed();
|
|
283
|
+
expect(emit).toHaveBeenCalledWith('update:collapsed', true);
|
|
284
|
+
expect(emit).toHaveBeenCalledWith('toggle', true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('closes submenus when collapsed', async () => {
|
|
288
|
+
const emit = vi.fn();
|
|
289
|
+
const nestedItems = [
|
|
290
|
+
{
|
|
291
|
+
id: 'section',
|
|
292
|
+
label: 'Section',
|
|
293
|
+
children: [{ id: 'child', label: 'Child', href: '/child' }]
|
|
294
|
+
}
|
|
295
|
+
];
|
|
296
|
+
const state = useSidebarState({ items: nestedItems }, emit);
|
|
297
|
+
|
|
298
|
+
state.toggleSubmenu(nestedItems[0]);
|
|
299
|
+
expect(state.openSubmenus.value.length).toBe(1);
|
|
300
|
+
|
|
301
|
+
state.toggleCollapsed();
|
|
302
|
+
// Wait for watcher to trigger
|
|
303
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
304
|
+
expect(state.openSubmenus.value).toEqual([]);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { ref, computed, watch, Ref, ComputedRef } from 'vue';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Navigation item structure
|
|
6
|
+
*/
|
|
7
|
+
export interface NavigationItem {
|
|
8
|
+
id?: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
icon?: string;
|
|
11
|
+
href?: string;
|
|
12
|
+
to?: string;
|
|
13
|
+
children?: NavigationItem[];
|
|
14
|
+
badge?: string | number;
|
|
15
|
+
badgeVariant?: string;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
type?: 'link' | 'group' | 'divider';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Configuration options for useSidebarState
|
|
22
|
+
*/
|
|
23
|
+
export interface SidebarStateOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Navigation items configuration
|
|
26
|
+
*/
|
|
27
|
+
items: NavigationItem[];
|
|
28
|
+
/**
|
|
29
|
+
* Initial collapsed state
|
|
30
|
+
*/
|
|
31
|
+
initialCollapsed?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Current active route path
|
|
34
|
+
*/
|
|
35
|
+
activeRoute?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Return type for useSidebarState
|
|
40
|
+
*/
|
|
41
|
+
export interface SidebarState {
|
|
42
|
+
// Collapse state
|
|
43
|
+
collapsed: Ref<boolean>;
|
|
44
|
+
|
|
45
|
+
// Submenu state
|
|
46
|
+
openSubmenus: Ref<string[]>;
|
|
47
|
+
|
|
48
|
+
// Filtered navigation items
|
|
49
|
+
navigationItems: ComputedRef<NavigationItem[]>;
|
|
50
|
+
|
|
51
|
+
// Methods
|
|
52
|
+
toggleCollapsed: () => void;
|
|
53
|
+
isSubmenuOpen: (item: NavigationItem) => boolean;
|
|
54
|
+
toggleSubmenu: (item: NavigationItem) => void;
|
|
55
|
+
isItemActive: (item: NavigationItem) => boolean;
|
|
56
|
+
hasActiveChild: (item: NavigationItem) => boolean;
|
|
57
|
+
initializeOpenSubmenus: () => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Composable for managing navigation sidebar state
|
|
62
|
+
*
|
|
63
|
+
* Handles collapsed state, submenu navigation, and active route detection.
|
|
64
|
+
*
|
|
65
|
+
* @param options - Configuration options for the sidebar state
|
|
66
|
+
* @param emit - Emit function from the component setup
|
|
67
|
+
* @returns Object containing reactive state and methods for sidebar operations
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const sidebarState = useSidebarState({
|
|
72
|
+
* items: navigationItems,
|
|
73
|
+
* activeRoute: '/dashboard'
|
|
74
|
+
* }, emit);
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function useSidebarState(
|
|
78
|
+
options: SidebarStateOptions,
|
|
79
|
+
emit: (event: string, ...args: any[]) => void
|
|
80
|
+
): SidebarState {
|
|
81
|
+
const { items, initialCollapsed = false, activeRoute = '' } = options;
|
|
82
|
+
|
|
83
|
+
// Reactive state
|
|
84
|
+
const collapsed = ref(initialCollapsed);
|
|
85
|
+
const openSubmenus = ref<string[]>([]);
|
|
86
|
+
|
|
87
|
+
// Computed: Filtered navigation items (excluding invalid entries)
|
|
88
|
+
const navigationItems = computed(() => {
|
|
89
|
+
return items.filter(
|
|
90
|
+
(item) => item && (item.label || item.type === 'divider')
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if an item is currently active
|
|
96
|
+
*/
|
|
97
|
+
const isItemActive = (item: NavigationItem): boolean => {
|
|
98
|
+
if (!activeRoute) return false;
|
|
99
|
+
|
|
100
|
+
const itemPath = item.to || item.href;
|
|
101
|
+
if (!itemPath) return false;
|
|
102
|
+
|
|
103
|
+
// Exact match
|
|
104
|
+
if (activeRoute === itemPath) return true;
|
|
105
|
+
|
|
106
|
+
// For nested routes: check if active route starts with item path
|
|
107
|
+
// followed by '/' or end of string to avoid partial matches
|
|
108
|
+
// e.g., '/users' should not match '/user-settings'
|
|
109
|
+
if (itemPath !== '/') {
|
|
110
|
+
return activeRoute.startsWith(itemPath + '/') || activeRoute === itemPath;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return false;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if a parent item has an active child
|
|
118
|
+
*/
|
|
119
|
+
const hasActiveChild = (item: NavigationItem): boolean => {
|
|
120
|
+
if (!item.children || item.children.length === 0) return false;
|
|
121
|
+
return item.children.some((child) => isItemActive(child));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check if a submenu is open
|
|
126
|
+
*/
|
|
127
|
+
const isSubmenuOpen = (item: NavigationItem): boolean => {
|
|
128
|
+
const key = item.id || item.label || '';
|
|
129
|
+
return openSubmenus.value.includes(key);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Toggle submenu open state
|
|
134
|
+
*/
|
|
135
|
+
const toggleSubmenu = (item: NavigationItem): void => {
|
|
136
|
+
const key = item.id || item.label || '';
|
|
137
|
+
const index = openSubmenus.value.indexOf(key);
|
|
138
|
+
|
|
139
|
+
if (index === -1) {
|
|
140
|
+
openSubmenus.value.push(key);
|
|
141
|
+
} else {
|
|
142
|
+
openSubmenus.value.splice(index, 1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
emit('submenu-toggle', { item, open: index === -1 });
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Toggle sidebar collapsed state
|
|
150
|
+
*/
|
|
151
|
+
const toggleCollapsed = (): void => {
|
|
152
|
+
collapsed.value = !collapsed.value;
|
|
153
|
+
emit('update:collapsed', collapsed.value);
|
|
154
|
+
emit('toggle', collapsed.value);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Initialize open submenus based on active route
|
|
159
|
+
*/
|
|
160
|
+
const initializeOpenSubmenus = (): void => {
|
|
161
|
+
if (!activeRoute) return;
|
|
162
|
+
|
|
163
|
+
items.forEach((item) => {
|
|
164
|
+
if (item.children && item.children.length > 0) {
|
|
165
|
+
const hasActiveChildItem = item.children.some((child) =>
|
|
166
|
+
isItemActive(child)
|
|
167
|
+
);
|
|
168
|
+
const key = item.id || item.label || '';
|
|
169
|
+
if (hasActiveChildItem && !openSubmenus.value.includes(key)) {
|
|
170
|
+
openSubmenus.value.push(key);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Watch: Close submenus when sidebar is collapsed
|
|
177
|
+
watch(collapsed, (newValue) => {
|
|
178
|
+
if (newValue) {
|
|
179
|
+
openSubmenus.value = [];
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
// Collapse state
|
|
185
|
+
collapsed,
|
|
186
|
+
|
|
187
|
+
// Submenu state
|
|
188
|
+
openSubmenus,
|
|
189
|
+
|
|
190
|
+
// Filtered navigation items
|
|
191
|
+
navigationItems,
|
|
192
|
+
|
|
193
|
+
// Methods
|
|
194
|
+
toggleCollapsed,
|
|
195
|
+
isSubmenuOpen,
|
|
196
|
+
toggleSubmenu,
|
|
197
|
+
isItemActive,
|
|
198
|
+
hasActiveChild,
|
|
199
|
+
initializeOpenSubmenus
|
|
200
|
+
};
|
|
201
|
+
}
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
declare module '*.vue' {
|
|
4
|
+
import type { DefineComponent } from 'vue';
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-explicit-any
|
|
6
|
+
const component: DefineComponent<{}, {}, any>;
|
|
7
|
+
export default component;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Déclaration du module principal avec types enrichis
|
|
11
|
+
declare module '@pyreweb/fabric' {
|
|
12
|
+
export * from './components';
|
|
13
|
+
export * from './types';
|
|
14
|
+
}
|