@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.
Files changed (85) hide show
  1. package/README.md +66 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +89 -41
  4. package/dist/cli.js.map +1 -1
  5. package/dist/cli.test.d.ts +2 -0
  6. package/dist/cli.test.d.ts.map +1 -0
  7. package/dist/cli.test.js +36 -0
  8. package/dist/cli.test.js.map +1 -0
  9. package/dist/components.d.ts.map +1 -1
  10. package/dist/components.js +61 -32
  11. package/dist/components.js.map +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/registry.test.d.ts +2 -0
  16. package/dist/registry.test.d.ts.map +1 -0
  17. package/dist/registry.test.js +82 -0
  18. package/dist/registry.test.js.map +1 -0
  19. package/dist/smoke.test.js +5 -3
  20. package/dist/smoke.test.js.map +1 -1
  21. package/package.json +7 -7
  22. package/registry/components/accordion.tsx +125 -16
  23. package/registry/components/alert-dialog.tsx +102 -0
  24. package/registry/components/alert.tsx +114 -14
  25. package/registry/components/aspect-ratio.tsx +18 -0
  26. package/registry/components/avatar.tsx +59 -7
  27. package/registry/components/badge.tsx +29 -14
  28. package/registry/components/breadcrumb.tsx +7 -13
  29. package/registry/components/button-group.tsx +28 -0
  30. package/registry/components/button.tsx +113 -28
  31. package/registry/components/calendar.tsx +92 -0
  32. package/registry/components/callout.tsx +14 -14
  33. package/registry/components/card.tsx +87 -12
  34. package/registry/components/carousel.tsx +41 -0
  35. package/registry/components/chart.tsx +50 -0
  36. package/registry/components/checkbox.tsx +5 -5
  37. package/registry/components/code-block.tsx +118 -0
  38. package/registry/components/code.tsx +2 -3
  39. package/registry/components/collapsible.tsx +60 -0
  40. package/registry/components/combobox.tsx +102 -0
  41. package/registry/components/command.tsx +5 -5
  42. package/registry/components/context-menu.tsx +81 -0
  43. package/registry/components/data-table.tsx +71 -0
  44. package/registry/components/date-picker.tsx +58 -0
  45. package/registry/components/dialog.tsx +2 -2
  46. package/registry/components/direction.tsx +17 -0
  47. package/registry/components/drawer.tsx +77 -0
  48. package/registry/components/dropdown-menu.tsx +5 -5
  49. package/registry/components/empty.tsx +34 -0
  50. package/registry/components/field.tsx +27 -0
  51. package/registry/components/file-upload.tsx +116 -0
  52. package/registry/components/form.tsx +3 -4
  53. package/registry/components/hover-card.tsx +59 -0
  54. package/registry/components/input-group.tsx +34 -0
  55. package/registry/components/input-otp.tsx +50 -0
  56. package/registry/components/input.tsx +71 -7
  57. package/registry/components/item.tsx +42 -0
  58. package/registry/components/kbd.tsx +3 -4
  59. package/registry/components/label.tsx +34 -4
  60. package/registry/components/menubar.tsx +60 -0
  61. package/registry/components/native-select.tsx +35 -0
  62. package/registry/components/navigation-menu.tsx +3 -3
  63. package/registry/components/pagination.tsx +4 -5
  64. package/registry/components/popover.tsx +1 -1
  65. package/registry/components/progress.tsx +10 -5
  66. package/registry/components/radio-group.tsx +9 -9
  67. package/registry/components/resizable.tsx +77 -0
  68. package/registry/components/scroll-area.tsx +20 -0
  69. package/registry/components/select.tsx +2 -3
  70. package/registry/components/separator.tsx +1 -2
  71. package/registry/components/sheet.tsx +1 -1
  72. package/registry/components/sidebar.tsx +72 -0
  73. package/registry/components/skeleton.tsx +1 -6
  74. package/registry/components/slider.tsx +6 -3
  75. package/registry/components/sonner.tsx +52 -0
  76. package/registry/components/spinner.tsx +19 -6
  77. package/registry/components/stepper.tsx +63 -0
  78. package/registry/components/switch.tsx +7 -6
  79. package/registry/components/table.tsx +2 -3
  80. package/registry/components/tabs.tsx +3 -3
  81. package/registry/components/textarea.tsx +42 -6
  82. package/registry/components/toast.tsx +2 -2
  83. package/registry/components/toggle-group.tsx +72 -0
  84. package/registry/components/toggle.tsx +45 -20
  85. package/registry/components/tooltip.tsx +4 -2
