@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/tabs.tsx
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
5
|
+
|
|
6
|
+
import { cn } from "./utils.js";
|
|
7
|
+
|
|
8
|
+
const Tabs = TabsPrimitive.Root;
|
|
9
|
+
|
|
10
|
+
const TabsList = React.forwardRef<
|
|
11
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
12
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
13
|
+
>(({ className, ...props }, ref) => (
|
|
14
|
+
<TabsPrimitive.List
|
|
15
|
+
ref={ref}
|
|
16
|
+
className={cn("inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", className)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
));
|
|
20
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
21
|
+
|
|
22
|
+
const TabsTrigger = React.forwardRef<
|
|
23
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
24
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
25
|
+
>(({ className, ...props }, ref) => (
|
|
26
|
+
<TabsPrimitive.Trigger
|
|
27
|
+
ref={ref}
|
|
28
|
+
className={cn(
|
|
29
|
+
"inline-flex items-center justify-center rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap ring-offset-background transition-all focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
));
|
|
35
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
36
|
+
|
|
37
|
+
const TabsContent = React.forwardRef<
|
|
38
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
39
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
40
|
+
>(({ className, ...props }, ref) => (
|
|
41
|
+
<TabsPrimitive.Content
|
|
42
|
+
ref={ref}
|
|
43
|
+
className={cn(
|
|
44
|
+
"mt-2 ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none",
|
|
45
|
+
className,
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
));
|
|
50
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
51
|
+
|
|
52
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
package/src/textarea.tsx
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "./utils.js";
|
|
4
|
+
|
|
5
|
+
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
|
|
6
|
+
({ className, ...props }, ref) => {
|
|
7
|
+
return (
|
|
8
|
+
<textarea
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
ref={ref}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
Textarea.displayName = "Textarea";
|
|
20
|
+
|
|
21
|
+
export { Textarea };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
|
|
4
|
+
import { cn } from "./utils.js";
|
|
5
|
+
|
|
6
|
+
// No shadcn registry item exists for typography (docs-only class
|
|
7
|
+
// recipes), so these two thin wrappers carry the documented styles.
|
|
8
|
+
// They exist so consumers can ban raw h1-h6/p tags entirely.
|
|
9
|
+
|
|
10
|
+
const headingVariants = cva("scroll-m-20 tracking-tight", {
|
|
11
|
+
variants: {
|
|
12
|
+
level: {
|
|
13
|
+
1: "text-4xl font-extrabold lg:text-5xl",
|
|
14
|
+
2: "text-3xl font-semibold",
|
|
15
|
+
3: "text-2xl font-semibold",
|
|
16
|
+
4: "text-xl font-semibold",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
level: 1,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export type HeadingProps = React.HTMLAttributes<HTMLHeadingElement> &
|
|
25
|
+
VariantProps<typeof headingVariants> & {
|
|
26
|
+
/** Heading level: picks the default tag (`h1`-`h4`) and size. */
|
|
27
|
+
level?: 1 | 2 | 3 | 4;
|
|
28
|
+
/** Tag override when the outline needs a different element. */
|
|
29
|
+
as?: "h1" | "h2" | "h3" | "h4" | "div";
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function Heading({ level = 1, as, className, ...props }: HeadingProps) {
|
|
33
|
+
const Tag = (as ?? `h${level}`) as "h1";
|
|
34
|
+
return <Tag className={cn(headingVariants({ level }), className)} {...props} />;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const textVariants = cva("leading-7", {
|
|
38
|
+
variants: {
|
|
39
|
+
variant: {
|
|
40
|
+
body: "",
|
|
41
|
+
muted: "text-sm text-muted-foreground",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
defaultVariants: {
|
|
45
|
+
variant: "body",
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export type TextProps = React.HTMLAttributes<HTMLParagraphElement> &
|
|
50
|
+
VariantProps<typeof textVariants> & {
|
|
51
|
+
/** Tag override (`p` by default). */
|
|
52
|
+
as?: "p" | "span" | "div";
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function Text({ variant, as: Tag = "p", className, ...props }: TextProps) {
|
|
56
|
+
return <Tag className={cn(textVariants({ variant }), className)} {...props} />;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { Heading, headingVariants, Text, textVariants };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
+
import {
|
|
5
|
+
useForm,
|
|
6
|
+
type FieldValues,
|
|
7
|
+
type Resolver,
|
|
8
|
+
type UseFormProps,
|
|
9
|
+
type UseFormReturn,
|
|
10
|
+
} from "react-hook-form";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validated form in one call: the zod schema is the single source of
|
|
14
|
+
* truth (in a real app it lives in the shared contracts package next
|
|
15
|
+
* to the API validators) and the resolver wiring stays inside the
|
|
16
|
+
* kit, so consumers never import the resolver library directly.
|
|
17
|
+
*/
|
|
18
|
+
export function useValidatedForm<TFieldValues extends FieldValues>(
|
|
19
|
+
schema: Parameters<typeof zodResolver>[0],
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- mirrors useForm's own context type (any)
|
|
21
|
+
options: Omit<UseFormProps<TFieldValues, any>, "resolver"> = {},
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- mirrors useForm's own context type (any)
|
|
23
|
+
): UseFormReturn<TFieldValues, any, TFieldValues> {
|
|
24
|
+
// The resolver is exact for this schema, but react-hook-form's
|
|
25
|
+
// Resolver is invariant in its options type, so a schema-typed
|
|
26
|
+
// resolver cannot flow through the generic untouched (same
|
|
27
|
+
// upstream-invariance situation as the data-table columns).
|
|
28
|
+
const resolver = zodResolver(schema) as Resolver<TFieldValues>;
|
|
29
|
+
return useForm<TFieldValues>({ ...options, resolver });
|
|
30
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Merges class names, with tailwind-merge resolving conflicting
|
|
6
|
+
* Tailwind utilities (last one wins). Every component takes an
|
|
7
|
+
* optional className that flows through here.
|
|
8
|
+
*/
|
|
9
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
10
|
+
return twMerge(clsx(inputs));
|
|
11
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CheckIcon } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "./utils.js";
|
|
4
|
+
|
|
5
|
+
export interface WizardStepsProps {
|
|
6
|
+
steps: string[];
|
|
7
|
+
/** 0-based index of the current step. */
|
|
8
|
+
current: number;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function WizardSteps({ steps, current, className }: WizardStepsProps) {
|
|
13
|
+
return (
|
|
14
|
+
<ol className={cn("flex flex-wrap items-center gap-x-3 gap-y-2", className)} aria-label="Progress">
|
|
15
|
+
{steps.map((step, index) => (
|
|
16
|
+
<li
|
|
17
|
+
key={step}
|
|
18
|
+
aria-current={index === current ? "step" : undefined}
|
|
19
|
+
className="flex items-center gap-2 text-sm"
|
|
20
|
+
>
|
|
21
|
+
<span
|
|
22
|
+
aria-hidden
|
|
23
|
+
className={cn(
|
|
24
|
+
"flex size-6 items-center justify-center rounded-full border text-xs font-medium",
|
|
25
|
+
index < current && "border-primary bg-primary text-primary-foreground",
|
|
26
|
+
index === current && "border-primary text-primary",
|
|
27
|
+
index > current && "text-muted-foreground",
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
{index < current ? <CheckIcon className="size-3.5" /> : index + 1}
|
|
31
|
+
</span>
|
|
32
|
+
<span className={cn(index === current ? "font-medium" : "text-muted-foreground")}>{step}</span>
|
|
33
|
+
{index < steps.length - 1 ? (
|
|
34
|
+
<span aria-hidden className="ml-1 h-px w-6 bg-border" />
|
|
35
|
+
) : null}
|
|
36
|
+
</li>
|
|
37
|
+
))}
|
|
38
|
+
</ol>
|
|
39
|
+
);
|
|
40
|
+
}
|