bmj-ui 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.
@@ -0,0 +1,53 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+ import { LucideIcon } from 'lucide-react';
4
+
5
+ interface SidebarItem {
6
+ title: string;
7
+ icon: React.ElementType;
8
+ url: string;
9
+ }
10
+ declare function AppSidebar({ items, onItemClick, activeItem, }: {
11
+ items?: SidebarItem[];
12
+ onItemClick?: (title: string) => void;
13
+ activeItem?: string;
14
+ }): react_jsx_runtime.JSX.Element;
15
+
16
+ interface DashboardLayoutProps {
17
+ children: React.ReactNode;
18
+ sidebarItems?: SidebarItem[];
19
+ onSidebarItemClick?: (title: string) => void;
20
+ activeSidebarItem?: string;
21
+ }
22
+ declare function DashboardLayout({ children, sidebarItems, onSidebarItemClick, activeSidebarItem, }: DashboardLayoutProps): react_jsx_runtime.JSX.Element;
23
+
24
+ interface StatCardProps {
25
+ title: string;
26
+ value: string | number;
27
+ description?: string;
28
+ trend?: {
29
+ value: number;
30
+ isPositive: boolean;
31
+ };
32
+ icon?: LucideIcon;
33
+ className?: string;
34
+ loading?: boolean;
35
+ }
36
+ declare function StatCard({ title, value, description, trend, icon: Icon, className, loading, }: StatCardProps): react_jsx_runtime.JSX.Element;
37
+
38
+ interface ChartCardProps {
39
+ title: string;
40
+ description?: string;
41
+ data: any[];
42
+ dataKey: string;
43
+ categoryKey: string;
44
+ color?: string;
45
+ className?: string;
46
+ loading?: boolean;
47
+ type?: "area" | "bar" | "line" | "scatter";
48
+ }
49
+ declare function ChartCard({ title, description, data, dataKey, categoryKey, color, className, loading, type, }: ChartCardProps): react_jsx_runtime.JSX.Element;
50
+
51
+ declare function Topbar(): react_jsx_runtime.JSX.Element;
52
+
53
+ export { AppSidebar, ChartCard, DashboardLayout, StatCard, Topbar };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Topbar=exports.StatCard=exports.DashboardLayout=exports.ChartCard=exports.AppSidebar=void 0;const e=require("./components/bmj-ui");Object.defineProperty(exports,"AppSidebar",{enumerable:!0,get:function(){return e.AppSidebar}});const r=require("./components/bmj-ui");Object.defineProperty(exports,"ChartCard",{enumerable:!0,get:function(){return r.ChartCard}});const t=require("./components/bmj-ui");Object.defineProperty(exports,"DashboardLayout",{enumerable:!0,get:function(){return t.DashboardLayout}});const o=require("./components/bmj-ui");Object.defineProperty(exports,"StatCard",{enumerable:!0,get:function(){return o.StatCard}});const n=require("./components/bmj-ui");Object.defineProperty(exports,"Topbar",{enumerable:!0,get:function(){return n.Topbar}});
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { AppSidebar } from \"./components/bmj-ui\";\nimport { ChartCard } from \"./components/bmj-ui\";\nimport { DashboardLayout } from \"./components/bmj-ui\";\nimport { StatCard } from \"./components/bmj-ui\";\nimport { Topbar } from \"./components/bmj-ui\";\n\nexport { AppSidebar, ChartCard, DashboardLayout, StatCard, Topbar };\n"],"names":["bmj_ui_1","require","Object","defineProperty","exports","enumerable","get","bmj_ui_2","bmj_ui_3","bmj_ui_4","bmj_ui_5"],"mappings":"wKAAA,MAAAA,EAAAC,QAAA,uBAMSC,OAAAC,eAAAC,QAAA,aAAA,CAAAC,YAAA,EAAAC,IAAA,WAAA,OANAN,YAAU,IACnB,MAAAO,EAAAN,QAAA,uBAKqBC,OAAAC,eAAAC,QAAA,YAAA,CAAAC,YAAA,EAAAC,IAAA,WAAA,OALZC,WAAS,IAClB,MAAAC,EAAAP,QAAA,uBAIgCC,OAAAC,eAAAC,QAAA,kBAAA,CAAAC,YAAA,EAAAC,IAAA,WAAA,OAJvBE,iBAAe,IACxB,MAAAC,EAAAR,QAAA,uBAGiDC,OAAAC,eAAAC,QAAA,WAAA,CAAAC,YAAA,EAAAC,IAAA,WAAA,OAHxCG,UAAQ,IACjB,MAAAC,EAAAT,QAAA,uBAE2DC,OAAAC,eAAAC,QAAA,SAAA,CAAAC,YAAA,EAAAC,IAAA,WAAA,OAFlDI,QAAM"}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.Topbar=exports.StatCard=exports.DashboardLayout=exports.ChartCard=exports.AppSidebar=void 0;const e=require("./components/bmj-ui");Object.defineProperty(exports,"AppSidebar",{enumerable:!0,get:function(){return e.AppSidebar}});const r=require("./components/bmj-ui");Object.defineProperty(exports,"ChartCard",{enumerable:!0,get:function(){return r.ChartCard}});const t=require("./components/bmj-ui");Object.defineProperty(exports,"DashboardLayout",{enumerable:!0,get:function(){return t.DashboardLayout}});const o=require("./components/bmj-ui");Object.defineProperty(exports,"StatCard",{enumerable:!0,get:function(){return o.StatCard}});const n=require("./components/bmj-ui");Object.defineProperty(exports,"Topbar",{enumerable:!0,get:function(){return n.Topbar}});
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../src/index.ts"],"sourcesContent":["import { AppSidebar } from \"./components/bmj-ui\";\nimport { ChartCard } from \"./components/bmj-ui\";\nimport { DashboardLayout } from \"./components/bmj-ui\";\nimport { StatCard } from \"./components/bmj-ui\";\nimport { Topbar } from \"./components/bmj-ui\";\n\nexport { AppSidebar, ChartCard, DashboardLayout, StatCard, Topbar };\n"],"names":["bmj_ui_1","require","Object","defineProperty","exports","enumerable","get","bmj_ui_2","bmj_ui_3","bmj_ui_4","bmj_ui_5"],"mappings":"2JAAA,MAAAA,EAAAC,QAAA,uBAMSC,OAAAC,eAAAC,QAAA,aAAA,CAAAC,YAAA,EAAAC,IAAA,WAAA,OANAN,YAAU,IACnB,MAAAO,EAAAN,QAAA,uBAKqBC,OAAAC,eAAAC,QAAA,YAAA,CAAAC,YAAA,EAAAC,IAAA,WAAA,OALZC,WAAS,IAClB,MAAAC,EAAAP,QAAA,uBAIgCC,OAAAC,eAAAC,QAAA,kBAAA,CAAAC,YAAA,EAAAC,IAAA,WAAA,OAJvBE,iBAAe,IACxB,MAAAC,EAAAR,QAAA,uBAGiDC,OAAAC,eAAAC,QAAA,WAAA,CAAAC,YAAA,EAAAC,IAAA,WAAA,OAHxCG,UAAQ,IACjB,MAAAC,EAAAT,QAAA,uBAE2DC,OAAAC,eAAAC,QAAA,SAAA,CAAAC,YAAA,EAAAC,IAAA,WAAA,OAFlDI,QAAM"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "bmj-ui",
3
+ "version": "1.0.0",
4
+ "description": "Headless component library meticulously crafted for rapid dashboard development. Professional, polished, and ready for production.",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.mjs",
9
+ "types": "dist/index.d.ts",
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1",
12
+ "rollup": "rollup -c --bundleConfigAsCjs"
13
+ },
14
+ "devDependencies": {
15
+ "@rollup/plugin-commonjs": "^29.0.2",
16
+ "@rollup/plugin-node-resolve": "^16.0.3",
17
+ "@rollup/plugin-terser": "^1.0.0",
18
+ "@rollup/plugin-typescript": "^12.3.0",
19
+ "@types/react": "^19.2.14",
20
+ "@types/react-dom": "^19.2.3",
21
+ "autoprefixer": "^10.4.27",
22
+ "postcss": "^8.5.9",
23
+ "react": "^19.2.5",
24
+ "react-dom": "^19.2.5",
25
+ "rollup": "^4.60.1",
26
+ "rollup-plugin-dts": "^6.4.1",
27
+ "rollup-plugin-peer-deps-external": "^2.2.4",
28
+ "rollup-plugin-postcss": "^4.0.2",
29
+ "tailwindcss": "^4.2.2",
30
+ "tslib": "^2.8.1",
31
+ "typescript": "^6.0.2"
32
+ },
33
+ "dependencies": {
34
+ "@base-ui/react": "^1.3.0",
35
+ "@fontsource-variable/geist": "^5.2.8",
36
+ "class-variance-authority": "^0.7.1",
37
+ "clsx": "^2.1.1",
38
+ "lucide-react": "^1.8.0",
39
+ "motion": "^12.38.0",
40
+ "recharts": "^3.8.1",
41
+ "sonner": "^2.0.7"
42
+ }
43
+ }
@@ -0,0 +1,39 @@
1
+ import resolve from "@rollup/plugin-node-resolve";
2
+ import commonjs from "@rollup/plugin-commonjs";
3
+ import typescript from "@rollup/plugin-typescript";
4
+ import dts from "rollup-plugin-dts";
5
+ import terser from "@rollup/plugin-terser";
6
+ import peerDepsExternal from "rollup-plugin-peer-deps-external";
7
+
8
+ const packageJson = require("./package.json");
9
+
10
+ export default [
11
+ {
12
+ input: "src/index.ts",
13
+ output: [
14
+ {
15
+ file: packageJson.main,
16
+ format: "cjs",
17
+ sourcemap: true,
18
+ },
19
+ {
20
+ file: packageJson.module,
21
+ format: "esm",
22
+ sourcemap: true,
23
+ },
24
+ ],
25
+ plugins: [
26
+ peerDepsExternal(),
27
+ resolve(),
28
+ commonjs(),
29
+ typescript({ tsconfig: "./tsconfig.json" }),
30
+ terser(),
31
+ ],
32
+ external: ["react", "react-dom"],
33
+ },
34
+ {
35
+ input: "src/index.ts",
36
+ output: [{ file: packageJson.types }],
37
+ plugins: [dts.default()],
38
+ },
39
+ ];
@@ -0,0 +1,43 @@
1
+ import * as React from "react";
2
+ import { Moon, Sun } from "lucide-react";
3
+ import { Button } from "./ui/button";
4
+
5
+ export function ThemeToggle({ className }: { className?: string }) {
6
+ const [theme, setTheme] = React.useState<"light" | "dark">(() => {
7
+ if (typeof window !== "undefined") {
8
+ const saved = localStorage.getItem("bmj-ui-theme");
9
+ if (saved === "light" || saved === "dark") return saved;
10
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
11
+ ? "dark"
12
+ : "light";
13
+ }
14
+ return "light";
15
+ });
16
+
17
+ React.useEffect(() => {
18
+ const root = window.document.documentElement;
19
+ root.classList.remove("light", "dark");
20
+ root.classList.add(theme);
21
+ localStorage.setItem("bmj-ui-theme", theme);
22
+ }, [theme]);
23
+
24
+ const toggleTheme = () => {
25
+ setTheme((prev) => (prev === "light" ? "dark" : "light"));
26
+ };
27
+
28
+ return (
29
+ <Button
30
+ variant="ghost"
31
+ size="icon"
32
+ onClick={toggleTheme}
33
+ className={className}
34
+ aria-label="Toggle theme"
35
+ >
36
+ {theme === "light" ? (
37
+ <Moon className="w-5 h-5 transition-all" />
38
+ ) : (
39
+ <Sun className="w-5 h-5 transition-all" />
40
+ )}
41
+ </Button>
42
+ );
43
+ }
@@ -0,0 +1,154 @@
1
+ import * as React from "react";
2
+ import {
3
+ Sidebar,
4
+ SidebarContent,
5
+ SidebarFooter,
6
+ SidebarHeader,
7
+ SidebarMenu,
8
+ SidebarMenuItem,
9
+ SidebarMenuButton,
10
+ SidebarGroup,
11
+ SidebarGroupLabel,
12
+ SidebarGroupContent,
13
+ useSidebar,
14
+ } from "../ui/sidebar";
15
+ import {
16
+ LayoutDashboard,
17
+ Settings,
18
+ Users,
19
+ BarChart3,
20
+ Package,
21
+ HelpCircle,
22
+ ListTodo,
23
+ } from "lucide-react";
24
+ import { motion, AnimatePresence } from "motion/react";
25
+
26
+ const defaultItems = [
27
+ { title: "Dashboard", icon: LayoutDashboard, url: "#" },
28
+ { title: "Analytics", icon: BarChart3, url: "#" },
29
+ { title: "Products", icon: Package, url: "#" },
30
+ { title: "Customers", icon: Users, url: "#" },
31
+ { title: "Tasks", icon: ListTodo, url: "#" },
32
+ { title: "Settings", icon: Settings, url: "#" },
33
+ { title: "Help", icon: HelpCircle, url: "#" },
34
+ ];
35
+
36
+ export interface SidebarItem {
37
+ title: string;
38
+ icon: React.ElementType;
39
+ url: string;
40
+ }
41
+
42
+ export function AppSidebar({
43
+ items = defaultItems,
44
+ onItemClick,
45
+ activeItem,
46
+ }: {
47
+ items?: SidebarItem[];
48
+ onItemClick?: (title: string) => void;
49
+ activeItem?: string;
50
+ }) {
51
+ const { state } = useSidebar();
52
+ const isCollapsed = state === "collapsed";
53
+
54
+ return (
55
+ <Sidebar variant="inset" collapsible="icon">
56
+ <SidebarHeader className="h-16 flex items-center px-6 border-b border-border/50">
57
+ <div className="flex items-center gap-2 font-bold text-xl tracking-tight">
58
+ <div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center text-primary-foreground shrink-0">
59
+ B
60
+ </div>
61
+ <AnimatePresence mode="wait">
62
+ {!isCollapsed && (
63
+ <motion.span
64
+ initial={{ opacity: 0, x: -10 }}
65
+ animate={{ opacity: 1, x: 0 }}
66
+ exit={{ opacity: 0, x: -10 }}
67
+ transition={{ duration: 0.2 }}
68
+ className="whitespace-nowrap"
69
+ >
70
+ BMJ UI
71
+ </motion.span>
72
+ )}
73
+ </AnimatePresence>
74
+ </div>
75
+ </SidebarHeader>
76
+ <SidebarContent>
77
+ <SidebarGroup>
78
+ <SidebarGroupLabel>
79
+ <AnimatePresence mode="wait">
80
+ {!isCollapsed && (
81
+ <motion.span
82
+ initial={{ opacity: 0 }}
83
+ animate={{ opacity: 1 }}
84
+ exit={{ opacity: 0 }}
85
+ transition={{ duration: 0.2 }}
86
+ >
87
+ Main Menu
88
+ </motion.span>
89
+ )}
90
+ </AnimatePresence>
91
+ </SidebarGroupLabel>
92
+ <SidebarGroupContent>
93
+ <SidebarMenu>
94
+ {items.map((item) => (
95
+ <SidebarMenuItem key={item.title}>
96
+ <SidebarMenuButton
97
+ tooltip={item.title}
98
+ isActive={activeItem === item.title}
99
+ onClick={() => onItemClick?.(item.title)}
100
+ className="transition-all duration-200 hover:bg-primary/5 hover:text-primary"
101
+ render={
102
+ <button
103
+ type="button"
104
+ className="flex items-center w-full"
105
+ >
106
+ <item.icon className="shrink-0" />
107
+ <AnimatePresence mode="wait">
108
+ {!isCollapsed && (
109
+ <motion.span
110
+ initial={{ opacity: 0, width: 0, marginLeft: 0 }}
111
+ animate={{
112
+ opacity: 1,
113
+ width: "auto",
114
+ marginLeft: 8,
115
+ }}
116
+ exit={{ opacity: 0, width: 0, marginLeft: 0 }}
117
+ transition={{ duration: 0.2 }}
118
+ className="overflow-hidden whitespace-nowrap"
119
+ >
120
+ {item.title}
121
+ </motion.span>
122
+ )}
123
+ </AnimatePresence>
124
+ </button>
125
+ }
126
+ />
127
+ </SidebarMenuItem>
128
+ ))}
129
+ </SidebarMenu>
130
+ </SidebarGroupContent>
131
+ </SidebarGroup>
132
+ </SidebarContent>
133
+ <SidebarFooter className="p-4 border-t border-border/50">
134
+ <div className="flex items-center gap-3 min-h-10">
135
+ <div className="w-8 h-8 rounded-full bg-muted shrink-0" />
136
+ <AnimatePresence mode="wait">
137
+ {!isCollapsed && (
138
+ <motion.div
139
+ initial={{ opacity: 0, x: -10 }}
140
+ animate={{ opacity: 1, x: 0 }}
141
+ exit={{ opacity: 0, x: -10 }}
142
+ transition={{ duration: 0.2 }}
143
+ className="flex flex-col overflow-hidden whitespace-nowrap"
144
+ >
145
+ <span className="text-sm font-medium">Bahry Jarbou</span>
146
+ <span className="text-xs text-muted-foreground">Admin</span>
147
+ </motion.div>
148
+ )}
149
+ </AnimatePresence>
150
+ </div>
151
+ </SidebarFooter>
152
+ </Sidebar>
153
+ );
154
+ }
@@ -0,0 +1,205 @@
1
+ import {
2
+ Card,
3
+ CardContent,
4
+ CardHeader,
5
+ CardTitle,
6
+ CardDescription,
7
+ } from "../ui/card";
8
+ import { Skeleton } from "../ui/skeleton";
9
+ import {
10
+ ResponsiveContainer,
11
+ AreaChart,
12
+ Area,
13
+ BarChart,
14
+ Bar,
15
+ LineChart,
16
+ Line,
17
+ ScatterChart,
18
+ Scatter,
19
+ XAxis,
20
+ YAxis,
21
+ Tooltip,
22
+ CartesianGrid,
23
+ } from "recharts";
24
+ import { cn } from "../../lib/utils";
25
+
26
+ interface ChartCardProps {
27
+ title: string;
28
+ description?: string;
29
+ data: any[];
30
+ dataKey: string;
31
+ categoryKey: string;
32
+ color?: string;
33
+ className?: string;
34
+ loading?: boolean;
35
+ type?: "area" | "bar" | "line" | "scatter";
36
+ }
37
+
38
+ export function ChartCard({
39
+ title,
40
+ description,
41
+ data,
42
+ dataKey,
43
+ categoryKey,
44
+ color = "var(--primary)",
45
+ className,
46
+ loading,
47
+ type = "area",
48
+ }: ChartCardProps) {
49
+ if (loading) {
50
+ return (
51
+ <Card className={cn("col-span-full lg:col-span-2", className)}>
52
+ <CardHeader>
53
+ <Skeleton className="h-6 w-40 mb-2" />
54
+ <Skeleton className="h-4 w-64" />
55
+ </CardHeader>
56
+ <CardContent className="h-75 w-full pt-4">
57
+ <Skeleton className="h-full w-full rounded-lg" />
58
+ </CardContent>
59
+ </Card>
60
+ );
61
+ }
62
+
63
+ const renderChart = () => {
64
+ const commonProps = {
65
+ data,
66
+ margin: { top: 10, right: 10, left: -20, bottom: 0 },
67
+ };
68
+
69
+ const axisProps = {
70
+ xAxis: (
71
+ <XAxis
72
+ dataKey={categoryKey}
73
+ axisLine={false}
74
+ tickLine={false}
75
+ tick={{ fontSize: 12, fill: "var(--muted-foreground)" }}
76
+ dy={10}
77
+ type={type === "scatter" ? "number" : "category"}
78
+ />
79
+ ),
80
+ yAxis: (
81
+ <YAxis
82
+ dataKey={type === "scatter" ? dataKey : undefined}
83
+ axisLine={false}
84
+ tickLine={false}
85
+ tick={{ fontSize: 12, fill: "var(--muted-foreground)" }}
86
+ type="number"
87
+ />
88
+ ),
89
+ grid: (
90
+ <CartesianGrid
91
+ strokeDasharray="3 3"
92
+ vertical={false}
93
+ stroke="var(--border)"
94
+ opacity={0.5}
95
+ />
96
+ ),
97
+ tooltip: (
98
+ <Tooltip
99
+ cursor={type === "scatter" ? { strokeDasharray: "3 3" } : undefined}
100
+ contentStyle={{
101
+ backgroundColor: "var(--card)",
102
+ borderRadius: "8px",
103
+ border: "1px solid var(--border)",
104
+ boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
105
+ color: "var(--card-foreground)",
106
+ }}
107
+ itemStyle={{
108
+ color: "var(--card-foreground)",
109
+ }}
110
+ />
111
+ ),
112
+ };
113
+
114
+ switch (type) {
115
+ case "bar":
116
+ return (
117
+ <BarChart {...commonProps}>
118
+ {axisProps.grid}
119
+ {axisProps.xAxis}
120
+ {axisProps.yAxis}
121
+ {axisProps.tooltip}
122
+ <Bar
123
+ dataKey={dataKey}
124
+ fill={color}
125
+ radius={[4, 4, 0, 0]}
126
+ barSize={32}
127
+ />
128
+ </BarChart>
129
+ );
130
+ case "line":
131
+ return (
132
+ <LineChart {...commonProps}>
133
+ {axisProps.grid}
134
+ {axisProps.xAxis}
135
+ {axisProps.yAxis}
136
+ {axisProps.tooltip}
137
+ <Line
138
+ type="monotone"
139
+ dataKey={dataKey}
140
+ stroke={color}
141
+ strokeWidth={2}
142
+ dot={{ r: 4, fill: color, strokeWidth: 2, stroke: "var(--card)" }}
143
+ activeDot={{ r: 6, strokeWidth: 0 }}
144
+ />
145
+ </LineChart>
146
+ );
147
+ case "scatter":
148
+ return (
149
+ <ScatterChart {...commonProps}>
150
+ {axisProps.grid}
151
+ {axisProps.xAxis}
152
+ {axisProps.yAxis}
153
+ {axisProps.tooltip}
154
+ <Scatter name={title} data={data} fill={color} shape="circle" />
155
+ </ScatterChart>
156
+ );
157
+ case "area":
158
+ default:
159
+ return (
160
+ <AreaChart {...commonProps}>
161
+ <defs>
162
+ <linearGradient
163
+ id={`colorValue-${title.replace(/\s+/g, "-")}`}
164
+ x1="0"
165
+ y1="0"
166
+ x2="0"
167
+ y2="1"
168
+ >
169
+ <stop offset="5%" stopColor={color} stopOpacity={0.1} />
170
+ <stop offset="95%" stopColor={color} stopOpacity={0} />
171
+ </linearGradient>
172
+ </defs>
173
+ {axisProps.grid}
174
+ {axisProps.xAxis}
175
+ {axisProps.yAxis}
176
+ {axisProps.tooltip}
177
+ <Area
178
+ type="monotone"
179
+ dataKey={dataKey}
180
+ stroke={color}
181
+ strokeWidth={2}
182
+ fillOpacity={1}
183
+ fill={`url(#colorValue-${title.replace(/\s+/g, "-")})`}
184
+ />
185
+ </AreaChart>
186
+ );
187
+ }
188
+ };
189
+
190
+ return (
191
+ <Card
192
+ className={cn("bmj-card-hover col-span-full lg:col-span-2", className)}
193
+ >
194
+ <CardHeader>
195
+ <CardTitle className="text-lg font-semibold">{title}</CardTitle>
196
+ {description && <CardDescription>{description}</CardDescription>}
197
+ </CardHeader>
198
+ <CardContent className="h-75 w-full pt-4">
199
+ <ResponsiveContainer width="100%" height="100%">
200
+ {renderChart()}
201
+ </ResponsiveContainer>
202
+ </CardContent>
203
+ </Card>
204
+ );
205
+ }
@@ -0,0 +1,32 @@
1
+ import * as React from "react";
2
+ import { SidebarProvider, SidebarInset } from "../ui/sidebar";
3
+ import { AppSidebar, SidebarItem } from "./AppSidebar";
4
+ import { Topbar } from "./Topbar";
5
+
6
+ interface DashboardLayoutProps {
7
+ children: React.ReactNode;
8
+ sidebarItems?: SidebarItem[];
9
+ onSidebarItemClick?: (title: string) => void;
10
+ activeSidebarItem?: string;
11
+ }
12
+
13
+ export function DashboardLayout({
14
+ children,
15
+ sidebarItems,
16
+ onSidebarItemClick,
17
+ activeSidebarItem,
18
+ }: DashboardLayoutProps) {
19
+ return (
20
+ <SidebarProvider>
21
+ <AppSidebar
22
+ items={sidebarItems}
23
+ onItemClick={onSidebarItemClick}
24
+ activeItem={activeSidebarItem}
25
+ />
26
+ <SidebarInset className="flex flex-col">
27
+ <Topbar />
28
+ <main className="flex-1 p-6 overflow-y-auto">{children}</main>
29
+ </SidebarInset>
30
+ </SidebarProvider>
31
+ );
32
+ }
@@ -0,0 +1,85 @@
1
+ import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
2
+ import { Skeleton } from "../ui/skeleton";
3
+ import { TrendingUp, TrendingDown, LucideIcon } from "lucide-react";
4
+ import { cn } from "../../lib/utils";
5
+ import { motion } from "motion/react";
6
+
7
+ interface StatCardProps {
8
+ title: string;
9
+ value: string | number;
10
+ description?: string;
11
+ trend?: {
12
+ value: number;
13
+ isPositive: boolean;
14
+ };
15
+ icon?: LucideIcon;
16
+ className?: string;
17
+ loading?: boolean;
18
+ }
19
+
20
+ export function StatCard({
21
+ title,
22
+ value,
23
+ description,
24
+ trend,
25
+ icon: Icon,
26
+ className,
27
+ loading,
28
+ }: StatCardProps) {
29
+ if (loading) {
30
+ return (
31
+ <Card className={cn("overflow-hidden", className)}>
32
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
33
+ <Skeleton className="h-4 w-24" />
34
+ <Skeleton className="h-4 w-4 rounded-full" />
35
+ </CardHeader>
36
+ <CardContent className="space-y-2">
37
+ <Skeleton className="h-8 w-32" />
38
+ <Skeleton className="h-3 w-40" />
39
+ </CardContent>
40
+ </Card>
41
+ );
42
+ }
43
+
44
+ return (
45
+ <motion.div
46
+ initial={{ opacity: 0, y: 20 }}
47
+ animate={{ opacity: 1, y: 0 }}
48
+ transition={{ duration: 0.4 }}
49
+ >
50
+ <Card className={cn("bmj-card-hover overflow-hidden", className)}>
51
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
52
+ <CardTitle className="text-sm font-medium text-muted-foreground uppercase tracking-wider">
53
+ {title}
54
+ </CardTitle>
55
+ {Icon && <Icon className="h-4 w-4 text-muted-foreground" />}
56
+ </CardHeader>
57
+ <CardContent>
58
+ <div className="text-2xl font-bold tracking-tight">{value}</div>
59
+ {(description || trend) && (
60
+ <div className="flex items-center gap-2 mt-1">
61
+ {trend && (
62
+ <span
63
+ className={cn(
64
+ "flex items-center text-xs font-medium",
65
+ trend.isPositive ? "text-emerald-500" : "text-rose-500",
66
+ )}
67
+ >
68
+ {trend.isPositive ? (
69
+ <TrendingUp className="w-3 h-3 mr-1" />
70
+ ) : (
71
+ <TrendingDown className="w-3 h-3 mr-1" />
72
+ )}
73
+ {trend.value}%
74
+ </span>
75
+ )}
76
+ {description && (
77
+ <p className="text-xs text-muted-foreground">{description}</p>
78
+ )}
79
+ </div>
80
+ )}
81
+ </CardContent>
82
+ </Card>
83
+ </motion.div>
84
+ );
85
+ }