ucode-agent 1.3.0 → 1.5.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.
Files changed (56) hide show
  1. package/README.md +42 -12
  2. package/package.json +5 -3
  3. package/skills/build-app/SKILL.md +23 -3
  4. package/skills/ui-ux/SKILL.md +5 -1
  5. package/src/core/loop.js +181 -20
  6. package/src/core/provider.js +40 -12
  7. package/src/core/updater.js +95 -0
  8. package/src/tools/browser.js +258 -0
  9. package/src/tools/index.js +51 -1
  10. package/src/tools/scaffold.js +130 -0
  11. package/src/tools/shell.js +8 -1
  12. package/src/ui/screen.js +20 -6
  13. package/templates/next-shadcn/AGENTS.md +9 -0
  14. package/templates/next-shadcn/README.md +9 -0
  15. package/templates/next-shadcn/TEMPLATE.md +39 -0
  16. package/templates/next-shadcn/_gitignore +41 -0
  17. package/templates/next-shadcn/_package-lock.json +10278 -0
  18. package/templates/next-shadcn/components.json +25 -0
  19. package/templates/next-shadcn/eslint.config.mjs +18 -0
  20. package/templates/next-shadcn/next-env.d.ts +5 -0
  21. package/templates/next-shadcn/next.config.ts +7 -0
  22. package/templates/next-shadcn/package.json +37 -0
  23. package/templates/next-shadcn/postcss.config.mjs +7 -0
  24. package/templates/next-shadcn/src/app/favicon.ico +0 -0
  25. package/templates/next-shadcn/src/app/globals.css +155 -0
  26. package/templates/next-shadcn/src/app/layout.tsx +42 -0
  27. package/templates/next-shadcn/src/app/page.tsx +14 -0
  28. package/templates/next-shadcn/src/components/theme-provider.tsx +7 -0
  29. package/templates/next-shadcn/src/components/theme-toggle.tsx +27 -0
  30. package/templates/next-shadcn/src/components/ui/alert-dialog.tsx +187 -0
  31. package/templates/next-shadcn/src/components/ui/avatar.tsx +108 -0
  32. package/templates/next-shadcn/src/components/ui/badge.tsx +51 -0
  33. package/templates/next-shadcn/src/components/ui/button.tsx +57 -0
  34. package/templates/next-shadcn/src/components/ui/calendar.tsx +221 -0
  35. package/templates/next-shadcn/src/components/ui/card.tsx +102 -0
  36. package/templates/next-shadcn/src/components/ui/checkbox.tsx +28 -0
  37. package/templates/next-shadcn/src/components/ui/command.tsx +196 -0
  38. package/templates/next-shadcn/src/components/ui/dialog.tsx +160 -0
  39. package/templates/next-shadcn/src/components/ui/dropdown-menu.tsx +267 -0
  40. package/templates/next-shadcn/src/components/ui/input-group.tsx +158 -0
  41. package/templates/next-shadcn/src/components/ui/input.tsx +19 -0
  42. package/templates/next-shadcn/src/components/ui/label.tsx +19 -0
  43. package/templates/next-shadcn/src/components/ui/popover.tsx +89 -0
  44. package/templates/next-shadcn/src/components/ui/progress.tsx +82 -0
  45. package/templates/next-shadcn/src/components/ui/scroll-area.tsx +54 -0
  46. package/templates/next-shadcn/src/components/ui/select.tsx +200 -0
  47. package/templates/next-shadcn/src/components/ui/separator.tsx +24 -0
  48. package/templates/next-shadcn/src/components/ui/sheet.tsx +138 -0
  49. package/templates/next-shadcn/src/components/ui/skeleton.tsx +13 -0
  50. package/templates/next-shadcn/src/components/ui/sonner.tsx +49 -0
  51. package/templates/next-shadcn/src/components/ui/switch.tsx +31 -0
  52. package/templates/next-shadcn/src/components/ui/tabs.tsx +81 -0
  53. package/templates/next-shadcn/src/components/ui/textarea.tsx +17 -0
  54. package/templates/next-shadcn/src/components/ui/tooltip.tsx +65 -0
  55. package/templates/next-shadcn/src/lib/utils.ts +1 -0
  56. package/templates/next-shadcn/tsconfig.json +34 -0
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "base-nova",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "rtl": false,
15
+ "aliases": {
16
+ "components": "@/components",
17
+ "utils": "@/lib/utils",
18
+ "ui": "@/components/ui",
19
+ "lib": "@/lib",
20
+ "hooks": "@/hooks"
21
+ },
22
+ "menuColor": "default",
23
+ "menuAccent": "subtle",
24
+ "registries": {}
25
+ }
@@ -0,0 +1,18 @@
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
@@ -0,0 +1,5 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // NOTE: This file should not be edited
5
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "__APP_SLUG__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "eslint"
10
+ },
11
+ "dependencies": {
12
+ "@base-ui/react": "^1.8.0",
13
+ "class-variance-authority": "^0.7.1",
14
+ "cmdk": "^1.1.1",
15
+ "cn": "^0.2.6",
16
+ "date-fns": "^4.4.0",
17
+ "lucide-react": "^1.44.0",
18
+ "next": "16.3.4",
19
+ "next-themes": "^0.4.6",
20
+ "react": "19.2.8",
21
+ "react-day-picker": "^10.0.1",
22
+ "react-dom": "19.2.8",
23
+ "shadcn": "^4.21.0",
24
+ "sonner": "^2.0.8",
25
+ "tw-animate-css": "^1.4.0"
26
+ },
27
+ "devDependencies": {
28
+ "@tailwindcss/postcss": "^4",
29
+ "@types/node": "^20",
30
+ "@types/react": "^19",
31
+ "@types/react-dom": "^19",
32
+ "eslint": "^9",
33
+ "eslint-config-next": "16.3.4",
34
+ "tailwindcss": "^4",
35
+ "typescript": "^5"
36
+ }
37
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,155 @@
1
+ /*
2
+ * Design tokens. This palette is a considered starting point, not a finished
3
+ * look: re-tint --primary, --accent and the neutrals (the third number in each
4
+ * oklch() is the hue) so each app has its own direction.
5
+ */
6
+ @import "tailwindcss";
7
+ @import "tw-animate-css";
8
+ @import "shadcn/tailwind.css";
9
+
10
+ @custom-variant dark (&:is(.dark *));
11
+
12
+ @theme inline {
13
+ --color-background: var(--background);
14
+ --color-foreground: var(--foreground);
15
+ --font-sans: var(--font-sans);
16
+ --font-mono: var(--font-geist-mono);
17
+ --font-heading: var(--font-sans);
18
+ --color-sidebar-ring: var(--sidebar-ring);
19
+ --color-sidebar-border: var(--sidebar-border);
20
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
21
+ --color-sidebar-accent: var(--sidebar-accent);
22
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
23
+ --color-sidebar-primary: var(--sidebar-primary);
24
+ --color-sidebar-foreground: var(--sidebar-foreground);
25
+ --color-sidebar: var(--sidebar);
26
+ --color-chart-5: var(--chart-5);
27
+ --color-chart-4: var(--chart-4);
28
+ --color-chart-3: var(--chart-3);
29
+ --color-chart-2: var(--chart-2);
30
+ --color-chart-1: var(--chart-1);
31
+ --color-ring: var(--ring);
32
+ --color-input: var(--input);
33
+ --color-border: var(--border);
34
+ --color-destructive: var(--destructive);
35
+ --color-success: var(--success);
36
+ --color-warning: var(--warning);
37
+ --color-accent-foreground: var(--accent-foreground);
38
+ --color-accent: var(--accent);
39
+ --color-muted-foreground: var(--muted-foreground);
40
+ --color-muted: var(--muted);
41
+ --color-secondary-foreground: var(--secondary-foreground);
42
+ --color-secondary: var(--secondary);
43
+ --color-primary-foreground: var(--primary-foreground);
44
+ --color-primary: var(--primary);
45
+ --color-popover-foreground: var(--popover-foreground);
46
+ --color-popover: var(--popover);
47
+ --color-card-foreground: var(--card-foreground);
48
+ --color-card: var(--card);
49
+ --radius-sm: calc(var(--radius) * 0.6);
50
+ --radius-md: calc(var(--radius) * 0.8);
51
+ --radius-lg: var(--radius);
52
+ --radius-xl: calc(var(--radius) * 1.4);
53
+ --radius-2xl: calc(var(--radius) * 1.8);
54
+ --radius-3xl: calc(var(--radius) * 2.2);
55
+ --radius-4xl: calc(var(--radius) * 2.6);
56
+ }
57
+
58
+ :root {
59
+ --background: oklch(0.985 0.004 255);
60
+ --foreground: oklch(0.21 0.02 262);
61
+ --card: oklch(1 0 0);
62
+ --card-foreground: oklch(0.21 0.02 262);
63
+ --popover: oklch(1 0 0);
64
+ --popover-foreground: oklch(0.21 0.02 262);
65
+ --primary: oklch(0.53 0.2 264);
66
+ --primary-foreground: oklch(0.99 0 0);
67
+ --secondary: oklch(0.955 0.008 255);
68
+ --secondary-foreground: oklch(0.27 0.03 262);
69
+ --muted: oklch(0.96 0.006 255);
70
+ --muted-foreground: oklch(0.52 0.02 262);
71
+ --accent: oklch(0.95 0.025 264);
72
+ --accent-foreground: oklch(0.36 0.13 264);
73
+ --destructive: oklch(0.577 0.215 27);
74
+ --success: oklch(0.6 0.15 155);
75
+ --warning: oklch(0.74 0.15 72);
76
+ --border: oklch(0.915 0.008 255);
77
+ --input: oklch(0.9 0.01 255);
78
+ --ring: oklch(0.53 0.2 264);
79
+ --chart-1: oklch(0.53 0.2 264);
80
+ --chart-2: oklch(0.6 0.15 155);
81
+ --chart-3: oklch(0.74 0.15 72);
82
+ --chart-4: oklch(0.62 0.19 330);
83
+ --chart-5: oklch(0.52 0.02 262);
84
+ --radius: 0.75rem;
85
+ --sidebar: oklch(0.975 0.006 255);
86
+ --sidebar-foreground: oklch(0.21 0.02 262);
87
+ --sidebar-primary: oklch(0.53 0.2 264);
88
+ --sidebar-primary-foreground: oklch(0.99 0 0);
89
+ --sidebar-accent: oklch(0.95 0.025 264);
90
+ --sidebar-accent-foreground: oklch(0.36 0.13 264);
91
+ --sidebar-border: oklch(0.915 0.008 255);
92
+ --sidebar-ring: oklch(0.53 0.2 264);
93
+ }
94
+
95
+ .dark {
96
+ --background: oklch(0.165 0.012 262);
97
+ --foreground: oklch(0.95 0.008 262);
98
+ --card: oklch(0.2 0.014 262);
99
+ --card-foreground: oklch(0.95 0.008 262);
100
+ --popover: oklch(0.215 0.015 262);
101
+ --popover-foreground: oklch(0.95 0.008 262);
102
+ --primary: oklch(0.7 0.16 264);
103
+ --primary-foreground: oklch(0.17 0.02 262);
104
+ --secondary: oklch(0.25 0.015 262);
105
+ --secondary-foreground: oklch(0.93 0.01 262);
106
+ --muted: oklch(0.24 0.014 262);
107
+ --muted-foreground: oklch(0.7 0.02 262);
108
+ --accent: oklch(0.28 0.045 264);
109
+ --accent-foreground: oklch(0.9 0.05 264);
110
+ --destructive: oklch(0.7 0.19 22);
111
+ --success: oklch(0.72 0.15 155);
112
+ --warning: oklch(0.8 0.14 75);
113
+ --border: oklch(1 0 0 / 9%);
114
+ --input: oklch(1 0 0 / 13%);
115
+ --ring: oklch(0.7 0.16 264);
116
+ --chart-1: oklch(0.7 0.16 264);
117
+ --chart-2: oklch(0.72 0.15 155);
118
+ --chart-3: oklch(0.8 0.14 75);
119
+ --chart-4: oklch(0.72 0.17 330);
120
+ --chart-5: oklch(0.7 0.02 262);
121
+ --sidebar: oklch(0.185 0.013 262);
122
+ --sidebar-foreground: oklch(0.95 0.008 262);
123
+ --sidebar-primary: oklch(0.7 0.16 264);
124
+ --sidebar-primary-foreground: oklch(0.17 0.02 262);
125
+ --sidebar-accent: oklch(0.28 0.045 264);
126
+ --sidebar-accent-foreground: oklch(0.9 0.05 264);
127
+ --sidebar-border: oklch(1 0 0 / 9%);
128
+ --sidebar-ring: oklch(0.7 0.16 264);
129
+ }
130
+
131
+ @layer base {
132
+ * {
133
+ @apply border-border outline-ring/50;
134
+ }
135
+ body {
136
+ @apply bg-background text-foreground;
137
+ }
138
+ html {
139
+ @apply font-sans;
140
+ }
141
+ h1, h2, h3 {
142
+ letter-spacing: -0.02em;
143
+ text-wrap: balance;
144
+ }
145
+ p {
146
+ text-wrap: pretty;
147
+ }
148
+ }
149
+
150
+ @media (prefers-reduced-motion: reduce) {
151
+ *, *::before, *::after {
152
+ animation-duration: 1ms !important;
153
+ transition-duration: 1ms !important;
154
+ }
155
+ }
@@ -0,0 +1,42 @@
1
+ import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono } from "next/font/google";
3
+ import { ThemeProvider } from "@/components/theme-provider";
4
+ import { Toaster } from "@/components/ui/sonner";
5
+ import { TooltipProvider } from "@/components/ui/tooltip";
6
+ import "./globals.css";
7
+
8
+ // Registered as --font-sans, which is the variable globals.css and every
9
+ // shadcn component read. Swap Geist for the font the app's direction calls for.
10
+ const sans = Geist({
11
+ variable: "--font-sans",
12
+ subsets: ["latin"],
13
+ });
14
+
15
+ const mono = Geist_Mono({
16
+ variable: "--font-geist-mono",
17
+ subsets: ["latin"],
18
+ });
19
+
20
+ export const metadata: Metadata = {
21
+ title: "__APP_NAME__",
22
+ description: "__APP_DESCRIPTION__",
23
+ };
24
+
25
+ export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
26
+ return (
27
+ <html
28
+ lang="en"
29
+ suppressHydrationWarning
30
+ className={`${sans.variable} ${mono.variable} h-full antialiased`}
31
+ >
32
+ <body className="min-h-full flex flex-col">
33
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
34
+ <TooltipProvider>
35
+ {children}
36
+ <Toaster richColors closeButton />
37
+ </TooltipProvider>
38
+ </ThemeProvider>
39
+ </body>
40
+ </html>
41
+ );
42
+ }
@@ -0,0 +1,14 @@
1
+ import { ThemeToggle } from "@/components/theme-toggle";
2
+
3
+ // Placeholder — replace this whole page with the app.
4
+ export default function Home() {
5
+ return (
6
+ <main className="flex flex-1 items-center justify-center p-8">
7
+ <div className="space-y-3 text-center">
8
+ <h1 className="text-3xl font-semibold tracking-tight">__APP_NAME__</h1>
9
+ <p className="text-muted-foreground">Replace src/app/page.tsx with the app.</p>
10
+ <ThemeToggle />
11
+ </div>
12
+ </main>
13
+ );
14
+ }
@@ -0,0 +1,7 @@
1
+ "use client";
2
+
3
+ import { ThemeProvider as NextThemesProvider } from "next-themes";
4
+
5
+ export function ThemeProvider({ children, ...props }: React.ComponentProps<typeof NextThemesProvider>) {
6
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
7
+ }
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { useTheme } from "next-themes";
5
+ import { MoonIcon, SunIcon } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+
8
+ /** Light / dark switch. Renders a same-size placeholder until mounted, so nothing shifts. */
9
+ export function ThemeToggle() {
10
+ const { resolvedTheme, setTheme } = useTheme();
11
+ const [mounted, setMounted] = useState(false);
12
+ useEffect(() => setMounted(true), []);
13
+
14
+ if (!mounted) return <span aria-hidden className="inline-block size-8" />;
15
+
16
+ const dark = resolvedTheme === "dark";
17
+ return (
18
+ <Button
19
+ variant="ghost"
20
+ size="icon"
21
+ aria-label={dark ? "Switch to light mode" : "Switch to dark mode"}
22
+ onClick={() => setTheme(dark ? "light" : "dark")}
23
+ >
24
+ {dark ? <SunIcon /> : <MoonIcon />}
25
+ </Button>
26
+ );
27
+ }
@@ -0,0 +1,187 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
5
+ import { cn } from "cn"
6
+
7
+ import { Button } from "@/components/ui/button"
8
+
9
+ function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
10
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
11
+ }
12
+
13
+ function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
14
+ return (
15
+ <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
16
+ )
17
+ }
18
+
19
+ function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
20
+ return (
21
+ <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
22
+ )
23
+ }
24
+
25
+ function AlertDialogOverlay({
26
+ className,
27
+ ...props
28
+ }: AlertDialogPrimitive.Backdrop.Props) {
29
+ return (
30
+ <AlertDialogPrimitive.Backdrop
31
+ data-slot="alert-dialog-overlay"
32
+ className={cn(
33
+ "fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
34
+ className
35
+ )}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function AlertDialogContent({
42
+ className,
43
+ size = "default",
44
+ ...props
45
+ }: AlertDialogPrimitive.Popup.Props & {
46
+ size?: "default" | "sm"
47
+ }) {
48
+ return (
49
+ <AlertDialogPortal>
50
+ <AlertDialogOverlay />
51
+ <AlertDialogPrimitive.Popup
52
+ data-slot="alert-dialog-content"
53
+ data-size={size}
54
+ className={cn(
55
+ "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
56
+ className
57
+ )}
58
+ {...props}
59
+ />
60
+ </AlertDialogPortal>
61
+ )
62
+ }
63
+
64
+ function AlertDialogHeader({
65
+ className,
66
+ ...props
67
+ }: React.ComponentProps<"div">) {
68
+ return (
69
+ <div
70
+ data-slot="alert-dialog-header"
71
+ className={cn(
72
+ "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
73
+ className
74
+ )}
75
+ {...props}
76
+ />
77
+ )
78
+ }
79
+
80
+ function AlertDialogFooter({
81
+ className,
82
+ ...props
83
+ }: React.ComponentProps<"div">) {
84
+ return (
85
+ <div
86
+ data-slot="alert-dialog-footer"
87
+ className={cn(
88
+ "-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
89
+ className
90
+ )}
91
+ {...props}
92
+ />
93
+ )
94
+ }
95
+
96
+ function AlertDialogMedia({
97
+ className,
98
+ ...props
99
+ }: React.ComponentProps<"div">) {
100
+ return (
101
+ <div
102
+ data-slot="alert-dialog-media"
103
+ className={cn(
104
+ "mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
105
+ className
106
+ )}
107
+ {...props}
108
+ />
109
+ )
110
+ }
111
+
112
+ function AlertDialogTitle({
113
+ className,
114
+ ...props
115
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
116
+ return (
117
+ <AlertDialogPrimitive.Title
118
+ data-slot="alert-dialog-title"
119
+ className={cn(
120
+ "font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
121
+ className
122
+ )}
123
+ {...props}
124
+ />
125
+ )
126
+ }
127
+
128
+ function AlertDialogDescription({
129
+ className,
130
+ ...props
131
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
132
+ return (
133
+ <AlertDialogPrimitive.Description
134
+ data-slot="alert-dialog-description"
135
+ className={cn(
136
+ "text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
137
+ className
138
+ )}
139
+ {...props}
140
+ />
141
+ )
142
+ }
143
+
144
+ function AlertDialogAction({
145
+ className,
146
+ ...props
147
+ }: React.ComponentProps<typeof Button>) {
148
+ return (
149
+ <Button
150
+ data-slot="alert-dialog-action"
151
+ className={cn(className)}
152
+ {...props}
153
+ />
154
+ )
155
+ }
156
+
157
+ function AlertDialogCancel({
158
+ className,
159
+ variant = "outline",
160
+ size = "default",
161
+ ...props
162
+ }: AlertDialogPrimitive.Close.Props &
163
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
164
+ return (
165
+ <AlertDialogPrimitive.Close
166
+ data-slot="alert-dialog-cancel"
167
+ className={cn(className)}
168
+ render={<Button variant={variant} size={size} />}
169
+ {...props}
170
+ />
171
+ )
172
+ }
173
+
174
+ export {
175
+ AlertDialog,
176
+ AlertDialogAction,
177
+ AlertDialogCancel,
178
+ AlertDialogContent,
179
+ AlertDialogDescription,
180
+ AlertDialogFooter,
181
+ AlertDialogHeader,
182
+ AlertDialogMedia,
183
+ AlertDialogOverlay,
184
+ AlertDialogPortal,
185
+ AlertDialogTitle,
186
+ AlertDialogTrigger,
187
+ }
@@ -0,0 +1,108 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
5
+ import { cn } from "cn"
6
+
7
+ function Avatar({
8
+ className,
9
+ size = "default",
10
+ ...props
11
+ }: AvatarPrimitive.Root.Props & {
12
+ size?: "default" | "sm" | "lg"
13
+ }) {
14
+ return (
15
+ <AvatarPrimitive.Root
16
+ data-slot="avatar"
17
+ data-size={size}
18
+ className={cn(
19
+ "group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
20
+ className
21
+ )}
22
+ {...props}
23
+ />
24
+ )
25
+ }
26
+
27
+ function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
28
+ return (
29
+ <AvatarPrimitive.Image
30
+ data-slot="avatar-image"
31
+ className={cn(
32
+ "aspect-square size-full rounded-full object-cover",
33
+ className
34
+ )}
35
+ {...props}
36
+ />
37
+ )
38
+ }
39
+
40
+ function AvatarFallback({
41
+ className,
42
+ ...props
43
+ }: AvatarPrimitive.Fallback.Props) {
44
+ return (
45
+ <AvatarPrimitive.Fallback
46
+ data-slot="avatar-fallback"
47
+ className={cn(
48
+ "flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
49
+ className
50
+ )}
51
+ {...props}
52
+ />
53
+ )
54
+ }
55
+
56
+ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
57
+ return (
58
+ <span
59
+ data-slot="avatar-badge"
60
+ className={cn(
61
+ "absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
62
+ "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
63
+ "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
64
+ "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
65
+ className
66
+ )}
67
+ {...props}
68
+ />
69
+ )
70
+ }
71
+
72
+ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
73
+ return (
74
+ <div
75
+ data-slot="avatar-group"
76
+ className={cn(
77
+ "group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
78
+ className
79
+ )}
80
+ {...props}
81
+ />
82
+ )
83
+ }
84
+
85
+ function AvatarGroupCount({
86
+ className,
87
+ ...props
88
+ }: React.ComponentProps<"div">) {
89
+ return (
90
+ <div
91
+ data-slot="avatar-group-count"
92
+ className={cn(
93
+ "relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
94
+ className
95
+ )}
96
+ {...props}
97
+ />
98
+ )
99
+ }
100
+
101
+ export {
102
+ Avatar,
103
+ AvatarImage,
104
+ AvatarFallback,
105
+ AvatarGroup,
106
+ AvatarGroupCount,
107
+ AvatarBadge,
108
+ }