@cyberbloxai/ui-kit 0.1.2 → 0.2.1
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 +30 -1
- package/package.json +6 -2
- package/src/App.css +42 -0
- package/src/App.tsx +31 -0
- package/src/cli.js +393 -0
- package/src/components/CodeBlock.tsx +50 -0
- package/src/components/ComponentLivePreview.tsx +310 -0
- package/src/components/NavLink.tsx +28 -0
- package/src/components/PropTable.tsx +54 -0
- package/src/components/showcase/AuthShowcase.tsx +164 -0
- package/src/components/showcase/ComponentsShowcase.tsx +133 -0
- package/src/components/showcase/DashboardShowcase.tsx +153 -0
- package/src/components/showcase/ErrorShowcase.tsx +55 -0
- package/src/components/showcase/NotificationShowcase.tsx +102 -0
- package/src/components/ui/accordion.tsx +52 -0
- package/src/components/ui/alert-dialog.tsx +104 -0
- package/src/components/ui/alert.tsx +43 -0
- package/src/components/ui/aspect-ratio.tsx +5 -0
- package/src/components/ui/avatar.tsx +38 -0
- package/src/components/ui/badge.tsx +29 -0
- package/src/components/ui/breadcrumb.tsx +90 -0
- package/src/components/ui/button.tsx +47 -0
- package/src/components/ui/calendar.tsx +54 -0
- package/src/components/ui/card.tsx +43 -0
- package/src/components/ui/carousel.tsx +224 -0
- package/src/components/ui/chart.tsx +303 -0
- package/src/components/ui/checkbox.tsx +26 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/command.tsx +132 -0
- package/src/components/ui/context-menu.tsx +178 -0
- package/src/components/ui/dialog.tsx +95 -0
- package/src/components/ui/drawer.tsx +87 -0
- package/src/components/ui/dropdown-menu.tsx +179 -0
- package/src/components/ui/form.tsx +129 -0
- package/src/components/ui/hover-card.tsx +27 -0
- package/src/components/ui/input-otp.tsx +61 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +17 -0
- package/src/components/ui/menubar.tsx +207 -0
- package/src/components/ui/navigation-menu.tsx +120 -0
- package/src/components/ui/pagination.tsx +81 -0
- package/src/components/ui/popover.tsx +29 -0
- package/src/components/ui/progress.tsx +23 -0
- package/src/components/ui/radio-group.tsx +36 -0
- package/src/components/ui/resizable.tsx +37 -0
- package/src/components/ui/scroll-area.tsx +38 -0
- package/src/components/ui/select.tsx +143 -0
- package/src/components/ui/separator.tsx +20 -0
- package/src/components/ui/sheet.tsx +107 -0
- package/src/components/ui/sidebar.tsx +637 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/slider.tsx +23 -0
- package/src/components/ui/sonner.tsx +27 -0
- package/src/components/ui/switch.tsx +27 -0
- package/src/components/ui/table.tsx +72 -0
- package/src/components/ui/tabs.tsx +53 -0
- package/src/components/ui/textarea.tsx +21 -0
- package/src/components/ui/toast.tsx +111 -0
- package/src/components/ui/toaster.tsx +24 -0
- package/src/components/ui/toggle-group.tsx +49 -0
- package/src/components/ui/toggle.tsx +37 -0
- package/src/components/ui/tooltip.tsx +28 -0
- package/src/components/ui/use-toast.ts +3 -0
- package/src/data/componentRegistry.ts +501 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/hooks/use-toast.ts +186 -0
- package/src/index.css +105 -0
- package/src/lib/index.ts +58 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +5 -0
- package/src/pages/ComponentDetail.tsx +167 -0
- package/src/pages/ComponentsList.tsx +126 -0
- package/src/pages/Index.tsx +223 -0
- package/src/pages/NotFound.tsx +24 -0
- package/src/test/example.test.ts +7 -0
- package/src/test/setup.ts +15 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { motion } from "framer-motion";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
import { Input } from "@/components/ui/input";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { Switch } from "@/components/ui/switch";
|
|
6
|
+
import { Slider } from "@/components/ui/slider";
|
|
7
|
+
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
8
|
+
import { Progress } from "@/components/ui/progress";
|
|
9
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
10
|
+
import { Label } from "@/components/ui/label";
|
|
11
|
+
import { Loader2, Star, Download, Heart, Zap, Check } from "lucide-react";
|
|
12
|
+
|
|
13
|
+
const ComponentsShowcase = () => {
|
|
14
|
+
return (
|
|
15
|
+
<div className="w-full max-w-2xl mx-auto space-y-8">
|
|
16
|
+
{/* Buttons */}
|
|
17
|
+
<div className="space-y-3">
|
|
18
|
+
<h4 className="text-xs font-mono text-muted-foreground uppercase tracking-wider">Buttons</h4>
|
|
19
|
+
<div className="flex flex-wrap gap-2">
|
|
20
|
+
<Button className="bg-primary text-primary-foreground hover:bg-primary/90">Primary</Button>
|
|
21
|
+
<Button variant="secondary" className="bg-secondary text-secondary-foreground">Secondary</Button>
|
|
22
|
+
<Button variant="outline" className="border-border/60 hover:bg-muted/60">Outline</Button>
|
|
23
|
+
<Button variant="destructive">Destructive</Button>
|
|
24
|
+
<Button className="bg-primary text-primary-foreground" disabled>
|
|
25
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Loading
|
|
26
|
+
</Button>
|
|
27
|
+
<Button variant="ghost" className="hover:bg-muted/60">Ghost</Button>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
{/* Inputs */}
|
|
32
|
+
<div className="space-y-3">
|
|
33
|
+
<h4 className="text-xs font-mono text-muted-foreground uppercase tracking-wider">Inputs</h4>
|
|
34
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
35
|
+
<Input placeholder="Default input" className="bg-muted/50 border-border/60" />
|
|
36
|
+
<Input placeholder="Disabled input" disabled className="bg-muted/30 border-border/40" />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
{/* Badges */}
|
|
41
|
+
<div className="space-y-3">
|
|
42
|
+
<h4 className="text-xs font-mono text-muted-foreground uppercase tracking-wider">Badges</h4>
|
|
43
|
+
<div className="flex flex-wrap gap-2">
|
|
44
|
+
<Badge className="bg-primary/20 text-primary border-0">New</Badge>
|
|
45
|
+
<Badge variant="secondary" className="bg-secondary">Beta</Badge>
|
|
46
|
+
<Badge variant="destructive">Critical</Badge>
|
|
47
|
+
<Badge variant="outline" className="border-success/40 text-success">Stable</Badge>
|
|
48
|
+
<Badge variant="outline" className="border-warning/40 text-warning">Pending</Badge>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{/* Controls row */}
|
|
53
|
+
<div className="space-y-3">
|
|
54
|
+
<h4 className="text-xs font-mono text-muted-foreground uppercase tracking-wider">Controls</h4>
|
|
55
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
56
|
+
<div className="glass-card p-4 space-y-3">
|
|
57
|
+
<div className="flex items-center justify-between">
|
|
58
|
+
<Label className="text-sm text-secondary-foreground">Dark Mode</Label>
|
|
59
|
+
<Switch />
|
|
60
|
+
</div>
|
|
61
|
+
<div className="flex items-center justify-between">
|
|
62
|
+
<Label className="text-sm text-secondary-foreground">Notifications</Label>
|
|
63
|
+
<Switch defaultChecked />
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div className="glass-card p-4 space-y-4">
|
|
67
|
+
<Label className="text-sm text-secondary-foreground">Volume</Label>
|
|
68
|
+
<Slider defaultValue={[65]} max={100} step={1} />
|
|
69
|
+
<Label className="text-sm text-secondary-foreground">Brightness</Label>
|
|
70
|
+
<Slider defaultValue={[80]} max={100} step={1} />
|
|
71
|
+
</div>
|
|
72
|
+
<div className="glass-card p-4 space-y-3">
|
|
73
|
+
<div className="flex items-center gap-2">
|
|
74
|
+
<Checkbox id="t1" defaultChecked />
|
|
75
|
+
<Label htmlFor="t1" className="text-sm text-secondary-foreground">Design system</Label>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="flex items-center gap-2">
|
|
78
|
+
<Checkbox id="t2" defaultChecked />
|
|
79
|
+
<Label htmlFor="t2" className="text-sm text-secondary-foreground">Components</Label>
|
|
80
|
+
</div>
|
|
81
|
+
<div className="flex items-center gap-2">
|
|
82
|
+
<Checkbox id="t3" />
|
|
83
|
+
<Label htmlFor="t3" className="text-sm text-secondary-foreground">Documentation</Label>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{/* Avatars & Progress */}
|
|
90
|
+
<div className="space-y-3">
|
|
91
|
+
<h4 className="text-xs font-mono text-muted-foreground uppercase tracking-wider">Avatars & Progress</h4>
|
|
92
|
+
<div className="flex items-center gap-6 flex-wrap">
|
|
93
|
+
<div className="flex -space-x-2">
|
|
94
|
+
{["AK", "BR", "CS", "DW", "+3"].map((initials, i) => (
|
|
95
|
+
<Avatar key={i} className="h-8 w-8 border-2 border-background">
|
|
96
|
+
<AvatarFallback className={`text-[10px] ${i === 4 ? "bg-muted/60 text-muted-foreground" : "bg-primary/20 text-primary"}`}>
|
|
97
|
+
{initials}
|
|
98
|
+
</AvatarFallback>
|
|
99
|
+
</Avatar>
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
<div className="flex-1 space-y-1.5 min-w-[200px]">
|
|
103
|
+
<div className="flex justify-between text-xs text-muted-foreground">
|
|
104
|
+
<span>Upload Progress</span>
|
|
105
|
+
<span>73%</span>
|
|
106
|
+
</div>
|
|
107
|
+
<Progress value={73} className="h-2 bg-muted/60" />
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
{/* Icon buttons */}
|
|
113
|
+
<div className="space-y-3">
|
|
114
|
+
<h4 className="text-xs font-mono text-muted-foreground uppercase tracking-wider">Icon Actions</h4>
|
|
115
|
+
<div className="flex flex-wrap gap-2">
|
|
116
|
+
{[
|
|
117
|
+
{ icon: Star, label: "Star" },
|
|
118
|
+
{ icon: Download, label: "Download" },
|
|
119
|
+
{ icon: Heart, label: "Like" },
|
|
120
|
+
{ icon: Zap, label: "Boost" },
|
|
121
|
+
{ icon: Check, label: "Approve" },
|
|
122
|
+
].map(({ icon: Icon, label }) => (
|
|
123
|
+
<Button key={label} variant="outline" size="sm" className="bg-muted/30 border-border/60 hover:bg-muted/60 text-xs gap-1.5">
|
|
124
|
+
<Icon className="h-3.5 w-3.5" /> {label}
|
|
125
|
+
</Button>
|
|
126
|
+
))}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export default ComponentsShowcase;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { motion } from "framer-motion";
|
|
2
|
+
import { TrendingUp, Users, DollarSign, Activity, BarChart3, ArrowUpRight, ArrowDownRight, Bell, Search, ChevronDown } from "lucide-react";
|
|
3
|
+
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { Progress } from "@/components/ui/progress";
|
|
6
|
+
|
|
7
|
+
const stats = [
|
|
8
|
+
{ label: "Revenue", value: "$45,231", change: "+20.1%", up: true, icon: DollarSign },
|
|
9
|
+
{ label: "Users", value: "2,350", change: "+180", up: true, icon: Users },
|
|
10
|
+
{ label: "Active Now", value: "573", change: "-2.4%", up: false, icon: Activity },
|
|
11
|
+
{ label: "Growth", value: "12.5%", change: "+4.1%", up: true, icon: TrendingUp },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const activities = [
|
|
15
|
+
{ user: "AK", name: "Alice Kim", action: "deployed v2.4.1", time: "2m ago" },
|
|
16
|
+
{ user: "BR", name: "Bob Ross", action: "merged PR #142", time: "15m ago" },
|
|
17
|
+
{ user: "CS", name: "Carol Smith", action: "opened issue #89", time: "1h ago" },
|
|
18
|
+
{ user: "DW", name: "Dave Wilson", action: "updated config", time: "3h ago" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const DashboardShowcase = () => {
|
|
22
|
+
return (
|
|
23
|
+
<div className="w-full space-y-4">
|
|
24
|
+
{/* Mini header */}
|
|
25
|
+
<div className="flex items-center justify-between glass-card p-3 px-4">
|
|
26
|
+
<div className="flex items-center gap-3">
|
|
27
|
+
<BarChart3 className="h-5 w-5 text-primary" />
|
|
28
|
+
<span className="font-semibold text-sm text-foreground">Dashboard</span>
|
|
29
|
+
</div>
|
|
30
|
+
<div className="flex items-center gap-2">
|
|
31
|
+
<div className="relative hidden sm:block">
|
|
32
|
+
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
|
33
|
+
<input className="bg-muted/50 border border-border/60 rounded-md pl-8 pr-3 py-1.5 text-xs text-foreground placeholder:text-muted-foreground w-40 focus:outline-none focus:border-primary/50" placeholder="Search..." />
|
|
34
|
+
</div>
|
|
35
|
+
<button className="relative p-1.5 rounded-md hover:bg-muted/60 transition-colors">
|
|
36
|
+
<Bell className="h-4 w-4 text-muted-foreground" />
|
|
37
|
+
<span className="absolute top-1 right-1 h-1.5 w-1.5 bg-primary rounded-full" />
|
|
38
|
+
</button>
|
|
39
|
+
<div className="flex items-center gap-1.5 pl-2 border-l border-border/60">
|
|
40
|
+
<Avatar className="h-6 w-6">
|
|
41
|
+
<AvatarFallback className="bg-primary/20 text-primary text-[10px]">JD</AvatarFallback>
|
|
42
|
+
</Avatar>
|
|
43
|
+
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
{/* Stat cards */}
|
|
49
|
+
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
|
50
|
+
{stats.map((stat, i) => (
|
|
51
|
+
<motion.div
|
|
52
|
+
key={stat.label}
|
|
53
|
+
initial={{ opacity: 0, y: 10 }}
|
|
54
|
+
animate={{ opacity: 1, y: 0 }}
|
|
55
|
+
transition={{ delay: i * 0.1 }}
|
|
56
|
+
className="glass-card p-3 space-y-2"
|
|
57
|
+
>
|
|
58
|
+
<div className="flex items-center justify-between">
|
|
59
|
+
<span className="text-xs text-muted-foreground">{stat.label}</span>
|
|
60
|
+
<stat.icon className="h-3.5 w-3.5 text-muted-foreground" />
|
|
61
|
+
</div>
|
|
62
|
+
<div className="text-xl font-bold text-foreground">{stat.value}</div>
|
|
63
|
+
<div className="flex items-center gap-1">
|
|
64
|
+
{stat.up ? (
|
|
65
|
+
<ArrowUpRight className="h-3 w-3 text-success" />
|
|
66
|
+
) : (
|
|
67
|
+
<ArrowDownRight className="h-3 w-3 text-destructive" />
|
|
68
|
+
)}
|
|
69
|
+
<span className={`text-xs ${stat.up ? "text-success" : "text-destructive"}`}>{stat.change}</span>
|
|
70
|
+
</div>
|
|
71
|
+
</motion.div>
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
{/* Chart placeholder + Activity */}
|
|
76
|
+
<div className="grid grid-cols-1 lg:grid-cols-5 gap-3">
|
|
77
|
+
<div className="lg:col-span-3 glass-card p-4 space-y-3">
|
|
78
|
+
<div className="flex items-center justify-between">
|
|
79
|
+
<span className="text-sm font-medium text-foreground">Revenue Overview</span>
|
|
80
|
+
<Badge variant="secondary" className="text-xs bg-muted/60">Monthly</Badge>
|
|
81
|
+
</div>
|
|
82
|
+
<div className="flex items-end gap-1.5 h-32">
|
|
83
|
+
{[40, 65, 45, 80, 55, 90, 70, 85, 60, 95, 75, 88].map((h, i) => (
|
|
84
|
+
<motion.div
|
|
85
|
+
key={i}
|
|
86
|
+
initial={{ height: 0 }}
|
|
87
|
+
animate={{ height: `${h}%` }}
|
|
88
|
+
transition={{ delay: i * 0.05, duration: 0.5 }}
|
|
89
|
+
className="flex-1 rounded-t-sm bg-primary/30 hover:bg-primary/50 transition-colors relative group"
|
|
90
|
+
>
|
|
91
|
+
<div className="absolute -top-6 left-1/2 -translate-x-1/2 bg-card border border-border/60 text-[10px] text-foreground px-1.5 py-0.5 rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
|
|
92
|
+
${(h * 520).toLocaleString()}
|
|
93
|
+
</div>
|
|
94
|
+
</motion.div>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
<div className="flex justify-between text-[10px] text-muted-foreground">
|
|
98
|
+
<span>Jan</span><span>Feb</span><span>Mar</span><span>Apr</span><span>May</span><span>Jun</span>
|
|
99
|
+
<span>Jul</span><span>Aug</span><span>Sep</span><span>Oct</span><span>Nov</span><span>Dec</span>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div className="lg:col-span-2 glass-card p-4 space-y-3">
|
|
104
|
+
<span className="text-sm font-medium text-foreground">Recent Activity</span>
|
|
105
|
+
<div className="space-y-3">
|
|
106
|
+
{activities.map((a, i) => (
|
|
107
|
+
<motion.div
|
|
108
|
+
key={i}
|
|
109
|
+
initial={{ opacity: 0, x: 10 }}
|
|
110
|
+
animate={{ opacity: 1, x: 0 }}
|
|
111
|
+
transition={{ delay: i * 0.1 }}
|
|
112
|
+
className="flex items-center gap-3"
|
|
113
|
+
>
|
|
114
|
+
<Avatar className="h-7 w-7">
|
|
115
|
+
<AvatarFallback className="bg-accent/20 text-accent text-[10px]">{a.user}</AvatarFallback>
|
|
116
|
+
</Avatar>
|
|
117
|
+
<div className="flex-1 min-w-0">
|
|
118
|
+
<p className="text-xs text-foreground truncate">
|
|
119
|
+
<span className="font-medium">{a.name}</span>{" "}
|
|
120
|
+
<span className="text-muted-foreground">{a.action}</span>
|
|
121
|
+
</p>
|
|
122
|
+
<p className="text-[10px] text-muted-foreground">{a.time}</p>
|
|
123
|
+
</div>
|
|
124
|
+
</motion.div>
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Progress section */}
|
|
131
|
+
<div className="glass-card p-4 space-y-3">
|
|
132
|
+
<span className="text-sm font-medium text-foreground">Project Progress</span>
|
|
133
|
+
<div className="space-y-3">
|
|
134
|
+
{[
|
|
135
|
+
{ name: "UI Components", value: 85 },
|
|
136
|
+
{ name: "API Integration", value: 62 },
|
|
137
|
+
{ name: "Testing", value: 40 },
|
|
138
|
+
].map((p) => (
|
|
139
|
+
<div key={p.name} className="space-y-1.5">
|
|
140
|
+
<div className="flex justify-between text-xs">
|
|
141
|
+
<span className="text-secondary-foreground">{p.name}</span>
|
|
142
|
+
<span className="text-muted-foreground">{p.value}%</span>
|
|
143
|
+
</div>
|
|
144
|
+
<Progress value={p.value} className="h-1.5 bg-muted/60" />
|
|
145
|
+
</div>
|
|
146
|
+
))}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export default DashboardShowcase;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { motion } from "framer-motion";
|
|
3
|
+
import { FileQuestion, ServerCrash, WifiOff, ShieldX, Wrench } from "lucide-react";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
|
|
6
|
+
const errors = [
|
|
7
|
+
{ key: "404", icon: FileQuestion, title: "404 — Not Found", desc: "The page you're looking for doesn't exist or has been moved.", color: "text-warning" },
|
|
8
|
+
{ key: "500", icon: ServerCrash, title: "500 — Server Error", desc: "Something went wrong on our end. We're working on it.", color: "text-destructive" },
|
|
9
|
+
{ key: "offline", icon: WifiOff, title: "No Connection", desc: "You appear to be offline. Check your internet connection.", color: "text-muted-foreground" },
|
|
10
|
+
{ key: "403", icon: ShieldX, title: "403 — Forbidden", desc: "You don't have permission to access this resource.", color: "text-accent" },
|
|
11
|
+
{ key: "maintenance", icon: Wrench, title: "Under Maintenance", desc: "We're making improvements. We'll be back shortly.", color: "text-info" },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const ErrorShowcase = () => {
|
|
15
|
+
const [active, setActive] = useState("404");
|
|
16
|
+
const current = errors.find((e) => e.key === active)!;
|
|
17
|
+
const Icon = current.icon;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="w-full max-w-lg mx-auto space-y-6">
|
|
21
|
+
<div className="flex flex-wrap gap-2 justify-center">
|
|
22
|
+
{errors.map((e) => (
|
|
23
|
+
<Button
|
|
24
|
+
key={e.key}
|
|
25
|
+
variant={active === e.key ? "default" : "outline"}
|
|
26
|
+
size="sm"
|
|
27
|
+
onClick={() => setActive(e.key)}
|
|
28
|
+
className={`text-xs ${active === e.key ? "bg-primary text-primary-foreground" : "bg-muted/30 border-border/60 hover:bg-muted/60"}`}
|
|
29
|
+
>
|
|
30
|
+
{e.key}
|
|
31
|
+
</Button>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<motion.div
|
|
36
|
+
key={active}
|
|
37
|
+
initial={{ opacity: 0, scale: 0.95 }}
|
|
38
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
39
|
+
className="glass-card p-8 flex flex-col items-center text-center space-y-4"
|
|
40
|
+
>
|
|
41
|
+
<div className={`p-4 rounded-2xl bg-muted/40 ${current.color}`}>
|
|
42
|
+
<Icon className="h-12 w-12" />
|
|
43
|
+
</div>
|
|
44
|
+
<h3 className="text-xl font-bold text-foreground">{current.title}</h3>
|
|
45
|
+
<p className="text-sm text-muted-foreground max-w-xs">{current.desc}</p>
|
|
46
|
+
<div className="flex gap-2 pt-2">
|
|
47
|
+
<Button size="sm" className="bg-primary text-primary-foreground hover:bg-primary/90 text-xs">Go Home</Button>
|
|
48
|
+
<Button size="sm" variant="outline" className="bg-muted/30 border-border/60 hover:bg-muted/60 text-xs">Try Again</Button>
|
|
49
|
+
</div>
|
|
50
|
+
</motion.div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default ErrorShowcase;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
3
|
+
import { CheckCircle, AlertTriangle, XCircle, Info, X } from "lucide-react";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
|
|
6
|
+
type NotifType = "success" | "error" | "warning" | "info";
|
|
7
|
+
|
|
8
|
+
interface Notification {
|
|
9
|
+
id: number;
|
|
10
|
+
type: NotifType;
|
|
11
|
+
title: string;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const config: Record<NotifType, { icon: typeof CheckCircle; colorClass: string; borderClass: string }> = {
|
|
16
|
+
success: { icon: CheckCircle, colorClass: "text-success", borderClass: "border-l-success" },
|
|
17
|
+
error: { icon: XCircle, colorClass: "text-destructive", borderClass: "border-l-destructive" },
|
|
18
|
+
warning: { icon: AlertTriangle, colorClass: "text-warning", borderClass: "border-l-warning" },
|
|
19
|
+
info: { icon: Info, colorClass: "text-info", borderClass: "border-l-info" },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const messages: Record<NotifType, { title: string; message: string }> = {
|
|
23
|
+
success: { title: "Success!", message: "Your changes have been saved successfully." },
|
|
24
|
+
error: { title: "Error", message: "Something went wrong. Please try again." },
|
|
25
|
+
warning: { title: "Warning", message: "This action cannot be undone." },
|
|
26
|
+
info: { title: "Info", message: "A new version is available for download." },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let idCounter = 0;
|
|
30
|
+
|
|
31
|
+
const NotificationShowcase = () => {
|
|
32
|
+
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
33
|
+
|
|
34
|
+
const addNotification = (type: NotifType) => {
|
|
35
|
+
const notif: Notification = { id: ++idCounter, type, ...messages[type] };
|
|
36
|
+
setNotifications((prev) => [...prev, notif]);
|
|
37
|
+
setTimeout(() => removeNotification(notif.id), 4000);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const removeNotification = (id: number) => {
|
|
41
|
+
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="w-full max-w-lg mx-auto space-y-6">
|
|
46
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
|
47
|
+
{(["success", "error", "warning", "info"] as NotifType[]).map((type) => {
|
|
48
|
+
const Icon = config[type].icon;
|
|
49
|
+
return (
|
|
50
|
+
<Button
|
|
51
|
+
key={type}
|
|
52
|
+
variant="outline"
|
|
53
|
+
onClick={() => addNotification(type)}
|
|
54
|
+
className="bg-muted/30 border-border/60 hover:bg-muted/60 capitalize text-xs gap-1.5"
|
|
55
|
+
>
|
|
56
|
+
<Icon className={`h-3.5 w-3.5 ${config[type].colorClass}`} />
|
|
57
|
+
{type}
|
|
58
|
+
</Button>
|
|
59
|
+
);
|
|
60
|
+
})}
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div className="space-y-2 min-h-[200px]">
|
|
64
|
+
<AnimatePresence mode="popLayout">
|
|
65
|
+
{notifications.map((notif) => {
|
|
66
|
+
const { icon: Icon, colorClass, borderClass } = config[notif.type];
|
|
67
|
+
return (
|
|
68
|
+
<motion.div
|
|
69
|
+
key={notif.id}
|
|
70
|
+
initial={{ opacity: 0, y: -10, scale: 0.95 }}
|
|
71
|
+
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
72
|
+
exit={{ opacity: 0, x: 100, scale: 0.95 }}
|
|
73
|
+
layout
|
|
74
|
+
className={`glass-card border-l-4 ${borderClass} p-3 flex items-start gap-3`}
|
|
75
|
+
>
|
|
76
|
+
<Icon className={`h-4 w-4 mt-0.5 flex-shrink-0 ${colorClass}`} />
|
|
77
|
+
<div className="flex-1 min-w-0">
|
|
78
|
+
<p className="text-sm font-medium text-foreground">{notif.title}</p>
|
|
79
|
+
<p className="text-xs text-muted-foreground mt-0.5">{notif.message}</p>
|
|
80
|
+
</div>
|
|
81
|
+
<button
|
|
82
|
+
onClick={() => removeNotification(notif.id)}
|
|
83
|
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
|
84
|
+
>
|
|
85
|
+
<X className="h-3.5 w-3.5" />
|
|
86
|
+
</button>
|
|
87
|
+
</motion.div>
|
|
88
|
+
);
|
|
89
|
+
})}
|
|
90
|
+
</AnimatePresence>
|
|
91
|
+
|
|
92
|
+
{notifications.length === 0 && (
|
|
93
|
+
<div className="flex items-center justify-center h-[200px] text-sm text-muted-foreground">
|
|
94
|
+
Click a button above to trigger notifications
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export default NotificationShowcase;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|
3
|
+
import { ChevronDown } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
const Accordion = AccordionPrimitive.Root;
|
|
8
|
+
|
|
9
|
+
const AccordionItem = React.forwardRef<
|
|
10
|
+
React.ElementRef<typeof AccordionPrimitive.Item>,
|
|
11
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
|
12
|
+
>(({ className, ...props }, ref) => (
|
|
13
|
+
<AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
|
|
14
|
+
));
|
|
15
|
+
AccordionItem.displayName = "AccordionItem";
|
|
16
|
+
|
|
17
|
+
const AccordionTrigger = React.forwardRef<
|
|
18
|
+
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
|
19
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
|
20
|
+
>(({ className, children, ...props }, ref) => (
|
|
21
|
+
<AccordionPrimitive.Header className="flex">
|
|
22
|
+
<AccordionPrimitive.Trigger
|
|
23
|
+
ref={ref}
|
|
24
|
+
className={cn(
|
|
25
|
+
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
|
26
|
+
className,
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
|
32
|
+
</AccordionPrimitive.Trigger>
|
|
33
|
+
</AccordionPrimitive.Header>
|
|
34
|
+
));
|
|
35
|
+
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
|
36
|
+
|
|
37
|
+
const AccordionContent = React.forwardRef<
|
|
38
|
+
React.ElementRef<typeof AccordionPrimitive.Content>,
|
|
39
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
|
40
|
+
>(({ className, children, ...props }, ref) => (
|
|
41
|
+
<AccordionPrimitive.Content
|
|
42
|
+
ref={ref}
|
|
43
|
+
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
|
47
|
+
</AccordionPrimitive.Content>
|
|
48
|
+
));
|
|
49
|
+
|
|
50
|
+
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
|
51
|
+
|
|
52
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
import { buttonVariants } from "@/components/ui/button";
|
|
6
|
+
|
|
7
|
+
const AlertDialog = AlertDialogPrimitive.Root;
|
|
8
|
+
|
|
9
|
+
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
|
|
10
|
+
|
|
11
|
+
const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
|
12
|
+
|
|
13
|
+
const AlertDialogOverlay = React.forwardRef<
|
|
14
|
+
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
|
15
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
|
16
|
+
>(({ className, ...props }, ref) => (
|
|
17
|
+
<AlertDialogPrimitive.Overlay
|
|
18
|
+
className={cn(
|
|
19
|
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
ref={ref}
|
|
24
|
+
/>
|
|
25
|
+
));
|
|
26
|
+
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
|
27
|
+
|
|
28
|
+
const AlertDialogContent = React.forwardRef<
|
|
29
|
+
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
|
30
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
|
31
|
+
>(({ className, ...props }, ref) => (
|
|
32
|
+
<AlertDialogPortal>
|
|
33
|
+
<AlertDialogOverlay />
|
|
34
|
+
<AlertDialogPrimitive.Content
|
|
35
|
+
ref={ref}
|
|
36
|
+
className={cn(
|
|
37
|
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
</AlertDialogPortal>
|
|
43
|
+
));
|
|
44
|
+
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
|
|
45
|
+
|
|
46
|
+
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
47
|
+
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
|
|
48
|
+
);
|
|
49
|
+
AlertDialogHeader.displayName = "AlertDialogHeader";
|
|
50
|
+
|
|
51
|
+
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
52
|
+
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
|
|
53
|
+
);
|
|
54
|
+
AlertDialogFooter.displayName = "AlertDialogFooter";
|
|
55
|
+
|
|
56
|
+
const AlertDialogTitle = React.forwardRef<
|
|
57
|
+
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
|
58
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
|
59
|
+
>(({ className, ...props }, ref) => (
|
|
60
|
+
<AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} />
|
|
61
|
+
));
|
|
62
|
+
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
|
|
63
|
+
|
|
64
|
+
const AlertDialogDescription = React.forwardRef<
|
|
65
|
+
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
|
66
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
|
67
|
+
>(({ className, ...props }, ref) => (
|
|
68
|
+
<AlertDialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
|
|
69
|
+
));
|
|
70
|
+
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
|
|
71
|
+
|
|
72
|
+
const AlertDialogAction = React.forwardRef<
|
|
73
|
+
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
|
74
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
|
75
|
+
>(({ className, ...props }, ref) => (
|
|
76
|
+
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
|
|
77
|
+
));
|
|
78
|
+
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
|
|
79
|
+
|
|
80
|
+
const AlertDialogCancel = React.forwardRef<
|
|
81
|
+
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
|
82
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
|
83
|
+
>(({ className, ...props }, ref) => (
|
|
84
|
+
<AlertDialogPrimitive.Cancel
|
|
85
|
+
ref={ref}
|
|
86
|
+
className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
));
|
|
90
|
+
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
AlertDialog,
|
|
94
|
+
AlertDialogPortal,
|
|
95
|
+
AlertDialogOverlay,
|
|
96
|
+
AlertDialogTrigger,
|
|
97
|
+
AlertDialogContent,
|
|
98
|
+
AlertDialogHeader,
|
|
99
|
+
AlertDialogFooter,
|
|
100
|
+
AlertDialogTitle,
|
|
101
|
+
AlertDialogDescription,
|
|
102
|
+
AlertDialogAction,
|
|
103
|
+
AlertDialogCancel,
|
|
104
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
const alertVariants = cva(
|
|
7
|
+
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-background text-foreground",
|
|
12
|
+
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
variant: "default",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const Alert = React.forwardRef<
|
|
22
|
+
HTMLDivElement,
|
|
23
|
+
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
|
24
|
+
>(({ className, variant, ...props }, ref) => (
|
|
25
|
+
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
|
|
26
|
+
));
|
|
27
|
+
Alert.displayName = "Alert";
|
|
28
|
+
|
|
29
|
+
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
|
30
|
+
({ className, ...props }, ref) => (
|
|
31
|
+
<h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} />
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
AlertTitle.displayName = "AlertTitle";
|
|
35
|
+
|
|
36
|
+
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
37
|
+
({ className, ...props }, ref) => (
|
|
38
|
+
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
|
|
39
|
+
),
|
|
40
|
+
);
|
|
41
|
+
AlertDescription.displayName = "AlertDescription";
|
|
42
|
+
|
|
43
|
+
export { Alert, AlertTitle, AlertDescription };
|