@alpic-ai/ui 0.0.0-dev.4a35dc7 → 0.0.0-dev.85c8341
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/dist/components/accordion-card.d.mts +1 -1
- package/dist/components/accordion.d.mts +1 -1
- package/dist/components/alert.d.mts +1 -1
- package/dist/components/attachment-tile.mjs +1 -1
- package/dist/components/avatar.d.mts +1 -1
- package/dist/components/breadcrumb.d.mts +1 -1
- package/dist/components/button.d.mts +1 -1
- package/dist/components/card.d.mts +1 -1
- package/dist/components/checkbox.d.mts +1 -1
- package/dist/components/collapsible.d.mts +1 -1
- package/dist/components/combobox.d.mts +1 -1
- package/dist/components/combobox.mjs +1 -1
- package/dist/components/command.d.mts +1 -1
- package/dist/components/copyable.d.mts +1 -1
- package/dist/components/copyable.mjs +1 -1
- package/dist/components/description-list.d.mts +1 -1
- package/dist/components/dialog.d.mts +1 -1
- package/dist/components/dropdown-menu.d.mts +1 -1
- package/dist/components/form.d.mts +119 -0
- package/dist/components/form.mjs +192 -0
- package/dist/components/input-group.d.mts +1 -1
- package/dist/components/input.d.mts +3 -1
- package/dist/components/input.mjs +20 -11
- package/dist/components/label.d.mts +1 -1
- package/dist/components/pagination.d.mts +1 -1
- package/dist/components/popover.d.mts +1 -1
- package/dist/components/radio-group.d.mts +1 -1
- package/dist/components/scroll-area.d.mts +1 -1
- package/dist/components/select-trigger-variants.mjs +1 -0
- package/dist/components/select.d.mts +1 -1
- package/dist/components/separator.d.mts +1 -1
- package/dist/components/sheet.d.mts +1 -1
- package/dist/components/sidebar.d.mts +1 -1
- package/dist/components/sidebar.mjs +2 -2
- package/dist/components/sonner.d.mts +1 -1
- package/dist/components/switch.d.mts +1 -1
- package/dist/components/table.d.mts +1 -1
- package/dist/components/tabs.d.mts +1 -1
- package/dist/components/tabs.mjs +1 -1
- package/dist/components/textarea.d.mts +3 -1
- package/dist/components/textarea.mjs +20 -11
- package/dist/components/toggle-group.d.mts +1 -1
- package/dist/components/toggle-group.mjs +1 -1
- package/dist/components/tooltip-icon-button.mjs +1 -1
- package/dist/components/tooltip.d.mts +1 -1
- package/package.json +4 -5
- package/src/components/form.tsx +343 -0
- package/src/components/input.tsx +12 -0
- package/src/components/select-trigger-variants.ts +1 -0
- package/src/components/textarea.tsx +12 -1
- package/src/stories/accordion-card.stories.tsx +53 -0
- package/src/stories/accordion.stories.tsx +65 -0
- package/src/stories/alert.stories.tsx +58 -0
- package/src/stories/attachment-tile.stories.tsx +37 -0
- package/src/stories/avatar.stories.tsx +54 -0
- package/src/stories/badge.stories.tsx +50 -0
- package/src/stories/breadcrumb.stories.tsx +107 -0
- package/src/stories/button.stories.tsx +342 -0
- package/src/stories/card.stories.tsx +89 -0
- package/src/stories/checkbox.stories.tsx +56 -0
- package/src/stories/collapsible.stories.tsx +69 -0
- package/src/stories/combobox.stories.tsx +214 -0
- package/src/stories/command.stories.tsx +95 -0
- package/src/stories/copyable.stories.tsx +29 -0
- package/src/stories/description-list.stories.tsx +71 -0
- package/src/stories/dialog.stories.tsx +135 -0
- package/src/stories/dropdown-menu.stories.tsx +191 -0
- package/src/stories/form.stories.tsx +91 -0
- package/src/stories/input-group.stories.tsx +63 -0
- package/src/stories/input.stories.tsx +72 -0
- package/src/stories/label.stories.tsx +26 -0
- package/src/stories/ladle.css +3 -0
- package/src/stories/pagination.stories.tsx +35 -0
- package/src/stories/popover.stories.tsx +34 -0
- package/src/stories/radio-group.stories.tsx +59 -0
- package/src/stories/scroll-area.stories.tsx +43 -0
- package/src/stories/select.stories.tsx +95 -0
- package/src/stories/separator.stories.tsx +36 -0
- package/src/stories/sheet.stories.tsx +76 -0
- package/src/stories/sidebar.stories.tsx +255 -0
- package/src/stories/skeleton.stories.tsx +47 -0
- package/src/stories/sonner.stories.tsx +91 -0
- package/src/stories/spinner.stories.tsx +66 -0
- package/src/stories/status-dot.stories.tsx +27 -0
- package/src/stories/switch.stories.tsx +46 -0
- package/src/stories/table.stories.tsx +242 -0
- package/src/stories/tabs.stories.tsx +169 -0
- package/src/stories/tag.stories.tsx +82 -0
- package/src/stories/textarea.stories.tsx +60 -0
- package/src/stories/toggle-group.stories.tsx +142 -0
- package/src/stories/tooltip-icon-button.stories.tsx +59 -0
- package/src/stories/tooltip.stories.tsx +54 -0
- package/README.md +0 -33
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useIsMobile } from "../hooks/use-mobile.mjs";
|
|
3
2
|
import { cn } from "../lib/cn.mjs";
|
|
4
3
|
import { Button } from "./button.mjs";
|
|
5
4
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.mjs";
|
|
6
5
|
import { Separator } from "./separator.mjs";
|
|
7
6
|
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./sheet.mjs";
|
|
7
|
+
import { useIsMobile } from "../hooks/use-mobile.mjs";
|
|
8
8
|
import { Skeleton } from "./skeleton.mjs";
|
|
9
|
-
import * as React from "react";
|
|
10
9
|
import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
|
|
11
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
11
|
import { cva } from "class-variance-authority";
|
|
12
|
+
import * as React from "react";
|
|
13
13
|
import { Slot } from "@radix-ui/react-slot";
|
|
14
14
|
//#region src/components/sidebar.tsx
|
|
15
15
|
const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
1
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
2
|
import { VariantProps } from "class-variance-authority";
|
|
3
|
+
import * as React from "react";
|
|
4
4
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
5
5
|
import * as _$class_variance_authority_types0 from "class-variance-authority/types";
|
|
6
6
|
|
package/dist/components/tabs.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.mjs";
|
|
3
|
-
import { createContext, use } from "react";
|
|
4
3
|
import { jsx } from "react/jsx-runtime";
|
|
5
4
|
import { cva } from "class-variance-authority";
|
|
5
|
+
import { createContext, use } from "react";
|
|
6
6
|
import { Slot } from "@radix-ui/react-slot";
|
|
7
7
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
8
8
|
//#region src/components/tabs.tsx
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
1
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/components/textarea.d.ts
|
|
5
5
|
interface TextareaProps extends React.ComponentProps<"textarea"> {
|
|
@@ -7,6 +7,7 @@ interface TextareaProps extends React.ComponentProps<"textarea"> {
|
|
|
7
7
|
required?: boolean;
|
|
8
8
|
hint?: string;
|
|
9
9
|
error?: string;
|
|
10
|
+
tooltip?: string;
|
|
10
11
|
}
|
|
11
12
|
declare function Textarea({
|
|
12
13
|
className,
|
|
@@ -15,6 +16,7 @@ declare function Textarea({
|
|
|
15
16
|
required,
|
|
16
17
|
hint,
|
|
17
18
|
error,
|
|
19
|
+
tooltip,
|
|
18
20
|
...props
|
|
19
21
|
}: TextareaProps): _$react_jsx_runtime0.JSX.Element;
|
|
20
22
|
//#endregion
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.mjs";
|
|
3
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.mjs";
|
|
3
4
|
import { Label } from "./label.mjs";
|
|
4
|
-
import
|
|
5
|
+
import { Info } from "lucide-react";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import * as React from "react";
|
|
6
8
|
//#region src/components/textarea.tsx
|
|
7
|
-
function Textarea({ className, id, label, required, hint, error, ...props }) {
|
|
9
|
+
function Textarea({ className, id, label, required, hint, error, tooltip, ...props }) {
|
|
8
10
|
const generatedId = React.useId();
|
|
9
11
|
const fieldId = id ?? generatedId;
|
|
10
12
|
const textarea = /* @__PURE__ */ jsx("textarea", {
|
|
@@ -21,15 +23,22 @@ function Textarea({ className, id, label, required, hint, error, ...props }) {
|
|
|
21
23
|
children: [
|
|
22
24
|
label && /* @__PURE__ */ jsxs("div", {
|
|
23
25
|
className: "flex items-center gap-0.5",
|
|
24
|
-
children: [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
children: [
|
|
27
|
+
/* @__PURE__ */ jsx(Label, {
|
|
28
|
+
htmlFor: fieldId,
|
|
29
|
+
className: "type-text-sm font-medium text-muted-foreground",
|
|
30
|
+
children: label
|
|
31
|
+
}),
|
|
32
|
+
required && /* @__PURE__ */ jsx("span", {
|
|
33
|
+
"aria-hidden": true,
|
|
34
|
+
className: "type-text-sm font-medium text-required",
|
|
35
|
+
children: "*"
|
|
36
|
+
}),
|
|
37
|
+
tooltip && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
38
|
+
asChild: true,
|
|
39
|
+
children: /* @__PURE__ */ jsx(Info, { className: "size-4 text-muted-foreground" })
|
|
40
|
+
}), /* @__PURE__ */ jsx(TooltipContent, { children: tooltip })] })
|
|
41
|
+
]
|
|
33
42
|
}),
|
|
34
43
|
textarea,
|
|
35
44
|
(hint || error) && /* @__PURE__ */ jsx("p", {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
1
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
2
|
import { VariantProps } from "class-variance-authority";
|
|
3
|
+
import * as React from "react";
|
|
4
4
|
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
|
5
5
|
import * as _$class_variance_authority_types0 from "class-variance-authority/types";
|
|
6
6
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.mjs";
|
|
3
|
-
import * as React from "react";
|
|
4
3
|
import { jsx } from "react/jsx-runtime";
|
|
5
4
|
import { cva } from "class-variance-authority";
|
|
5
|
+
import * as React from "react";
|
|
6
6
|
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
|
7
7
|
//#region src/components/toggle-group.tsx
|
|
8
8
|
const toggleGroupItemVariants = cva([
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Button } from "./button.mjs";
|
|
3
3
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.mjs";
|
|
4
|
-
import { forwardRef } from "react";
|
|
5
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { forwardRef } from "react";
|
|
6
6
|
import { Slottable } from "@radix-ui/react-slot";
|
|
7
7
|
//#region src/components/tooltip-icon-button.tsx
|
|
8
8
|
const TooltipIconButton = forwardRef(({ children, tooltip, side = "bottom", ...rest }, ref) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpic-ai/ui",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.85c8341",
|
|
4
4
|
"description": "Alpic design system — shared UI components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -20,15 +20,13 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
|
23
|
-
"src
|
|
24
|
-
"src/hooks",
|
|
25
|
-
"src/lib",
|
|
26
|
-
"src/styles/tokens.css"
|
|
23
|
+
"src"
|
|
27
24
|
],
|
|
28
25
|
"peerDependencies": {
|
|
29
26
|
"lucide-react": "^1.0.0",
|
|
30
27
|
"react": "^19.0.0",
|
|
31
28
|
"react-dom": "^19.0.0",
|
|
29
|
+
"react-hook-form": "^7.72.1",
|
|
32
30
|
"sonner": "^2.0.0",
|
|
33
31
|
"tailwindcss": "^4.0.0",
|
|
34
32
|
"tw-animate-css": "^1.0.0"
|
|
@@ -62,6 +60,7 @@
|
|
|
62
60
|
"@types/react": "19.2.14",
|
|
63
61
|
"@types/react-dom": "19.2.3",
|
|
64
62
|
"lucide-react": "^1.7.0",
|
|
63
|
+
"react-hook-form": "^7.72.1",
|
|
65
64
|
"shx": "^0.4.0",
|
|
66
65
|
"sonner": "^2.0.7",
|
|
67
66
|
"tailwindcss": "^4.2.2",
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
4
|
+
import { Info } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import {
|
|
7
|
+
Controller,
|
|
8
|
+
type ControllerProps,
|
|
9
|
+
type FieldPath,
|
|
10
|
+
type FieldValues,
|
|
11
|
+
FormProvider,
|
|
12
|
+
useFormContext,
|
|
13
|
+
useFormState,
|
|
14
|
+
} from "react-hook-form";
|
|
15
|
+
|
|
16
|
+
import { cn } from "../lib/cn";
|
|
17
|
+
import { Input, type InputProps } from "./input";
|
|
18
|
+
import { Label } from "./label";
|
|
19
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
|
|
20
|
+
import { Textarea, type TextareaProps } from "./textarea";
|
|
21
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
22
|
+
|
|
23
|
+
const Form = FormProvider;
|
|
24
|
+
|
|
25
|
+
type FormFieldContextValue<
|
|
26
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
27
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
28
|
+
> = {
|
|
29
|
+
name: TName;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
|
33
|
+
|
|
34
|
+
const FormField = <
|
|
35
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
36
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
37
|
+
>({
|
|
38
|
+
...props
|
|
39
|
+
}: ControllerProps<TFieldValues, TName>) => {
|
|
40
|
+
return (
|
|
41
|
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
42
|
+
<Controller {...props} />
|
|
43
|
+
</FormFieldContext.Provider>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const useFormField = () => {
|
|
48
|
+
const fieldContext = React.useContext(FormFieldContext);
|
|
49
|
+
const itemContext = React.useContext(FormItemContext);
|
|
50
|
+
|
|
51
|
+
if (!fieldContext.name) {
|
|
52
|
+
throw new Error("useFormField should be used within <FormField>");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { getFieldState } = useFormContext();
|
|
56
|
+
const formState = useFormState({ name: fieldContext.name });
|
|
57
|
+
const fieldState = getFieldState(fieldContext.name, formState);
|
|
58
|
+
|
|
59
|
+
const { id } = itemContext;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
id,
|
|
63
|
+
name: fieldContext.name,
|
|
64
|
+
formItemId: `${id}-form-item`,
|
|
65
|
+
formDescriptionId: `${id}-form-item-description`,
|
|
66
|
+
formMessageId: `${id}-form-item-message`,
|
|
67
|
+
...fieldState,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
type FormItemContextValue = {
|
|
72
|
+
id: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
|
76
|
+
|
|
77
|
+
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
|
78
|
+
const id = React.useId();
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<FormItemContext.Provider value={{ id }}>
|
|
82
|
+
<div data-slot="form-item" className={cn("grid gap-2", className)} {...props} />
|
|
83
|
+
</FormItemContext.Provider>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface FormLabelProps extends React.ComponentProps<typeof Label> {
|
|
88
|
+
required?: boolean;
|
|
89
|
+
tooltip?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function FormLabel({ className, required, tooltip, children, ...props }: FormLabelProps) {
|
|
93
|
+
const { error, formItemId } = useFormField();
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="flex items-center gap-0.5">
|
|
97
|
+
<Label
|
|
98
|
+
data-slot="form-label"
|
|
99
|
+
data-error={!!error}
|
|
100
|
+
className={cn("data-[error=true]:text-destructive", className)}
|
|
101
|
+
htmlFor={formItemId}
|
|
102
|
+
{...props}
|
|
103
|
+
>
|
|
104
|
+
{children}
|
|
105
|
+
</Label>
|
|
106
|
+
{required && (
|
|
107
|
+
<span aria-hidden className="type-text-sm font-medium text-required">
|
|
108
|
+
*
|
|
109
|
+
</span>
|
|
110
|
+
)}
|
|
111
|
+
{tooltip && (
|
|
112
|
+
<Tooltip>
|
|
113
|
+
<TooltipTrigger asChild>
|
|
114
|
+
<Info className="size-4 text-quaternary-foreground" />
|
|
115
|
+
</TooltipTrigger>
|
|
116
|
+
<TooltipContent>{tooltip}</TooltipContent>
|
|
117
|
+
</Tooltip>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
|
124
|
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<Slot
|
|
128
|
+
data-slot="form-control"
|
|
129
|
+
id={formItemId}
|
|
130
|
+
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
|
131
|
+
aria-invalid={!!error}
|
|
132
|
+
{...props}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
138
|
+
const { formDescriptionId } = useFormField();
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<p
|
|
142
|
+
data-slot="form-description"
|
|
143
|
+
id={formDescriptionId}
|
|
144
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
|
151
|
+
const { error, formMessageId } = useFormField();
|
|
152
|
+
const body = error ? String(error?.message ?? "") : props.children;
|
|
153
|
+
|
|
154
|
+
if (!body) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<p data-slot="form-message" id={formMessageId} className={cn("text-destructive text-sm", className)} {...props}>
|
|
160
|
+
{body}
|
|
161
|
+
</p>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* ── Layout components ───────────────────────────────────────────────────── */
|
|
166
|
+
|
|
167
|
+
interface FormHeaderProps extends React.ComponentProps<"div"> {
|
|
168
|
+
title: string;
|
|
169
|
+
description?: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function FormHeader({ title, description, className, ...props }: FormHeaderProps) {
|
|
173
|
+
return (
|
|
174
|
+
<div className={cn("flex flex-col gap-1", className)} {...props}>
|
|
175
|
+
<h2 className="type-display-sm font-semibold text-foreground">{title}</h2>
|
|
176
|
+
{description && <p className="type-text-xl text-subtle-foreground">{description}</p>}
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function FormFields({ className, ...props }: React.ComponentProps<"div">) {
|
|
182
|
+
return <div className={cn("flex flex-col gap-6", className)} {...props} />;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* ── Convenience field wrappers ─────────────────────────────────────────── */
|
|
186
|
+
|
|
187
|
+
interface FormFieldBaseProps<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>> {
|
|
188
|
+
control: ControllerProps<TFieldValues, TName>["control"];
|
|
189
|
+
name: TName;
|
|
190
|
+
rules?: ControllerProps<TFieldValues, TName>["rules"];
|
|
191
|
+
required?: boolean;
|
|
192
|
+
label?: string;
|
|
193
|
+
description?: string;
|
|
194
|
+
tooltip?: string;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface InputFieldProps<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>
|
|
198
|
+
extends FormFieldBaseProps<TFieldValues, TName>,
|
|
199
|
+
Omit<InputProps, "name" | "label"> {}
|
|
200
|
+
|
|
201
|
+
function InputField<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>({
|
|
202
|
+
control,
|
|
203
|
+
name,
|
|
204
|
+
rules,
|
|
205
|
+
required,
|
|
206
|
+
label,
|
|
207
|
+
description,
|
|
208
|
+
tooltip,
|
|
209
|
+
...inputProps
|
|
210
|
+
}: InputFieldProps<TFieldValues, TName>) {
|
|
211
|
+
return (
|
|
212
|
+
<FormField
|
|
213
|
+
control={control}
|
|
214
|
+
name={name}
|
|
215
|
+
rules={rules}
|
|
216
|
+
render={({ field }) => (
|
|
217
|
+
<FormItem>
|
|
218
|
+
{label && (
|
|
219
|
+
<FormLabel required={required} tooltip={tooltip}>
|
|
220
|
+
{label}
|
|
221
|
+
</FormLabel>
|
|
222
|
+
)}
|
|
223
|
+
<FormControl>
|
|
224
|
+
<Input {...inputProps} {...field} />
|
|
225
|
+
</FormControl>
|
|
226
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
227
|
+
<FormMessage />
|
|
228
|
+
</FormItem>
|
|
229
|
+
)}
|
|
230
|
+
/>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
interface TextareaFieldProps<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>
|
|
235
|
+
extends FormFieldBaseProps<TFieldValues, TName>,
|
|
236
|
+
Omit<TextareaProps, "name" | "label"> {}
|
|
237
|
+
|
|
238
|
+
function TextareaField<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>({
|
|
239
|
+
control,
|
|
240
|
+
name,
|
|
241
|
+
rules,
|
|
242
|
+
required,
|
|
243
|
+
label,
|
|
244
|
+
description,
|
|
245
|
+
tooltip,
|
|
246
|
+
...textareaProps
|
|
247
|
+
}: TextareaFieldProps<TFieldValues, TName>) {
|
|
248
|
+
return (
|
|
249
|
+
<FormField
|
|
250
|
+
control={control}
|
|
251
|
+
name={name}
|
|
252
|
+
rules={rules}
|
|
253
|
+
render={({ field }) => (
|
|
254
|
+
<FormItem>
|
|
255
|
+
{label && (
|
|
256
|
+
<FormLabel required={required} tooltip={tooltip}>
|
|
257
|
+
{label}
|
|
258
|
+
</FormLabel>
|
|
259
|
+
)}
|
|
260
|
+
<FormControl>
|
|
261
|
+
<Textarea {...textareaProps} {...field} />
|
|
262
|
+
</FormControl>
|
|
263
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
264
|
+
<FormMessage />
|
|
265
|
+
</FormItem>
|
|
266
|
+
)}
|
|
267
|
+
/>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
interface SelectFieldOption {
|
|
272
|
+
value: string;
|
|
273
|
+
label: string;
|
|
274
|
+
disabled?: boolean;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
interface SelectFieldProps<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>
|
|
278
|
+
extends FormFieldBaseProps<TFieldValues, TName> {
|
|
279
|
+
options: SelectFieldOption[];
|
|
280
|
+
placeholder?: string;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function SelectField<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>({
|
|
284
|
+
control,
|
|
285
|
+
name,
|
|
286
|
+
rules,
|
|
287
|
+
required,
|
|
288
|
+
label,
|
|
289
|
+
description,
|
|
290
|
+
tooltip,
|
|
291
|
+
options,
|
|
292
|
+
placeholder,
|
|
293
|
+
}: SelectFieldProps<TFieldValues, TName>) {
|
|
294
|
+
return (
|
|
295
|
+
<FormField
|
|
296
|
+
control={control}
|
|
297
|
+
name={name}
|
|
298
|
+
rules={rules}
|
|
299
|
+
render={({ field }) => (
|
|
300
|
+
<FormItem>
|
|
301
|
+
{label && (
|
|
302
|
+
<FormLabel required={required} tooltip={tooltip}>
|
|
303
|
+
{label}
|
|
304
|
+
</FormLabel>
|
|
305
|
+
)}
|
|
306
|
+
<Select value={field.value} onValueChange={field.onChange}>
|
|
307
|
+
<FormControl>
|
|
308
|
+
<SelectTrigger>
|
|
309
|
+
<SelectValue placeholder={placeholder} />
|
|
310
|
+
</SelectTrigger>
|
|
311
|
+
</FormControl>
|
|
312
|
+
<SelectContent>
|
|
313
|
+
{options.map((option) => (
|
|
314
|
+
<SelectItem key={option.value} value={option.value} disabled={option.disabled}>
|
|
315
|
+
{option.label}
|
|
316
|
+
</SelectItem>
|
|
317
|
+
))}
|
|
318
|
+
</SelectContent>
|
|
319
|
+
</Select>
|
|
320
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
321
|
+
<FormMessage />
|
|
322
|
+
</FormItem>
|
|
323
|
+
)}
|
|
324
|
+
/>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export type { SelectFieldOption };
|
|
329
|
+
export {
|
|
330
|
+
Form,
|
|
331
|
+
FormControl,
|
|
332
|
+
FormDescription,
|
|
333
|
+
FormField,
|
|
334
|
+
FormFields,
|
|
335
|
+
FormHeader,
|
|
336
|
+
FormItem,
|
|
337
|
+
FormLabel,
|
|
338
|
+
FormMessage,
|
|
339
|
+
InputField,
|
|
340
|
+
SelectField,
|
|
341
|
+
TextareaField,
|
|
342
|
+
useFormField,
|
|
343
|
+
};
|
package/src/components/input.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { Info } from "lucide-react";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
|
|
5
6
|
import { cn } from "../lib/cn";
|
|
6
7
|
import { Label } from "./label";
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
7
9
|
|
|
8
10
|
const inputSizeStyles = {
|
|
9
11
|
sm: {
|
|
@@ -29,6 +31,7 @@ interface InputProps extends Omit<React.ComponentProps<"input">, "size"> {
|
|
|
29
31
|
required?: boolean;
|
|
30
32
|
hint?: string;
|
|
31
33
|
error?: string;
|
|
34
|
+
tooltip?: string;
|
|
32
35
|
leadingText?: string;
|
|
33
36
|
leadingIcon?: React.ReactNode;
|
|
34
37
|
size?: "sm" | "md";
|
|
@@ -41,6 +44,7 @@ function Input({
|
|
|
41
44
|
required,
|
|
42
45
|
hint,
|
|
43
46
|
error,
|
|
47
|
+
tooltip,
|
|
44
48
|
leadingText,
|
|
45
49
|
leadingIcon,
|
|
46
50
|
size = "md",
|
|
@@ -126,6 +130,14 @@ function Input({
|
|
|
126
130
|
*
|
|
127
131
|
</span>
|
|
128
132
|
)}
|
|
133
|
+
{tooltip && (
|
|
134
|
+
<Tooltip>
|
|
135
|
+
<TooltipTrigger asChild>
|
|
136
|
+
<Info className="size-4 text-muted-foreground" />
|
|
137
|
+
</TooltipTrigger>
|
|
138
|
+
<TooltipContent>{tooltip}</TooltipContent>
|
|
139
|
+
</Tooltip>
|
|
140
|
+
)}
|
|
129
141
|
</div>
|
|
130
142
|
)}
|
|
131
143
|
{wrappedInput}
|
|
@@ -12,6 +12,7 @@ export const selectTriggerVariants = cva(
|
|
|
12
12
|
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
13
13
|
"data-[state=open]:ring-2 data-[state=open]:ring-ring data-[state=open]:ring-offset-2 data-[state=open]:ring-offset-background",
|
|
14
14
|
"disabled:pointer-events-none disabled:bg-disabled disabled:text-disabled-foreground disabled:border-disabled",
|
|
15
|
+
"aria-invalid:border-border-error",
|
|
15
16
|
"[&_svg:not([class*='size-'])]:size-5 [&_svg]:shrink-0",
|
|
16
17
|
].join(" "),
|
|
17
18
|
{
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { Info } from "lucide-react";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
|
|
5
6
|
import { cn } from "../lib/cn";
|
|
6
7
|
import { Label } from "./label";
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
7
9
|
|
|
8
10
|
interface TextareaProps extends React.ComponentProps<"textarea"> {
|
|
9
11
|
label?: string;
|
|
10
12
|
required?: boolean;
|
|
11
13
|
hint?: string;
|
|
12
14
|
error?: string;
|
|
15
|
+
tooltip?: string;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
function Textarea({ className, id, label, required, hint, error, ...props }: TextareaProps) {
|
|
18
|
+
function Textarea({ className, id, label, required, hint, error, tooltip, ...props }: TextareaProps) {
|
|
16
19
|
const generatedId = React.useId();
|
|
17
20
|
const fieldId = id ?? generatedId;
|
|
18
21
|
|
|
@@ -51,6 +54,14 @@ function Textarea({ className, id, label, required, hint, error, ...props }: Tex
|
|
|
51
54
|
*
|
|
52
55
|
</span>
|
|
53
56
|
)}
|
|
57
|
+
{tooltip && (
|
|
58
|
+
<Tooltip>
|
|
59
|
+
<TooltipTrigger asChild>
|
|
60
|
+
<Info className="size-4 text-muted-foreground" />
|
|
61
|
+
</TooltipTrigger>
|
|
62
|
+
<TooltipContent>{tooltip}</TooltipContent>
|
|
63
|
+
</Tooltip>
|
|
64
|
+
)}
|
|
54
65
|
</div>
|
|
55
66
|
)}
|
|
56
67
|
{textarea}
|