@@ -0,0 +1,63 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ export interface StepItem {
6
+ title: string;
7
+ description?: string;
8
+ }
9
+
10
+ export function Stepper({
11
+ steps,
12
+ current = 0,
13
+ orientation = "horizontal",
14
+ className,
15
+ }: {
16
+ steps: StepItem[];
17
+ current?: number;
18
+ orientation?: "horizontal" | "vertical";
19
+ className?: string;
20
+ }) {
21
+ const vertical = orientation === "vertical";
22
+ return (
23
+ <ol className={cn("flex w-full", vertical ? "flex-col gap-0" : "items-start", className)}>
24
+ {steps.map((step, i) => {
25
+ const state = i < current ? "complete" : i === current ? "current" : "upcoming";
26
+ const last = i === steps.length - 1;
27
+ return (
28
+ <li key={step.title} className={cn("flex", vertical ? "gap-3" : "flex-1 flex-col items-center text-center")}>
29
+ <div className={cn("flex items-center", vertical ? "flex-col" : "w-full")}>
30
+ {!vertical && i > 0 && (
31
+ <span className={cn("h-0.5 flex-1", i <= current ? "bg-primary" : "bg-border")} />
32
+ )}
33
+ <span
34
+ className={cn(
35
+ "flex size-8 shrink-0 items-center justify-center rounded-full border text-sm font-medium transition-colors",
36
+ state === "complete" && "border-primary bg-primary text-primary-foreground",
37
+ state === "current" && "border-primary text-primary",
38
+ state === "upcoming" && "border-border text-muted-foreground",
39
+ )}
40
+ >
41
+ {state === "complete" ? (
42
+ <svg viewBox="0 0 24 24" className="size-4" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden><path d="M20 6 9 17l-5-5" strokeLinecap="round" strokeLinejoin="round" /></svg>
43
+ ) : (
44
+ i + 1
45
+ )}
46
+ </span>
47
+ {!vertical && !last && (
48
+ <span className={cn("h-0.5 flex-1", i < current ? "bg-primary" : "bg-border")} />
49
+ )}
50
+ {vertical && !last && <span className={cn("my-1 w-0.5 flex-1", i < current ? "bg-primary" : "bg-border")} />}
51
+ </div>
52
+ <div className={cn(vertical ? "pb-6 pt-1" : "mt-2")}>
53
+ <p className={cn("text-sm font-medium", state === "upcoming" ? "text-muted-foreground" : "text-foreground")}>
54
+ {step.title}
55
+ </p>
56
+ {step.description && <p className="mt-0.5 text-xs text-muted-foreground">{step.description}</p>}
57
+ </div>
58
+ </li>
59
+ );
60
+ })}
61
+ </ol>
62
+ );
63
+ }
@@ -1,18 +1,19 @@
1
- "use client";
2
1
  import * as React from "react";
3
2
  import { cn } from "@/lib/utils";
4
3
 
