@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/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 };
@@ -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
+ }