@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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { describe, expect, test } from "bun:test";
|
|
4
|
+
const REGISTRY = join(import.meta.dir, "..", "registry", "components");
|
|
5
|
+
// The style dimension ("design" prop — React reserves `style`) shipped first on
|
|
6
|
+
// these components; each must offer the full design set.
|
|
7
|
+
const DESIGN_COMPONENTS = ["accordion", "alert", "avatar", "button", "card", "input"];
|
|
8
|
+
const DESIGNS = ["flat", "soft", "3d", "glass", "neo", "brutal", "gradient"];
|
|
9
|
+
// Purely presentational components must NOT carry 'use client'. Under swift-rust's
|
|
10
|
+
// SSR + islands model, a 'use client' component used directly in a server page is
|
|
11
|
+
// wrapped as an island and its children don't cross the boundary — hydration then
|
|
12
|
+
// wipes the children (an Alert rendered empty). Static SSR keeps children and ships
|
|
13
|
+
// zero JS. Only genuinely stateful components (accordion: useState/context) opt in.
|
|
14
|
+
const PRESENTATIONAL = ["button", "card", "input", "label", "avatar", "alert"];
|
|
15
|
+
const STATEFUL = ["accordion"];
|
|
16
|
+
describe("registry components", () => {
|
|
17
|
+
const files = readdirSync(REGISTRY).filter((f) => f.endsWith(".tsx"));
|
|
18
|
+
const firstLine = (slug) => readFileSync(join(REGISTRY, `${slug}.tsx`), "utf8").split("\n")[0]?.trim();
|
|
19
|
+
test("presentational components are server-renderable (no 'use client')", () => {
|
|
20
|
+
for (const slug of PRESENTATIONAL) {
|
|
21
|
+
expect({ slug, directive: firstLine(slug) }).not.toEqual({
|
|
22
|
+
slug,
|
|
23
|
+
directive: '"use client";',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
test("stateful components declare 'use client'", () => {
|
|
28
|
+
for (const slug of STATEFUL) {
|
|
29
|
+
expect(firstLine(slug)).toBe('"use client";');
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
test("every component transpiles as TSX", () => {
|
|
33
|
+
const transpiler = new Bun.Transpiler({ loader: "tsx" });
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
const src = readFileSync(join(REGISTRY, file), "utf8");
|
|
36
|
+
expect(() => transpiler.transformSync(src)).not.toThrow();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
test("no Tailwind v3-only classnames (renamed or removed in v4)", () => {
|
|
40
|
+
// Utilities that v4 removed or renamed; using them warns or silently breaks.
|
|
41
|
+
const banned = [
|
|
42
|
+
/\boutline-none\b/, // → outline-hidden (v4's outline-none means something else)
|
|
43
|
+
/\bbg-gradient-to-/, // → bg-linear-to-*
|
|
44
|
+
/\bflex-shrink-/, // → shrink-*
|
|
45
|
+
/\bflex-grow\b/, // → grow
|
|
46
|
+
/\b(?:bg|text|border|ring|divide|placeholder)-opacity-\d/, // → color/NN syntax
|
|
47
|
+
/\boverflow-ellipsis\b/, // → text-ellipsis
|
|
48
|
+
/\bdecoration-(?:slice|clone)\b/, // → box-decoration-*
|
|
49
|
+
];
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
const src = readFileSync(join(REGISTRY, file), "utf8");
|
|
52
|
+
for (const re of banned) {
|
|
53
|
+
expect({ file, matches: re.test(src) ? re.source : null }).toEqual({
|
|
54
|
+
file,
|
|
55
|
+
matches: null,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
test("design-dimension components expose every design style", () => {
|
|
61
|
+
for (const name of DESIGN_COMPONENTS) {
|
|
62
|
+
const src = readFileSync(join(REGISTRY, `${name}.tsx`), "utf8");
|
|
63
|
+
for (const design of DESIGNS) {
|
|
64
|
+
expect({ name, design, present: src.includes(`"${design}"`) }).toEqual({
|
|
65
|
+
name,
|
|
66
|
+
design,
|
|
67
|
+
present: true,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
test("button exposes the full variant and size matrix", () => {
|
|
73
|
+
const src = readFileSync(join(REGISTRY, "button.tsx"), "utf8");
|
|
74
|
+
for (const v of ["default", "outline", "secondary", "ghost", "destructive", "link"]) {
|
|
75
|
+
expect(src).toContain(`${v}:`);
|
|
76
|
+
}
|
|
77
|
+
for (const s of ["xs", "sm", "md", "lg", "icon", "icon-xs", "icon-sm", "icon-md", "icon-lg"]) {
|
|
78
|
+
expect(src).toContain(`"${s}"`);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
//# sourceMappingURL=registry.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.test.js","sourceRoot":"","sources":["../src/registry.test.ts"],"names":[],"mappings":"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"}
|
package/dist/smoke.test.js
CHANGED
|
@@ -6,13 +6,15 @@ test("package exports core API", () => {
|
|
|
6
6
|
expect(typeof mod.add).toBe("function");
|
|
7
7
|
expect(typeof mod.list).toBe("function");
|
|
8
8
|
});
|
|
9
|
-
test("COMPONENTS map lists
|
|
9
|
+
test("COMPONENTS map lists the full registry", () => {
|
|
10
10
|
const components = mod.COMPONENTS;
|
|
11
|
-
expect(Object.keys(components).length).
|
|
11
|
+
expect(Object.keys(components).length).toBeGreaterThanOrEqual(60);
|
|
12
12
|
const button = components.button;
|
|
13
13
|
expect(button).toBeDefined();
|
|
14
14
|
expect(button?.files).toContain("button.tsx");
|
|
15
15
|
expect(components["dropdown-menu"]).toBeDefined();
|
|
16
|
-
expect(components["
|
|
16
|
+
expect(components["data-table"]).toBeDefined();
|
|
17
|
+
expect(components["code-block"]).toBeDefined();
|
|
18
|
+
expect(components.stepper).toBeDefined();
|
|
17
19
|
});
|
|
18
20
|
//# sourceMappingURL=smoke.test.js.map
|
package/dist/smoke.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smoke.test.js","sourceRoot":"","sources":["../src/smoke.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,GAAG,MAAM,SAAS,CAAC;AAE/B,IAAI,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACpC,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1B,MAAM,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxC,MAAM,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,wCAAwC,EAAE,GAAG,EAAE;IAClD,MAAM,UAAU,GAAG,GAAG,CAAC,UAAiD,CAAC;IACzE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"smoke.test.js","sourceRoot":"","sources":["../src/smoke.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,GAAG,MAAM,SAAS,CAAC;AAE/B,IAAI,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACpC,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1B,MAAM,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxC,MAAM,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,wCAAwC,EAAE,GAAG,EAAE;IAClD,MAAM,UAAU,GAAG,GAAG,CAAC,UAAiD,CAAC;IACzE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IACjC,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAC7B,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IAC9C,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAClD,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/C,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/C,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;AAC3C,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swift-rust/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "shadcn-style component registry for
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "swift-rust ui — a shadcn-style component registry with a third style dimension (variant × size × design) built for Tailwind v4.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://swift-rust.dev",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/
|
|
9
|
+
"url": "https://github.com/colesites/swift-rust.git",
|
|
10
10
|
"directory": "packages/ui"
|
|
11
11
|
},
|
|
12
12
|
"type": "module",
|
|
@@ -36,19 +36,19 @@
|
|
|
36
36
|
"clean": "rm -rf dist .turbo"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@clack/prompts": "^
|
|
39
|
+
"@clack/prompts": "^1.5.1",
|
|
40
40
|
"picocolors": "^1.0.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/bun": "^1.3.0",
|
|
44
|
-
"clsx": "^2.1.
|
|
45
|
-
"tailwind-merge": "^
|
|
44
|
+
"clsx": "^2.1.1",
|
|
45
|
+
"tailwind-merge": "^3.6.0",
|
|
46
46
|
"typescript": "^6.0.0"
|
|
47
47
|
},
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
51
|
"bugs": {
|
|
52
|
-
"url": "https://github.com/
|
|
52
|
+
"url": "https://github.com/colesites/swift-rust/issues"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -2,23 +2,101 @@
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* swift-rust ui · Accordion
|
|
7
|
+
*
|
|
8
|
+
* variant — default (divided), outline (separated items), secondary, ghost
|
|
9
|
+
* size — sm, default, lg
|
|
10
|
+
* design — flat, soft, 3d, glass, neo, brutal, gradient
|
|
11
|
+
*
|
|
12
|
+
* variant/size/design are set once on <Accordion> and flow to items via context.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export type AccordionVariant = "default" | "outline" | "secondary" | "ghost";
|
|
16
|
+
export type AccordionSize = "default" | "sm" | "lg";
|
|
17
|
+
export type AccordionDesign = "flat" | "soft" | "3d" | "glass" | "neo" | "brutal" | "gradient";
|
|
18
|
+
|
|
19
|
+
// Item chrome. The default variant is a classic divided list; the others render
|
|
20
|
+
// separated, surfaced items so designs (glass, brutal, …) have a card to paint.
|
|
21
|
+
const ITEM_VARIANTS: Record<AccordionVariant, string> = {
|
|
22
|
+
default: "border-b border-border last:border-b-0",
|
|
23
|
+
outline: "mb-2 rounded-lg border border-border px-4 last:mb-0",
|
|
24
|
+
secondary: "mb-2 rounded-lg bg-secondary px-4 text-secondary-foreground last:mb-0",
|
|
25
|
+
ghost: "rounded-lg px-2 hover:bg-secondary/50",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const TRIGGER_SIZES: Record<AccordionSize, string> = {
|
|
29
|
+
sm: "py-3 text-xs",
|
|
30
|
+
default: "py-4 text-sm",
|
|
31
|
+
lg: "py-5 text-base",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const CONTENT_SIZES: Record<AccordionSize, string> = {
|
|
35
|
+
sm: "pb-3 text-xs",
|
|
36
|
+
default: "pb-4 text-sm",
|
|
37
|
+
lg: "pb-5 text-base",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Designs only decorate non-default variants' item surfaces (the divided list
|
|
41
|
+
// has no surface to paint).
|
|
42
|
+
const ITEM_DESIGNS: Record<AccordionDesign, string> = {
|
|
43
|
+
flat: "",
|
|
44
|
+
soft: "rounded-xl border-transparent bg-muted",
|
|
45
|
+
// Depth via a darker bottom lip + a top sheen — no drop shadow.
|
|
46
|
+
"3d": "border-transparent border-b-4 border-b-black/15 bg-linear-to-b from-white/20 to-transparent dark:border-b-black/40",
|
|
47
|
+
// Liquid glass panels.
|
|
48
|
+
glass:
|
|
49
|
+
"border-white/40 bg-white/15 backdrop-blur-xl backdrop-saturate-200 " +
|
|
50
|
+
"bg-linear-to-br from-white/30 via-white/10 to-white/5 " +
|
|
51
|
+
"dark:border-white/20 dark:bg-white/10 dark:from-white/15 dark:via-white/5 dark:to-transparent",
|
|
52
|
+
neo:
|
|
53
|
+
"border-transparent bg-background " +
|
|
54
|
+
"shadow-[5px_5px_10px_rgba(0,0,0,0.12),-5px_-5px_10px_rgba(255,255,255,0.8)] " +
|
|
55
|
+
"dark:shadow-[5px_5px_10px_rgba(0,0,0,0.6),-5px_-5px_10px_rgba(255,255,255,0.05)]",
|
|
56
|
+
brutal:
|
|
57
|
+
"rounded-none border-2 border-foreground shadow-[4px_4px_0_0_var(--color-foreground)]",
|
|
58
|
+
gradient:
|
|
59
|
+
"border-2 border-transparent " +
|
|
60
|
+
"[background:linear-gradient(var(--color-background),var(--color-background))_padding-box," +
|
|
61
|
+
"linear-gradient(135deg,#8b5cf6,#d946ef,#fb923c)_border-box]",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
interface AccordionContextValue {
|
|
6
65
|
openItems: Set<string>;
|
|
7
66
|
toggle: (value: string) => void;
|
|
8
67
|
type: "single" | "multiple";
|
|
9
|
-
|
|
68
|
+
variant: AccordionVariant;
|
|
69
|
+
size: AccordionSize;
|
|
70
|
+
design: AccordionDesign;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const AccordionContext = React.createContext<AccordionContextValue | null>(null);
|
|
74
|
+
|
|
75
|
+
function useAccordion(): AccordionContextValue {
|
|
76
|
+
const ctx = React.useContext(AccordionContext);
|
|
77
|
+
if (!ctx) throw new Error("Accordion components must be used inside <Accordion>");
|
|
78
|
+
return ctx;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface AccordionProps {
|
|
82
|
+
type?: "single" | "multiple";
|
|
83
|
+
defaultValue?: string | string[];
|
|
84
|
+
variant?: AccordionVariant;
|
|
85
|
+
size?: AccordionSize;
|
|
86
|
+
design?: AccordionDesign;
|
|
87
|
+
className?: string;
|
|
88
|
+
children: React.ReactNode;
|
|
89
|
+
}
|
|
10
90
|
|
|
11
91
|
export function Accordion({
|
|
12
92
|
type = "single",
|
|
13
93
|
defaultValue,
|
|
94
|
+
variant = "default",
|
|
95
|
+
size = "default",
|
|
96
|
+
design = "flat",
|
|
14
97
|
className,
|
|
15
98
|
children,
|
|
16
|
-
}: {
|
|
17
|
-
type?: "single" | "multiple";
|
|
18
|
-
defaultValue?: string | string[];
|
|
19
|
-
className?: string;
|
|
20
|
-
children: React.ReactNode;
|
|
21
|
-
}) {
|
|
99
|
+
}: AccordionProps) {
|
|
22
100
|
const [openItems, setOpenItems] = React.useState<Set<string>>(
|
|
23
101
|
() => new Set(Array.isArray(defaultValue) ? defaultValue : defaultValue ? [defaultValue] : []),
|
|
24
102
|
);
|
|
@@ -34,7 +112,7 @@ export function Accordion({
|
|
|
34
112
|
});
|
|
35
113
|
};
|
|
36
114
|
return (
|
|
37
|
-
<AccordionContext.Provider value={{ openItems, toggle, type }}>
|
|
115
|
+
<AccordionContext.Provider value={{ openItems, toggle, type, variant, size, design }}>
|
|
38
116
|
<div className={className}>{children}</div>
|
|
39
117
|
</AccordionContext.Provider>
|
|
40
118
|
);
|
|
@@ -42,15 +120,28 @@ export function Accordion({
|
|
|
42
120
|
|
|
43
121
|
export function AccordionItem({
|
|
44
122
|
value,
|
|
123
|
+
disabled,
|
|
45
124
|
className,
|
|
46
125
|
children,
|
|
47
126
|
}: {
|
|
48
127
|
value: string;
|
|
128
|
+
disabled?: boolean;
|
|
49
129
|
className?: string;
|
|
50
130
|
children: React.ReactNode;
|
|
51
131
|
}) {
|
|
132
|
+
const { openItems, variant, design } = useAccordion();
|
|
52
133
|
return (
|
|
53
|
-
<div
|
|
134
|
+
<div
|
|
135
|
+
data-value={value}
|
|
136
|
+
data-disabled={disabled ? "" : undefined}
|
|
137
|
+
data-state={openItems.has(value) ? "open" : "closed"}
|
|
138
|
+
className={cn(
|
|
139
|
+
ITEM_VARIANTS[variant],
|
|
140
|
+
variant !== "default" && ITEM_DESIGNS[design],
|
|
141
|
+
variant !== "default" && design !== "flat" && "mb-3 last:mb-0",
|
|
142
|
+
className,
|
|
143
|
+
)}
|
|
144
|
+
>
|
|
54
145
|
{children}
|
|
55
146
|
</div>
|
|
56
147
|
);
|
|
@@ -58,32 +149,43 @@ export function AccordionItem({
|
|
|
58
149
|
|
|
59
150
|
export function AccordionTrigger({
|
|
60
151
|
value,
|
|
152
|
+
disabled,
|
|
61
153
|
className,
|
|
62
154
|
children,
|
|
63
155
|
}: {
|
|
64
156
|
value: string;
|
|
157
|
+
disabled?: boolean;
|
|
65
158
|
className?: string;
|
|
66
159
|
children: React.ReactNode;
|
|
67
160
|
}) {
|
|
68
|
-
const ctx =
|
|
161
|
+
const ctx = useAccordion();
|
|
69
162
|
const open = ctx.openItems.has(value);
|
|
70
163
|
return (
|
|
71
164
|
<button
|
|
72
165
|
type="button"
|
|
166
|
+
disabled={disabled}
|
|
167
|
+
aria-expanded={open}
|
|
168
|
+
data-state={open ? "open" : "closed"}
|
|
73
169
|
onClick={() => ctx.toggle(value)}
|
|
74
170
|
className={cn(
|
|
75
|
-
"flex w-full items-center justify-between
|
|
76
|
-
"hover:
|
|
171
|
+
"flex w-full items-center justify-between gap-4 text-left font-medium transition-all",
|
|
172
|
+
"rounded-md hover:underline focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring/50",
|
|
173
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
174
|
+
TRIGGER_SIZES[ctx.size],
|
|
77
175
|
className,
|
|
78
176
|
)}
|
|
79
177
|
>
|
|
80
178
|
{children}
|
|
81
179
|
<svg
|
|
82
180
|
viewBox="0 0 24 24"
|
|
83
|
-
className={cn(
|
|
181
|
+
className={cn(
|
|
182
|
+
"size-4 shrink-0 text-muted-foreground transition-transform duration-200",
|
|
183
|
+
open && "rotate-180",
|
|
184
|
+
)}
|
|
84
185
|
fill="none"
|
|
85
186
|
stroke="currentColor"
|
|
86
187
|
strokeWidth="2"
|
|
188
|
+
aria-hidden="true"
|
|
87
189
|
>
|
|
88
190
|
<path d="M6 9l6 6 6-6" strokeLinecap="round" strokeLinejoin="round" />
|
|
89
191
|
</svg>
|
|
@@ -100,7 +202,14 @@ export function AccordionContent({
|
|
|
100
202
|
className?: string;
|
|
101
203
|
children: React.ReactNode;
|
|
102
204
|
}) {
|
|
103
|
-
const ctx =
|
|
205
|
+
const ctx = useAccordion();
|
|
104
206
|
if (!ctx.openItems.has(value)) return null;
|
|
105
|
-
return
|
|
207
|
+
return (
|
|
208
|
+
<div
|
|
209
|
+
data-state="open"
|
|
210
|
+
className={cn("text-muted-foreground", CONTENT_SIZES[ctx.size], className)}
|
|
211
|
+
>
|
|
212
|
+
{children}
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
106
215
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const AlertDialogContext = React.createContext<{ open: boolean; setOpen: (v: boolean) => void } | null>(null);
|
|
6
|
+
|
|
7
|
+
export function AlertDialog({
|
|
8
|
+
open: controlled,
|
|
9
|
+
onOpenChange,
|
|
10
|
+
children,
|
|
11
|
+
}: {
|
|
12
|
+
open?: boolean;
|
|
13
|
+
onOpenChange?: (v: boolean) => void;
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
}) {
|
|
16
|
+
const [internal, setInternal] = React.useState(false);
|
|
17
|
+
const open = controlled ?? internal;
|
|
18
|
+
const setOpen = (v: boolean) => {
|
|
19
|
+
if (controlled === undefined) setInternal(v);
|
|
20
|
+
onOpenChange?.(v);
|
|
21
|
+
};
|
|
22
|
+
return (
|
|
23
|
+
<AlertDialogContext.Provider value={{ open, setOpen }}>
|
|
24
|
+
{children}
|
|
25
|
+
{open ? <div className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm" aria-hidden /> : null}
|
|
26
|
+
</AlertDialogContext.Provider>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function AlertDialogTrigger({ asChild, children }: { asChild?: boolean; children: React.ReactNode }) {
|
|
31
|
+
const ctx = React.useContext(AlertDialogContext);
|
|
32
|
+
if (asChild && React.isValidElement(children)) {
|
|
33
|
+
return React.cloneElement(children as React.ReactElement<{ onClick?: () => void }>, {
|
|
34
|
+
onClick: () => ctx?.setOpen(true),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return <button type="button" onClick={() => ctx?.setOpen(true)}>{children}</button>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function AlertDialogContent({ className, children }: { className?: string; children: React.ReactNode }) {
|
|
41
|
+
const ctx = React.useContext(AlertDialogContext);
|
|
42
|
+
if (!ctx?.open) return null;
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
role="alertdialog"
|
|
46
|
+
aria-modal
|
|
47
|
+
className={cn(
|
|
48
|
+
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl border border-border bg-background p-6 shadow-lg",
|
|
49
|
+
className,
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function AlertDialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
58
|
+
return <div className={cn("flex flex-col gap-2 text-left", className)} {...props} />;
|
|
59
|
+
}
|
|
60
|
+
export function AlertDialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
61
|
+
return <div className={cn("flex justify-end gap-2 pt-2", className)} {...props} />;
|
|
62
|
+
}
|
|
63
|
+
export function AlertDialogTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
|
64
|
+
return <h2 className={cn("text-lg font-semibold", className)} {...props} />;
|
|
65
|
+
}
|
|
66
|
+
export function AlertDialogDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
67
|
+
return <p className={cn("text-sm text-muted-foreground", className)} {...props} />;
|
|
68
|
+
}
|
|
69
|
+
export function AlertDialogAction({ className, onClick, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
|
|
70
|
+
const ctx = React.useContext(AlertDialogContext);
|
|
71
|
+
return (
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
onClick={(e) => {
|
|
75
|
+
onClick?.(e);
|
|
76
|
+
ctx?.setOpen(false);
|
|
77
|
+
}}
|
|
78
|
+
className={cn(
|
|
79
|
+
"inline-flex h-9 items-center justify-center rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground hover:bg-primary/90",
|
|
80
|
+
className,
|
|
81
|
+
)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
export function AlertDialogCancel({ className, onClick, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
|
|
87
|
+
const ctx = React.useContext(AlertDialogContext);
|
|
88
|
+
return (
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
onClick={(e) => {
|
|
92
|
+
onClick?.(e);
|
|
93
|
+
ctx?.setOpen(false);
|
|
94
|
+
}}
|
|
95
|
+
className={cn(
|
|
96
|
+
"inline-flex h-9 items-center justify-center rounded-lg border border-input bg-background px-4 text-sm font-medium hover:bg-secondary",
|
|
97
|
+
className,
|
|
98
|
+
)}
|
|
99
|
+
{...props}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -1,34 +1,134 @@
|
|
|
1
|
-
"use client";
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import { cn } from "@/lib/utils";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
/**
|
|
5
|
+
* swift-rust ui · Alert
|
|
6
|
+
*
|
|
7
|
+
* variant — default, destructive, success, warning, info, outline, secondary
|
|
8
|
+
* size — sm, default, lg
|
|
9
|
+
* design — flat, soft, 3d, glass, neo, brutal, gradient
|
|
10
|
+
*
|
|
11
|
+
* (`tone` is accepted as a deprecated alias of `variant`.)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type AlertVariant =
|
|
15
|
+
| "default"
|
|
16
|
+
| "destructive"
|
|
17
|
+
| "success"
|
|
18
|
+
| "warning"
|
|
19
|
+
| "info"
|
|
20
|
+
| "outline"
|
|
21
|
+
| "secondary";
|
|
22
|
+
export type AlertSize = "default" | "sm" | "lg";
|
|
23
|
+
export type AlertDesign = "flat" | "soft" | "3d" | "glass" | "neo" | "brutal" | "gradient";
|
|
24
|
+
|
|
25
|
+
const VARIANTS: Record<AlertVariant, string> = {
|
|
26
|
+
default: "border-border bg-card text-card-foreground",
|
|
27
|
+
destructive:
|
|
28
|
+
"border-destructive/30 bg-destructive/10 text-destructive [&_[data-alert-description]]:text-destructive/90",
|
|
29
|
+
success:
|
|
30
|
+
"border-emerald-600/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 " +
|
|
31
|
+
"[&_[data-alert-description]]:text-emerald-700/90 dark:[&_[data-alert-description]]:text-emerald-400/90",
|
|
32
|
+
warning:
|
|
33
|
+
"border-amber-600/30 bg-amber-500/10 text-amber-700 dark:text-amber-400 " +
|
|
34
|
+
"[&_[data-alert-description]]:text-amber-700/90 dark:[&_[data-alert-description]]:text-amber-400/90",
|
|
35
|
+
info:
|
|
36
|
+
"border-sky-600/30 bg-sky-500/10 text-sky-700 dark:text-sky-400 " +
|
|
37
|
+
"[&_[data-alert-description]]:text-sky-700/90 dark:[&_[data-alert-description]]:text-sky-400/90",
|
|
38
|
+
outline: "border-2 border-border bg-transparent text-foreground",
|
|
39
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const SIZES: Record<AlertSize, string> = {
|
|
43
|
+
sm: "p-3 text-xs [&_[data-alert-title]]:text-sm",
|
|
44
|
+
default: "p-4 text-sm",
|
|
45
|
+
lg: "p-6 text-base",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const DESIGNS: Record<AlertDesign, string> = {
|
|
49
|
+
flat: "",
|
|
50
|
+
soft: "rounded-2xl border-transparent",
|
|
51
|
+
// Depth via a darker bottom lip + a top sheen — no drop shadow.
|
|
52
|
+
"3d": "border-b-4 border-b-black/15 bg-linear-to-b from-white/20 to-transparent dark:border-b-black/40",
|
|
53
|
+
// Liquid glass panel.
|
|
54
|
+
glass:
|
|
55
|
+
"border-white/40 bg-white/15 backdrop-blur-xl backdrop-saturate-200 " +
|
|
56
|
+
"bg-linear-to-br from-white/30 via-white/10 to-white/5 " +
|
|
57
|
+
"shadow-[inset_0_1px_1px_rgba(255,255,255,0.55),0_6px_24px_rgba(31,38,135,0.16)] " +
|
|
58
|
+
"dark:border-white/20 dark:bg-white/10 dark:from-white/15 dark:via-white/5 dark:to-transparent " +
|
|
59
|
+
"dark:shadow-[inset_0_1px_1px_rgba(255,255,255,0.2),0_6px_24px_rgba(0,0,0,0.4)]",
|
|
60
|
+
neo:
|
|
61
|
+
"border-transparent bg-background " +
|
|
62
|
+
"shadow-[6px_6px_12px_rgba(0,0,0,0.12),-6px_-6px_12px_rgba(255,255,255,0.8)] " +
|
|
63
|
+
"dark:shadow-[6px_6px_12px_rgba(0,0,0,0.6),-6px_-6px_12px_rgba(255,255,255,0.05)]",
|
|
64
|
+
brutal: "rounded-none border-2 border-foreground shadow-[4px_4px_0_0_var(--color-foreground)]",
|
|
65
|
+
gradient:
|
|
66
|
+
"border-2 border-transparent " +
|
|
67
|
+
"[background:linear-gradient(var(--color-card),var(--color-card))_padding-box," +
|
|
68
|
+
"linear-gradient(135deg,#8b5cf6,#d946ef,#fb923c)_border-box]",
|
|
12
69
|
};
|
|
13
70
|
|
|
14
71
|
export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
15
|
-
|
|
72
|
+
variant?: AlertVariant;
|
|
73
|
+
/** @deprecated use `variant` */
|
|
74
|
+
tone?: AlertVariant;
|
|
75
|
+
size?: AlertSize;
|
|
76
|
+
design?: AlertDesign;
|
|
16
77
|
}
|
|
17
78
|
|
|
18
|
-
export function Alert({
|
|
79
|
+
export function Alert({
|
|
80
|
+
className,
|
|
81
|
+
variant,
|
|
82
|
+
tone,
|
|
83
|
+
size = "default",
|
|
84
|
+
design = "flat",
|
|
85
|
+
...props
|
|
86
|
+
}: AlertProps) {
|
|
87
|
+
const v = variant ?? tone ?? "default";
|
|
19
88
|
return (
|
|
20
89
|
<div
|
|
21
90
|
role="alert"
|
|
22
|
-
className={cn(
|
|
91
|
+
className={cn(
|
|
92
|
+
"relative grid w-full grid-cols-[0_1fr] gap-y-0.5 rounded-lg border",
|
|
93
|
+
"has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5",
|
|
94
|
+
// Reserve room on the trailing edge when an AlertAction is present.
|
|
95
|
+
"has-[[data-alert-action]]:pr-16",
|
|
96
|
+
VARIANTS[v],
|
|
97
|
+
SIZES[size],
|
|
98
|
+
DESIGNS[design],
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
23
101
|
{...props}
|
|
24
102
|
/>
|
|
25
103
|
);
|
|
26
104
|
}
|
|
27
105
|
|
|
28
106
|
export function AlertTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
|
29
|
-
return
|
|
107
|
+
return (
|
|
108
|
+
<h5
|
|
109
|
+
data-alert-title
|
|
110
|
+
className={cn("col-start-2 mb-1 font-medium leading-none tracking-tight", className)}
|
|
111
|
+
{...props}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
30
114
|
}
|
|
31
115
|
|
|
32
|
-
export function AlertDescription({
|
|
33
|
-
|
|
116
|
+
export function AlertDescription({
|
|
117
|
+
className,
|
|
118
|
+
...props
|
|
119
|
+
}: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
120
|
+
return (
|
|
121
|
+
<div
|
|
122
|
+
data-alert-description
|
|
123
|
+
className={cn("col-start-2 text-muted-foreground [&_p]:leading-relaxed", className)}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** A trailing action (button, link, …) pinned to the top-right of the alert. */
|
|
130
|
+
export function AlertAction({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
131
|
+
return (
|
|
132
|
+
<div data-alert-action className={cn("absolute right-3 top-3", className)} {...props} />
|
|
133
|
+
);
|
|
34
134
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export interface AspectRatioProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
ratio?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const AspectRatio = React.forwardRef<HTMLDivElement, AspectRatioProps>(
|
|
9
|
+
({ ratio = 16 / 9, className, style, ...props }, ref) => (
|
|
10
|
+
<div
|
|
11
|
+
ref={ref}
|
|
12
|
+
className={cn("relative w-full overflow-hidden", className)}
|
|
13
|
+
style={{ aspectRatio: String(ratio), ...style }}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
),
|
|
17
|
+
);
|
|
18
|
+
AspectRatio.displayName = "AspectRatio";
|