@pattern-stack/frontend-patterns 1.0.0
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 +310 -0
- package/dist/atoms/composed/Accordion/Accordion.d.ts +20 -0
- package/dist/atoms/composed/Accordion/Accordion.d.ts.map +1 -0
- package/dist/atoms/composed/Accordion/index.d.ts +2 -0
- package/dist/atoms/composed/Accordion/index.d.ts.map +1 -0
- package/dist/atoms/composed/Alert/Alert.d.ts +25 -0
- package/dist/atoms/composed/Alert/Alert.d.ts.map +1 -0
- package/dist/atoms/composed/Alert/index.d.ts +2 -0
- package/dist/atoms/composed/Alert/index.d.ts.map +1 -0
- package/dist/atoms/composed/Breadcrumb/Breadcrumb.d.ts +17 -0
- package/dist/atoms/composed/Breadcrumb/Breadcrumb.d.ts.map +1 -0
- package/dist/atoms/composed/Breadcrumb/index.d.ts +2 -0
- package/dist/atoms/composed/Breadcrumb/index.d.ts.map +1 -0
- package/dist/atoms/composed/Chart/Chart.d.ts +37 -0
- package/dist/atoms/composed/Chart/Chart.d.ts.map +1 -0
- package/dist/atoms/composed/Chart/index.d.ts +3 -0
- package/dist/atoms/composed/Chart/index.d.ts.map +1 -0
- package/dist/atoms/composed/ColorSwatch/ColorSwatch.d.ts +19 -0
- package/dist/atoms/composed/ColorSwatch/ColorSwatch.d.ts.map +1 -0
- package/dist/atoms/composed/ColorSwatch/index.d.ts +2 -0
- package/dist/atoms/composed/ColorSwatch/index.d.ts.map +1 -0
- package/dist/atoms/composed/DarkModeToggle.d.ts +4 -0
- package/dist/atoms/composed/DarkModeToggle.d.ts.map +1 -0
- package/dist/atoms/composed/DataBadge/DataBadge.d.ts +13 -0
- package/dist/atoms/composed/DataBadge/DataBadge.d.ts.map +1 -0
- package/dist/atoms/composed/DataBadge/index.d.ts +2 -0
- package/dist/atoms/composed/DataBadge/index.d.ts.map +1 -0
- package/dist/atoms/composed/DataTable/DataTable.d.ts +28 -0
- package/dist/atoms/composed/DataTable/DataTable.d.ts.map +1 -0
- package/dist/atoms/composed/DataTable/TableCellWithTooltip.d.ts +10 -0
- package/dist/atoms/composed/DataTable/TableCellWithTooltip.d.ts.map +1 -0
- package/dist/atoms/composed/DataTable/index.d.ts +3 -0
- package/dist/atoms/composed/DataTable/index.d.ts.map +1 -0
- package/dist/atoms/composed/DateTimePicker/DateTimePicker.d.ts +45 -0
- package/dist/atoms/composed/DateTimePicker/DateTimePicker.d.ts.map +1 -0
- package/dist/atoms/composed/DateTimePicker/index.d.ts +3 -0
- package/dist/atoms/composed/DateTimePicker/index.d.ts.map +1 -0
- package/dist/atoms/composed/DetailedCard/DetailedCard.d.ts +30 -0
- package/dist/atoms/composed/DetailedCard/DetailedCard.d.ts.map +1 -0
- package/dist/atoms/composed/DetailedCard/index.d.ts +3 -0
- package/dist/atoms/composed/DetailedCard/index.d.ts.map +1 -0
- package/dist/atoms/composed/EmptyState/EmptyState.d.ts +18 -0
- package/dist/atoms/composed/EmptyState/EmptyState.d.ts.map +1 -0
- package/dist/atoms/composed/EmptyState/index.d.ts +2 -0
- package/dist/atoms/composed/EmptyState/index.d.ts.map +1 -0
- package/dist/atoms/composed/FileUpload/FileUpload.d.ts +46 -0
- package/dist/atoms/composed/FileUpload/FileUpload.d.ts.map +1 -0
- package/dist/atoms/composed/FileUpload/index.d.ts +3 -0
- package/dist/atoms/composed/FileUpload/index.d.ts.map +1 -0
- package/dist/atoms/composed/FormField/FormField.d.ts +23 -0
- package/dist/atoms/composed/FormField/FormField.d.ts.map +1 -0
- package/dist/atoms/composed/FormField/index.d.ts +2 -0
- package/dist/atoms/composed/FormField/index.d.ts.map +1 -0
- package/dist/atoms/composed/GlobalSearch/GlobalSearch.d.ts +8 -0
- package/dist/atoms/composed/GlobalSearch/GlobalSearch.d.ts.map +1 -0
- package/dist/atoms/composed/GlobalSearch/index.d.ts +2 -0
- package/dist/atoms/composed/GlobalSearch/index.d.ts.map +1 -0
- package/dist/atoms/composed/IconBadge/IconBadge.d.ts +16 -0
- package/dist/atoms/composed/IconBadge/IconBadge.d.ts.map +1 -0
- package/dist/atoms/composed/IconBadge/index.d.ts +3 -0
- package/dist/atoms/composed/IconBadge/index.d.ts.map +1 -0
- package/dist/atoms/composed/Modal/Modal.d.ts +18 -0
- package/dist/atoms/composed/Modal/Modal.d.ts.map +1 -0
- package/dist/atoms/composed/Modal/index.d.ts +3 -0
- package/dist/atoms/composed/Modal/index.d.ts.map +1 -0
- package/dist/atoms/composed/PaletteSwitcher.d.ts +7 -0
- package/dist/atoms/composed/PaletteSwitcher.d.ts.map +1 -0
- package/dist/atoms/composed/ProgressBar/ProgressBar.d.ts +25 -0
- package/dist/atoms/composed/ProgressBar/ProgressBar.d.ts.map +1 -0
- package/dist/atoms/composed/ProgressBar/index.d.ts +2 -0
- package/dist/atoms/composed/ProgressBar/index.d.ts.map +1 -0
- package/dist/atoms/composed/StatCard/StatCard.d.ts +21 -0
- package/dist/atoms/composed/StatCard/StatCard.d.ts.map +1 -0
- package/dist/atoms/composed/StatCard/index.d.ts +2 -0
- package/dist/atoms/composed/StatCard/index.d.ts.map +1 -0
- package/dist/atoms/composed/StyleGuide.d.ts +3 -0
- package/dist/atoms/composed/StyleGuide.d.ts.map +1 -0
- package/dist/atoms/composed/Toast/Toast.d.ts +40 -0
- package/dist/atoms/composed/Toast/Toast.d.ts.map +1 -0
- package/dist/atoms/composed/Toast/index.d.ts +2 -0
- package/dist/atoms/composed/Toast/index.d.ts.map +1 -0
- package/dist/atoms/composed/Tooltip/Tooltip.d.ts +16 -0
- package/dist/atoms/composed/Tooltip/Tooltip.d.ts.map +1 -0
- package/dist/atoms/composed/Tooltip/index.d.ts +2 -0
- package/dist/atoms/composed/Tooltip/index.d.ts.map +1 -0
- package/dist/atoms/composed/UserAvatar/UserAvatar.d.ts +8 -0
- package/dist/atoms/composed/UserAvatar/UserAvatar.d.ts.map +1 -0
- package/dist/atoms/composed/UserAvatar/index.d.ts +2 -0
- package/dist/atoms/composed/UserAvatar/index.d.ts.map +1 -0
- package/dist/atoms/composed/UserMenu/UserMenu.d.ts +8 -0
- package/dist/atoms/composed/UserMenu/UserMenu.d.ts.map +1 -0
- package/dist/atoms/composed/UserMenu/index.d.ts +2 -0
- package/dist/atoms/composed/UserMenu/index.d.ts.map +1 -0
- package/dist/atoms/composed/index.d.ts +25 -0
- package/dist/atoms/composed/index.d.ts.map +1 -0
- package/dist/atoms/hooks/useApi.d.ts +25 -0
- package/dist/atoms/hooks/useApi.d.ts.map +1 -0
- package/dist/atoms/hooks/useHealth.d.ts +19 -0
- package/dist/atoms/hooks/useHealth.d.ts.map +1 -0
- package/dist/atoms/index.d.ts +9 -0
- package/dist/atoms/index.d.ts.map +1 -0
- package/dist/atoms/services/api/client.d.ts +20 -0
- package/dist/atoms/services/api/client.d.ts.map +1 -0
- package/dist/atoms/services/auth-service.d.ts +24 -0
- package/dist/atoms/services/auth-service.d.ts.map +1 -0
- package/dist/atoms/services/health.d.ts +7 -0
- package/dist/atoms/services/health.d.ts.map +1 -0
- package/dist/atoms/services/index.d.ts +4 -0
- package/dist/atoms/services/index.d.ts.map +1 -0
- package/dist/atoms/shared/config/constants.d.ts +15 -0
- package/dist/atoms/shared/config/constants.d.ts.map +1 -0
- package/dist/atoms/shared/config/dashboard-sizes.d.ts +83 -0
- package/dist/atoms/shared/config/dashboard-sizes.d.ts.map +1 -0
- package/dist/atoms/shared/config/environment.d.ts +10 -0
- package/dist/atoms/shared/config/environment.d.ts.map +1 -0
- package/dist/atoms/shared/index.d.ts +4 -0
- package/dist/atoms/shared/index.d.ts.map +1 -0
- package/dist/atoms/types/auth.d.ts +56 -0
- package/dist/atoms/types/auth.d.ts.map +1 -0
- package/dist/atoms/types/generated.d.ts +1469 -0
- package/dist/atoms/types/generated.d.ts.map +1 -0
- package/dist/atoms/types/index.d.ts +4 -0
- package/dist/atoms/types/index.d.ts.map +1 -0
- package/dist/atoms/types/loading.d.ts +26 -0
- package/dist/atoms/types/loading.d.ts.map +1 -0
- package/dist/atoms/ui/Badge.d.ts +10 -0
- package/dist/atoms/ui/Badge.d.ts.map +1 -0
- package/dist/atoms/ui/ErrorBoundary.d.ts +18 -0
- package/dist/atoms/ui/ErrorBoundary.d.ts.map +1 -0
- package/dist/atoms/ui/Select.d.ts +28 -0
- package/dist/atoms/ui/Select.d.ts.map +1 -0
- package/dist/atoms/ui/Switch.d.ts +9 -0
- package/dist/atoms/ui/Switch.d.ts.map +1 -0
- package/dist/atoms/ui/Tabs.d.ts +30 -0
- package/dist/atoms/ui/Tabs.d.ts.map +1 -0
- package/dist/atoms/ui/avatar.d.ts +7 -0
- package/dist/atoms/ui/avatar.d.ts.map +1 -0
- package/dist/atoms/ui/button.d.ts +14 -0
- package/dist/atoms/ui/button.d.ts.map +1 -0
- package/dist/atoms/ui/card.d.ts +12 -0
- package/dist/atoms/ui/card.d.ts.map +1 -0
- package/dist/atoms/ui/dropdown-menu.d.ts +28 -0
- package/dist/atoms/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/atoms/ui/index.d.ts +15 -0
- package/dist/atoms/ui/index.d.ts.map +1 -0
- package/dist/atoms/ui/input.d.ts +5 -0
- package/dist/atoms/ui/input.d.ts.map +1 -0
- package/dist/atoms/ui/label.d.ts +6 -0
- package/dist/atoms/ui/label.d.ts.map +1 -0
- package/dist/atoms/ui/skeleton.d.ts +3 -0
- package/dist/atoms/ui/skeleton.d.ts.map +1 -0
- package/dist/atoms/ui/spinner.d.ts +14 -0
- package/dist/atoms/ui/spinner.d.ts.map +1 -0
- package/dist/atoms/ui/table.d.ts +11 -0
- package/dist/atoms/ui/table.d.ts.map +1 -0
- package/dist/atoms/utils/animations.d.ts +65 -0
- package/dist/atoms/utils/animations.d.ts.map +1 -0
- package/dist/atoms/utils/tooltip-helpers.d.ts +71 -0
- package/dist/atoms/utils/tooltip-helpers.d.ts.map +1 -0
- package/dist/atoms/utils/utils.d.ts +4 -0
- package/dist/atoms/utils/utils.d.ts.map +1 -0
- package/dist/features/auth/components/LoginForm.d.ts +2 -0
- package/dist/features/auth/components/LoginForm.d.ts.map +1 -0
- package/dist/features/auth/components/LogoutButton.d.ts +2 -0
- package/dist/features/auth/components/LogoutButton.d.ts.map +1 -0
- package/dist/features/auth/components/ProtectedRoute.d.ts +10 -0
- package/dist/features/auth/components/ProtectedRoute.d.ts.map +1 -0
- package/dist/features/auth/components/index.d.ts +4 -0
- package/dist/features/auth/components/index.d.ts.map +1 -0
- package/dist/features/auth/hooks/index.d.ts +3 -0
- package/dist/features/auth/hooks/index.d.ts.map +1 -0
- package/dist/features/auth/hooks/useAuth.d.ts +10 -0
- package/dist/features/auth/hooks/useAuth.d.ts.map +1 -0
- package/dist/features/auth/hooks/usePermissions.d.ts +13 -0
- package/dist/features/auth/hooks/usePermissions.d.ts.map +1 -0
- package/dist/features/auth/index.d.ts +3 -0
- package/dist/features/auth/index.d.ts.map +1 -0
- package/dist/features/index.d.ts +2 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/frontend-patterns.css +567 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +10152 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.js +10170 -0
- package/dist/index.js.map +1 -0
- package/dist/molecules/forms/FormGroup.d.ts +17 -0
- package/dist/molecules/forms/FormGroup.d.ts.map +1 -0
- package/dist/molecules/forms/SearchInput.d.ts +36 -0
- package/dist/molecules/forms/SearchInput.d.ts.map +1 -0
- package/dist/molecules/forms/index.d.ts +3 -0
- package/dist/molecules/forms/index.d.ts.map +1 -0
- package/dist/molecules/index.d.ts +4 -0
- package/dist/molecules/index.d.ts.map +1 -0
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts +7 -0
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +1 -0
- package/dist/molecules/layout/AppHeader/index.d.ts +2 -0
- package/dist/molecules/layout/AppHeader/index.d.ts.map +1 -0
- package/dist/molecules/layout/AppLayout.d.ts +2 -0
- package/dist/molecules/layout/AppLayout.d.ts.map +1 -0
- package/dist/molecules/layout/PageTemplate.d.ts +19 -0
- package/dist/molecules/layout/PageTemplate.d.ts.map +1 -0
- package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts +24 -0
- package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts.map +1 -0
- package/dist/molecules/layout/SectionHeader/index.d.ts +2 -0
- package/dist/molecules/layout/SectionHeader/index.d.ts.map +1 -0
- package/dist/molecules/layout/ShowcaseSection.d.ts +22 -0
- package/dist/molecules/layout/ShowcaseSection.d.ts.map +1 -0
- package/dist/molecules/layout/Sidebar.d.ts +6 -0
- package/dist/molecules/layout/Sidebar.d.ts.map +1 -0
- package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts +13 -0
- package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts.map +1 -0
- package/dist/molecules/layout/SidebarButton/index.d.ts +2 -0
- package/dist/molecules/layout/SidebarButton/index.d.ts.map +1 -0
- package/dist/molecules/layout/SidebarContext.d.ts +12 -0
- package/dist/molecules/layout/SidebarContext.d.ts.map +1 -0
- package/dist/molecules/layout/index.d.ts +8 -0
- package/dist/molecules/layout/index.d.ts.map +1 -0
- package/dist/molecules/navigation/NavMenu.d.ts +20 -0
- package/dist/molecules/navigation/NavMenu.d.ts.map +1 -0
- package/dist/molecules/navigation/Pagination.d.ts +14 -0
- package/dist/molecules/navigation/Pagination.d.ts.map +1 -0
- package/dist/molecules/navigation/index.d.ts +3 -0
- package/dist/molecules/navigation/index.d.ts.map +1 -0
- package/dist/organisms/index.d.ts +2 -0
- package/dist/organisms/index.d.ts.map +1 -0
- package/dist/organisms/showcase/ComponentShowcasePage.d.ts +3 -0
- package/dist/organisms/showcase/ComponentShowcasePage.d.ts.map +1 -0
- package/dist/templates/AuthTemplate.d.ts +68 -0
- package/dist/templates/AuthTemplate.d.ts.map +1 -0
- package/dist/templates/ComponentShowcaseTemplate.d.ts +53 -0
- package/dist/templates/ComponentShowcaseTemplate.d.ts.map +1 -0
- package/dist/templates/DashboardTemplate.d.ts +62 -0
- package/dist/templates/DashboardTemplate.d.ts.map +1 -0
- package/dist/templates/DataTemplate.d.ts +78 -0
- package/dist/templates/DataTemplate.d.ts.map +1 -0
- package/dist/templates/admin/AdminCRUDTemplate.d.ts +105 -0
- package/dist/templates/admin/AdminCRUDTemplate.d.ts.map +1 -0
- package/dist/templates/admin/AdminDashboardTemplate.d.ts +89 -0
- package/dist/templates/admin/AdminDashboardTemplate.d.ts.map +1 -0
- package/dist/templates/admin/AdminDetailTemplate.d.ts +132 -0
- package/dist/templates/admin/AdminDetailTemplate.d.ts.map +1 -0
- package/dist/templates/admin/index.d.ts +4 -0
- package/dist/templates/admin/index.d.ts.map +1 -0
- package/dist/templates/factory.d.ts +28 -0
- package/dist/templates/factory.d.ts.map +1 -0
- package/dist/templates/index.d.ts +7 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/package.json +118 -0
- package/src/App.css +42 -0
- package/src/App.tsx +54 -0
- package/src/__tests__/README.md +221 -0
- package/src/__tests__/atoms/hooks/simple-hooks.test.ts +44 -0
- package/src/__tests__/atoms/ui/button.test.tsx +68 -0
- package/src/__tests__/atoms/utils/simple.test.ts +18 -0
- package/src/__tests__/atoms/utils/utils.test.ts +77 -0
- package/src/__tests__/features/auth/simple-auth.test.tsx +40 -0
- package/src/__tests__/molecules/layout/simple-layout.test.tsx +81 -0
- package/src/__tests__/organisms/showcase/simple-showcase.test.tsx +167 -0
- package/src/__tests__/setup.ts +51 -0
- package/src/__tests__/utils.tsx +123 -0
- package/src/atoms/composed/Accordion/Accordion.tsx +271 -0
- package/src/atoms/composed/Accordion/index.ts +1 -0
- package/src/atoms/composed/Alert/Alert.tsx +132 -0
- package/src/atoms/composed/Alert/index.ts +1 -0
- package/src/atoms/composed/Breadcrumb/Breadcrumb.tsx +83 -0
- package/src/atoms/composed/Breadcrumb/index.ts +1 -0
- package/src/atoms/composed/Chart/Chart.tsx +425 -0
- package/src/atoms/composed/Chart/index.ts +2 -0
- package/src/atoms/composed/ColorSwatch/ColorSwatch.tsx +72 -0
- package/src/atoms/composed/ColorSwatch/index.ts +1 -0
- package/src/atoms/composed/DarkModeToggle.tsx +66 -0
- package/src/atoms/composed/DataBadge/DataBadge.tsx +81 -0
- package/src/atoms/composed/DataBadge/index.ts +1 -0
- package/src/atoms/composed/DataTable/DataTable.tsx +394 -0
- package/src/atoms/composed/DataTable/TableCellWithTooltip.tsx +41 -0
- package/src/atoms/composed/DataTable/index.ts +2 -0
- package/src/atoms/composed/DateTimePicker/DateTimePicker.tsx +611 -0
- package/src/atoms/composed/DateTimePicker/index.ts +2 -0
- package/src/atoms/composed/DetailedCard/DetailedCard.tsx +181 -0
- package/src/atoms/composed/DetailedCard/index.ts +2 -0
- package/src/atoms/composed/EmptyState/EmptyState.tsx +90 -0
- package/src/atoms/composed/EmptyState/index.ts +1 -0
- package/src/atoms/composed/FileUpload/FileUpload.tsx +477 -0
- package/src/atoms/composed/FileUpload/index.ts +2 -0
- package/src/atoms/composed/FormField/FormField.tsx +92 -0
- package/src/atoms/composed/FormField/index.ts +1 -0
- package/src/atoms/composed/GlobalSearch/GlobalSearch.tsx +37 -0
- package/src/atoms/composed/GlobalSearch/index.ts +1 -0
- package/src/atoms/composed/IconBadge/IconBadge.tsx +95 -0
- package/src/atoms/composed/IconBadge/index.ts +2 -0
- package/src/atoms/composed/Modal/Modal.tsx +223 -0
- package/src/atoms/composed/Modal/index.ts +2 -0
- package/src/atoms/composed/PaletteSwitcher.tsx +386 -0
- package/src/atoms/composed/ProgressBar/ProgressBar.tsx +116 -0
- package/src/atoms/composed/ProgressBar/index.ts +1 -0
- package/src/atoms/composed/StatCard/StatCard.tsx +219 -0
- package/src/atoms/composed/StatCard/index.ts +1 -0
- package/src/atoms/composed/StyleGuide.tsx +717 -0
- package/src/atoms/composed/Toast/Toast.tsx +219 -0
- package/src/atoms/composed/Toast/index.ts +1 -0
- package/src/atoms/composed/Tooltip/Tooltip.tsx +213 -0
- package/src/atoms/composed/Tooltip/index.ts +1 -0
- package/src/atoms/composed/UserAvatar/UserAvatar.tsx +139 -0
- package/src/atoms/composed/UserAvatar/index.ts +1 -0
- package/src/atoms/composed/UserMenu/UserMenu.tsx +16 -0
- package/src/atoms/composed/UserMenu/index.ts +1 -0
- package/src/atoms/composed/index.ts +29 -0
- package/src/atoms/hooks/useApi.ts +80 -0
- package/src/atoms/hooks/useHealth.ts +17 -0
- package/src/atoms/index.ts +13 -0
- package/src/atoms/services/api/client.ts +134 -0
- package/src/atoms/services/auth-service.ts +248 -0
- package/src/atoms/services/health.ts +15 -0
- package/src/atoms/services/index.ts +3 -0
- package/src/atoms/shared/config/constants.ts +17 -0
- package/src/atoms/shared/config/dashboard-sizes.ts +111 -0
- package/src/atoms/shared/config/environment.ts +10 -0
- package/src/atoms/shared/index.ts +4 -0
- package/src/atoms/shared/styles/color-palettes.css +566 -0
- package/src/atoms/types/auth.ts +62 -0
- package/src/atoms/types/generated.ts +1469 -0
- package/src/atoms/types/index.ts +4 -0
- package/src/atoms/types/loading.ts +28 -0
- package/src/atoms/ui/Badge.tsx +30 -0
- package/src/atoms/ui/ErrorBoundary.tsx +59 -0
- package/src/atoms/ui/Select.tsx +53 -0
- package/src/atoms/ui/Switch.tsx +42 -0
- package/src/atoms/ui/Tabs.tsx +118 -0
- package/src/atoms/ui/avatar.tsx +48 -0
- package/src/atoms/ui/button.tsx +70 -0
- package/src/atoms/ui/card.tsx +76 -0
- package/src/atoms/ui/dropdown-menu.tsx +199 -0
- package/src/atoms/ui/index.ts +39 -0
- package/src/atoms/ui/input.tsx +23 -0
- package/src/atoms/ui/label.tsx +23 -0
- package/src/atoms/ui/skeleton.tsx +13 -0
- package/src/atoms/ui/spinner.tsx +49 -0
- package/src/atoms/ui/table.tsx +116 -0
- package/src/atoms/utils/animations.ts +135 -0
- package/src/atoms/utils/tooltip-helpers.ts +140 -0
- package/src/atoms/utils/utils.ts +9 -0
- package/src/features/auth/components/LoginForm.tsx +168 -0
- package/src/features/auth/components/LogoutButton.tsx +19 -0
- package/src/features/auth/components/ProtectedRoute.tsx +60 -0
- package/src/features/auth/components/index.ts +4 -0
- package/src/features/auth/hooks/index.ts +2 -0
- package/src/features/auth/hooks/useAuth.tsx +205 -0
- package/src/features/auth/hooks/usePermissions.ts +35 -0
- package/src/features/auth/index.ts +2 -0
- package/src/features/index.ts +2 -0
- package/src/index.css +704 -0
- package/src/index.ts +13 -0
- package/src/main.tsx +48 -0
- package/src/molecules/.gitkeep +0 -0
- package/src/molecules/forms/FormGroup.tsx +75 -0
- package/src/molecules/forms/SearchInput.tsx +259 -0
- package/src/molecules/forms/index.ts +4 -0
- package/src/molecules/index.ts +4 -0
- package/src/molecules/layout/AppHeader/AppHeader.tsx +42 -0
- package/src/molecules/layout/AppHeader/index.ts +1 -0
- package/src/molecules/layout/AppLayout.tsx +29 -0
- package/src/molecules/layout/PageTemplate.tsx +87 -0
- package/src/molecules/layout/SectionHeader/SectionHeader.tsx +87 -0
- package/src/molecules/layout/SectionHeader/index.ts +1 -0
- package/src/molecules/layout/ShowcaseSection.tsx +57 -0
- package/src/molecules/layout/Sidebar.tsx +144 -0
- package/src/molecules/layout/SidebarButton/SidebarButton.tsx +99 -0
- package/src/molecules/layout/SidebarButton/index.ts +1 -0
- package/src/molecules/layout/SidebarContext.tsx +31 -0
- package/src/molecules/layout/index.ts +7 -0
- package/src/molecules/navigation/NavMenu.tsx +188 -0
- package/src/molecules/navigation/Pagination.tsx +172 -0
- package/src/molecules/navigation/index.ts +4 -0
- package/src/organisms/index.ts +5 -0
- package/src/organisms/showcase/ComponentShowcasePage.tsx +2496 -0
- package/src/organisms/showcase/index.ts +1 -0
- package/src/pages/AdminShowcase/AdminCRUDShowcase.tsx +242 -0
- package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +171 -0
- package/src/pages/AdminShowcase/AdminDetailShowcase.tsx +385 -0
- package/src/pages/AdminShowcase/index.tsx +3 -0
- package/src/pages/ComponentShowcase/BadgesShowcase.tsx +188 -0
- package/src/pages/ComponentShowcase/CardsShowcase.tsx +392 -0
- package/src/pages/ComponentShowcase/PalettesShowcase.tsx +207 -0
- package/src/pages/ComponentShowcase/StatesShowcase.tsx +485 -0
- package/src/pages/ComponentShowcase/TablesShowcase.tsx +134 -0
- package/src/pages/ComponentShowcase/TypographyShowcase.tsx +255 -0
- package/src/pages/ComponentShowcase/index.tsx +188 -0
- package/src/pages/index.ts +2 -0
- package/src/templates/AuthTemplate.tsx +216 -0
- package/src/templates/ComponentShowcaseTemplate.tsx +173 -0
- package/src/templates/DashboardTemplate.tsx +232 -0
- package/src/templates/DataTemplate.tsx +319 -0
- package/src/templates/admin/AdminCRUDTemplate.tsx +630 -0
- package/src/templates/admin/AdminDashboardTemplate.tsx +351 -0
- package/src/templates/admin/AdminDetailTemplate.tsx +563 -0
- package/src/templates/admin/index.ts +29 -0
- package/src/templates/factory.tsx +169 -0
- package/src/templates/index.ts +37 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { X, AlertCircle, CheckCircle, AlertTriangle, Info } from 'lucide-react';
|
|
3
|
+
import { cn } from '../../utils/utils';
|
|
4
|
+
import { Button } from '../../ui/button';
|
|
5
|
+
import { getAnimationClasses, animationPresets } from '../../utils/animations';
|
|
6
|
+
|
|
7
|
+
export interface AlertProps {
|
|
8
|
+
/** Alert content */
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
/** Alert status type */
|
|
11
|
+
status?: 'success' | 'warning' | 'error' | 'info' | 'neutral';
|
|
12
|
+
/** Alert title */
|
|
13
|
+
title?: string;
|
|
14
|
+
/** Whether alert can be dismissed */
|
|
15
|
+
dismissible?: boolean;
|
|
16
|
+
/** Dismiss handler */
|
|
17
|
+
onDismiss?: () => void;
|
|
18
|
+
/** Visual variant */
|
|
19
|
+
variant?: 'filled' | 'outlined' | 'soft';
|
|
20
|
+
/** Size variant */
|
|
21
|
+
size?: 'sm' | 'md' | 'lg';
|
|
22
|
+
/** Additional CSS classes */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** Custom icon */
|
|
25
|
+
icon?: React.ReactNode;
|
|
26
|
+
/** Hide default icon */
|
|
27
|
+
hideIcon?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const statusIcons = {
|
|
31
|
+
success: CheckCircle,
|
|
32
|
+
warning: AlertTriangle,
|
|
33
|
+
error: AlertCircle,
|
|
34
|
+
info: Info,
|
|
35
|
+
neutral: Info
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Alert: React.FC<AlertProps> = ({
|
|
39
|
+
children,
|
|
40
|
+
status = 'neutral',
|
|
41
|
+
title,
|
|
42
|
+
dismissible = false,
|
|
43
|
+
onDismiss,
|
|
44
|
+
variant = 'soft',
|
|
45
|
+
size = 'md',
|
|
46
|
+
className,
|
|
47
|
+
icon,
|
|
48
|
+
hideIcon = false
|
|
49
|
+
}) => {
|
|
50
|
+
const IconComponent = statusIcons[status];
|
|
51
|
+
const displayIcon = icon || (!hideIcon && <IconComponent className="w-5 h-5" />);
|
|
52
|
+
|
|
53
|
+
const sizeClasses = {
|
|
54
|
+
sm: 'p-3 text-sm',
|
|
55
|
+
md: 'p-4 text-sm',
|
|
56
|
+
lg: 'p-6 text-base'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const variantClasses = {
|
|
60
|
+
filled: {
|
|
61
|
+
success: 'bg-status-success text-white border-status-success',
|
|
62
|
+
warning: 'bg-status-warning text-white border-status-warning',
|
|
63
|
+
error: 'bg-status-error text-white border-status-error',
|
|
64
|
+
info: 'bg-status-info text-white border-status-info',
|
|
65
|
+
neutral: 'bg-muted text-foreground border-border'
|
|
66
|
+
},
|
|
67
|
+
outlined: {
|
|
68
|
+
success: 'bg-background text-status-success border-status-success',
|
|
69
|
+
warning: 'bg-background text-status-warning border-status-warning',
|
|
70
|
+
error: 'bg-background text-status-error border-status-error',
|
|
71
|
+
info: 'bg-background text-status-info border-status-info',
|
|
72
|
+
neutral: 'bg-background text-foreground border-border'
|
|
73
|
+
},
|
|
74
|
+
soft: {
|
|
75
|
+
success: 'bg-status-success/10 text-status-success border-status-success/20',
|
|
76
|
+
warning: 'bg-status-warning/10 text-status-warning border-status-warning/20',
|
|
77
|
+
error: 'bg-status-error/10 text-status-error border-status-error/20',
|
|
78
|
+
info: 'bg-status-info/10 text-status-info border-status-info/20',
|
|
79
|
+
neutral: 'bg-muted/50 text-foreground border-border/50'
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
className={cn(
|
|
86
|
+
'rounded-lg border flex items-start gap-3 relative',
|
|
87
|
+
'transition-all duration-200',
|
|
88
|
+
sizeClasses[size],
|
|
89
|
+
variantClasses[variant][status],
|
|
90
|
+
getAnimationClasses(animationPresets.subtle),
|
|
91
|
+
className
|
|
92
|
+
)}
|
|
93
|
+
role="alert"
|
|
94
|
+
data-component-name="Alert"
|
|
95
|
+
>
|
|
96
|
+
{/* Icon */}
|
|
97
|
+
{displayIcon && (
|
|
98
|
+
<div className="flex-shrink-0 mt-0.5">
|
|
99
|
+
{displayIcon}
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{/* Content */}
|
|
104
|
+
<div className="flex-1 min-w-0">
|
|
105
|
+
{title && (
|
|
106
|
+
<h4 className="font-semibold mb-1">
|
|
107
|
+
{title}
|
|
108
|
+
</h4>
|
|
109
|
+
)}
|
|
110
|
+
<div className="text-current">
|
|
111
|
+
{children}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* Dismiss button */}
|
|
116
|
+
{dismissible && (
|
|
117
|
+
<Button
|
|
118
|
+
variant="ghost"
|
|
119
|
+
size="sm"
|
|
120
|
+
onClick={onDismiss}
|
|
121
|
+
className={cn(
|
|
122
|
+
'flex-shrink-0 h-auto p-1 -mt-1 -mr-1',
|
|
123
|
+
'hover:bg-current/10 text-current'
|
|
124
|
+
)}
|
|
125
|
+
aria-label="Dismiss alert"
|
|
126
|
+
>
|
|
127
|
+
<X className="w-4 h-4" />
|
|
128
|
+
</Button>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Alert, type AlertProps } from './Alert';
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ChevronRight, Home } from 'lucide-react';
|
|
3
|
+
import { cn } from '../../utils/utils';
|
|
4
|
+
|
|
5
|
+
export interface BreadcrumbItem {
|
|
6
|
+
label: string;
|
|
7
|
+
href?: string;
|
|
8
|
+
active?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface BreadcrumbProps {
|
|
12
|
+
items: BreadcrumbItem[];
|
|
13
|
+
showHome?: boolean;
|
|
14
|
+
homeHref?: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
separator?: React.ReactNode;
|
|
17
|
+
category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const Breadcrumb: React.FC<BreadcrumbProps> = ({
|
|
21
|
+
items,
|
|
22
|
+
showHome = true,
|
|
23
|
+
homeHref = '/',
|
|
24
|
+
className,
|
|
25
|
+
separator = <ChevronRight className="w-4 h-4" />,
|
|
26
|
+
category = 1
|
|
27
|
+
}) => {
|
|
28
|
+
const allItems = showHome
|
|
29
|
+
? [{ label: 'Home', href: homeHref, icon: <Home className="w-4 h-4" />, active: false }, ...items]
|
|
30
|
+
: items;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<nav
|
|
34
|
+
className={cn('flex items-center space-x-1 text-sm', className)}
|
|
35
|
+
aria-label="Breadcrumb"
|
|
36
|
+
data-component-name="Breadcrumb"
|
|
37
|
+
>
|
|
38
|
+
<ol className="flex items-center space-x-1">
|
|
39
|
+
{allItems.map((item, index) => {
|
|
40
|
+
const isLast = index === allItems.length - 1;
|
|
41
|
+
const isActive = item.active || isLast;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<li key={index} className="flex items-center space-x-1">
|
|
45
|
+
{item.href && !isActive ? (
|
|
46
|
+
<a
|
|
47
|
+
href={item.href}
|
|
48
|
+
className={cn(
|
|
49
|
+
'flex items-center space-x-1 hover:text-foreground transition-colors',
|
|
50
|
+
`text-category-${category}/70 hover:text-category-${category}`,
|
|
51
|
+
'hover:underline'
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
{'icon' in item && item.icon}
|
|
55
|
+
<span>{item.label}</span>
|
|
56
|
+
</a>
|
|
57
|
+
) : (
|
|
58
|
+
<span
|
|
59
|
+
className={cn(
|
|
60
|
+
'flex items-center space-x-1',
|
|
61
|
+
isActive
|
|
62
|
+
? `text-category-${category} font-medium`
|
|
63
|
+
: 'text-muted-foreground'
|
|
64
|
+
)}
|
|
65
|
+
aria-current={isActive ? 'page' : undefined}
|
|
66
|
+
>
|
|
67
|
+
{'icon' in item && item.icon}
|
|
68
|
+
<span>{item.label}</span>
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
71
|
+
|
|
72
|
+
{!isLast && (
|
|
73
|
+
<span className="text-muted-foreground ml-1">
|
|
74
|
+
{separator}
|
|
75
|
+
</span>
|
|
76
|
+
)}
|
|
77
|
+
</li>
|
|
78
|
+
);
|
|
79
|
+
})}
|
|
80
|
+
</ol>
|
|
81
|
+
</nav>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Breadcrumb, type BreadcrumbItem } from './Breadcrumb';
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// WIP: Chart component - basic implementation for showcase
|
|
2
|
+
// TODO: Rework chart implementations, improve styling, add more chart types
|
|
3
|
+
// TODO: Consider external chart library integration for production use
|
|
4
|
+
|
|
5
|
+
import { Card } from '../../ui/card';
|
|
6
|
+
import { Skeleton } from '../../ui/skeleton';
|
|
7
|
+
import { DataBadge } from '../DataBadge';
|
|
8
|
+
import { cn } from '../../utils/utils';
|
|
9
|
+
import type { ILoadable } from '../../types';
|
|
10
|
+
import { DASHBOARD_CHART_HEIGHTS } from '../../shared/config/dashboard-sizes';
|
|
11
|
+
import { TrendingUp, TrendingDown, Minus, BarChart3, LineChart, AreaChart } from 'lucide-react';
|
|
12
|
+
|
|
13
|
+
export type ChartType = 'line' | 'bar' | 'area' | 'pie';
|
|
14
|
+
export type ChartVariant = 'default' | 'minimal' | 'detailed';
|
|
15
|
+
|
|
16
|
+
export interface ChartDataPoint {
|
|
17
|
+
label: string;
|
|
18
|
+
value: number;
|
|
19
|
+
category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
20
|
+
color?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ChartProps extends ILoadable {
|
|
24
|
+
title: string;
|
|
25
|
+
subtitle?: string;
|
|
26
|
+
data: ChartDataPoint[];
|
|
27
|
+
type: ChartType;
|
|
28
|
+
variant?: ChartVariant;
|
|
29
|
+
category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
30
|
+
showLegend?: boolean;
|
|
31
|
+
showValues?: boolean;
|
|
32
|
+
showTrend?: boolean;
|
|
33
|
+
trend?: {
|
|
34
|
+
value: number;
|
|
35
|
+
label?: string;
|
|
36
|
+
};
|
|
37
|
+
/** Chart height - use standardized sizes from dashboard-sizes.ts */
|
|
38
|
+
height?: number | keyof typeof DASHBOARD_CHART_HEIGHTS;
|
|
39
|
+
onClick?: () => void;
|
|
40
|
+
className?: string;
|
|
41
|
+
/** Enable accessibility features like data table fallback */
|
|
42
|
+
includeDataTable?: boolean;
|
|
43
|
+
/** Custom color overrides for data points */
|
|
44
|
+
colorOverrides?: string[];
|
|
45
|
+
/** Don't render the outer Card wrapper (for embedded use) */
|
|
46
|
+
noWrapper?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const Chart = ({
|
|
50
|
+
title,
|
|
51
|
+
subtitle,
|
|
52
|
+
data,
|
|
53
|
+
type,
|
|
54
|
+
variant = 'default',
|
|
55
|
+
category,
|
|
56
|
+
showLegend = true,
|
|
57
|
+
showValues = false,
|
|
58
|
+
showTrend = false,
|
|
59
|
+
trend,
|
|
60
|
+
height = 'medium',
|
|
61
|
+
onClick,
|
|
62
|
+
className,
|
|
63
|
+
includeDataTable = true,
|
|
64
|
+
colorOverrides,
|
|
65
|
+
isLoading = false,
|
|
66
|
+
noWrapper = false
|
|
67
|
+
}: ChartProps) => {
|
|
68
|
+
// Resolve height to numeric value
|
|
69
|
+
const chartHeight = typeof height === 'string' ? DASHBOARD_CHART_HEIGHTS[height] : height;
|
|
70
|
+
const getChartIcon = () => {
|
|
71
|
+
switch (type) {
|
|
72
|
+
case 'line': return <LineChart className="w-4 h-4" />;
|
|
73
|
+
case 'bar': return <BarChart3 className="w-4 h-4" />;
|
|
74
|
+
case 'area': return <AreaChart className="w-4 h-4" />;
|
|
75
|
+
default: return <BarChart3 className="w-4 h-4" />;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const getTrendIcon = () => {
|
|
80
|
+
if (!trend) return null;
|
|
81
|
+
|
|
82
|
+
if (trend.value > 0) {
|
|
83
|
+
return <TrendingUp className="w-4 h-4" />;
|
|
84
|
+
} else if (trend.value < 0) {
|
|
85
|
+
return <TrendingDown className="w-4 h-4" />;
|
|
86
|
+
}
|
|
87
|
+
return <Minus className="w-4 h-4" />;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const getTrendColor = () => {
|
|
91
|
+
if (!trend) return '';
|
|
92
|
+
|
|
93
|
+
if (trend.value > 0) return 'text-status-success';
|
|
94
|
+
if (trend.value < 0) return 'text-status-error';
|
|
95
|
+
return 'text-status-neutral';
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const getDataPointColor = (index: number, point: ChartDataPoint) => {
|
|
99
|
+
// Priority: custom color override > point category > point color > chart category > default category cycle
|
|
100
|
+
if (colorOverrides && colorOverrides[index]) {
|
|
101
|
+
return colorOverrides[index];
|
|
102
|
+
}
|
|
103
|
+
if (point.category) {
|
|
104
|
+
return `hsl(var(--category-${point.category}))`;
|
|
105
|
+
}
|
|
106
|
+
if (point.color) {
|
|
107
|
+
return point.color;
|
|
108
|
+
}
|
|
109
|
+
if (category) {
|
|
110
|
+
return `hsl(var(--category-${category}))`;
|
|
111
|
+
}
|
|
112
|
+
// Default to cycling through categories
|
|
113
|
+
const categoryNum = ((index % 8) + 1) as 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
114
|
+
return `hsl(var(--category-${categoryNum}))`;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const maxValue = Math.max(...data.map(d => d.value));
|
|
118
|
+
|
|
119
|
+
const renderBarChart = () => {
|
|
120
|
+
return (
|
|
121
|
+
<div className="space-y-3" style={{ height: chartHeight }}>
|
|
122
|
+
<div className="flex items-end justify-between h-full gap-2">
|
|
123
|
+
{data.map((point, index) => {
|
|
124
|
+
const percentage = (point.value / maxValue) * 100;
|
|
125
|
+
const color = getDataPointColor(index, point);
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div key={index} className="flex flex-col items-center flex-1 h-full justify-end gap-2">
|
|
129
|
+
<div
|
|
130
|
+
className="w-full rounded-t transition-all hover:opacity-80 relative group"
|
|
131
|
+
style={{
|
|
132
|
+
height: `${percentage}%`,
|
|
133
|
+
backgroundColor: color,
|
|
134
|
+
minHeight: '4px'
|
|
135
|
+
}}
|
|
136
|
+
role="img"
|
|
137
|
+
aria-label={`${point.label}: ${point.value}`}
|
|
138
|
+
>
|
|
139
|
+
{showValues && (
|
|
140
|
+
<div className="absolute -top-6 left-1/2 transform -translate-x-1/2 text-xs font-medium text-foreground whitespace-nowrap">
|
|
141
|
+
{point.value}
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
{/* Tooltip on hover */}
|
|
145
|
+
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-1 px-2 py-1 bg-popover text-popover-foreground text-xs rounded shadow-lg opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-10">
|
|
146
|
+
{point.label}: {point.value}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
<div className="text-xs text-muted-foreground text-center truncate w-full">
|
|
150
|
+
{point.label}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
})}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const renderLineChart = () => {
|
|
161
|
+
const width = 300;
|
|
162
|
+
const internalHeight = chartHeight - 40;
|
|
163
|
+
const padding = 20;
|
|
164
|
+
const chartWidth = width - (padding * 2);
|
|
165
|
+
|
|
166
|
+
const points = data.map((point, index) => {
|
|
167
|
+
const x = padding + (index / (data.length - 1)) * chartWidth;
|
|
168
|
+
const y = internalHeight - ((point.value / maxValue) * (internalHeight - padding)) + padding;
|
|
169
|
+
return { x, y, ...point };
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const pathD = points.reduce((path, point, index) => {
|
|
173
|
+
const command = index === 0 ? 'M' : 'L';
|
|
174
|
+
return `${path} ${command} ${point.x} ${point.y}`;
|
|
175
|
+
}, '');
|
|
176
|
+
|
|
177
|
+
const areaPathD = `${pathD} L ${points[points.length - 1]?.x || 0} ${internalHeight} L ${padding} ${internalHeight} Z`;
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div className="relative" style={{ height: chartHeight }}>
|
|
181
|
+
<svg width={width} height={chartHeight} className="w-full h-full">
|
|
182
|
+
{/* Grid lines */}
|
|
183
|
+
<defs>
|
|
184
|
+
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
|
|
185
|
+
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="hsl(var(--border))" strokeWidth="0.5" opacity="0.3" />
|
|
186
|
+
</pattern>
|
|
187
|
+
</defs>
|
|
188
|
+
<rect width="100%" height="100%" fill="url(#grid)" />
|
|
189
|
+
|
|
190
|
+
{/* Area fill for area chart */}
|
|
191
|
+
{type === 'area' && (
|
|
192
|
+
<path
|
|
193
|
+
d={areaPathD}
|
|
194
|
+
fill={category ? `hsl(var(--category-${category}))` : 'hsl(var(--category-1))'}
|
|
195
|
+
fillOpacity="0.1"
|
|
196
|
+
/>
|
|
197
|
+
)}
|
|
198
|
+
|
|
199
|
+
{/* Line */}
|
|
200
|
+
<path
|
|
201
|
+
d={pathD}
|
|
202
|
+
fill="none"
|
|
203
|
+
stroke={category ? `hsl(var(--category-${category}))` : 'hsl(var(--category-1))'}
|
|
204
|
+
strokeWidth="2"
|
|
205
|
+
strokeLinecap="round"
|
|
206
|
+
strokeLinejoin="round"
|
|
207
|
+
/>
|
|
208
|
+
|
|
209
|
+
{/* Data points */}
|
|
210
|
+
{points.map((point, index) => (
|
|
211
|
+
<g key={index}>
|
|
212
|
+
<circle
|
|
213
|
+
cx={point.x}
|
|
214
|
+
cy={point.y}
|
|
215
|
+
r="4"
|
|
216
|
+
fill={getDataPointColor(index, point)}
|
|
217
|
+
stroke="hsl(var(--background))"
|
|
218
|
+
strokeWidth="2"
|
|
219
|
+
className="hover:r-6 transition-all cursor-pointer"
|
|
220
|
+
/>
|
|
221
|
+
{showValues && (
|
|
222
|
+
<text
|
|
223
|
+
x={point.x}
|
|
224
|
+
y={point.y - 10}
|
|
225
|
+
textAnchor="middle"
|
|
226
|
+
className="text-xs fill-foreground font-medium"
|
|
227
|
+
>
|
|
228
|
+
{point.value}
|
|
229
|
+
</text>
|
|
230
|
+
)}
|
|
231
|
+
</g>
|
|
232
|
+
))}
|
|
233
|
+
</svg>
|
|
234
|
+
|
|
235
|
+
{/* X-axis labels */}
|
|
236
|
+
<div className="absolute bottom-0 left-0 right-0 flex justify-between px-5">
|
|
237
|
+
{data.map((point, index) => (
|
|
238
|
+
<div key={index} className="text-xs text-muted-foreground text-center">
|
|
239
|
+
{point.label}
|
|
240
|
+
</div>
|
|
241
|
+
))}
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
const renderChart = () => {
|
|
249
|
+
switch (type) {
|
|
250
|
+
case 'line':
|
|
251
|
+
case 'area':
|
|
252
|
+
return renderLineChart();
|
|
253
|
+
case 'bar':
|
|
254
|
+
return renderBarChart();
|
|
255
|
+
default:
|
|
256
|
+
return renderBarChart();
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Loading state skeleton
|
|
261
|
+
if (isLoading) {
|
|
262
|
+
const loadingContent = (
|
|
263
|
+
<div className={cn("space-y-4", noWrapper && className)}>
|
|
264
|
+
{/* Header skeleton */}
|
|
265
|
+
<div className="flex items-center justify-between">
|
|
266
|
+
<div className="space-y-1">
|
|
267
|
+
<Skeleton className="h-5 w-32" />
|
|
268
|
+
{subtitle && <Skeleton className="h-4 w-24" />}
|
|
269
|
+
</div>
|
|
270
|
+
<Skeleton className="h-8 w-8" />
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
{/* Chart skeleton */}
|
|
274
|
+
<div className="space-y-2" style={{ height: chartHeight }}>
|
|
275
|
+
<Skeleton className="h-full w-full" />
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
{/* Legend skeleton */}
|
|
279
|
+
{showLegend && variant !== 'minimal' && (
|
|
280
|
+
<div className="space-y-2 pt-2 border-t border-border">
|
|
281
|
+
{Array.from({ length: Math.min(data.length || 3, 4) }, (_, i) => (
|
|
282
|
+
<div key={i} className="flex items-center gap-2">
|
|
283
|
+
<Skeleton className="h-3 w-3" />
|
|
284
|
+
<Skeleton className="h-4 w-20" />
|
|
285
|
+
</div>
|
|
286
|
+
))}
|
|
287
|
+
</div>
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
if (noWrapper) {
|
|
293
|
+
return loadingContent;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<Card
|
|
298
|
+
className={cn(
|
|
299
|
+
"card-container relative overflow-hidden",
|
|
300
|
+
className
|
|
301
|
+
)}
|
|
302
|
+
category={category}
|
|
303
|
+
data-component-name="Chart"
|
|
304
|
+
>
|
|
305
|
+
{loadingContent}
|
|
306
|
+
</Card>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const chartContent = (
|
|
311
|
+
<div className={cn("space-y-4", noWrapper && className)}>
|
|
312
|
+
{/* Header */}
|
|
313
|
+
{variant !== 'minimal' && (
|
|
314
|
+
<div className="flex items-center justify-between">
|
|
315
|
+
<div className="flex-1 min-w-0">
|
|
316
|
+
<div className="flex items-center gap-2">
|
|
317
|
+
<h3 className="text-lg font-semibold text-foreground">{title}</h3>
|
|
318
|
+
<div className={cn(
|
|
319
|
+
"p-2 rounded-md flex items-center justify-center transition-colors",
|
|
320
|
+
category ? `bg-category-${category}/8 text-category-${category}` : "bg-muted/50 text-muted-foreground"
|
|
321
|
+
)}>
|
|
322
|
+
{getChartIcon()}
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
{subtitle && (
|
|
326
|
+
<p className="text-sm text-muted-foreground mt-1">{subtitle}</p>
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
{showTrend && trend && (
|
|
330
|
+
<div className={cn("flex items-center gap-1.5 text-sm", getTrendColor())}>
|
|
331
|
+
{getTrendIcon()}
|
|
332
|
+
<span className="font-medium">
|
|
333
|
+
{trend.value > 0 && '+'}{trend.value}%
|
|
334
|
+
</span>
|
|
335
|
+
{trend.label && (
|
|
336
|
+
<span className="text-muted-foreground">{trend.label}</span>
|
|
337
|
+
)}
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
340
|
+
</div>
|
|
341
|
+
)}
|
|
342
|
+
|
|
343
|
+
{/* Chart */}
|
|
344
|
+
<div className="relative">
|
|
345
|
+
{renderChart()}
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
{/* Legend */}
|
|
349
|
+
{showLegend && variant !== 'minimal' && (
|
|
350
|
+
<div className="flex flex-wrap gap-3 pt-2 border-t border-border">
|
|
351
|
+
{data.map((point, index) => (
|
|
352
|
+
<div key={index} className="flex items-center gap-2">
|
|
353
|
+
<div
|
|
354
|
+
className="w-3 h-3 rounded-sm flex-shrink-0"
|
|
355
|
+
style={{ backgroundColor: getDataPointColor(index, point) }}
|
|
356
|
+
/>
|
|
357
|
+
<span className="text-sm text-foreground">{point.label}</span>
|
|
358
|
+
{point.category && (
|
|
359
|
+
<DataBadge variant="category" category={point.category} size="sm">
|
|
360
|
+
{`Cat ${point.category}`}
|
|
361
|
+
</DataBadge>
|
|
362
|
+
)}
|
|
363
|
+
</div>
|
|
364
|
+
))}
|
|
365
|
+
</div>
|
|
366
|
+
)}
|
|
367
|
+
|
|
368
|
+
{/* Accessibility: Data table fallback */}
|
|
369
|
+
{includeDataTable && !noWrapper && (
|
|
370
|
+
<details className="mt-4">
|
|
371
|
+
<summary className="text-sm text-muted-foreground cursor-pointer hover:text-foreground transition-colors">
|
|
372
|
+
View data table (accessible)
|
|
373
|
+
</summary>
|
|
374
|
+
<div className="mt-2 border border-border rounded overflow-hidden">
|
|
375
|
+
<table className="w-full text-sm">
|
|
376
|
+
<thead className="bg-muted/50">
|
|
377
|
+
<tr>
|
|
378
|
+
<th className="text-left p-2 border-b border-border">Label</th>
|
|
379
|
+
<th className="text-right p-2 border-b border-border">Value</th>
|
|
380
|
+
</tr>
|
|
381
|
+
</thead>
|
|
382
|
+
<tbody>
|
|
383
|
+
{data.map((point, index) => (
|
|
384
|
+
<tr key={index} className="border-b border-border last:border-b-0">
|
|
385
|
+
<td className="p-2 font-medium">{point.label}</td>
|
|
386
|
+
<td className="p-2 text-right">{point.value}</td>
|
|
387
|
+
</tr>
|
|
388
|
+
))}
|
|
389
|
+
</tbody>
|
|
390
|
+
</table>
|
|
391
|
+
</div>
|
|
392
|
+
</details>
|
|
393
|
+
)}
|
|
394
|
+
</div>
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
if (noWrapper) {
|
|
398
|
+
return chartContent;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return (
|
|
402
|
+
<Card
|
|
403
|
+
className={cn(
|
|
404
|
+
"card-container relative overflow-hidden transition-all animate-slide-up",
|
|
405
|
+
onClick && "cursor-pointer hover:shadow-md hover:scale-[1.01] active:scale-[0.99]",
|
|
406
|
+
category && `hover:shadow-category-${category}`,
|
|
407
|
+
"group",
|
|
408
|
+
className
|
|
409
|
+
)}
|
|
410
|
+
category={category}
|
|
411
|
+
data-component-name="Chart"
|
|
412
|
+
onClick={onClick}
|
|
413
|
+
role={onClick ? 'button' : undefined}
|
|
414
|
+
tabIndex={onClick ? 0 : undefined}
|
|
415
|
+
onKeyDown={onClick ? (e) => {
|
|
416
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
417
|
+
e.preventDefault();
|
|
418
|
+
onClick();
|
|
419
|
+
}
|
|
420
|
+
} : undefined}
|
|
421
|
+
>
|
|
422
|
+
{chartContent}
|
|
423
|
+
</Card>
|
|
424
|
+
);
|
|
425
|
+
};
|