5
- export const Switch = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
4
+ export type SwitchProps = React.InputHTMLAttributes<HTMLInputElement>;
5
+
6
+ export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
6
7
  ({ className, ...props }, ref) => (
7
8
  <label className="inline-flex cursor-pointer items-center">
8
9
  <input type="checkbox" ref={ref} className="peer sr-only" {...props} />
9
10
  <span
10
11
  className={cn(
11
- "relative h-5 w-9 rounded-full bg-[var(--ui-surface-2)] transition-colors",
12
- "peer-checked:bg-[var(--ui-accent)]",
13
- "peer-focus-visible:ring-2 peer-focus-visible:ring-[var(--ui-accent)] peer-focus-visible:ring-offset-2",
12
+ "relative h-5 w-9 rounded-full bg-input transition-colors",
13
+ "peer-checked:bg-primary",
14
+ "peer-focus-visible:ring-2 peer-focus-visible:ring-ring/50 peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-background",
14
15
  "peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
15
- "after:absolute after:left-0.5 after:top-0.5 after:h-4 after:w-4 after:rounded-full after:bg-white after:transition-transform",
16
+ "after:absolute after:left-0.5 after:top-0.5 after:size-4 after:rounded-full after:bg-background after:shadow-sm after:transition-transform",
16
17
  "peer-checked:after:translate-x-4",
17
18
  className,
18
19
  )}
@@ -1,4 +1,3 @@
1
- "use client";
2
1
  import * as React from "react";
3
2
  import { cn } from "@/lib/utils";
4
3
 
@@ -34,7 +33,7 @@ export const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttribut
34
33
  <tr
35
34
  ref={ref}
36
35
  className={cn(
37
- "border-b border-[var(--ui-border)] transition-colors hover:bg-[var(--ui-surface-2)] data-[state=selected]:bg-[var(--ui-surface-2)]",
36
+ "border-b border-border transition-colors hover:bg-muted data-[state=selected]:bg-muted",
38
37
  className,
39
38
  )}
40
39
  {...props}
@@ -48,7 +47,7 @@ export const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttr
48
47
  <th
49
48
  ref={ref}
50
49
  className={cn(
51
- "h-10 px-3 text-left align-middle text-xs font-semibold uppercase tracking-wider text-[var(--ui-fg-muted)]",
50
+ "h-10 px-3 text-left align-middle text-xs font-semibold uppercase tracking-wider text-muted-foreground",
52
51
  className,
53
52
  )}
54
53
  {...props}
@@ -35,7 +35,7 @@ export function TabsList({ className, ...props }: React.HTMLAttributes<HTMLDivEl
35
35
  <div
36
36
  role="tablist"
37
37
  className={cn(
38
- "inline-flex h-9 items-center justify-center rounded-lg bg-[var(--ui-surface-2)] p-1 text-[var(--ui-fg-muted)]",
38
+ "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
39
39
  className,
40
40
  )}
41
41
  {...props}
@@ -59,8 +59,8 @@ export function TabsTrigger({
59
59
  onClick={() => ctx.setValue(value)}
60
60
  className={cn(
61
61
  "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all",
62
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent)]",
63
- active ? "bg-[var(--ui-surface)] text-[var(--ui-fg)] shadow-sm" : "hover:text-[var(--ui-fg)]",
62
+ "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
63
+ active ? "bg-popover text-foreground shadow-sm" : "hover:text-foreground",
64
64
  className,
65
65
  )}
66
66
  {...props}
@@ -1,18 +1,54 @@
1
- "use client";
2
1
  import * as React from "react";
3
2
  import { cn } from "@/lib/utils";
4
3
 
5
- export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
4
+ export type TextareaVariant = "default" | "outline" | "secondary" | "ghost" | "destructive";
5
+ export type TextareaDesign = "flat" | "soft" | "3d" | "glass" | "neo" | "brutal" | "gradient";
6
+
7
+ const VARIANTS: Record<TextareaVariant, string> = {
8
+ default: "border border-input bg-background",
9
+ outline: "border-2 border-input bg-transparent",
10
+ secondary: "border border-transparent bg-secondary",
11
+ ghost: "border border-transparent bg-transparent hover:bg-secondary/50",
12
+ destructive:
13
+ "border border-destructive text-destructive placeholder:text-destructive/60 focus-visible:ring-destructive/40",
14
+ };
15
+
16
+ const DESIGNS: Record<TextareaDesign, string> = {
17
+ flat: "shadow-xs",
18
+ soft: "rounded-xl border-transparent bg-secondary shadow-none",
19
+ "3d": "border-t-2 border-t-black/15 bg-linear-to-b from-black/[0.06] to-transparent dark:border-t-black/40",
20
+ glass:
21
+ "border-white/40 bg-white/15 backdrop-blur-xl backdrop-saturate-200 placeholder:text-foreground/50 " +
22
+ "bg-linear-to-br from-white/30 to-white/5 dark:border-white/20 dark:bg-white/10 dark:from-white/15 dark:to-transparent",
23
+ neo:
24
+ "border-transparent bg-background " +
25
+ "shadow-[inset_3px_3px_6px_rgba(0,0,0,0.12),inset_-3px_-3px_6px_rgba(255,255,255,0.7)] " +
26
+ "dark:shadow-[inset_3px_3px_6px_rgba(0,0,0,0.6),inset_-3px_-3px_6px_rgba(255,255,255,0.05)]",
27
+ brutal:
28
+ "rounded-none border-2 border-foreground shadow-[3px_3px_0_0_var(--color-foreground)] " +
29
+ "focus-visible:shadow-[1px_1px_0_0_var(--color-foreground)] focus-visible:ring-0",
30
+ gradient:
31
+ "border-2 border-transparent " +
32
+ "[background:linear-gradient(var(--color-background),var(--color-background))_padding-box," +
33
+ "linear-gradient(135deg,#8b5cf6,#d946ef,#fb923c)_border-box]",
34
+ };
35
+
36
+ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
37
+ variant?: TextareaVariant;
38
+ design?: TextareaDesign;
39
+ }
6
40
 
7
41
  export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
8
- ({ className, ...props }, ref) => (
42
+ ({ className, variant = "default", design = "flat", ...props }, ref) => (
9
43
  <textarea
10
44
  ref={ref}
11
45
  className={cn(
12
- "flex min-h-[80px] w-full rounded-md border border-[var(--ui-border)] bg-[var(--ui-surface)] px-3 py-2 text-sm shadow-sm",
13
- "placeholder:text-[var(--ui-fg-subtle)]",
14
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent)] focus-visible:ring-offset-1",
46
+ "flex min-h-20 w-full rounded-lg px-3 py-2 text-sm text-foreground transition-[color,box-shadow,background-color]",
47
+ "placeholder:text-muted-foreground",
48
+ "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring/50",
15
49
  "disabled:cursor-not-allowed disabled:opacity-50",
50
+ VARIANTS[variant],
51
+ DESIGNS[design],
16
52
  className,
17
53
  )}
18
54
  {...props}
@@ -25,9 +25,9 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
25
25
  }, []);
