create-reactivite 1.0.3 → 1.1.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 +197 -177
- package/index.js +7 -0
- package/package.json +1 -1
- package/template/README.md +73 -73
- package/template/components.json +22 -22
- package/template/eslint.config.js +30 -23
- package/template/index.html +13 -13
- package/template/package.json +53 -51
- package/template/pnpm-lock.yaml +1521 -1766
- package/template/src/App.tsx +27 -13
- package/template/src/components/admin-page-components/activity-chart.tsx +80 -0
- package/template/src/components/admin-page-components/dashboard-header.tsx +38 -0
- package/template/src/components/admin-page-components/dashboard-layout.tsx +23 -0
- package/template/src/components/admin-page-components/dashboard-sales.tsx +67 -0
- package/template/src/components/admin-page-components/dashboard-sidebar.tsx +81 -0
- package/template/src/components/admin-page-components/revenu-chart.tsx +69 -0
- package/template/src/components/admin-page-components/stat-cards.tsx +50 -0
- package/template/src/components/home-page-components/header.tsx +2 -2
- package/template/src/components/ui/accordion.tsx +64 -64
- package/template/src/components/ui/alert.tsx +66 -66
- package/template/src/components/ui/avatar.tsx +51 -51
- package/template/src/components/ui/badge.tsx +46 -46
- package/template/src/components/ui/button-group.tsx +83 -83
- package/template/src/components/ui/button.tsx +60 -60
- package/template/src/components/ui/calendar.tsx +210 -211
- package/template/src/components/ui/card.tsx +92 -92
- package/template/src/components/ui/checkbox.tsx +30 -30
- package/template/src/components/ui/collapsible.tsx +31 -31
- package/template/src/components/ui/dialog.tsx +141 -141
- package/template/src/components/ui/input.tsx +21 -0
- package/template/src/components/ui/select.tsx +185 -185
- package/template/src/components/ui/separator.tsx +26 -26
- package/template/src/components/ui/sonner.tsx +38 -38
- package/template/src/components/ui/spinner.tsx +16 -16
- package/template/src/components/ui/table.tsx +114 -114
- package/template/src/components/ui/toggle.tsx +45 -45
- package/template/src/components/ui/tooltip.tsx +59 -59
- package/template/src/lib/utils.ts +6 -6
- package/template/src/main.tsx +10 -10
- package/template/src/pages/Dashboard/Dashboard.tsx +32 -0
- package/template/tsconfig.app.json +31 -28
- package/template/tsconfig.json +12 -14
- package/template/tsconfig.node.json +26 -26
- package/template/vite.config.ts +13 -14
package/template/src/App.tsx
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
import './App.css'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
|
|
2
|
+
import './App.css'
|
|
3
|
+
|
|
4
|
+
import { createBrowserRouter } from "react-router";
|
|
5
|
+
import { RouterProvider } from "react-router/dom";
|
|
6
|
+
import Home from './pages/Homepage/Homepage';
|
|
7
|
+
import DashboardPage from './pages/Dashboard/Dashboard';
|
|
8
|
+
const router = createBrowserRouter([
|
|
9
|
+
{
|
|
10
|
+
path: "/",
|
|
11
|
+
element: <Home />,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
path: "/portfolio",
|
|
15
|
+
element: <h1>Portfolio</h1>,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
path: "/dashboard",
|
|
19
|
+
element: <DashboardPage />,
|
|
20
|
+
},
|
|
21
|
+
]);
|
|
22
|
+
function App() {
|
|
23
|
+
return <RouterProvider router={router} />
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default App
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
Area,
|
|
4
|
+
AreaChart,
|
|
5
|
+
ResponsiveContainer,
|
|
6
|
+
XAxis,
|
|
7
|
+
YAxis,
|
|
8
|
+
Tooltip,
|
|
9
|
+
} from "recharts";
|
|
10
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
|
11
|
+
|
|
12
|
+
const data = [
|
|
13
|
+
{ time: "00:00", users: 120 },
|
|
14
|
+
{ time: "04:00", users: 80 },
|
|
15
|
+
{ time: "08:00", users: 250 },
|
|
16
|
+
{ time: "12:00", users: 420 },
|
|
17
|
+
{ time: "16:00", users: 380 },
|
|
18
|
+
{ time: "20:00", users: 290 },
|
|
19
|
+
{ time: "23:59", users: 180 },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export function ActivityChart() {
|
|
23
|
+
return (
|
|
24
|
+
<Card>
|
|
25
|
+
<CardHeader>
|
|
26
|
+
<CardTitle>User Activity</CardTitle>
|
|
27
|
+
<CardDescription>Active users throughout the day</CardDescription>
|
|
28
|
+
</CardHeader>
|
|
29
|
+
<CardContent>
|
|
30
|
+
<ResponsiveContainer width="100%" height={300}>
|
|
31
|
+
<AreaChart data={data}>
|
|
32
|
+
<defs>
|
|
33
|
+
<linearGradient id="colorUsers" x1="0" y1="0" x2="0" y2="1">
|
|
34
|
+
<stop
|
|
35
|
+
offset="5%"
|
|
36
|
+
stopColor="hsl(var(--primary))"
|
|
37
|
+
stopOpacity={0.3}
|
|
38
|
+
/>
|
|
39
|
+
<stop
|
|
40
|
+
offset="95%"
|
|
41
|
+
stopColor="hsl(var(--primary))"
|
|
42
|
+
stopOpacity={0}
|
|
43
|
+
/>
|
|
44
|
+
</linearGradient>
|
|
45
|
+
</defs>
|
|
46
|
+
<XAxis
|
|
47
|
+
dataKey="time"
|
|
48
|
+
stroke="hsl(var(--muted-foreground))"
|
|
49
|
+
fontSize={12}
|
|
50
|
+
tickLine={false}
|
|
51
|
+
axisLine={false}
|
|
52
|
+
/>
|
|
53
|
+
<YAxis
|
|
54
|
+
stroke="hsl(var(--muted-foreground))"
|
|
55
|
+
fontSize={12}
|
|
56
|
+
tickLine={false}
|
|
57
|
+
axisLine={false}
|
|
58
|
+
/>
|
|
59
|
+
<Tooltip
|
|
60
|
+
contentStyle={{
|
|
61
|
+
backgroundColor: "hsl(var(--popover))",
|
|
62
|
+
border: "1px solid hsl(var(--border))",
|
|
63
|
+
borderRadius: "8px",
|
|
64
|
+
}}
|
|
65
|
+
labelStyle={{ color: "hsl(var(--popover-foreground))" }}
|
|
66
|
+
/>
|
|
67
|
+
<Area
|
|
68
|
+
type="monotone"
|
|
69
|
+
dataKey="users"
|
|
70
|
+
stroke="hsl(var(--primary))"
|
|
71
|
+
fillOpacity={1}
|
|
72
|
+
fill="url(#colorUsers)"
|
|
73
|
+
strokeWidth={2}
|
|
74
|
+
/>
|
|
75
|
+
</AreaChart>
|
|
76
|
+
</ResponsiveContainer>
|
|
77
|
+
</CardContent>
|
|
78
|
+
</Card>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
import { Search, Bell, Menu } from "lucide-react";
|
|
3
|
+
import { Button } from "../ui/button";
|
|
4
|
+
import { Input } from "../ui/input";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export function DashboardHeader({ onMenuClick }: { onMenuClick: () => void }) {
|
|
8
|
+
return (
|
|
9
|
+
<header className="sticky top-0 z-40 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
10
|
+
<div className="flex h-16 items-center gap-4 px-6">
|
|
11
|
+
<Button
|
|
12
|
+
variant="ghost"
|
|
13
|
+
size="icon"
|
|
14
|
+
className="lg:hidden"
|
|
15
|
+
onClick={onMenuClick}
|
|
16
|
+
>
|
|
17
|
+
<Menu className="h-5 w-5" />
|
|
18
|
+
</Button>
|
|
19
|
+
|
|
20
|
+
<div className="flex-1 flex items-center gap-4">
|
|
21
|
+
<div className="relative w-full max-w-md">
|
|
22
|
+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
23
|
+
<Input
|
|
24
|
+
type="search"
|
|
25
|
+
placeholder="Search..."
|
|
26
|
+
className="pl-9 bg-muted/50 border-0"
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<Button variant="ghost" size="icon" className="relative">
|
|
32
|
+
<Bell className="h-5 w-5" />
|
|
33
|
+
<span className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-primary" />
|
|
34
|
+
</Button>
|
|
35
|
+
</div>
|
|
36
|
+
</header>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { DashboardSidebar } from "./dashboard-sidebar";
|
|
5
|
+
import { DashboardHeader } from "./dashboard-header";
|
|
6
|
+
|
|
7
|
+
export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
8
|
+
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="dark min-h-screen bg-background">
|
|
12
|
+
<DashboardSidebar open={sidebarOpen} />
|
|
13
|
+
<div
|
|
14
|
+
className={`transition-all duration-300 ${
|
|
15
|
+
sidebarOpen ? "lg:pl-64" : "lg:pl-0"
|
|
16
|
+
}`}
|
|
17
|
+
>
|
|
18
|
+
<DashboardHeader onMenuClick={() => setSidebarOpen(!sidebarOpen)} />
|
|
19
|
+
<main className="p-6 lg:p-8">{children}</main>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
import { Avatar, AvatarFallback } from "../ui/avatar";
|
|
3
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
|
4
|
+
|
|
5
|
+
const recentSales = [
|
|
6
|
+
{
|
|
7
|
+
name: "Olivia Martin",
|
|
8
|
+
email: "olivia.martin@email.com",
|
|
9
|
+
amount: "+$1,999.00",
|
|
10
|
+
initials: "OM",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "Jackson Lee",
|
|
14
|
+
email: "jackson.lee@email.com",
|
|
15
|
+
amount: "+$39.00",
|
|
16
|
+
initials: "JL",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "Isabella Nguyen",
|
|
20
|
+
email: "isabella.nguyen@email.com",
|
|
21
|
+
amount: "+$299.00",
|
|
22
|
+
initials: "IN",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "William Kim",
|
|
26
|
+
email: "will@email.com",
|
|
27
|
+
amount: "+$99.00",
|
|
28
|
+
initials: "WK",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "Sofia Davis",
|
|
32
|
+
email: "sofia.davis@email.com",
|
|
33
|
+
amount: "+$39.00",
|
|
34
|
+
initials: "SD",
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export function RecentSales() {
|
|
39
|
+
return (
|
|
40
|
+
<Card className="col-span-3">
|
|
41
|
+
<CardHeader>
|
|
42
|
+
<CardTitle>Recent Sales</CardTitle>
|
|
43
|
+
<CardDescription>You made 265 sales this month</CardDescription>
|
|
44
|
+
</CardHeader>
|
|
45
|
+
<CardContent>
|
|
46
|
+
<div className="space-y-6">
|
|
47
|
+
{recentSales.map((sale) => (
|
|
48
|
+
<div key={sale.email} className="flex items-center gap-4">
|
|
49
|
+
<Avatar className="h-10 w-10">
|
|
50
|
+
<AvatarFallback className="bg-primary/10 text-primary font-medium">
|
|
51
|
+
{sale.initials}
|
|
52
|
+
</AvatarFallback>
|
|
53
|
+
</Avatar>
|
|
54
|
+
<div className="flex-1 min-w-0">
|
|
55
|
+
<p className="text-sm font-medium leading-none">{sale.name}</p>
|
|
56
|
+
<p className="text-sm text-muted-foreground mt-1">
|
|
57
|
+
{sale.email}
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
<div className="font-medium">{sale.amount}</div>
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
</CardContent>
|
|
65
|
+
</Card>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LayoutDashboard,
|
|
3
|
+
Users,
|
|
4
|
+
ShoppingCart,
|
|
5
|
+
BarChart3,
|
|
6
|
+
Settings,
|
|
7
|
+
FileText,
|
|
8
|
+
Package,
|
|
9
|
+
CreditCard,
|
|
10
|
+
} from "lucide-react";
|
|
11
|
+
import { cn } from "../../lib/utils";
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const navigation = [
|
|
15
|
+
{ name: "Dashboard", icon: LayoutDashboard, href: "#", current: true },
|
|
16
|
+
{ name: "Customers", icon: Users, href: "#", current: false },
|
|
17
|
+
{ name: "Products", icon: Package, href: "#", current: false },
|
|
18
|
+
{ name: "Orders", icon: ShoppingCart, href: "#", current: false },
|
|
19
|
+
{ name: "Analytics", icon: BarChart3, href: "#", current: false },
|
|
20
|
+
{ name: "Transactions", icon: CreditCard, href: "#", current: false },
|
|
21
|
+
{ name: "Reports", icon: FileText, href: "#", current: false },
|
|
22
|
+
{ name: "Settings", icon: Settings, href: "#", current: false },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export function DashboardSidebar({ open }: { open: boolean }) {
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<div
|
|
29
|
+
className={cn(
|
|
30
|
+
"fixed inset-y-0 left-0 z-50 w-64 bg-sidebar border-r border-sidebar-border transition-transform duration-300 lg:translate-x-0",
|
|
31
|
+
open ? "translate-x-0" : "-translate-x-full"
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
<div className="flex h-full flex-col">
|
|
35
|
+
<div className="flex h-16 items-center gap-2 border-b border-sidebar-border px-6">
|
|
36
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary">
|
|
37
|
+
<LayoutDashboard className="h-5 w-5 text-primary-foreground" />
|
|
38
|
+
</div>
|
|
39
|
+
<span className="text-lg font-semibold text-sidebar-foreground">
|
|
40
|
+
Admin Panel
|
|
41
|
+
</span>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<nav className="flex-1 space-y-1 px-3 py-4">
|
|
45
|
+
{navigation.map((item) => (
|
|
46
|
+
<a
|
|
47
|
+
key={item.name}
|
|
48
|
+
href={item.href}
|
|
49
|
+
className={cn(
|
|
50
|
+
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors",
|
|
51
|
+
item.current
|
|
52
|
+
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
53
|
+
: "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
<item.icon className="h-5 w-5" />
|
|
57
|
+
{item.name}
|
|
58
|
+
</a>
|
|
59
|
+
))}
|
|
60
|
+
</nav>
|
|
61
|
+
|
|
62
|
+
<div className="border-t border-sidebar-border p-4">
|
|
63
|
+
<div className="flex items-center gap-3">
|
|
64
|
+
<div className="h-10 w-10 rounded-full bg-primary/20 flex items-center justify-center">
|
|
65
|
+
<span className="text-sm font-semibold text-primary">JD</span>
|
|
66
|
+
</div>
|
|
67
|
+
<div className="flex-1 min-w-0">
|
|
68
|
+
<p className="text-sm font-medium text-sidebar-foreground truncate">
|
|
69
|
+
John Doe
|
|
70
|
+
</p>
|
|
71
|
+
<p className="text-xs text-sidebar-foreground/60 truncate">
|
|
72
|
+
john@example.com
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
Bar,
|
|
4
|
+
BarChart,
|
|
5
|
+
ResponsiveContainer,
|
|
6
|
+
XAxis,
|
|
7
|
+
YAxis,
|
|
8
|
+
Tooltip,
|
|
9
|
+
} from "recharts";
|
|
10
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
|
11
|
+
|
|
12
|
+
const data = [
|
|
13
|
+
{ name: "Jan", total: 4500 },
|
|
14
|
+
{ name: "Feb", total: 3800 },
|
|
15
|
+
{ name: "Mar", total: 5200 },
|
|
16
|
+
{ name: "Apr", total: 4700 },
|
|
17
|
+
{ name: "May", total: 6100 },
|
|
18
|
+
{ name: "Jun", total: 5800 },
|
|
19
|
+
{ name: "Jul", total: 7200 },
|
|
20
|
+
{ name: "Aug", total: 6800 },
|
|
21
|
+
{ name: "Sep", total: 7500 },
|
|
22
|
+
{ name: "Oct", total: 8200 },
|
|
23
|
+
{ name: "Nov", total: 7800 },
|
|
24
|
+
{ name: "Dec", total: 9100 },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export function RevenueChart() {
|
|
28
|
+
return (
|
|
29
|
+
<Card className="col-span-4">
|
|
30
|
+
<CardHeader>
|
|
31
|
+
<CardTitle>Revenue Overview</CardTitle>
|
|
32
|
+
<CardDescription>Monthly revenue for the current year</CardDescription>
|
|
33
|
+
</CardHeader>
|
|
34
|
+
<CardContent className="pl-2">
|
|
35
|
+
<ResponsiveContainer width="100%" height={350}>
|
|
36
|
+
<BarChart data={data}>
|
|
37
|
+
<XAxis
|
|
38
|
+
dataKey="name"
|
|
39
|
+
stroke="hsl(var(--muted-foreground))"
|
|
40
|
+
fontSize={12}
|
|
41
|
+
tickLine={false}
|
|
42
|
+
axisLine={false}
|
|
43
|
+
/>
|
|
44
|
+
<YAxis
|
|
45
|
+
stroke="hsl(var(--muted-foreground))"
|
|
46
|
+
fontSize={12}
|
|
47
|
+
tickLine={false}
|
|
48
|
+
axisLine={false}
|
|
49
|
+
tickFormatter={(value) => `$${value}`}
|
|
50
|
+
/>
|
|
51
|
+
<Tooltip
|
|
52
|
+
contentStyle={{
|
|
53
|
+
backgroundColor: "hsl(var(--popover))",
|
|
54
|
+
border: "1px solid hsl(var(--border))",
|
|
55
|
+
borderRadius: "8px",
|
|
56
|
+
}}
|
|
57
|
+
labelStyle={{ color: "hsl(var(--popover-foreground))" }}
|
|
58
|
+
/>
|
|
59
|
+
<Bar
|
|
60
|
+
dataKey="total"
|
|
61
|
+
fill="hsl(var(--primary))"
|
|
62
|
+
radius={[8, 8, 0, 0]}
|
|
63
|
+
/>
|
|
64
|
+
</BarChart>
|
|
65
|
+
</ResponsiveContainer>
|
|
66
|
+
</CardContent>
|
|
67
|
+
</Card>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DollarSign, Users, CreditCard, Activity } from "lucide-react";
|
|
2
|
+
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
|
|
3
|
+
|
|
4
|
+
const stats = [
|
|
5
|
+
{
|
|
6
|
+
title: "Total Revenue",
|
|
7
|
+
value: "$45,231.89",
|
|
8
|
+
change: "+20.1% from last month",
|
|
9
|
+
icon: DollarSign,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
title: "Active Users",
|
|
13
|
+
value: "+2,350",
|
|
14
|
+
change: "+180.1% from last month",
|
|
15
|
+
icon: Users,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
title: "Sales",
|
|
19
|
+
value: "+12,234",
|
|
20
|
+
change: "+19% from last month",
|
|
21
|
+
icon: CreditCard,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
title: "Active Now",
|
|
25
|
+
value: "+573",
|
|
26
|
+
change: "+201 since last hour",
|
|
27
|
+
icon: Activity,
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export function StatsCards() {
|
|
32
|
+
return (
|
|
33
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
34
|
+
{stats.map((stat) => (
|
|
35
|
+
<Card key={stat.title}>
|
|
36
|
+
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
37
|
+
<CardTitle className="text-sm font-medium text-muted-foreground">
|
|
38
|
+
{stat.title}
|
|
39
|
+
</CardTitle>
|
|
40
|
+
<stat.icon className="h-4 w-4 text-muted-foreground" />
|
|
41
|
+
</CardHeader>
|
|
42
|
+
<CardContent>
|
|
43
|
+
<div className="text-2xl font-bold">{stat.value}</div>
|
|
44
|
+
<p className="text-xs text-muted-foreground mt-1">{stat.change}</p>
|
|
45
|
+
</CardContent>
|
|
46
|
+
</Card>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { GitBranch } from "lucide-react";
|
|
4
4
|
import { Button } from "../ui/button";
|
|
5
5
|
|
|
6
6
|
export function Header() {
|
|
@@ -32,7 +32,7 @@ export function Header() {
|
|
|
32
32
|
</div>
|
|
33
33
|
<div className="flex items-center gap-4">
|
|
34
34
|
<Button variant="ghost" size="sm">
|
|
35
|
-
<
|
|
35
|
+
<GitBranch className="h-4 w-4 mr-2" />
|
|
36
36
|
GitHub
|
|
37
37
|
</Button>
|
|
38
38
|
<Button size="sm">Get Started</Button>
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
|
3
|
-
import { ChevronDownIcon } from "lucide-react"
|
|
4
|
-
|
|
5
|
-
import { cn } from "@/lib/utils"
|
|
6
|
-
|
|
7
|
-
function Accordion({
|
|
8
|
-
...props
|
|
9
|
-
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
|
10
|
-
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function AccordionItem({
|
|
14
|
-
className,
|
|
15
|
-
...props
|
|
16
|
-
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
17
|
-
return (
|
|
18
|
-
<AccordionPrimitive.Item
|
|
19
|
-
data-slot="accordion-item"
|
|
20
|
-
className={cn("border-b last:border-b-0", className)}
|
|
21
|
-
{...props}
|
|
22
|
-
/>
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function AccordionTrigger({
|
|
27
|
-
className,
|
|
28
|
-
children,
|
|
29
|
-
...props
|
|
30
|
-
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
|
31
|
-
return (
|
|
32
|
-
<AccordionPrimitive.Header className="flex">
|
|
33
|
-
<AccordionPrimitive.Trigger
|
|
34
|
-
data-slot="accordion-trigger"
|
|
35
|
-
className={cn(
|
|
36
|
-
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
|
37
|
-
className
|
|
38
|
-
)}
|
|
39
|
-
{...props}
|
|
40
|
-
>
|
|
41
|
-
{children}
|
|
42
|
-
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
|
43
|
-
</AccordionPrimitive.Trigger>
|
|
44
|
-
</AccordionPrimitive.Header>
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function AccordionContent({
|
|
49
|
-
className,
|
|
50
|
-
children,
|
|
51
|
-
...props
|
|
52
|
-
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
|
53
|
-
return (
|
|
54
|
-
<AccordionPrimitive.Content
|
|
55
|
-
data-slot="accordion-content"
|
|
56
|
-
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
|
57
|
-
{...props}
|
|
58
|
-
>
|
|
59
|
-
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
|
60
|
-
</AccordionPrimitive.Content>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
|
3
|
+
import { ChevronDownIcon } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
function Accordion({
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
|
10
|
+
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function AccordionItem({
|
|
14
|
+
className,
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
17
|
+
return (
|
|
18
|
+
<AccordionPrimitive.Item
|
|
19
|
+
data-slot="accordion-item"
|
|
20
|
+
className={cn("border-b last:border-b-0", className)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function AccordionTrigger({
|
|
27
|
+
className,
|
|
28
|
+
children,
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
|
31
|
+
return (
|
|
32
|
+
<AccordionPrimitive.Header className="flex">
|
|
33
|
+
<AccordionPrimitive.Trigger
|
|
34
|
+
data-slot="accordion-trigger"
|
|
35
|
+
className={cn(
|
|
36
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
|
37
|
+
className
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
|
43
|
+
</AccordionPrimitive.Trigger>
|
|
44
|
+
</AccordionPrimitive.Header>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function AccordionContent({
|
|
49
|
+
className,
|
|
50
|
+
children,
|
|
51
|
+
...props
|
|
52
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
|
53
|
+
return (
|
|
54
|
+
<AccordionPrimitive.Content
|
|
55
|
+
data-slot="accordion-content"
|
|
56
|
+
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
|
57
|
+
{...props}
|
|
58
|
+
>
|
|
59
|
+
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
|
60
|
+
</AccordionPrimitive.Content>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|