@swift-rust/ui 0.2.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/dist/cli.d.ts +17 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +437 -0
- package/dist/cli.js.map +1 -0
- package/dist/components.d.ts +8 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +39 -0
- package/dist/components.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/smoke.test.d.ts +2 -0
- package/dist/smoke.test.d.ts.map +1 -0
- package/dist/smoke.test.js +18 -0
- package/dist/smoke.test.js.map +1 -0
- package/package.json +54 -0
- package/registry/components/accordion.tsx +106 -0
- package/registry/components/alert.tsx +34 -0
- package/registry/components/avatar.tsx +37 -0
- package/registry/components/badge.tsx +30 -0
- package/registry/components/breadcrumb.tsx +56 -0
- package/registry/components/button.tsx +48 -0
- package/registry/components/callout.tsx +20 -0
- package/registry/components/card.tsx +49 -0
- package/registry/components/checkbox.tsx +21 -0
- package/registry/components/code.tsx +27 -0
- package/registry/components/command.tsx +93 -0
- package/registry/components/dialog.tsx +118 -0
- package/registry/components/dropdown-menu.tsx +96 -0
- package/registry/components/form.tsx +28 -0
- package/registry/components/input.tsx +23 -0
- package/registry/components/kbd.tsx +17 -0
- package/registry/components/label.tsx +19 -0
- package/registry/components/navigation-menu.tsx +74 -0
- package/registry/components/pagination.tsx +56 -0
- package/registry/components/popover.tsx +58 -0
- package/registry/components/progress.tsx +22 -0
- package/registry/components/radio-group.tsx +78 -0
- package/registry/components/select.tsx +21 -0
- package/registry/components/separator.tsx +23 -0
- package/registry/components/sheet.tsx +68 -0
- package/registry/components/skeleton.tsx +14 -0
- package/registry/components/slider.tsx +18 -0
- package/registry/components/spinner.tsx +17 -0
- package/registry/components/switch.tsx +23 -0
- package/registry/components/table.tsx +65 -0
- package/registry/components/tabs.tsx +86 -0
- package/registry/components/textarea.tsx +22 -0
- package/registry/components/toast.tsx +58 -0
- package/registry/components/toggle.tsx +46 -0
- package/registry/components/tooltip.tsx +70 -0
- package/registry/lib/utils.ts +6 -0
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@swift-rust/ui",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "shadcn-style component registry for swift-rust.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://swift-rust.dev",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/swift-rust/swift-rust.git",
|
|
10
|
+
"directory": "packages/ui"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"swift-rust": "./dist/cli.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"registry",
|
|
28
|
+
"templates"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.json",
|
|
32
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
33
|
+
"lint": "biome check --no-errors-on-unmatched $(pwd)/src",
|
|
34
|
+
"test": "bun test",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"clean": "rm -rf dist .turbo"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@clack/prompts": "^0.7.0",
|
|
40
|
+
"picocolors": "^1.0.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/bun": "^1.3.0",
|
|
44
|
+
"clsx": "^2.1.0",
|
|
45
|
+
"tailwind-merge": "^2.5.0",
|
|
46
|
+
"typescript": "^6.0.0"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/swift-rust/swift-rust/issues"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
export const AccordionContext = React.createContext<{
|
|
6
|
+
openItems: Set<string>;
|
|
7
|
+
toggle: (value: string) => void;
|
|
8
|
+
type: "single" | "multiple";
|
|
9
|
+
} | null>(null);
|
|
10
|
+
|
|
11
|
+
export function Accordion({
|
|
12
|
+
type = "single",
|
|
13
|
+
defaultValue,
|
|
14
|
+
className,
|
|
15
|
+
children,
|
|
16
|
+
}: {
|
|
17
|
+
type?: "single" | "multiple";
|
|
18
|
+
defaultValue?: string | string[];
|
|
19
|
+
className?: string;
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
}) {
|
|
22
|
+
const [openItems, setOpenItems] = React.useState<Set<string>>(
|
|
23
|
+
() => new Set(Array.isArray(defaultValue) ? defaultValue : defaultValue ? [defaultValue] : []),
|
|
24
|
+
);
|
|
25
|
+
const toggle = (value: string) => {
|
|
26
|
+
setOpenItems((prev) => {
|
|
27
|
+
const next = new Set(prev);
|
|
28
|
+
if (next.has(value)) next.delete(value);
|
|
29
|
+
else if (type === "single") {
|
|
30
|
+
next.clear();
|
|
31
|
+
next.add(value);
|
|
32
|
+
} else next.add(value);
|
|
33
|
+
return next;
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
return (
|
|
37
|
+
<AccordionContext.Provider value={{ openItems, toggle, type }}>
|
|
38
|
+
<div className={className}>{children}</div>
|
|
39
|
+
</AccordionContext.Provider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function AccordionItem({
|
|
44
|
+
value,
|
|
45
|
+
className,
|
|
46
|
+
children,
|
|
47
|
+
}: {
|
|
48
|
+
value: string;
|
|
49
|
+
className?: string;
|
|
50
|
+
children: React.ReactNode;
|
|
51
|
+
}) {
|
|
52
|
+
return (
|
|
53
|
+
<div data-value={value} className={cn("border-b border-[var(--ui-border)]", className)}>
|
|
54
|
+
{children}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function AccordionTrigger({
|
|
60
|
+
value,
|
|
61
|
+
className,
|
|
62
|
+
children,
|
|
63
|
+
}: {
|
|
64
|
+
value: string;
|
|
65
|
+
className?: string;
|
|
66
|
+
children: React.ReactNode;
|
|
67
|
+
}) {
|
|
68
|
+
const ctx = React.useContext(AccordionContext)!;
|
|
69
|
+
const open = ctx.openItems.has(value);
|
|
70
|
+
return (
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
onClick={() => ctx.toggle(value)}
|
|
74
|
+
className={cn(
|
|
75
|
+
"flex w-full items-center justify-between py-4 text-sm font-medium transition-all",
|
|
76
|
+
"hover:text-[var(--ui-accent)]",
|
|
77
|
+
className,
|
|
78
|
+
)}
|
|
79
|
+
>
|
|
80
|
+
{children}
|
|
81
|
+
<svg
|
|
82
|
+
viewBox="0 0 24 24"
|
|
83
|
+
className={cn("h-4 w-4 shrink-0 transition-transform", open && "rotate-180")}
|
|
84
|
+
fill="none"
|
|
85
|
+
stroke="currentColor"
|
|
86
|
+
strokeWidth="2"
|
|
87
|
+
>
|
|
88
|
+
<path d="M6 9l6 6 6-6" strokeLinecap="round" strokeLinejoin="round" />
|
|
89
|
+
</svg>
|
|
90
|
+
</button>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function AccordionContent({
|
|
95
|
+
value,
|
|
96
|
+
className,
|
|
97
|
+
children,
|
|
98
|
+
}: {
|
|
99
|
+
value: string;
|
|
100
|
+
className?: string;
|
|
101
|
+
children: React.ReactNode;
|
|
102
|
+
}) {
|
|
103
|
+
const ctx = React.useContext(AccordionContext)!;
|
|
104
|
+
if (!ctx.openItems.has(value)) return null;
|
|
105
|
+
return <div className={cn("pb-4 text-sm text-[var(--ui-fg-muted)]", className)}>{children}</div>;
|
|
106
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
type Tone = "default" | "success" | "warning" | "destructive" | "info";
|
|
6
|
+
const TONES: Record<Tone, string> = {
|
|
7
|
+
default: "bg-[var(--ui-surface-2)] text-[var(--ui-fg)]",
|
|
8
|
+
success: "bg-[#dcfce7] text-[#166534]",
|
|
9
|
+
warning: "bg-[#fef3c7] text-[#92400e]",
|
|
10
|
+
destructive: "bg-[#fee2e2] text-[#991b1b]",
|
|
11
|
+
info: "bg-[#dbeafe] text-[#1e40af]",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
15
|
+
tone?: Tone;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Alert({ className, tone = "default", ...props }: AlertProps) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
role="alert"
|
|
22
|
+
className={cn("relative w-full rounded-lg border border-[var(--ui-border)] p-4 text-sm", TONES[tone], className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function AlertTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
|
29
|
+
return <h5 className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function AlertDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
33
|
+
return <div className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />;
|
|
34
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const Avatar = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
6
|
+
({ className, ...props }, ref) => (
|
|
7
|
+
<div
|
|
8
|
+
ref={ref}
|
|
9
|
+
className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
),
|
|
13
|
+
);
|
|
14
|
+
Avatar.displayName = "Avatar";
|
|
15
|
+
|
|
16
|
+
const AvatarImage = React.forwardRef<HTMLImageElement, React.ImgHTMLAttributes<HTMLImageElement>>(
|
|
17
|
+
({ className, ...props }, ref) => (
|
|
18
|
+
<img ref={ref} className={cn("aspect-square h-full w-full object-cover", className)} {...props} />
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
AvatarImage.displayName = "AvatarImage";
|
|
22
|
+
|
|
23
|
+
const AvatarFallback = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
24
|
+
({ className, ...props }, ref) => (
|
|
25
|
+
<div
|
|
26
|
+
ref={ref}
|
|
27
|
+
className={cn(
|
|
28
|
+
"flex h-full w-full items-center justify-center rounded-full bg-[var(--ui-surface-2)] text-sm font-medium",
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
AvatarFallback.displayName = "AvatarFallback";
|
|
36
|
+
|
|
37
|
+
export { Avatar, AvatarImage, AvatarFallback };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
type Variant = "default" | "secondary" | "destructive" | "outline" | "success" | "warning";
|
|
6
|
+
const VARIANTS: Record<Variant, string> = {
|
|
7
|
+
default: "border-transparent bg-[var(--ui-fg)] text-[var(--ui-bg)]",
|
|
8
|
+
secondary: "border-transparent bg-[var(--ui-surface-2)] text-[var(--ui-fg)]",
|
|
9
|
+
destructive: "border-transparent bg-[var(--ui-danger)] text-white",
|
|
10
|
+
outline: "border-[var(--ui-border-strong)] text-[var(--ui-fg)]",
|
|
11
|
+
success: "border-transparent bg-[#dcfce7] text-[#166534]",
|
|
12
|
+
warning: "border-transparent bg-[#fef3c7] text-[#92400e]",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
16
|
+
variant?: Variant;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Badge({ className, variant = "default", ...props }: BadgeProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className={cn(
|
|
23
|
+
"inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-xs font-medium",
|
|
24
|
+
VARIANTS[variant],
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const BreadcrumbContext = React.createContext<{ separator: React.ReactNode } | null>(null);
|
|
6
|
+
|
|
7
|
+
export function Breadcrumb({
|
|
8
|
+
separator,
|
|
9
|
+
children,
|
|
10
|
+
className,
|
|
11
|
+
}: {
|
|
12
|
+
separator?: React.ReactNode;
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
className?: string;
|
|
15
|
+
}) {
|
|
16
|
+
const sep = separator ?? <span className="text-[var(--ui-fg-subtle)]">/</span>;
|
|
17
|
+
return (
|
|
18
|
+
<BreadcrumbContext.Provider value={{ separator: sep }}>
|
|
19
|
+
<nav aria-label="Breadcrumb" className={className}>
|
|
20
|
+
<ol className="flex flex-wrap items-center gap-1.5 text-sm text-[var(--ui-fg-muted)]">
|
|
21
|
+
{children}
|
|
22
|
+
</ol>
|
|
23
|
+
</nav>
|
|
24
|
+
</BreadcrumbContext.Provider>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function BreadcrumbItem({ children, className }: { children: React.ReactNode; className?: string }) {
|
|
29
|
+
const ctx = React.useContext(BreadcrumbContext)!;
|
|
30
|
+
return (
|
|
31
|
+
<li className={cn("inline-flex items-center gap-1.5", className)}>
|
|
32
|
+
{children}
|
|
33
|
+
</li>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function BreadcrumbLink({ href, children }: { href: string; children: React.ReactNode }) {
|
|
38
|
+
return (
|
|
39
|
+
<a href={href} className="hover:text-[var(--ui-fg)]">
|
|
40
|
+
{children}
|
|
41
|
+
</a>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function BreadcrumbSeparator() {
|
|
46
|
+
const ctx = React.useContext(BreadcrumbContext)!;
|
|
47
|
+
return <li aria-hidden>{ctx.separator}</li>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function BreadcrumbPage({ children }: { children: React.ReactNode }) {
|
|
51
|
+
return (
|
|
52
|
+
<li aria-current="page" className="font-medium text-[var(--ui-fg)]">
|
|
53
|
+
{children}
|
|
54
|
+
</li>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
type ButtonVariant = "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
|
|
6
|
+
type ButtonSize = "default" | "sm" | "lg" | "icon";
|
|
7
|
+
|
|
8
|
+
const VARIANTS: Record<ButtonVariant, string> = {
|
|
9
|
+
default: "bg-[var(--ui-fg)] text-[var(--ui-bg)] hover:opacity-90",
|
|
10
|
+
destructive: "bg-[var(--ui-danger)] text-white hover:opacity-90",
|
|
11
|
+
outline: "border border-[var(--ui-border-strong)] bg-transparent hover:bg-[var(--ui-surface-2)]",
|
|
12
|
+
secondary: "bg-[var(--ui-surface-2)] text-[var(--ui-fg)] hover:opacity-90",
|
|
13
|
+
ghost: "hover:bg-[var(--ui-surface-2)]",
|
|
14
|
+
link: "text-[var(--ui-accent)] underline-offset-4 hover:underline",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const SIZES: Record<ButtonSize, string> = {
|
|
18
|
+
default: "h-9 px-4 text-sm",
|
|
19
|
+
sm: "h-8 px-3 text-xs",
|
|
20
|
+
lg: "h-10 px-6 text-sm",
|
|
21
|
+
icon: "h-9 w-9",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
25
|
+
variant?: ButtonVariant;
|
|
26
|
+
size?: ButtonSize;
|
|
27
|
+
asChild?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
31
|
+
({ className, variant = "default", size = "default", ...props }, ref) => (
|
|
32
|
+
<button
|
|
33
|
+
ref={ref}
|
|
34
|
+
className={cn(
|
|
35
|
+
"inline-flex items-center justify-center gap-2 rounded-md font-medium transition-colors",
|
|
36
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent)] focus-visible:ring-offset-2",
|
|
37
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
38
|
+
VARIANTS[variant],
|
|
39
|
+
SIZES[size],
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
Button.displayName = "Button";
|
|
47
|
+
|
|
48
|
+
export { VARIANTS as buttonVariants };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
type Tone = "default" | "success" | "warning" | "destructive";
|
|
6
|
+
const TONE: Record<Tone, string> = {
|
|
7
|
+
default: "border-[var(--ui-border)] bg-[var(--ui-surface)]",
|
|
8
|
+
success: "border-[#86efac] bg-[#f0fdf4]",
|
|
9
|
+
warning: "border-[#fde68a] bg-[#fffbeb]",
|
|
10
|
+
destructive: "border-[#fecaca] bg-[#fef2f2]",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function Callout({ className, tone = "default", ...props }: React.HTMLAttributes<HTMLDivElement> & { tone?: Tone }) {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
className={cn("my-4 rounded-lg border p-4 text-sm", TONE[tone], className)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
6
|
+
({ className, ...props }, ref) => (
|
|
7
|
+
<div
|
|
8
|
+
ref={ref}
|
|
9
|
+
className={cn("rounded-xl border border-[var(--ui-border)] bg-[var(--ui-surface)] text-[var(--ui-fg)] shadow-sm", className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
),
|
|
13
|
+
);
|
|
14
|
+
Card.displayName = "Card";
|
|
15
|
+
|
|
16
|
+
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
17
|
+
({ className, ...props }, ref) => (
|
|
18
|
+
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
CardHeader.displayName = "CardHeader";
|
|
22
|
+
|
|
23
|
+
const CardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
|
24
|
+
({ className, ...props }, ref) => (
|
|
25
|
+
<h3 ref={ref} className={cn("text-lg font-semibold leading-none tracking-tight", className)} {...props} />
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
CardTitle.displayName = "CardTitle";
|
|
29
|
+
|
|
30
|
+
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
31
|
+
({ className, ...props }, ref) => (
|
|
32
|
+
<p ref={ref} className={cn("text-sm text-[var(--ui-fg-muted)]", className)} {...props} />
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
CardDescription.displayName = "CardDescription";
|
|
36
|
+
|
|
37
|
+
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
38
|
+
({ className, ...props }, ref) => <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />,
|
|
39
|
+
);
|
|
40
|
+
CardContent.displayName = "CardContent";
|
|
41
|
+
|
|
42
|
+
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
43
|
+
({ className, ...props }, ref) => (
|
|
44
|
+
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
CardFooter.displayName = "CardFooter";
|
|
48
|
+
|
|
49
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
export const Checkbox = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
|
|
6
|
+
({ className, ...props }, ref) => (
|
|
7
|
+
<input
|
|
8
|
+
type="checkbox"
|
|
9
|
+
ref={ref}
|
|
10
|
+
className={cn(
|
|
11
|
+
"h-4 w-4 shrink-0 rounded border border-[var(--ui-border-strong)] bg-[var(--ui-surface)]",
|
|
12
|
+
"accent-[var(--ui-accent)]",
|
|
13
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent)] focus-visible:ring-offset-1",
|
|
14
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
Checkbox.displayName = "Checkbox";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
export function Code({ className, ...props }: React.HTMLAttributes<HTMLElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<code
|
|
8
|
+
className={cn(
|
|
9
|
+
"rounded border border-[var(--ui-border)] bg-[var(--ui-surface-2)] px-1.5 py-0.5 font-mono text-[0.85em]",
|
|
10
|
+
className,
|
|
11
|
+
)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Pre({ className, ...props }: React.HTMLAttributes<HTMLPreElement>) {
|
|
18
|
+
return (
|
|
19
|
+
<pre
|
|
20
|
+
className={cn(
|
|
21
|
+
"overflow-x-auto rounded-lg border border-[var(--ui-border)] bg-[var(--ui-fg)] p-4 font-mono text-[0.85em] text-[var(--ui-bg)]",
|
|
22
|
+
className,
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
export interface CommandItem {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
group?: string;
|
|
9
|
+
onSelect?: () => void;
|
|
10
|
+
shortcut?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Command({
|
|
14
|
+
items,
|
|
15
|
+
onSelect,
|
|
16
|
+
placeholder = "Type a command…",
|
|
17
|
+
className,
|
|
18
|
+
}: {
|
|
19
|
+
items: CommandItem[];
|
|
20
|
+
onSelect?: (item: CommandItem) => void;
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
className?: string;
|
|
23
|
+
}) {
|
|
24
|
+
const [query, setQuery] = React.useState("");
|
|
25
|
+
const [activeIndex, setActiveIndex] = React.useState(0);
|
|
26
|
+
|
|
27
|
+
const filtered = React.useMemo(() => {
|
|
28
|
+
const q = query.toLowerCase();
|
|
29
|
+
if (!q) return items;
|
|
30
|
+
return items.filter((i) => i.label.toLowerCase().includes(q));
|
|
31
|
+
}, [items, query]);
|
|
32
|
+
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
setActiveIndex(0);
|
|
35
|
+
}, [query]);
|
|
36
|
+
|
|
37
|
+
const onKey = (e: React.KeyboardEvent) => {
|
|
38
|
+
if (e.key === "ArrowDown") {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
setActiveIndex((i) => Math.min(filtered.length - 1, i + 1));
|
|
41
|
+
} else if (e.key === "ArrowUp") {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
setActiveIndex((i) => Math.max(0, i - 1));
|
|
44
|
+
} else if (e.key === "Enter") {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
const item = filtered[activeIndex];
|
|
47
|
+
if (item) {
|
|
48
|
+
item.onSelect?.();
|
|
49
|
+
onSelect?.(item);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className={cn("rounded-lg border border-[var(--ui-border)] bg-[var(--ui-surface)] shadow-md", className)}>
|
|
56
|
+
<input
|
|
57
|
+
autoFocus
|
|
58
|
+
value={query}
|
|
59
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
60
|
+
onKeyDown={onKey}
|
|
61
|
+
placeholder={placeholder}
|
|
62
|
+
className="w-full border-b border-[var(--ui-border)] bg-transparent px-4 py-3 text-sm outline-none placeholder:text-[var(--ui-fg-subtle)]"
|
|
63
|
+
/>
|
|
64
|
+
<ul className="max-h-72 overflow-y-auto p-1">
|
|
65
|
+
{filtered.length === 0 ? (
|
|
66
|
+
<li className="px-3 py-6 text-center text-sm text-[var(--ui-fg-subtle)]">No results</li>
|
|
67
|
+
) : (
|
|
68
|
+
filtered.map((item, i) => (
|
|
69
|
+
<li
|
|
70
|
+
key={item.id}
|
|
71
|
+
onMouseEnter={() => setActiveIndex(i)}
|
|
72
|
+
onClick={() => {
|
|
73
|
+
item.onSelect?.();
|
|
74
|
+
onSelect?.(item);
|
|
75
|
+
}}
|
|
76
|
+
className={cn(
|
|
77
|
+
"flex cursor-pointer items-center justify-between rounded-sm px-3 py-2 text-sm",
|
|
78
|
+
i === activeIndex && "bg-[var(--ui-accent-soft)] text-[var(--ui-accent)]",
|
|
79
|
+
)}
|
|
80
|
+
>
|
|
81
|
+
<span>{item.label}</span>
|
|
82
|
+
{item.shortcut ? (
|
|
83
|
+
<kbd className="rounded border border-[var(--ui-border)] px-1 font-mono text-[0.6875rem]">
|
|
84
|
+
{item.shortcut}
|
|
85
|
+
</kbd>
|
|
86
|
+
) : null}
|
|
87
|
+
</li>
|
|
88
|
+
))
|
|
89
|
+
)}
|
|
90
|
+
</ul>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|