@pattern-stack/frontend-patterns 0.0.3 → 0.0.4
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/dist/index.es.js +1 -1
- package/dist/index.js +1 -0
- package/package.json +5 -3
- 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,173 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Card } from '../atoms/ui/card';
|
|
3
|
+
import { ShowcaseSection } from '../molecules/layout';
|
|
4
|
+
|
|
5
|
+
interface PrimaryComponentSection {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
badge: {
|
|
9
|
+
text: string;
|
|
10
|
+
variant?: 'status' | 'category';
|
|
11
|
+
status?: 'success' | 'warning' | 'error' | 'info' | 'neutral';
|
|
12
|
+
category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
13
|
+
size?: 'sm' | 'md' | 'lg';
|
|
14
|
+
};
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ComposedExampleSection {
|
|
19
|
+
title: string;
|
|
20
|
+
description: string;
|
|
21
|
+
badge?: {
|
|
22
|
+
text: string;
|
|
23
|
+
variant?: 'status' | 'category';
|
|
24
|
+
status?: 'success' | 'warning' | 'error' | 'info' | 'neutral';
|
|
25
|
+
category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
26
|
+
size?: 'sm' | 'md' | 'lg';
|
|
27
|
+
};
|
|
28
|
+
children: React.ReactNode;
|
|
29
|
+
category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ComponentShowcaseTemplateProps {
|
|
33
|
+
/** Category for overall theming */
|
|
34
|
+
category: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
35
|
+
/** Page title */
|
|
36
|
+
title: string;
|
|
37
|
+
/** Page description */
|
|
38
|
+
description: string;
|
|
39
|
+
/** Primary component section - core component with basic variants */
|
|
40
|
+
primaryComponent: PrimaryComponentSection;
|
|
41
|
+
/** Optional composed examples - complex usage patterns and real-world scenarios */
|
|
42
|
+
composedExamples?: {
|
|
43
|
+
title: string;
|
|
44
|
+
description: string;
|
|
45
|
+
sections: ComposedExampleSection[];
|
|
46
|
+
};
|
|
47
|
+
/** Optional ephemeral examples - inline customized components */
|
|
48
|
+
ephemeralExamples?: {
|
|
49
|
+
title: string;
|
|
50
|
+
description: string;
|
|
51
|
+
sections: ComposedExampleSection[];
|
|
52
|
+
};
|
|
53
|
+
/** Additional content that doesn't fit the primary/composed pattern */
|
|
54
|
+
additionalSections?: React.ReactNode;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const ComponentShowcaseTemplate: React.FC<ComponentShowcaseTemplateProps> = ({
|
|
58
|
+
category,
|
|
59
|
+
title,
|
|
60
|
+
description,
|
|
61
|
+
primaryComponent,
|
|
62
|
+
composedExamples,
|
|
63
|
+
ephemeralExamples,
|
|
64
|
+
additionalSections
|
|
65
|
+
}) => {
|
|
66
|
+
return (
|
|
67
|
+
<div className="space-y-8">
|
|
68
|
+
{/* Page Header */}
|
|
69
|
+
<div className="space-y-2 mb-8">
|
|
70
|
+
<h2 className="text-2xl font-bold text-foreground">{title}</h2>
|
|
71
|
+
<p className="text-muted-foreground">{description}</p>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{/* Primary Component Section */}
|
|
75
|
+
<Card className="p-6" category={category}>
|
|
76
|
+
<ShowcaseSection
|
|
77
|
+
title={primaryComponent.title}
|
|
78
|
+
description={primaryComponent.description}
|
|
79
|
+
badge={primaryComponent.badge}
|
|
80
|
+
>
|
|
81
|
+
{primaryComponent.children}
|
|
82
|
+
</ShowcaseSection>
|
|
83
|
+
</Card>
|
|
84
|
+
|
|
85
|
+
{/* Composed Examples Section */}
|
|
86
|
+
{composedExamples && (
|
|
87
|
+
<div className="space-y-6">
|
|
88
|
+
{/* Composed Examples Header */}
|
|
89
|
+
<div className="space-y-2 mt-12 mb-8">
|
|
90
|
+
<h3 className="text-xl font-bold text-foreground">{composedExamples.title}</h3>
|
|
91
|
+
<p className="text-muted-foreground">{composedExamples.description}</p>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{/* Composed Example Sections */}
|
|
95
|
+
<div className="grid gap-6">
|
|
96
|
+
{composedExamples.sections.map((section, index) => (
|
|
97
|
+
<Card
|
|
98
|
+
key={index}
|
|
99
|
+
className="p-6"
|
|
100
|
+
category={section.category || category}
|
|
101
|
+
>
|
|
102
|
+
{section.badge ? (
|
|
103
|
+
<ShowcaseSection
|
|
104
|
+
title={section.title}
|
|
105
|
+
description={section.description}
|
|
106
|
+
badge={section.badge}
|
|
107
|
+
>
|
|
108
|
+
{section.children}
|
|
109
|
+
</ShowcaseSection>
|
|
110
|
+
) : (
|
|
111
|
+
<div className="space-y-6">
|
|
112
|
+
<div className="space-y-4">
|
|
113
|
+
<h4 className="text-lg font-medium">{section.title}</h4>
|
|
114
|
+
<p className="text-sm text-muted-foreground">{section.description}</p>
|
|
115
|
+
</div>
|
|
116
|
+
<div>{section.children}</div>
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</Card>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
{/* Ephemeral Examples Section */}
|
|
126
|
+
{ephemeralExamples && (
|
|
127
|
+
<div className="space-y-6">
|
|
128
|
+
{/* Ephemeral Examples Header */}
|
|
129
|
+
<div className="space-y-2 mt-12 mb-8">
|
|
130
|
+
<h3 className="text-xl font-bold text-foreground">{ephemeralExamples.title}</h3>
|
|
131
|
+
<p className="text-muted-foreground">{ephemeralExamples.description}</p>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
{/* Ephemeral Example Sections */}
|
|
135
|
+
<div className="grid gap-6">
|
|
136
|
+
{ephemeralExamples.sections.map((section, index) => (
|
|
137
|
+
<Card
|
|
138
|
+
key={index}
|
|
139
|
+
className="p-6"
|
|
140
|
+
category={section.category || category}
|
|
141
|
+
>
|
|
142
|
+
{section.badge ? (
|
|
143
|
+
<ShowcaseSection
|
|
144
|
+
title={section.title}
|
|
145
|
+
description={section.description}
|
|
146
|
+
badge={section.badge}
|
|
147
|
+
>
|
|
148
|
+
{section.children}
|
|
149
|
+
</ShowcaseSection>
|
|
150
|
+
) : (
|
|
151
|
+
<div className="space-y-6">
|
|
152
|
+
<div className="space-y-4">
|
|
153
|
+
<h4 className="text-lg font-medium">{section.title}</h4>
|
|
154
|
+
<p className="text-sm text-muted-foreground">{section.description}</p>
|
|
155
|
+
</div>
|
|
156
|
+
<div>{section.children}</div>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
</Card>
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{/* Additional Sections */}
|
|
166
|
+
{additionalSections && (
|
|
167
|
+
<div className="space-y-6">
|
|
168
|
+
{additionalSections}
|
|
169
|
+
</div>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cn } from '../atoms/utils/utils';
|
|
3
|
+
import { SectionHeader } from '../molecules/layout/SectionHeader';
|
|
4
|
+
import { Card } from '../atoms/ui/card';
|
|
5
|
+
|
|
6
|
+
export interface DashboardTemplateProps {
|
|
7
|
+
/** Page title */
|
|
8
|
+
title: string;
|
|
9
|
+
/** Page description */
|
|
10
|
+
description?: string;
|
|
11
|
+
/** Header actions */
|
|
12
|
+
actions?: React.ReactNode;
|
|
13
|
+
/** Main content */
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
/** Additional CSS classes */
|
|
16
|
+
className?: string;
|
|
17
|
+
/** Category-based styling */
|
|
18
|
+
category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
19
|
+
/** Whether to show breadcrumbs */
|
|
20
|
+
showBreadcrumbs?: boolean;
|
|
21
|
+
/** Breadcrumb items */
|
|
22
|
+
breadcrumbs?: Array<{ label: string; href?: string }>;
|
|
23
|
+
/** Page subtitle */
|
|
24
|
+
subtitle?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const DashboardTemplate: React.FC<DashboardTemplateProps> = ({
|
|
28
|
+
title,
|
|
29
|
+
description,
|
|
30
|
+
actions,
|
|
31
|
+
children,
|
|
32
|
+
className,
|
|
33
|
+
category = 1,
|
|
34
|
+
showBreadcrumbs = false,
|
|
35
|
+
breadcrumbs = [],
|
|
36
|
+
subtitle
|
|
37
|
+
}) => {
|
|
38
|
+
return (
|
|
39
|
+
<div className={cn('flex flex-col min-h-0 flex-1', className)}>
|
|
40
|
+
{/* Header Section */}
|
|
41
|
+
<div className="flex-shrink-0 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
42
|
+
<div className="container mx-auto px-6 py-6">
|
|
43
|
+
{/* Breadcrumbs */}
|
|
44
|
+
{showBreadcrumbs && breadcrumbs.length > 0 && (
|
|
45
|
+
<nav className="mb-4">
|
|
46
|
+
<ol className="flex items-center space-x-2 text-sm">
|
|
47
|
+
{breadcrumbs.map((crumb, index) => {
|
|
48
|
+
const isLast = index === breadcrumbs.length - 1;
|
|
49
|
+
return (
|
|
50
|
+
<li key={index} className="flex items-center">
|
|
51
|
+
{crumb.href && !isLast ? (
|
|
52
|
+
<a
|
|
53
|
+
href={crumb.href}
|
|
54
|
+
className={cn(
|
|
55
|
+
'text-muted-foreground hover:text-foreground transition-colors',
|
|
56
|
+
`hover:text-category-${category}`
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
{crumb.label}
|
|
60
|
+
</a>
|
|
61
|
+
) : (
|
|
62
|
+
<span className={isLast ? `text-category-${category} font-medium` : 'text-muted-foreground'}>
|
|
63
|
+
{crumb.label}
|
|
64
|
+
</span>
|
|
65
|
+
)}
|
|
66
|
+
{!isLast && (
|
|
67
|
+
<span className="mx-2 text-muted-foreground">/</span>
|
|
68
|
+
)}
|
|
69
|
+
</li>
|
|
70
|
+
);
|
|
71
|
+
})}
|
|
72
|
+
</ol>
|
|
73
|
+
</nav>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{/* Page Header */}
|
|
77
|
+
<div className="flex items-start justify-between">
|
|
78
|
+
<div className="flex-1 min-w-0">
|
|
79
|
+
<SectionHeader
|
|
80
|
+
title={title}
|
|
81
|
+
description={description}
|
|
82
|
+
subtitle={subtitle}
|
|
83
|
+
size="lg"
|
|
84
|
+
className="text-left"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{actions && (
|
|
89
|
+
<div className="ml-6 flex-shrink-0">
|
|
90
|
+
{actions}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
{/* Main Content */}
|
|
98
|
+
<div className="flex-1 min-h-0 overflow-auto">
|
|
99
|
+
<div className="container mx-auto px-6 py-6">
|
|
100
|
+
{children}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Dashboard Grid Layout Component
|
|
109
|
+
* Provides standardized grid layouts for dashboard content
|
|
110
|
+
*/
|
|
111
|
+
export interface DashboardGridProps {
|
|
112
|
+
/** Grid columns configuration */
|
|
113
|
+
columns?: 1 | 2 | 3 | 4;
|
|
114
|
+
/** Gap between grid items */
|
|
115
|
+
gap?: 'sm' | 'md' | 'lg';
|
|
116
|
+
/** Grid content */
|
|
117
|
+
children: React.ReactNode;
|
|
118
|
+
/** Additional CSS classes */
|
|
119
|
+
className?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const DashboardGrid: React.FC<DashboardGridProps> = ({
|
|
123
|
+
columns = 3,
|
|
124
|
+
gap = 'md',
|
|
125
|
+
children,
|
|
126
|
+
className
|
|
127
|
+
}) => {
|
|
128
|
+
const columnClasses = {
|
|
129
|
+
1: 'grid-cols-1',
|
|
130
|
+
2: 'grid-cols-1 lg:grid-cols-2',
|
|
131
|
+
3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
|
132
|
+
4: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const gapClasses = {
|
|
136
|
+
sm: 'gap-4',
|
|
137
|
+
md: 'gap-6',
|
|
138
|
+
lg: 'gap-8'
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<div
|
|
143
|
+
className={cn(
|
|
144
|
+
'grid',
|
|
145
|
+
columnClasses[columns],
|
|
146
|
+
gapClasses[gap],
|
|
147
|
+
className
|
|
148
|
+
)}
|
|
149
|
+
data-component-name="DashboardGrid"
|
|
150
|
+
>
|
|
151
|
+
{children}
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Dashboard Card Component
|
|
158
|
+
* Standardized card component for dashboard content
|
|
159
|
+
*/
|
|
160
|
+
export interface DashboardCardProps {
|
|
161
|
+
/** Card title */
|
|
162
|
+
title?: string;
|
|
163
|
+
/** Card description */
|
|
164
|
+
description?: string;
|
|
165
|
+
/** Card content */
|
|
166
|
+
children: React.ReactNode;
|
|
167
|
+
/** Card actions */
|
|
168
|
+
actions?: React.ReactNode;
|
|
169
|
+
/** Additional CSS classes */
|
|
170
|
+
className?: string;
|
|
171
|
+
/** Category-based styling */
|
|
172
|
+
category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
|
173
|
+
/** Card padding */
|
|
174
|
+
padding?: 'sm' | 'md' | 'lg';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const DashboardCard: React.FC<DashboardCardProps> = ({
|
|
178
|
+
title,
|
|
179
|
+
description,
|
|
180
|
+
children,
|
|
181
|
+
actions,
|
|
182
|
+
className,
|
|
183
|
+
category,
|
|
184
|
+
padding = 'md'
|
|
185
|
+
}) => {
|
|
186
|
+
const paddingClasses = {
|
|
187
|
+
sm: 'p-4',
|
|
188
|
+
md: 'p-6',
|
|
189
|
+
lg: 'p-8'
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<Card
|
|
194
|
+
className={cn(paddingClasses[padding], className)}
|
|
195
|
+
category={category}
|
|
196
|
+
>
|
|
197
|
+
{/* Card Header */}
|
|
198
|
+
{(title || description || actions) && (
|
|
199
|
+
<div className="mb-4">
|
|
200
|
+
<div className="flex items-start justify-between">
|
|
201
|
+
<div className="flex-1 min-w-0">
|
|
202
|
+
{title && (
|
|
203
|
+
<h3 className={cn(
|
|
204
|
+
'text-lg font-semibold text-foreground mb-1',
|
|
205
|
+
category && `text-category-${category}`
|
|
206
|
+
)}>
|
|
207
|
+
{title}
|
|
208
|
+
</h3>
|
|
209
|
+
)}
|
|
210
|
+
{description && (
|
|
211
|
+
<p className="text-sm text-muted-foreground">
|
|
212
|
+
{description}
|
|
213
|
+
</p>
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
{actions && (
|
|
218
|
+
<div className="ml-4 flex-shrink-0">
|
|
219
|
+
{actions}
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
|
|
226
|
+
{/* Card Content */}
|
|
227
|
+
<div>
|
|
228
|
+
{children}
|
|
229
|
+
</div>
|
|
230
|
+
</Card>
|
|
231
|
+
);
|
|
232
|
+
};
|