26
26
 
27
27
  const TONE: Record<ToastTone, string> = {
28
- default: "bg-[var(--ui-fg)] text-[var(--ui-bg)]",
28
+ default: "bg-primary text-primary-foreground",
29
29
  success: "bg-[#16a34a] text-white",
30
- error: "bg-[var(--ui-danger)] text-white",
30
+ error: "bg-destructive text-white",
31
31
  warning: "bg-[#f59e0b] text-white",
32
32
  };
33
33
 
@@ -0,0 +1,72 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const ToggleGroupContext = React.createContext<{
6
+ value: string[];
7
+ toggle: (v: string) => void;
8
+ } | null>(null);
9
+
10
+ export function ToggleGroup({
11
+ type = "single",
12
+ value: controlled,
13
+ defaultValue,
14
+ onValueChange,
15
+ className,
16
+ children,
17
+ }: {
18
+ type?: "single" | "multiple";
19
+ value?: string | string[];
20
+ defaultValue?: string | string[];
21
+ onValueChange?: (v: string | string[]) => void;
22
+ className?: string;
23
+ children: React.ReactNode;
24
+ }) {
25
+ const norm = (v: string | string[] | undefined) =>
26
+ v === undefined ? [] : Array.isArray(v) ? v : [v];
27
+ const [internal, setInternal] = React.useState<string[]>(norm(defaultValue));
28
+ const value = controlled !== undefined ? norm(controlled) : internal;
29
+ const toggle = (v: string) => {
30
+ let next: string[];
31
+ if (type === "single") next = value.includes(v) ? [] : [v];
32
+ else next = value.includes(v) ? value.filter((x) => x !== v) : [...value, v];
33
+ if (controlled === undefined) setInternal(next);
34
+ onValueChange?.(type === "single" ? (next[0] ?? "") : next);
35
+ };
36
+ return (
37
+ <ToggleGroupContext.Provider value={{ value, toggle }}>
38
+ <div role="group" className={cn("inline-flex gap-0.5 rounded-md border border-border p-0.5", className)}>
39
+ {children}
40
+ </div>
41
+ </ToggleGroupContext.Provider>
42
+ );
43
+ }
44
+
45
+ export function ToggleGroupItem({
46
+ value,
47
+ className,
48
+ children,
49
+ }: {
50
+ value: string;
51
+ className?: string;
52
+ children: React.ReactNode;
53
+ }) {
54
+ const ctx = React.useContext(ToggleGroupContext);
55
+ const active = ctx?.value.includes(value);
56
+ return (
57
+ <button
58
+ type="button"
59
+ aria-pressed={active}
60
+ data-state={active ? "on" : "off"}
61
+ onClick={() => ctx?.toggle(value)}
62
+ className={cn(
63
+ "inline-flex h-8 min-w-8 items-center justify-center gap-2 rounded px-2.5 text-sm font-medium transition-colors",
64
+ "hover:bg-muted focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring/50",
65
+ "data-[state=on]:bg-secondary data-[state=on]:text-secondary-foreground [&_svg]:size-4",
66
+ className,
67
+ )}
68
+ >
69
+ {children}
70
+ </button>
71
+ );
72
+ }
@@ -1,18 +1,42 @@
1
- "use client";
2
1
  import * as React from "react";
3
2
  import { cn } from "@/lib/utils";
4
3
 
