@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/LICENSE +21 -0
- package/README.md +48 -2
- package/package.json +83 -3
- package/src/alert.tsx +47 -0
- package/src/app-shell.tsx +24 -0
- package/src/badge.tsx +30 -0
- package/src/button.tsx +48 -0
- package/src/card.tsx +47 -0
- package/src/checkbox.tsx +28 -0
- package/src/confirm-dialog.tsx +65 -0
- package/src/data-table.tsx +176 -0
- package/src/detail-panel.tsx +26 -0
- package/src/dialog.tsx +102 -0
- package/src/field.tsx +230 -0
- package/src/form-submit.tsx +29 -0
- package/src/form.tsx +157 -0
- package/src/index.ts +95 -0
- package/src/input.tsx +21 -0
- package/src/label.tsx +20 -0
- package/src/layout.tsx +69 -0
- package/src/live-pulse.tsx +27 -0
- package/src/option-cards.tsx +47 -0
- package/src/page-header.tsx +22 -0
- package/src/secret-field.tsx +50 -0
- package/src/select.tsx +146 -0
- package/src/separator.tsx +30 -0
- package/src/skeleton.tsx +7 -0
- package/src/sonner.tsx +25 -0
- package/src/styles.css +117 -0
- package/src/switch.tsx +29 -0
- package/src/table.tsx +68 -0
- package/src/tabs.tsx +52 -0
- package/src/textarea.tsx +21 -0
- package/src/typography.tsx +59 -0
- package/src/use-validated-form.ts +30 -0
- package/src/utils.ts +11 -0
- package/src/wizard-steps.tsx +40 -0
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 };
|
package/src/skeleton.tsx
ADDED
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 };
|