@swift-rust/ui 0.2.0 → 0.6.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 +66 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +89 -41
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.d.ts +2 -0
- package/dist/cli.test.d.ts.map +1 -0
- package/dist/cli.test.js +36 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +61 -32
- package/dist/components.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/registry.test.d.ts +2 -0
- package/dist/registry.test.d.ts.map +1 -0
- package/dist/registry.test.js +82 -0
- package/dist/registry.test.js.map +1 -0
- package/dist/smoke.test.js +5 -3
- package/dist/smoke.test.js.map +1 -1
- package/package.json +7 -7
- package/registry/components/accordion.tsx +125 -16
- package/registry/components/alert-dialog.tsx +102 -0
- package/registry/components/alert.tsx +114 -14
- package/registry/components/aspect-ratio.tsx +18 -0
- package/registry/components/avatar.tsx +59 -7
- package/registry/components/badge.tsx +29 -14
- package/registry/components/breadcrumb.tsx +7 -13
- package/registry/components/button-group.tsx +28 -0
- package/registry/components/button.tsx +113 -28
- package/registry/components/calendar.tsx +92 -0
- package/registry/components/callout.tsx +14 -14
- package/registry/components/card.tsx +87 -12
- package/registry/components/carousel.tsx +41 -0
- package/registry/components/chart.tsx +50 -0
- package/registry/components/checkbox.tsx +5 -5
- package/registry/components/code-block.tsx +118 -0
- package/registry/components/code.tsx +2 -3
- package/registry/components/collapsible.tsx +60 -0
- package/registry/components/combobox.tsx +102 -0
- package/registry/components/command.tsx +5 -5
- package/registry/components/context-menu.tsx +81 -0
- package/registry/components/data-table.tsx +71 -0
- package/registry/components/date-picker.tsx +58 -0
- package/registry/components/dialog.tsx +2 -2
- package/registry/components/direction.tsx +17 -0
- package/registry/components/drawer.tsx +77 -0
- package/registry/components/dropdown-menu.tsx +5 -5
- package/registry/components/empty.tsx +34 -0
- package/registry/components/field.tsx +27 -0
- package/registry/components/file-upload.tsx +116 -0
- package/registry/components/form.tsx +3 -4
- package/registry/components/hover-card.tsx +59 -0
- package/registry/components/input-group.tsx +34 -0
- package/registry/components/input-otp.tsx +50 -0
- package/registry/components/input.tsx +71 -7
- package/registry/components/item.tsx +42 -0
- package/registry/components/kbd.tsx +3 -4
- package/registry/components/label.tsx +34 -4
- package/registry/components/menubar.tsx +60 -0
- package/registry/components/native-select.tsx +35 -0
- package/registry/components/navigation-menu.tsx +3 -3
- package/registry/components/pagination.tsx +4 -5
- package/registry/components/popover.tsx +1 -1
- package/registry/components/progress.tsx +10 -5
- package/registry/components/radio-group.tsx +9 -9
- package/registry/components/resizable.tsx +77 -0
- package/registry/components/scroll-area.tsx +20 -0
- package/registry/components/select.tsx +2 -3
- package/registry/components/separator.tsx +1 -2
- package/registry/components/sheet.tsx +1 -1
- package/registry/components/sidebar.tsx +72 -0
- package/registry/components/skeleton.tsx +1 -6
- package/registry/components/slider.tsx +6 -3
- package/registry/components/sonner.tsx +52 -0
- package/registry/components/spinner.tsx +19 -6
- package/registry/components/stepper.tsx +63 -0
- package/registry/components/switch.tsx +7 -6
- package/registry/components/table.tsx +2 -3
- package/registry/components/tabs.tsx +3 -3
- package/registry/components/textarea.tsx +42 -6
- package/registry/components/toast.tsx +2 -2
- package/registry/components/toggle-group.tsx +72 -0
- package/registry/components/toggle.tsx +45 -20
- package/registry/components/tooltip.tsx +4 -2
|
@@ -1,15 +1,45 @@
|
|
|
1
|
-
"use client";
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import { cn } from "@/lib/utils";
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* swift-rust ui · Label
|
|
6
|
+
*
|
|
7
|
+
* variant — default, secondary (muted), destructive (error)
|
|
8
|
+
* size — xs, sm, default, md, lg
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type LabelVariant = "default" | "secondary" | "destructive";
|
|
12
|
+
export type LabelSize = "default" | "xs" | "sm" | "md" | "lg";
|
|
13
|
+
|
|
14
|
+
const VARIANTS: Record<LabelVariant, string> = {
|
|
15
|
+
default: "text-foreground",
|
|
16
|
+
secondary: "text-muted-foreground",
|
|
17
|
+
destructive: "text-destructive",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const SIZES: Record<LabelSize, string> = {
|
|
21
|
+
default: "text-sm",
|
|
22
|
+
xs: "text-xs",
|
|
23
|
+
sm: "text-xs",
|
|
24
|
+
md: "text-sm",
|
|
25
|
+
lg: "text-base",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
|
|
29
|
+
variant?: LabelVariant;
|
|
30
|
+
size?: LabelSize;
|
|
31
|
+
}
|
|
6
32
|
|
|
7
33
|
export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|
8
|
-
({ className, ...props }, ref) => (
|
|
34
|
+
({ className, variant = "default", size = "default", ...props }, ref) => (
|
|
9
35
|
<label
|
|
10
36
|
ref={ref}
|
|
11
37
|
className={cn(
|
|
12
|
-
"
|
|
38
|
+
"flex select-none items-center gap-2 font-medium leading-none",
|
|
39
|
+
"peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
40
|
+
"group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",
|
|
41
|
+
VARIANTS[variant],
|
|
42
|
+
SIZES[size],
|
|
13
43
|
className,
|
|
14
44
|
)}
|
|
15
45
|
{...props}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const MenubarContext = React.createContext<{ openId: string | null; setOpenId: (id: string | null) => void } | null>(null);
|
|
6
|
+
|
|
7
|
+
export function Menubar({ className, children }: { className?: string; children: React.ReactNode }) {
|
|
8
|
+
const [openId, setOpenId] = React.useState<string | null>(null);
|
|
9
|
+
const ref = React.useRef<HTMLDivElement>(null);
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
const onClick = (e: MouseEvent) => {
|
|
12
|
+
if (ref.current && !ref.current.contains(e.target as Node)) setOpenId(null);
|
|
13
|
+
};
|
|
14
|
+
document.addEventListener("mousedown", onClick);
|
|
15
|
+
return () => document.removeEventListener("mousedown", onClick);
|
|
16
|
+
}, []);
|
|
17
|
+
return (
|
|
18
|
+
<MenubarContext.Provider value={{ openId, setOpenId }}>
|
|
19
|
+
<div ref={ref} className={cn("flex items-center gap-0.5 rounded-lg border border-border bg-background p-1", className)}>
|
|
20
|
+
{children}
|
|
21
|
+
</div>
|
|
22
|
+
</MenubarContext.Provider>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function MenubarMenu({ value, label, children }: { value: string; label: string; children: React.ReactNode }) {
|
|
27
|
+
const ctx = React.useContext(MenubarContext);
|
|
28
|
+
const open = ctx?.openId === value;
|
|
29
|
+
return (
|
|
30
|
+
<div className="relative">
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
onClick={() => ctx?.setOpenId(open ? null : value)}
|
|
34
|
+
onMouseEnter={() => ctx?.openId && ctx.setOpenId(value)}
|
|
35
|
+
className={cn("rounded-md px-3 py-1.5 text-sm font-medium transition-colors hover:bg-muted", open && "bg-muted")}
|
|
36
|
+
>
|
|
37
|
+
{label}
|
|
38
|
+
</button>
|
|
39
|
+
{open && (
|
|
40
|
+
<div role="menu" className="absolute left-0 z-50 mt-1 min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md">
|
|
41
|
+
{children}
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function MenubarItem({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
49
|
+
return (
|
|
50
|
+
<div role="menuitem" className={cn("flex cursor-pointer select-none items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-muted", className)} {...props} />
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function MenubarSeparator() {
|
|
55
|
+
return <div className="my-1 h-px bg-border" />;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function MenubarShortcut({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) {
|
|
59
|
+
return <span className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />;
|
|
60
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
// A native <select> with a chevron and the design-system token styling.
|
|
5
|
+
export type NativeSelectProps = React.SelectHTMLAttributes<HTMLSelectElement>;
|
|
6
|
+
|
|
7
|
+
export const NativeSelect = React.forwardRef<HTMLSelectElement, NativeSelectProps>(
|
|
8
|
+
({ className, children, ...props }, ref) => (
|
|
9
|
+
<div className="relative inline-flex w-full">
|
|
10
|
+
<select
|
|
11
|
+
ref={ref}
|
|
12
|
+
className={cn(
|
|
13
|
+
"h-9 w-full appearance-none rounded-lg border border-input bg-background px-3 pr-8 text-sm shadow-xs",
|
|
14
|
+
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring/50",
|
|
15
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
16
|
+
className,
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
>
|
|
20
|
+
{children}
|
|
21
|
+
</select>
|
|
22
|
+
<svg
|
|
23
|
+
viewBox="0 0 24 24"
|
|
24
|
+
className="pointer-events-none absolute right-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground"
|
|
25
|
+
fill="none"
|
|
26
|
+
stroke="currentColor"
|
|
27
|
+
strokeWidth="2"
|
|
28
|
+
aria-hidden
|
|
29
|
+
>
|
|
30
|
+
<path d="m6 9 6 6 6-6" strokeLinecap="round" strokeLinejoin="round" />
|
|
31
|
+
</svg>
|
|
32
|
+
</div>
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
NativeSelect.displayName = "NativeSelect";
|
|
@@ -34,7 +34,7 @@ export function NavigationMenuTrigger({ children }: { children: React.ReactNode
|
|
|
34
34
|
return (
|
|
35
35
|
<button
|
|
36
36
|
type="button"
|
|
37
|
-
className="inline-flex h-9 items-center gap-1 rounded-md px-3 py-1 text-sm hover:bg-
|
|
37
|
+
className="inline-flex h-9 items-center gap-1 rounded-md px-3 py-1 text-sm hover:bg-muted"
|
|
38
38
|
>
|
|
39
39
|
{children}
|
|
40
40
|
<svg viewBox="0 0 24 24" className="h-3 w-3" fill="none" stroke="currentColor" strokeWidth="2">
|
|
@@ -50,7 +50,7 @@ export function NavigationMenuContent({ value, className, children }: { value: s
|
|
|
50
50
|
return (
|
|
51
51
|
<div
|
|
52
52
|
className={cn(
|
|
53
|
-
"absolute left-0 top-full mt-1 min-w-[200px] rounded-md border border-
|
|
53
|
+
"absolute left-0 top-full mt-1 min-w-[200px] rounded-md border border-border bg-popover p-2 shadow-md",
|
|
54
54
|
className,
|
|
55
55
|
)}
|
|
56
56
|
>
|
|
@@ -64,7 +64,7 @@ export function NavigationMenuLink({ href, children, className }: { href: string
|
|
|
64
64
|
<a
|
|
65
65
|
href={href}
|
|
66
66
|
className={cn(
|
|
67
|
-
"block rounded-sm px-2 py-1.5 text-sm text-
|
|
67
|
+
"block rounded-sm px-2 py-1.5 text-sm text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
68
68
|
className,
|
|
69
69
|
)}
|
|
70
70
|
>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use client";
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import { cn } from "@/lib/utils";
|
|
4
3
|
|
|
@@ -22,13 +21,13 @@ export function Pagination({ page, total, onChange, className }: PaginationProps
|
|
|
22
21
|
type="button"
|
|
23
22
|
onClick={() => onChange(Math.max(1, page - 1))}
|
|
24
23
|
disabled={page === 1}
|
|
25
|
-
className="inline-flex h-8 items-center gap-1 rounded-md px-2 text-sm hover:bg-
|
|
24
|
+
className="inline-flex h-8 items-center gap-1 rounded-md px-2 text-sm hover:bg-muted disabled:opacity-50"
|
|
26
25
|
>
|
|
27
26
|
←
|
|
28
27
|
</button>
|
|
29
28
|
{pages.map((p, i) =>
|
|
30
29
|
p === "ellipsis" ? (
|
|
31
|
-
<span key={`e${i}`} className="px-2 text-
|
|
30
|
+
<span key={`e${i}`} className="px-2 text-muted-foreground">…</span>
|
|
32
31
|
) : (
|
|
33
32
|
<button
|
|
34
33
|
key={p}
|
|
@@ -36,7 +35,7 @@ export function Pagination({ page, total, onChange, className }: PaginationProps
|
|
|
36
35
|
onClick={() => onChange(p)}
|
|
37
36
|
className={cn(
|
|
38
37
|
"h-8 w-8 rounded-md text-sm transition-colors",
|
|
39
|
-
p === page ? "bg-
|
|
38
|
+
p === page ? "bg-primary text-primary-foreground" : "hover:bg-muted",
|
|
40
39
|
)}
|
|
41
40
|
>
|
|
42
41
|
{p}
|
|
@@ -47,7 +46,7 @@ export function Pagination({ page, total, onChange, className }: PaginationProps
|
|
|
47
46
|
type="button"
|
|
48
47
|
onClick={() => onChange(Math.min(total, page + 1))}
|
|
49
48
|
disabled={page === total}
|
|
50
|
-
className="inline-flex h-8 items-center gap-1 rounded-md px-2 text-sm hover:bg-
|
|
49
|
+
className="inline-flex h-8 items-center gap-1 rounded-md px-2 text-sm hover:bg-muted disabled:opacity-50"
|
|
51
50
|
>
|
|
52
51
|
→
|
|
53
52
|
</button>
|
|
@@ -46,7 +46,7 @@ export function PopoverContent({
|
|
|
46
46
|
return (
|
|
47
47
|
<div
|
|
48
48
|
className={cn(
|
|
49
|
-
"absolute z-50 mt-1 w-72 rounded-md border border-
|
|
49
|
+
"absolute z-50 mt-1 w-72 rounded-md border border-border bg-popover p-4 text-sm shadow-md",
|
|
50
50
|
align === "end" && "right-0",
|
|
51
51
|
align === "center" && "left-1/2 -translate-x-1/2",
|
|
52
52
|
className,
|
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
"use client";
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import { cn } from "@/lib/utils";
|
|
4
3
|
|
|
5
|
-
export
|
|
4
|
+
export interface ProgressProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
5
|
+
value: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
6
10
|
({ className, value, max = 100, ...props }, ref) => (
|
|
7
11
|
<div
|
|
8
12
|
ref={ref}
|
|
9
13
|
role="progressbar"
|
|
10
14
|
aria-valuenow={value}
|
|
15
|
+
aria-valuemin={0}
|
|
11
16
|
aria-valuemax={max}
|
|
12
|
-
className={cn("relative h-2 w-full overflow-hidden rounded-full bg-
|
|
17
|
+
className={cn("relative h-2 w-full overflow-hidden rounded-full bg-secondary", className)}
|
|
13
18
|
{...props}
|
|
14
19
|
>
|
|
15
20
|
<div
|
|
16
|
-
className="h-full bg-
|
|
17
|
-
style={{ width: `${Math.min(100, (value / max) * 100)}%` }}
|
|
21
|
+
className="h-full rounded-full bg-primary transition-all"
|
|
22
|
+
style={{ width: `${Math.min(100, Math.max(0, (value / max) * 100))}%` }}
|
|
18
23
|
/>
|
|
19
24
|
</div>
|
|
20
25
|
),
|
|
@@ -31,7 +31,7 @@ export function RadioGroup({
|
|
|
31
31
|
};
|
|
32
32
|
return (
|
|
33
33
|
<RadioGroupContext.Provider value={{ value, setValue, name }}>
|
|
34
|
-
<div role="radiogroup" className={cn("
|
|
34
|
+
<div role="radiogroup" className={cn("grid gap-1", className)}>
|
|
35
35
|
{children}
|
|
36
36
|
</div>
|
|
37
37
|
</RadioGroupContext.Provider>
|
|
@@ -47,29 +47,29 @@ export function RadioGroupItem({
|
|
|
47
47
|
label?: React.ReactNode;
|
|
48
48
|
className?: string;
|
|
49
49
|
}) {
|
|
50
|
-
const ctx = React.useContext(RadioGroupContext)
|
|
51
|
-
const active = ctx
|
|
50
|
+
const ctx = React.useContext(RadioGroupContext);
|
|
51
|
+
const active = ctx?.value === value;
|
|
52
52
|
return (
|
|
53
53
|
<label
|
|
54
54
|
className={cn(
|
|
55
|
-
"flex cursor-pointer items-center gap-2 rounded-md p-2 text-sm transition-colors hover:bg-
|
|
55
|
+
"flex cursor-pointer items-center gap-2 rounded-md p-2 text-sm transition-colors hover:bg-muted",
|
|
56
56
|
className,
|
|
57
57
|
)}
|
|
58
58
|
>
|
|
59
59
|
<span
|
|
60
60
|
className={cn(
|
|
61
|
-
"flex
|
|
62
|
-
active ? "border-
|
|
61
|
+
"flex size-4 items-center justify-center rounded-full border transition-colors",
|
|
62
|
+
active ? "border-primary" : "border-input",
|
|
63
63
|
)}
|
|
64
64
|
>
|
|
65
|
-
{active ? <span className="
|
|
65
|
+
{active ? <span className="size-2 rounded-full bg-primary" /> : null}
|
|
66
66
|
</span>
|
|
67
67
|
<input
|
|
68
68
|
type="radio"
|
|
69
|
-
name={ctx
|
|
69
|
+
name={ctx?.name}
|
|
70
70
|
value={value}
|
|
71
71
|
checked={active}
|
|
72
|
-
onChange={() => ctx
|
|
72
|
+
onChange={() => ctx?.setValue(value)}
|
|
73
73
|
className="sr-only"
|
|
74
74
|
/>
|
|
75
75
|
{label}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const ResizableContext = React.createContext<{ pct: number; setPct: (n: number) => void } | null>(null);
|
|
6
|
+
|
|
7
|
+
export function ResizablePanelGroup({
|
|
8
|
+
defaultSize = 50,
|
|
9
|
+
className,
|
|
10
|
+
children,
|
|
11
|
+
}: {
|
|
12
|
+
defaultSize?: number;
|
|
13
|
+
className?: string;
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
}) {
|
|
16
|
+
const [pct, setPct] = React.useState(defaultSize);
|
|
17
|
+
const ref = React.useRef<HTMLDivElement>(null);
|
|
18
|
+
|
|
19
|
+
const onPointerDown = (e: React.PointerEvent) => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
const el = ref.current;
|
|
22
|
+
if (!el) return;
|
|
23
|
+
const move = (ev: PointerEvent) => {
|
|
24
|
+
const rect = el.getBoundingClientRect();
|
|
25
|
+
const next = ((ev.clientX - rect.left) / rect.width) * 100;
|
|
26
|
+
setPct(Math.min(85, Math.max(15, next)));
|
|
27
|
+
};
|
|
28
|
+
const up = () => {
|
|
29
|
+
document.removeEventListener("pointermove", move);
|
|
30
|
+
document.removeEventListener("pointerup", up);
|
|
31
|
+
};
|
|
32
|
+
document.addEventListener("pointermove", move);
|
|
33
|
+
document.addEventListener("pointerup", up);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<ResizableContext.Provider value={{ pct, setPct }}>
|
|
38
|
+
<div ref={ref} className={cn("flex w-full overflow-hidden rounded-lg border border-border", className)}>
|
|
39
|
+
{React.Children.map(children, (child) => {
|
|
40
|
+
if (React.isValidElement(child) && (child.type as { __srHandle?: boolean }).__srHandle) {
|
|
41
|
+
return React.cloneElement(child as React.ReactElement<{ onPointerDown?: (e: React.PointerEvent) => void }>, { onPointerDown });
|
|
42
|
+
}
|
|
43
|
+
return child;
|
|
44
|
+
})}
|
|
45
|
+
</div>
|
|
46
|
+
</ResizableContext.Provider>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function ResizablePanel({
|
|
51
|
+
index = 0,
|
|
52
|
+
className,
|
|
53
|
+
children,
|
|
54
|
+
}: {
|
|
55
|
+
index?: number;
|
|
56
|
+
className?: string;
|
|
57
|
+
children: React.ReactNode;
|
|
58
|
+
}) {
|
|
59
|
+
const ctx = React.useContext(ResizableContext);
|
|
60
|
+
const pct = ctx?.pct ?? 50;
|
|
61
|
+
return (
|
|
62
|
+
<div className={cn("min-w-0 overflow-auto p-4", className)} style={{ flexBasis: `${index === 0 ? pct : 100 - pct}%` }}>
|
|
63
|
+
{children}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function ResizableHandle({ onPointerDown }: { onPointerDown?: (e: React.PointerEvent) => void }) {
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
role="separator"
|
|
72
|
+
onPointerDown={onPointerDown}
|
|
73
|
+
className="relative w-px shrink-0 cursor-col-resize bg-border after:absolute after:inset-y-0 after:-left-1 after:w-3 hover:bg-primary"
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
ResizableHandle.__srHandle = true;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export const ScrollArea = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
5
|
+
({ className, ...props }, ref) => (
|
|
6
|
+
<div
|
|
7
|
+
ref={ref}
|
|
8
|
+
className={cn(
|
|
9
|
+
"relative overflow-auto",
|
|
10
|
+
"[scrollbar-width:thin] [scrollbar-color:var(--color-border-strong)_transparent]",
|
|
11
|
+
"[&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar]:h-2",
|
|
12
|
+
"[&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border-strong",
|
|
13
|
+
"[&::-webkit-scrollbar-track]:bg-transparent",
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
ScrollArea.displayName = "ScrollArea";
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use client";
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import { cn } from "@/lib/utils";
|
|
4
3
|
|
|
@@ -7,8 +6,8 @@ export const Select = React.forwardRef<HTMLSelectElement, React.SelectHTMLAttrib
|
|
|
7
6
|
<select
|
|
8
7
|
ref={ref}
|
|
9
8
|
className={cn(
|
|
10
|
-
"flex h-9 w-full rounded-md border border-
|
|
11
|
-
"focus-visible:outline-
|
|
9
|
+
"flex h-9 w-full rounded-md border border-border bg-popover px-3 py-1 text-sm shadow-sm",
|
|
10
|
+
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
|
|
12
11
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
13
12
|
className,
|
|
14
13
|
)}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use client";
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import { cn } from "@/lib/utils";
|
|
4
3
|
|
|
@@ -12,7 +11,7 @@ export const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(
|
|
|
12
11
|
ref={ref}
|
|
13
12
|
role="separator"
|
|
14
13
|
className={cn(
|
|
15
|
-
"shrink-0 bg-
|
|
14
|
+
"shrink-0 bg-border",
|
|
16
15
|
orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
|
|
17
16
|
className,
|
|
18
17
|
)}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const SidebarContext = React.createContext<{ open: boolean; toggle: () => void } | null>(null);
|
|
6
|
+
|
|
7
|
+
export function SidebarProvider({ defaultOpen = true, children }: { defaultOpen?: boolean; children: React.ReactNode }) {
|
|
8
|
+
const [open, setOpen] = React.useState(defaultOpen);
|
|
9
|
+
return (
|
|
10
|
+
<SidebarContext.Provider value={{ open, toggle: () => setOpen((o) => !o) }}>
|
|
11
|
+
<div className="flex w-full">{children}</div>
|
|
12
|
+
</SidebarContext.Provider>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function Sidebar({ className, children }: { className?: string; children: React.ReactNode }) {
|
|
17
|
+
const ctx = React.useContext(SidebarContext);
|
|
18
|
+
return (
|
|
19
|
+
<aside
|
|
20
|
+
data-state={ctx?.open ? "open" : "collapsed"}
|
|
21
|
+
className={cn(
|
|
22
|
+
"flex shrink-0 flex-col gap-2 overflow-hidden border-r border-border bg-card p-2 text-card-foreground transition-[width] duration-200",
|
|
23
|
+
ctx?.open ? "w-60" : "w-0 border-r-0 p-0",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
</aside>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function SidebarHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
33
|
+
return <div className={cn("flex items-center gap-2 px-2 py-1.5", className)} {...props} />;
|
|
34
|
+
}
|
|
35
|
+
export function SidebarContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
36
|
+
return <div className={cn("flex flex-1 flex-col gap-1 overflow-y-auto", className)} {...props} />;
|
|
37
|
+
}
|
|
38
|
+
export function SidebarGroupLabel({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
39
|
+
return <div className={cn("px-2 pt-3 text-[0.7rem] font-semibold uppercase tracking-widest text-muted-foreground", className)} {...props} />;
|
|
40
|
+
}
|
|
41
|
+
export function SidebarItem({
|
|
42
|
+
active,
|
|
43
|
+
className,
|
|
44
|
+
...props
|
|
45
|
+
}: React.HTMLAttributes<HTMLButtonElement> & { active?: boolean }) {
|
|
46
|
+
return (
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
data-active={active}
|
|
50
|
+
className={cn(
|
|
51
|
+
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors [&_svg]:size-4",
|
|
52
|
+
active ? "bg-muted font-medium text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
53
|
+
className,
|
|
54
|
+
)}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
export function SidebarTrigger({ className, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
|
|
60
|
+
const ctx = React.useContext(SidebarContext);
|
|
61
|
+
return (
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
aria-label="Toggle sidebar"
|
|
65
|
+
onClick={() => ctx?.toggle()}
|
|
66
|
+
className={cn("inline-flex size-9 items-center justify-center rounded-lg border border-border hover:bg-muted", className)}
|
|
67
|
+
{...props}
|
|
68
|
+
>
|
|
69
|
+
<svg viewBox="0 0 24 24" className="size-4" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden><rect x="3" y="3" width="18" height="18" rx="2" /><path d="M9 3v18" /></svg>
|
|
70
|
+
</button>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
"use client";
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import { cn } from "@/lib/utils";
|
|
4
3
|
|
|
5
4
|
export const Skeleton = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
6
5
|
({ className, ...props }, ref) => (
|
|
7
|
-
<div
|
|
8
|
-
ref={ref}
|
|
9
|
-
className={cn("animate-pulse rounded-md bg-[var(--ui-surface-2)]", className)}
|
|
10
|
-
{...props}
|
|
11
|
-
/>
|
|
6
|
+
<div ref={ref} className={cn("animate-pulse rounded-md bg-muted", className)} {...props} />
|
|
12
7
|
),
|
|
13
8
|
);
|
|
14
9
|
Skeleton.displayName = "Skeleton";
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
"use client";
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import { cn } from "@/lib/utils";
|
|
4
3
|
|
|
5
|
-
export
|
|
4
|
+
export type SliderProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "type">;
|
|
5
|
+
|
|
6
|
+
export const Slider = React.forwardRef<HTMLInputElement, SliderProps>(
|
|
6
7
|
({ className, ...props }, ref) => (
|
|
7
8
|
<input
|
|
8
9
|
type="range"
|
|
9
10
|
ref={ref}
|
|
10
11
|
className={cn(
|
|
11
|
-
"h-1.5 w-full cursor-pointer appearance-none rounded-full bg-
|
|
12
|
+
"h-1.5 w-full cursor-pointer appearance-none rounded-full bg-secondary accent-primary",
|
|
13
|
+
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring/50",
|
|
14
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
12
15
|
className,
|
|
13
16
|
)}
|
|
14
17
|
{...props}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
export interface SonnerToast {
|
|
6
|
+
id: number;
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
tone?: "default" | "success" | "error";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const listeners = new Set<(t: SonnerToast) => void>();
|
|
13
|
+
let counter = 0;
|
|
14
|
+
|
|
15
|
+
export function toast(title: string, opts?: { description?: string; tone?: SonnerToast["tone"] }) {
|
|
16
|
+
const t: SonnerToast = { id: ++counter, title, description: opts?.description, tone: opts?.tone ?? "default" };
|
|
17
|
+
for (const l of listeners) l(t);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const TONES: Record<NonNullable<SonnerToast["tone"]>, string> = {
|
|
21
|
+
default: "border-border bg-popover text-popover-foreground",
|
|
22
|
+
success: "border-emerald-600/40 bg-popover text-popover-foreground",
|
|
23
|
+
error: "border-destructive/40 bg-popover text-popover-foreground",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function Toaster({ duration = 3500 }: { duration?: number }) {
|
|
27
|
+
const [toasts, setToasts] = React.useState<SonnerToast[]>([]);
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
const add = (t: SonnerToast) => {
|
|
30
|
+
setToasts((prev) => [...prev, t]);
|
|
31
|
+
setTimeout(() => setToasts((prev) => prev.filter((x) => x.id !== t.id)), duration);
|
|
32
|
+
};
|
|
33
|
+
listeners.add(add);
|
|
34
|
+
return () => {
|
|
35
|
+
listeners.delete(add);
|
|
36
|
+
};
|
|
37
|
+
}, [duration]);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="pointer-events-none fixed bottom-4 right-4 z-[100] flex w-80 flex-col gap-2">
|
|
41
|
+
{toasts.map((t) => (
|
|
42
|
+
<div
|
|
43
|
+
key={t.id}
|
|
44
|
+
className={cn("pointer-events-auto rounded-lg border p-4 shadow-lg", TONES[t.tone ?? "default"])}
|
|
45
|
+
>
|
|
46
|
+
<p className="text-sm font-medium">{t.title}</p>
|
|
47
|
+
{t.description ? <p className="mt-1 text-sm text-muted-foreground">{t.description}</p> : null}
|
|
48
|
+
</div>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
"use client";
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import { cn } from "@/lib/utils";
|
|
4
3
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
export type SpinnerSize = "sm" | "default" | "lg";
|
|
5
|
+
const SIZES: Record<SpinnerSize, string> = {
|
|
6
|
+
sm: "size-4 border-2",
|
|
7
|
+
default: "size-5 border-2",
|
|
8
|
+
lg: "size-8 border-[3px]",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export interface SpinnerProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
12
|
+
size?: SpinnerSize;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const Spinner = React.forwardRef<HTMLSpanElement, SpinnerProps>(
|
|
16
|
+
({ className, size = "default", ...props }, ref) => (
|
|
17
|
+
<span
|
|
8
18
|
ref={ref}
|
|
9
19
|
role="status"
|
|
10
20
|
aria-label="Loading"
|
|
11
|
-
className={cn(
|
|
12
|
-
|
|
21
|
+
className={cn(
|
|
22
|
+
"inline-block animate-spin rounded-full border-current border-t-transparent text-muted-foreground",
|
|
23
|
+
SIZES[size],
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
13
26
|
{...props}
|
|
14
27
|
/>
|
|
15
28
|
),
|