@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/dialog.tsx
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
5
|
+
import { X } from "lucide-react";
|
|
6
|
+
import { cn } from "./utils.js";
|
|
7
|
+
|
|
8
|
+
const Dialog = DialogPrimitive.Root;
|
|
9
|
+
const DialogTrigger = DialogPrimitive.Trigger;
|
|
10
|
+
const DialogClose = DialogPrimitive.Close;
|
|
11
|
+
const DialogPortal = DialogPrimitive.Portal;
|
|
12
|
+
|
|
13
|
+
const DialogOverlay = React.forwardRef<
|
|
14
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
15
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
16
|
+
>(({ className, ...props }, ref) => (
|
|
17
|
+
<DialogPrimitive.Overlay
|
|
18
|
+
ref={ref}
|
|
19
|
+
className={cn("fixed inset-0 z-50 bg-overlay/80", className)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
));
|
|
23
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
24
|
+
|
|
25
|
+
const DialogContent = React.forwardRef<
|
|
26
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
27
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
28
|
+
>(({ className, children, ...props }, ref) => (
|
|
29
|
+
<DialogPortal>
|
|
30
|
+
<DialogOverlay />
|
|
31
|
+
<DialogPrimitive.Content
|
|
32
|
+
ref={ref}
|
|
33
|
+
className={cn(
|
|
34
|
+
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg sm:rounded-lg",
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
|
41
|
+
<X className="h-4 w-4" />
|
|
42
|
+
<span className="sr-only">Close</span>
|
|
43
|
+
</DialogPrimitive.Close>
|
|
44
|
+
</DialogPrimitive.Content>
|
|
45
|
+
</DialogPortal>
|
|
46
|
+
));
|
|
47
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
48
|
+
|
|
49
|
+
function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const DialogTitle = React.forwardRef<
|
|
68
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
69
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
70
|
+
>(({ className, ...props }, ref) => (
|
|
71
|
+
<DialogPrimitive.Title
|
|
72
|
+
ref={ref}
|
|
73
|
+
className={cn("text-lg leading-none font-semibold tracking-tight", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
));
|
|
77
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
78
|
+
|
|
79
|
+
const DialogDescription = React.forwardRef<
|
|
80
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
81
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
82
|
+
>(({ className, ...props }, ref) => (
|
|
83
|
+
<DialogPrimitive.Description
|
|
84
|
+
ref={ref}
|
|
85
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
));
|
|
89
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
Dialog,
|
|
93
|
+
DialogClose,
|
|
94
|
+
DialogContent,
|
|
95
|
+
DialogDescription,
|
|
96
|
+
DialogFooter,
|
|
97
|
+
DialogHeader,
|
|
98
|
+
DialogOverlay,
|
|
99
|
+
DialogPortal,
|
|
100
|
+
DialogTitle,
|
|
101
|
+
DialogTrigger,
|
|
102
|
+
};
|
package/src/field.tsx
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type * as React from "react";
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
6
|
+
|
|
7
|
+
import { cn } from "./utils.js";
|
|
8
|
+
import { Label } from "./label.js";
|
|
9
|
+
import { Separator } from "./separator.js";
|
|
10
|
+
|
|
11
|
+
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
|
12
|
+
return (
|
|
13
|
+
<fieldset
|
|
14
|
+
data-slot="field-set"
|
|
15
|
+
className={cn(
|
|
16
|
+
"flex flex-col gap-6",
|
|
17
|
+
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function FieldLegend({
|
|
26
|
+
className,
|
|
27
|
+
variant = "legend",
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
|
|
30
|
+
return (
|
|
31
|
+
<legend
|
|
32
|
+
data-slot="field-legend"
|
|
33
|
+
data-variant={variant}
|
|
34
|
+
className={cn(
|
|
35
|
+
"mb-3 font-medium",
|
|
36
|
+
"data-[variant=legend]:text-base",
|
|
37
|
+
"data-[variant=label]:text-sm",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
data-slot="field-group"
|
|
49
|
+
className={cn(
|
|
50
|
+
"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
|
|
51
|
+
className,
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const fieldVariants = cva("group/field flex w-full gap-3 data-[invalid=true]:text-destructive", {
|
|
59
|
+
variants: {
|
|
60
|
+
orientation: {
|
|
61
|
+
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
|
|
62
|
+
horizontal: [
|
|
63
|
+
"flex-row items-center",
|
|
64
|
+
"[&>[data-slot=field-label]]:flex-auto",
|
|
65
|
+
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
66
|
+
],
|
|
67
|
+
responsive: [
|
|
68
|
+
"flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
|
|
69
|
+
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
|
|
70
|
+
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
defaultVariants: {
|
|
75
|
+
orientation: "vertical",
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function Field({
|
|
80
|
+
className,
|
|
81
|
+
orientation = "vertical",
|
|
82
|
+
...props
|
|
83
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
role="group"
|
|
87
|
+
data-slot="field"
|
|
88
|
+
data-orientation={orientation}
|
|
89
|
+
className={cn(fieldVariants({ orientation }), className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
96
|
+
return (
|
|
97
|
+
<div
|
|
98
|
+
data-slot="field-content"
|
|
99
|
+
className={cn("group/field-content flex flex-1 flex-col gap-1.5 leading-snug", className)}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
|
|
106
|
+
return (
|
|
107
|
+
<Label
|
|
108
|
+
data-slot="field-label"
|
|
109
|
+
className={cn(
|
|
110
|
+
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
|
|
111
|
+
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>[data-slot=field]]:p-4",
|
|
112
|
+
"has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10",
|
|
113
|
+
className,
|
|
114
|
+
)}
|
|
115
|
+
{...props}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
data-slot="field-label"
|
|
124
|
+
className={cn(
|
|
125
|
+
"flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
|
|
126
|
+
className,
|
|
127
|
+
)}
|
|
128
|
+
{...props}
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
134
|
+
return (
|
|
135
|
+
<p
|
|
136
|
+
data-slot="field-description"
|
|
137
|
+
className={cn(
|
|
138
|
+
"text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
|
|
139
|
+
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
|
|
140
|
+
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
141
|
+
className,
|
|
142
|
+
)}
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function FieldSeparator({
|
|
149
|
+
children,
|
|
150
|
+
className,
|
|
151
|
+
...props
|
|
152
|
+
}: React.ComponentProps<"div"> & {
|
|
153
|
+
children?: React.ReactNode;
|
|
154
|
+
}) {
|
|
155
|
+
return (
|
|
156
|
+
<div
|
|
157
|
+
data-slot="field-separator"
|
|
158
|
+
data-content={!!children}
|
|
159
|
+
className={cn("relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2", className)}
|
|
160
|
+
{...props}
|
|
161
|
+
>
|
|
162
|
+
<Separator className="absolute inset-0 top-1/2" />
|
|
163
|
+
{children && (
|
|
164
|
+
<span
|
|
165
|
+
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
|
|
166
|
+
data-slot="field-separator-content"
|
|
167
|
+
>
|
|
168
|
+
{children}
|
|
169
|
+
</span>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function FieldError({
|
|
176
|
+
className,
|
|
177
|
+
children,
|
|
178
|
+
errors,
|
|
179
|
+
...props
|
|
180
|
+
}: React.ComponentProps<"div"> & {
|
|
181
|
+
errors?: Array<{ message?: string } | undefined>;
|
|
182
|
+
}) {
|
|
183
|
+
const content = useMemo(() => {
|
|
184
|
+
if (children) {
|
|
185
|
+
return children;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!errors) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (errors?.length === 1 && errors[0]?.message) {
|
|
193
|
+
return errors[0].message;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<ul className="ml-4 flex list-disc flex-col gap-1">
|
|
198
|
+
{errors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
|
|
199
|
+
</ul>
|
|
200
|
+
);
|
|
201
|
+
}, [children, errors]);
|
|
202
|
+
|
|
203
|
+
if (!content) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div
|
|
209
|
+
role="alert"
|
|
210
|
+
data-slot="field-error"
|
|
211
|
+
className={cn("text-sm font-normal text-destructive", className)}
|
|
212
|
+
{...props}
|
|
213
|
+
>
|
|
214
|
+
{content}
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export {
|
|
220
|
+
Field,
|
|
221
|
+
FieldLabel,
|
|
222
|
+
FieldDescription,
|
|
223
|
+
FieldError,
|
|
224
|
+
FieldGroup,
|
|
225
|
+
FieldLegend,
|
|
226
|
+
FieldSeparator,
|
|
227
|
+
FieldSet,
|
|
228
|
+
FieldContent,
|
|
229
|
+
FieldTitle,
|
|
230
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type * as React from "react";
|
|
4
|
+
import { useFormContext } from "react-hook-form";
|
|
5
|
+
|
|
6
|
+
import { Button, type ButtonProps } from "./button.js";
|
|
7
|
+
|
|
8
|
+
export type FormSubmitProps = Omit<ButtonProps, "type"> & {
|
|
9
|
+
/**
|
|
10
|
+
* Label shown while the form submits. Defaults to `children`.
|
|
11
|
+
* Both come from the caller (message catalog), never hard-coded.
|
|
12
|
+
*/
|
|
13
|
+
pendingLabel?: React.ReactNode;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Submit button wired to the enclosing form: disabled while the
|
|
18
|
+
* submission is in flight, with an optional pending label.
|
|
19
|
+
* Must render inside `<Form>` (uses the form context).
|
|
20
|
+
*/
|
|
21
|
+
export function FormSubmit({ pendingLabel, children, disabled, ...props }: FormSubmitProps) {
|
|
22
|
+
const { formState } = useFormContext();
|
|
23
|
+
const pending = formState.isSubmitting;
|
|
24
|
+
return (
|
|
25
|
+
<Button type="submit" disabled={disabled === true || pending} {...props}>
|
|
26
|
+
{pending && pendingLabel !== undefined ? pendingLabel : children}
|
|
27
|
+
</Button>
|
|
28
|
+
);
|
|
29
|
+
}
|
package/src/form.tsx
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
5
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
6
|
+
import {
|
|
7
|
+
Controller,
|
|
8
|
+
FormProvider,
|
|
9
|
+
useFormContext,
|
|
10
|
+
type ControllerProps,
|
|
11
|
+
type FieldPath,
|
|
12
|
+
type FieldValues,
|
|
13
|
+
} from "react-hook-form";
|
|
14
|
+
|
|
15
|
+
import { cn } from "./utils.js";
|
|
16
|
+
import { Label } from "./label.js";
|
|
17
|
+
|
|
18
|
+
const Form = FormProvider;
|
|
19
|
+
|
|
20
|
+
type FormFieldContextValue<
|
|
21
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
22
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
23
|
+
> = {
|
|
24
|
+
name: TName;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const FormFieldContext = React.createContext<FormFieldContextValue | null>(null);
|
|
28
|
+
|
|
29
|
+
const FormField = <
|
|
30
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
31
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
32
|
+
>({
|
|
33
|
+
...props
|
|
34
|
+
}: ControllerProps<TFieldValues, TName>) => {
|
|
35
|
+
return (
|
|
36
|
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
37
|
+
<Controller {...props} />
|
|
38
|
+
</FormFieldContext.Provider>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const useFormField = () => {
|
|
43
|
+
const fieldContext = React.useContext(FormFieldContext);
|
|
44
|
+
const itemContext = React.useContext(FormItemContext);
|
|
45
|
+
const { getFieldState, formState } = useFormContext();
|
|
46
|
+
|
|
47
|
+
if (!fieldContext) {
|
|
48
|
+
throw new Error("useFormField should be used within <FormField>");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!itemContext) {
|
|
52
|
+
throw new Error("useFormField should be used within <FormItem>");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fieldState = getFieldState(fieldContext.name, formState);
|
|
56
|
+
|
|
57
|
+
const { id } = itemContext;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
id,
|
|
61
|
+
name: fieldContext.name,
|
|
62
|
+
formItemId: `${id}-form-item`,
|
|
63
|
+
formDescriptionId: `${id}-form-item-description`,
|
|
64
|
+
formMessageId: `${id}-form-item-message`,
|
|
65
|
+
...fieldState,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type FormItemContextValue = {
|
|
70
|
+
id: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const FormItemContext = React.createContext<FormItemContextValue | null>(null);
|
|
74
|
+
|
|
75
|
+
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
76
|
+
({ className, ...props }, ref) => {
|
|
77
|
+
const id = React.useId();
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<FormItemContext.Provider value={{ id }}>
|
|
81
|
+
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
|
82
|
+
</FormItemContext.Provider>
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
FormItem.displayName = "FormItem";
|
|
87
|
+
|
|
88
|
+
const FormLabel = React.forwardRef<
|
|
89
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
90
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
|
91
|
+
>(({ className, ...props }, ref) => {
|
|
92
|
+
const { error, formItemId } = useFormField();
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Label
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn(error && "text-destructive", className)}
|
|
98
|
+
htmlFor={formItemId}
|
|
99
|
+
{...props}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
FormLabel.displayName = "FormLabel";
|
|
104
|
+
|
|
105
|
+
const FormControl = React.forwardRef<
|
|
106
|
+
React.ElementRef<typeof Slot>,
|
|
107
|
+
React.ComponentPropsWithoutRef<typeof Slot>
|
|
108
|
+
>(({ ...props }, ref) => {
|
|
109
|
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Slot
|
|
113
|
+
ref={ref}
|
|
114
|
+
id={formItemId}
|
|
115
|
+
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
|
116
|
+
aria-invalid={!!error}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
FormControl.displayName = "FormControl";
|
|
122
|
+
|
|
123
|
+
const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
124
|
+
({ className, ...props }, ref) => {
|
|
125
|
+
const { formDescriptionId } = useFormField();
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<p ref={ref} id={formDescriptionId} className={cn("text-[0.8rem] text-muted-foreground", className)} {...props} />
|
|
129
|
+
);
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
FormDescription.displayName = "FormDescription";
|
|
133
|
+
|
|
134
|
+
const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
135
|
+
({ className, children, ...props }, ref) => {
|
|
136
|
+
const { error, formMessageId } = useFormField();
|
|
137
|
+
const body = error ? String(error?.message ?? "") : children;
|
|
138
|
+
|
|
139
|
+
if (!body) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<p
|
|
145
|
+
ref={ref}
|
|
146
|
+
id={formMessageId}
|
|
147
|
+
className={cn("text-[0.8rem] font-medium text-destructive", className)}
|
|
148
|
+
{...props}
|
|
149
|
+
>
|
|
150
|
+
{body}
|
|
151
|
+
</p>
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
FormMessage.displayName = "FormMessage";
|
|
156
|
+
|
|
157
|
+
export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export { Alert, AlertDescription, AlertTitle } from "./alert.js";
|
|
2
|
+
export { AppShell, type AppShellProps } from "./app-shell.js";
|
|
3
|
+
export { Badge, badgeVariants } from "./badge.js";
|
|
4
|
+
export { Button, buttonVariants, type ButtonProps } from "./button.js";
|
|
5
|
+
export { ConfirmDialog, type ConfirmDialogProps } from "./confirm-dialog.js";
|
|
6
|
+
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./card.js";
|
|
7
|
+
export { Checkbox } from "./checkbox.js";
|
|
8
|
+
export {
|
|
9
|
+
DataTable,
|
|
10
|
+
createDataTableColumns,
|
|
11
|
+
type DataTableColumn,
|
|
12
|
+
type DataTableProps,
|
|
13
|
+
} from "./data-table.js";
|
|
14
|
+
export {
|
|
15
|
+
Dialog,
|
|
16
|
+
DialogClose,
|
|
17
|
+
DialogContent,
|
|
18
|
+
DialogDescription,
|
|
19
|
+
DialogFooter,
|
|
20
|
+
DialogHeader,
|
|
21
|
+
DialogOverlay,
|
|
22
|
+
DialogPortal,
|
|
23
|
+
DialogTitle,
|
|
24
|
+
DialogTrigger,
|
|
25
|
+
} from "./dialog.js";
|
|
26
|
+
export { DetailPanel, type DetailItem, type DetailPanelProps } from "./detail-panel.js";
|
|
27
|
+
export {
|
|
28
|
+
Field,
|
|
29
|
+
FieldContent,
|
|
30
|
+
FieldDescription,
|
|
31
|
+
FieldError,
|
|
32
|
+
FieldGroup,
|
|
33
|
+
FieldLabel,
|
|
34
|
+
FieldLegend,
|
|
35
|
+
FieldSeparator,
|
|
36
|
+
FieldSet,
|
|
37
|
+
FieldTitle,
|
|
38
|
+
} from "./field.js";
|
|
39
|
+
export {
|
|
40
|
+
Form,
|
|
41
|
+
FormControl,
|
|
42
|
+
FormDescription,
|
|
43
|
+
FormField,
|
|
44
|
+
FormItem,
|
|
45
|
+
FormLabel,
|
|
46
|
+
FormMessage,
|
|
47
|
+
useFormField,
|
|
48
|
+
} from "./form.js";
|
|
49
|
+
export { FormSubmit, type FormSubmitProps } from "./form-submit.js";
|
|
50
|
+
export { useValidatedForm } from "./use-validated-form.js";
|
|
51
|
+
export { Input } from "./input.js";
|
|
52
|
+
export { Label } from "./label.js";
|
|
53
|
+
export { Section, Stack, stackVariants, type SectionProps, type StackProps } from "./layout.js";
|
|
54
|
+
export { LivePulse, type LivePulseProps } from "./live-pulse.js";
|
|
55
|
+
export { OptionCards, type OptionCard, type OptionCardsProps } from "./option-cards.js";
|
|
56
|
+
export { PageHeader, type PageHeaderProps } from "./page-header.js";
|
|
57
|
+
export { SecretField, type SecretFieldProps } from "./secret-field.js";
|
|
58
|
+
export {
|
|
59
|
+
Select,
|
|
60
|
+
SelectContent,
|
|
61
|
+
SelectGroup,
|
|
62
|
+
SelectItem,
|
|
63
|
+
SelectLabel,
|
|
64
|
+
SelectScrollDownButton,
|
|
65
|
+
SelectScrollUpButton,
|
|
66
|
+
SelectSeparator,
|
|
67
|
+
SelectTrigger,
|
|
68
|
+
SelectValue,
|
|
69
|
+
} from "./select.js";
|
|
70
|
+
export { Separator } from "./separator.js";
|
|
71
|
+
export { Skeleton } from "./skeleton.js";
|
|
72
|
+
export {
|
|
73
|
+
Table,
|
|
74
|
+
TableBody,
|
|
75
|
+
TableCaption,
|
|
76
|
+
TableCell,
|
|
77
|
+
TableFooter,
|
|
78
|
+
TableHead,
|
|
79
|
+
TableHeader,
|
|
80
|
+
TableRow,
|
|
81
|
+
} from "./table.js";
|
|
82
|
+
export { WizardSteps, type WizardStepsProps } from "./wizard-steps.js";
|
|
83
|
+
export { Switch } from "./switch.js";
|
|
84
|
+
export { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs.js";
|
|
85
|
+
export { Textarea } from "./textarea.js";
|
|
86
|
+
export { Toaster } from "./sonner.js";
|
|
87
|
+
export {
|
|
88
|
+
Heading,
|
|
89
|
+
headingVariants,
|
|
90
|
+
Text,
|
|
91
|
+
textVariants,
|
|
92
|
+
type HeadingProps,
|
|
93
|
+
type TextProps,
|
|
94
|
+
} from "./typography.js";
|
|
95
|
+
export { cn } from "./utils.js";
|
package/src/input.tsx
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
|
|
5
|
+
({ className, type, ...props }, ref) => {
|
|
6
|
+
return (
|
|
7
|
+
<input
|
|
8
|
+
type={type}
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
ref={ref}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
Input.displayName = "Input";
|
|
20
|
+
|
|
21
|
+
export { Input };
|
package/src/label.tsx
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
6
|
+
import { cn } from "./utils.js";
|
|
7
|
+
|
|
8
|
+
const labelVariants = cva(
|
|
9
|
+
"text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const Label = React.forwardRef<
|
|
13
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
|
|
15
|
+
>(({ className, ...props }, ref) => (
|
|
16
|
+
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
|
|
17
|
+
));
|
|
18
|
+
Label.displayName = LabelPrimitive.Root.displayName;
|
|
19
|
+
|
|
20
|
+
export { Label };
|