5
- export const Toggle = React.forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement> & { pressed?: boolean }>(
6
- ({ className, pressed, ...props }, ref) => (
4
+ export type ToggleVariant = "default" | "outline";
5
+ export type ToggleSize = "sm" | "default" | "lg";
6
+
7
+ const VARIANTS: Record<ToggleVariant, string> = {
8
+ default: "bg-transparent",
9
+ outline: "border border-input bg-transparent shadow-xs",
10
+ };
11
+
12
+ const SIZES: Record<ToggleSize, string> = {
13
+ sm: "h-8 min-w-8 px-2 text-xs",
14
+ default: "h-9 min-w-9 px-2.5 text-sm",
15
+ lg: "h-10 min-w-10 px-3 text-sm",
16
+ };
17
+
18
+ export interface ToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
19
+ pressed?: boolean;
20
+ variant?: ToggleVariant;
21
+ size?: ToggleSize;
22
+ }
23
+
24
+ export const Toggle = React.forwardRef<HTMLButtonElement, ToggleProps>(
25
+ ({ className, pressed, variant = "default", size = "default", ...props }, ref) => (
7
26
  <button
8
27
  ref={ref}
9
28
  type="button"
10
29
  aria-pressed={pressed}
11
30
  data-state={pressed ? "on" : "off"}
12
31
  className={cn(
13
- "inline-flex h-9 items-center gap-2 rounded-md px-3 text-sm font-medium transition-colors",
14
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-accent)]",
15
- pressed ? "bg-[var(--ui-surface-2)] text-[var(--ui-fg)]" : "hover:bg-[var(--ui-surface-2)]",
32
+ "inline-flex items-center justify-center gap-2 rounded-md font-medium transition-colors",
33
+ "hover:bg-muted hover:text-foreground",
34
+ "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring/50",
35
+ "disabled:pointer-events-none disabled:opacity-50",
36
+ "data-[state=on]:bg-secondary data-[state=on]:text-secondary-foreground",
37
+ "[&_svg]:size-4 [&_svg]:shrink-0",
38
+ VARIANTS[variant],
39
+ SIZES[size],
16
40
  className,
17
41
  )}
18
42
  {...props}
@@ -21,7 +45,7 @@ export const Toggle = React.forwardRef<HTMLButtonElement, React.ButtonHTMLAttrib
21
45
  );
22
46
  Toggle.displayName = "Toggle";
23
47
 
24
- export const ToggleGroup = ({
48
+ export function ToggleGroup({
25
49
  value,
26
50
  onValueChange,
27
51
  children,
@@ -31,16 +55,17 @@ export const ToggleGroup = ({
31
55
  onValueChange: (v: string) => void;
32
56
  children: React.ReactNode;
33
57
  className?: string;
34
- }) => (
35
- <div role="group" className={cn("inline-flex rounded-md border border-[var(--ui-border)] p-0.5", className)}>
36
- {React.Children.map(children, (child) => {
37
- if (!React.isValidElement(child)) return child;
38
- const c = child as React.ReactElement<{ value: string; pressed?: boolean; onClick?: () => void }>;
39
- const active = c.props.value === value;
40
- return React.cloneElement(c, {
41
- pressed: active,
42
- onClick: () => onValueChange(c.props.value),
43
- });
44
- })}
45
- </div>
46
- );
58
+ }) {
59
+ return (
60
+ <div role="group" className={cn("inline-flex gap-0.5 rounded-md border border-border p-0.5", className)}>
61
+ {React.Children.map(children, (child) => {
62
+ if (!React.isValidElement(child)) return child;
63
+ const c = child as React.ReactElement<{ value: string; pressed?: boolean; onClick?: () => void }>;
64
+ return React.cloneElement(c, {
65
+ pressed: c.props.value === value,
66
+ onClick: () => onValueChange(c.props.value),
67
+ });
68
+ })}
69
+ </div>
70
+ );
71
+ }
@@ -14,7 +14,7 @@ export function TooltipProvider({ children }: { children: React.ReactNode }) {
14
14
  {children}
15
15
  {label ? (
16
16
  <div
17
- className="pointer-events-none fixed z-50 -translate-x-1/2 -translate-y-full rounded-md bg-[var(--ui-fg)] px-2 py-1 text-xs text-[var(--ui-bg)] shadow-md"
17
+ className="pointer-events-none fixed z-50 -translate-x-1/2 -translate-y-full rounded-md bg-primary px-2 py-1 text-xs text-primary-foreground shadow-md"
18
18
  style={{ left: pos.x, top: pos.y - 6 }}
19
19
  >
20
20
  {label}
@@ -66,5 +66,7 @@ export function Tooltip({
66
66
  children: React.ReactElement;
67
67
  label: string;
68
68
  }) {
69
- return React.cloneElement(children, { "data-tooltip": label });
69
+ return React.cloneElement(children as React.ReactElement<Record<string, unknown>>, {
70
+ "data-tooltip": label,
71
+ });
70
72
  }