@zedtech/zedkit-ui 0.0.0-stage → 0.1.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/src/layout.tsx ADDED
@@ -0,0 +1,69 @@
1
+ import type * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "./utils.js";
5
+ import { Heading } from "./typography.js";
6
+ import { Text } from "./typography.js";
7
+
8
+ // No shadcn source exists for layout stacks/sections, so these two
9
+ // thin wrappers carry the documented spacing patterns. They exist so
10
+ // consumers can replace raw <section>/<div> stacks entirely.
11
+
12
+ const stackVariants = cva("flex", {
13
+ variants: {
14
+ direction: {
15
+ vertical: "flex-col",
16
+ horizontal: "flex-row items-center",
17
+ },
18
+ gap: {
19
+ 1: "gap-1",
20
+ 2: "gap-2",
21
+ 3: "gap-3",
22
+ 4: "gap-4",
23
+ 6: "gap-6",
24
+ 8: "gap-8",
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ direction: "vertical",
29
+ gap: 4,
30
+ },
31
+ });
32
+
33
+ export type StackProps = React.HTMLAttributes<HTMLDivElement> &
34
+ VariantProps<typeof stackVariants> & {
35
+ direction?: "vertical" | "horizontal";
36
+ gap?: 1 | 2 | 3 | 4 | 6 | 8;
37
+ };
38
+
39
+ function Stack({ direction, gap, className, ...props }: StackProps) {
40
+ return <div className={cn(stackVariants({ direction, gap }), className)} {...props} />;
41
+ }
42
+
43
+ export type SectionProps = React.HTMLAttributes<HTMLElement> & {
44
+ /** Section title, rendered as a level-2 heading. */
45
+ title?: React.ReactNode;
46
+ /** Muted description under the title. */
47
+ description?: React.ReactNode;
48
+ /** Actions row, right-aligned next to the title. */
49
+ actions?: React.ReactNode;
50
+ };
51
+
52
+ function Section({ title, description, actions, children, className, ...props }: SectionProps) {
53
+ return (
54
+ <section className={cn("space-y-4", className)} {...props}>
55
+ {(title || actions) && (
56
+ <div className="flex items-start justify-between gap-4">
57
+ <div>
58
+ {title && <Heading level={2}>{title}</Heading>}
59
+ {description && <Text variant="muted">{description}</Text>}
60
+ </div>
61
+ {actions}
62
+ </div>
63
+ )}
64
+ {children}
65
+ </section>
66
+ );
67
+ }
68
+
69
+ export { Stack, stackVariants, Section };
@@ -0,0 +1,27 @@
1
+ import { cn } from "./utils.js";
2
+
3
+ export interface LivePulseProps {
4
+ active?: boolean;
5
+ label?: string;
6
+ className?: string;
7
+ }
8
+
9
+ export function LivePulse({ active = true, label = "Live", className }: LivePulseProps) {
10
+ return (
11
+ <span
12
+ role="status"
13
+ aria-label={active ? label : "Idle"}
14
+ className={cn("inline-flex items-center gap-1.5 text-xs text-muted-foreground", className)}
15
+ >
16
+ <span className="relative flex size-2">
17
+ {active ? (
18
+ <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-success opacity-60" />
19
+ ) : null}
20
+ <span
21
+ className={cn("relative inline-flex size-2 rounded-full", active ? "bg-success" : "bg-muted-foreground/40")}
22
+ />
23
+ </span>
24
+ {active ? label : "Idle"}
25
+ </span>
26
+ );
27
+ }
@@ -0,0 +1,47 @@
1
+ import type * as React from "react";
2
+
3
+ import { cn } from "./utils.js";
4
+
5
+ export interface OptionCard {
6
+ id: string;
7
+ title: React.ReactNode;
8
+ description?: React.ReactNode;
9
+ }
10
+
11
+ export interface OptionCardsProps {
12
+ options: OptionCard[];
13
+ selected: string[];
14
+ onChange: (ids: string[]) => void;
15
+ label?: string;
16
+ className?: string;
17
+ }
18
+
19
+ export function OptionCards({ options, selected, onChange, label = "Options", className }: OptionCardsProps) {
20
+ function toggle(id: string): void {
21
+ onChange(selected.includes(id) ? selected.filter((entry) => entry !== id) : [...selected, id]);
22
+ }
23
+
24
+ return (
25
+ <div role="group" aria-label={label} className={cn("grid gap-3 sm:grid-cols-2", className)}>
26
+ {options.map((option) => (
27
+ <label
28
+ key={option.id}
29
+ className="flex cursor-pointer gap-3 rounded-lg border p-4 transition-colors hover:border-muted-foreground/40 has-[:checked]:border-primary has-[:checked]:bg-primary/5 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring"
30
+ >
31
+ <input
32
+ type="checkbox"
33
+ className="sr-only"
34
+ checked={selected.includes(option.id)}
35
+ onChange={() => toggle(option.id)}
36
+ />
37
+ <span>
38
+ <span className="block font-medium">{option.title}</span>
39
+ {option.description ? (
40
+ <span className="block text-sm text-muted-foreground">{option.description}</span>
41
+ ) : null}
42
+ </span>
43
+ </label>
44
+ ))}
45
+ </div>
46
+ );
47
+ }
@@ -0,0 +1,22 @@
1
+ import type * as React from "react";
2
+
3
+ import { cn } from "./utils.js";
4
+
5
+ export interface PageHeaderProps {
6
+ title: React.ReactNode;
7
+ description?: React.ReactNode;
8
+ actions?: React.ReactNode;
9
+ className?: string;
10
+ }
11
+
12
+ export function PageHeader({ title, description, actions, className }: PageHeaderProps) {
13
+ return (
14
+ <div className={cn("mb-6 flex flex-wrap items-start justify-between gap-4", className)}>
15
+ <div className="space-y-1">
16
+ <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
17
+ {description ? <p className="text-sm text-muted-foreground">{description}</p> : null}
18
+ </div>
19
+ {actions ? <div className="flex items-center gap-2">{actions}</div> : null}
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Check, Copy } from "lucide-react";
5
+ import { Button } from "./button.js";
6
+ import { Input } from "./input.js";
7
+
8
+ export interface SecretFieldProps {
9
+ /** Element id, so a Label can point at it. */
10
+ id?: string;
11
+ /** The secret text (shown once by the caller, never logged). */
12
+ value: string;
13
+ /** Accessible label for the copy button. */
14
+ copyLabel?: string;
15
+ /**
16
+ * Accessible name for the value input. Required unless an external
17
+ * Label points at `id` — every input must have a label.
18
+ */
19
+ "aria-label"?: string;
20
+ }
21
+
22
+ /**
23
+ * Shown-once secret with a copy button: read-only value, clipboard
24
+ * on click, check feedback for 2s. Falls back to no-op when the
25
+ * clipboard API is unavailable (insecure contexts).
26
+ */
27
+ function SecretField({ id, value, copyLabel = "Copy to clipboard", "aria-label": ariaLabel }: SecretFieldProps) {
28
+ const [copied, setCopied] = React.useState(false);
29
+ const copy = (): void => {
30
+ const clipboard = navigator.clipboard;
31
+ if (!clipboard) return;
32
+ clipboard.writeText(value).then(
33
+ () => {
34
+ setCopied(true);
35
+ setTimeout(() => setCopied(false), 2000);
36
+ },
37
+ () => {},
38
+ );
39
+ };
40
+ return (
41
+ <div className="flex w-full items-center gap-2">
42
+ <Input id={id} readOnly value={value} className="font-mono" aria-label={ariaLabel} />
43
+ <Button type="button" variant="outline" size="icon" aria-label={copyLabel} onClick={copy}>
44
+ {copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
45
+ </Button>
46
+ </div>
47
+ );
48
+ }
49
+
50
+ export { SecretField };
package/src/select.tsx ADDED
@@ -0,0 +1,146 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SelectPrimitive from "@radix-ui/react-select";
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react";
6
+
7
+ import { cn } from "./utils.js";
8
+
9
+ const Select = SelectPrimitive.Root;
10
+
11
+ const SelectGroup = SelectPrimitive.Group;
12
+
13
+ const SelectValue = SelectPrimitive.Value;
14
+
15
+ const SelectTrigger = React.forwardRef<
16
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
17
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
+ >(({ className, children, ...props }, ref) => (
19
+ <SelectPrimitive.Trigger
20
+ ref={ref}
21
+ className={cn(
22
+ "flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-sm ring-offset-background focus:ring-1 focus:ring-ring focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground [&>span]:line-clamp-1",
23
+ className,
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ <SelectPrimitive.Icon asChild>
29
+ <ChevronDown className="h-4 w-4 opacity-50" />
30
+ </SelectPrimitive.Icon>
31
+ </SelectPrimitive.Trigger>
32
+ ));
33
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
34
+
35
+ const SelectScrollUpButton = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
38
+ >(({ className, ...props }, ref) => (
39
+ <SelectPrimitive.ScrollUpButton
40
+ ref={ref}
41
+ className={cn("flex cursor-default items-center justify-center py-1", className)}
42
+ {...props}
43
+ >
44
+ <ChevronUp className="h-4 w-4" />
45
+ </SelectPrimitive.ScrollUpButton>
46
+ ));
47
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
48
+
49
+ const SelectScrollDownButton = React.forwardRef<
50
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
51
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
52
+ >(({ className, ...props }, ref) => (
53
+ <SelectPrimitive.ScrollDownButton
54
+ ref={ref}
55
+ className={cn("flex cursor-default items-center justify-center py-1", className)}
56
+ {...props}
57
+ >
58
+ <ChevronDown className="h-4 w-4" />
59
+ </SelectPrimitive.ScrollDownButton>
60
+ ));
61
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
62
+
63
+ const SelectContent = React.forwardRef<
64
+ React.ElementRef<typeof SelectPrimitive.Content>,
65
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
66
+ >(({ className, children, position = "popper", ...props }, ref) => (
67
+ <SelectPrimitive.Portal>
68
+ <SelectPrimitive.Content
69
+ ref={ref}
70
+ className={cn(
71
+ // No animate/fade/zoom/slide utilities: the kit ships without
72
+ // the animation plugin (same as dialog), where they would be
73
+ // dead classes. Kept: positioning, overflow, theme, popper offsets.
74
+ "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] origin-[--radix-select-content-transform-origin] overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md",
75
+ position === "popper" &&
76
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
77
+ className,
78
+ )}
79
+ position={position}
80
+ {...props}
81
+ >
82
+ <SelectScrollUpButton />
83
+ <SelectPrimitive.Viewport
84
+ className={cn(
85
+ "p-1",
86
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
87
+ )}
88
+ >
89
+ {children}
90
+ </SelectPrimitive.Viewport>
91
+ <SelectScrollDownButton />
92
+ </SelectPrimitive.Content>
93
+ </SelectPrimitive.Portal>
94
+ ));
95
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
96
+
97
+ const SelectLabel = React.forwardRef<
98
+ React.ElementRef<typeof SelectPrimitive.Label>,
99
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
100
+ >(({ className, ...props }, ref) => (
101
+ <SelectPrimitive.Label ref={ref} className={cn("px-2 py-1.5 text-sm font-semibold", className)} {...props} />
102
+ ));
103
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
104
+
105
+ const SelectItem = React.forwardRef<
106
+ React.ElementRef<typeof SelectPrimitive.Item>,
107
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
108
+ >(({ className, children, ...props }, ref) => (
109
+ <SelectPrimitive.Item
110
+ ref={ref}
111
+ className={cn(
112
+ "relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-8 pl-2 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
113
+ className,
114
+ )}
115
+ {...props}
116
+ >
117
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
118
+ <SelectPrimitive.ItemIndicator>
119
+ <Check className="h-4 w-4" />
120
+ </SelectPrimitive.ItemIndicator>
121
+ </span>
122
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
123
+ </SelectPrimitive.Item>
124
+ ));
125
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
126
+
127
+ const SelectSeparator = React.forwardRef<
128
+ React.ElementRef<typeof SelectPrimitive.Separator>,
129
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
130
+ >(({ className, ...props }, ref) => (
131
+ <SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
132
+ ));
133
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
134
+
135
+ export {
136
+ Select,
137
+ SelectGroup,
138
+ SelectValue,
139
+ SelectTrigger,
140
+ SelectContent,
141
+ SelectLabel,
142
+ SelectItem,
143
+ SelectSeparator,
144
+ SelectScrollUpButton,
145
+ SelectScrollDownButton,
146
+ };
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
5
+ import { cn } from "./utils.js";
6
+
7
+ const Separator = React.forwardRef<
8
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
10
+ >(
11
+ (
12
+ { className, orientation = "horizontal", decorative = true, ...props },
13
+ ref,
14
+ ) => (
15
+ <SeparatorPrimitive.Root
16
+ ref={ref}
17
+ decorative={decorative}
18
+ orientation={orientation}
19
+ className={cn(
20
+ "shrink-0 bg-border",
21
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
22
+ className,
23
+ )}
24
+ {...props}
25
+ />
26
+ ),
27
+ );
28
+ Separator.displayName = SeparatorPrimitive.Root.displayName;
29
+
30
+ export { Separator };
@@ -0,0 +1,7 @@
1
+ import { cn } from "./utils.js";
2
+
3
+ function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
4
+ return <div className={cn("animate-pulse rounded-md bg-muted", className)} {...props} />;
5
+ }
6
+
7
+ export { Skeleton };
package/src/sonner.tsx ADDED
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ import { Toaster as Sonner, type ToasterProps } from "sonner";
4
+
5
+ /**
6
+ * Themed toast viewport: render once near the app root, then call
7
+ * sonner's `toast()` anywhere. Styling follows the theme tokens.
8
+ */
9
+ function Toaster(props: ToasterProps) {
10
+ return (
11
+ <Sonner
12
+ toastOptions={{
13
+ classNames: {
14
+ toast: "bg-background text-foreground border-border",
15
+ description: "text-muted-foreground",
16
+ actionButton: "bg-primary text-primary-foreground",
17
+ cancelButton: "bg-muted text-muted-foreground",
18
+ },
19
+ }}
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ export { Toaster };
package/src/styles.css ADDED
@@ -0,0 +1,117 @@
1
+ @import "tailwindcss";
2
+
3
+ /*
4
+ * zedkit admin theme: neutral, calm, readable. Dark/light via the
5
+ * `.dark` class on <html> (next-themes `attribute="class"`).
6
+ * Consumers import this file once (see README) and add one `@source`
7
+ * line so Tailwind scans the components. System font stack on
8
+ * purpose: no font dependency, no layout shift.
9
+ */
10
+ :root {
11
+ --background: 0 0% 100%;
12
+ --foreground: 240 10% 3.9%;
13
+ --card: 0 0% 100%;
14
+ --card-foreground: 240 10% 3.9%;
15
+ --popover: 0 0% 100%;
16
+ --popover-foreground: 240 10% 3.9%;
17
+ --primary: 240 5.9% 10%;
18
+ --primary-foreground: 0 0% 98%;
19
+ --secondary: 240 4.8% 95.9%;
20
+ --secondary-foreground: 240 5.9% 10%;
21
+ --muted: 240 4.8% 95.9%;
22
+ --muted-foreground: 240 3.8% 46.1%;
23
+ --accent: 240 4.8% 95.9%;
24
+ --accent-foreground: 240 5.9% 10%;
25
+ --destructive: 0 84.2% 60.2%;
26
+ --destructive-foreground: 0 0% 98%;
27
+ --success: 142.1 76.2% 36.3%;
28
+ --success-foreground: 0 0% 98%;
29
+ --warning: 32 95% 44%;
30
+ --warning-foreground: 0 0% 98%;
31
+ --overlay: 0 0% 0%;
32
+ --border: 240 5.9% 90%;
33
+ --input: 240 5.9% 90%;
34
+ --ring: 240 5% 64.9%;
35
+ --radius: 0.5rem;
36
+ --sidebar: 0 0% 98%;
37
+ --sidebar-foreground: 240 5.9% 10%;
38
+ --sidebar-border: 240 5.9% 90%;
39
+ --sidebar-accent: 240 4.8% 95.9%;
40
+ }
41
+
42
+ .dark {
43
+ --background: 240 10% 3.9%;
44
+ --foreground: 0 0% 98%;
45
+ --card: 240 10% 3.9%;
46
+ --card-foreground: 0 0% 98%;
47
+ --popover: 240 10% 3.9%;
48
+ --popover-foreground: 0 0% 98%;
49
+ --primary: 0 0% 98%;
50
+ --primary-foreground: 240 5.9% 10%;
51
+ --secondary: 240 3.7% 15.9%;
52
+ --secondary-foreground: 0 0% 98%;
53
+ --muted: 240 3.7% 15.9%;
54
+ --muted-foreground: 240 5% 64.9%;
55
+ --accent: 240 3.7% 15.9%;
56
+ --accent-foreground: 0 0% 98%;
57
+ --destructive: 0 62.8% 50.6%;
58
+ --destructive-foreground: 0 0% 98%;
59
+ --success: 142.1 70.6% 45.3%;
60
+ --success-foreground: 240 10% 3.9%;
61
+ --warning: 38 92% 50%;
62
+ --warning-foreground: 240 10% 3.9%;
63
+ --overlay: 0 0% 0%;
64
+ --border: 240 3.7% 15.9%;
65
+ --input: 240 3.7% 15.9%;
66
+ --ring: 240 4.9% 83.9%;
67
+ --sidebar: 240 10% 3.9%;
68
+ --sidebar-foreground: 0 0% 98%;
69
+ --sidebar-border: 240 3.7% 15.9%;
70
+ --sidebar-accent: 240 3.7% 15.9%;
71
+ }
72
+
73
+ @theme inline {
74
+ --color-background: hsl(var(--background));
75
+ --color-foreground: hsl(var(--foreground));
76
+ --color-card: hsl(var(--card));
77
+ --color-card-foreground: hsl(var(--card-foreground));
78
+ --color-popover: hsl(var(--popover));
79
+ --color-popover-foreground: hsl(var(--popover-foreground));
80
+ --color-primary: hsl(var(--primary));
81
+ --color-primary-foreground: hsl(var(--primary-foreground));
82
+ --color-secondary: hsl(var(--secondary));
83
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
84
+ --color-muted: hsl(var(--muted));
85
+ --color-muted-foreground: hsl(var(--muted-foreground));
86
+ --color-accent: hsl(var(--accent));
87
+ --color-accent-foreground: hsl(var(--accent-foreground));
88
+ --color-destructive: hsl(var(--destructive));
89
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
90
+ --color-success: hsl(var(--success));
91
+ --color-success-foreground: hsl(var(--success-foreground));
92
+ --color-warning: hsl(var(--warning));
93
+ --color-warning-foreground: hsl(var(--warning-foreground));
94
+ --color-overlay: hsl(var(--overlay));
95
+ --color-border: hsl(var(--border));
96
+ --color-input: hsl(var(--input));
97
+ --color-ring: hsl(var(--ring));
98
+ --color-sidebar: hsl(var(--sidebar));
99
+ --color-sidebar-foreground: hsl(var(--sidebar-foreground));
100
+ --color-sidebar-border: hsl(var(--sidebar-border));
101
+ --color-sidebar-accent: hsl(var(--sidebar-accent));
102
+ --radius-lg: var(--radius);
103
+ --radius-md: calc(var(--radius) - 2px);
104
+ --radius-sm: calc(var(--radius) - 4px);
105
+ }
106
+
107
+ @layer base {
108
+ * {
109
+ border-color: var(--color-border);
110
+ }
111
+ body {
112
+ background-color: var(--color-background);
113
+ color: var(--color-foreground);
114
+ font-family:
115
+ ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
116
+ }
117
+ }
package/src/switch.tsx ADDED
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SwitchPrimitives from "@radix-ui/react-switch";
5
+
6
+ import { cn } from "./utils.js";
7
+
8
+ const Switch = React.forwardRef<
9
+ React.ElementRef<typeof SwitchPrimitives.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <SwitchPrimitives.Root
13
+ className={cn(
14
+ "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
15
+ className,
16
+ )}
17
+ {...props}
18
+ ref={ref}
19
+ >
20
+ <SwitchPrimitives.Thumb
21
+ className={cn(
22
+ "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
23
+ )}
24
+ />
25
+ </SwitchPrimitives.Root>
26
+ ));
27
+ Switch.displayName = SwitchPrimitives.Root.displayName;
28
+
29
+ export { Switch };
package/src/table.tsx ADDED
@@ -0,0 +1,68 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "./utils.js";
4
+
5
+ function Table({ className, ...props }: React.ComponentProps<"table">) {
6
+ return (
7
+ <div data-slot="table-container" className="relative w-full overflow-x-auto">
8
+ <table data-slot="table" className={cn("w-full caption-bottom text-sm", className)} {...props} />
9
+ </div>
10
+ );
11
+ }
12
+
13
+ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
14
+ return <thead data-slot="table-header" className={cn("[&_tr]:border-b", className)} {...props} />;
15
+ }
16
+
17
+ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
18
+ return <tbody data-slot="table-body" className={cn("[&_tr:last-child]:border-0", className)} {...props} />;
19
+ }
20
+
21
+ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
22
+ return (
23
+ <tfoot
24
+ data-slot="table-footer"
25
+ className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
26
+ {...props}
27
+ />
28
+ );
29
+ }
30
+
31
+ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
32
+ return (
33
+ <tr
34
+ data-slot="table-row"
35
+ className={cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className)}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
42
+ return (
43
+ <th
44
+ data-slot="table-head"
45
+ className={cn(
46
+ "h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
47
+ className,
48
+ )}
49
+ {...props}
50
+ />
51
+ );
52
+ }
53
+
54
+ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
55
+ return (
56
+ <td
57
+ data-slot="table-cell"
58
+ className={cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className)}
59
+ {...props}
60
+ />
61
+ );
62
+ }
63
+
64
+ function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
65
+ return <caption data-slot="table-caption" className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />;
66
+ }
67
+
68
+ export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };