@alpic-ai/ui 0.0.0 → 1.111.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.
Files changed (72) hide show
  1. package/dist/components/alert.d.mts +1 -1
  2. package/dist/components/avatar.d.mts +1 -1
  3. package/dist/components/badge.d.mts +1 -1
  4. package/dist/components/button.d.mts +2 -2
  5. package/dist/components/combobox.mjs +1 -1
  6. package/dist/components/dialog.mjs +2 -2
  7. package/dist/components/form.d.mts +119 -0
  8. package/dist/components/form.mjs +192 -0
  9. package/dist/components/input.d.mts +2 -0
  10. package/dist/components/input.mjs +19 -10
  11. package/dist/components/select-trigger-variants.mjs +1 -0
  12. package/dist/components/select.d.mts +1 -9
  13. package/dist/components/select.mjs +8 -28
  14. package/dist/components/sidebar.d.mts +1 -1
  15. package/dist/components/sidebar.mjs +6 -6
  16. package/dist/components/spinner.d.mts +1 -1
  17. package/dist/components/status-dot.d.mts +1 -1
  18. package/dist/components/tabs.d.mts +1 -1
  19. package/dist/components/textarea.d.mts +2 -0
  20. package/dist/components/textarea.mjs +19 -10
  21. package/dist/components/toggle-group.d.mts +1 -1
  22. package/package.json +31 -40
  23. package/src/components/combobox.tsx +1 -1
  24. package/src/components/dialog.tsx +2 -2
  25. package/src/components/form.tsx +343 -0
  26. package/src/components/input.tsx +12 -0
  27. package/src/components/select-trigger-variants.ts +1 -0
  28. package/src/components/select.tsx +2 -35
  29. package/src/components/sidebar.tsx +8 -10
  30. package/src/components/textarea.tsx +12 -1
  31. package/src/stories/accordion-card.stories.tsx +53 -0
  32. package/src/stories/accordion.stories.tsx +65 -0
  33. package/src/stories/alert.stories.tsx +58 -0
  34. package/src/stories/attachment-tile.stories.tsx +37 -0
  35. package/src/stories/avatar.stories.tsx +54 -0
  36. package/src/stories/badge.stories.tsx +50 -0
  37. package/src/stories/breadcrumb.stories.tsx +107 -0
  38. package/src/stories/button.stories.tsx +342 -0
  39. package/src/stories/card.stories.tsx +89 -0
  40. package/src/stories/checkbox.stories.tsx +56 -0
  41. package/src/stories/collapsible.stories.tsx +69 -0
  42. package/src/stories/combobox.stories.tsx +214 -0
  43. package/src/stories/command.stories.tsx +95 -0
  44. package/src/stories/copyable.stories.tsx +29 -0
  45. package/src/stories/description-list.stories.tsx +71 -0
  46. package/src/stories/dialog.stories.tsx +135 -0
  47. package/src/stories/dropdown-menu.stories.tsx +191 -0
  48. package/src/stories/form.stories.tsx +91 -0
  49. package/src/stories/input-group.stories.tsx +63 -0
  50. package/src/stories/input.stories.tsx +72 -0
  51. package/src/stories/label.stories.tsx +26 -0
  52. package/src/stories/ladle.css +3 -0
  53. package/src/stories/pagination.stories.tsx +35 -0
  54. package/src/stories/popover.stories.tsx +34 -0
  55. package/src/stories/radio-group.stories.tsx +59 -0
  56. package/src/stories/scroll-area.stories.tsx +43 -0
  57. package/src/stories/select.stories.tsx +95 -0
  58. package/src/stories/separator.stories.tsx +36 -0
  59. package/src/stories/sheet.stories.tsx +76 -0
  60. package/src/stories/sidebar.stories.tsx +255 -0
  61. package/src/stories/skeleton.stories.tsx +47 -0
  62. package/src/stories/sonner.stories.tsx +91 -0
  63. package/src/stories/spinner.stories.tsx +66 -0
  64. package/src/stories/status-dot.stories.tsx +27 -0
  65. package/src/stories/switch.stories.tsx +46 -0
  66. package/src/stories/table.stories.tsx +242 -0
  67. package/src/stories/tabs.stories.tsx +169 -0
  68. package/src/stories/tag.stories.tsx +82 -0
  69. package/src/stories/textarea.stories.tsx +60 -0
  70. package/src/stories/toggle-group.stories.tsx +142 -0
  71. package/src/stories/tooltip-icon-button.stories.tsx +59 -0
  72. package/src/stories/tooltip.stories.tsx +54 -0
@@ -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";
5
+ import { Info } from "lucide-react";
4
6
  import { jsx, jsxs } from "react/jsx-runtime";
5
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: [/* @__PURE__ */ jsx(Label, {
25
- htmlFor: fieldId,
26
- className: "type-text-sm font-medium text-muted-foreground",
27
- children: label
28
- }), required && /* @__PURE__ */ jsx("span", {
29
- "aria-hidden": true,
30
- className: "type-text-sm font-medium text-required",
31
- children: "*"
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", {
@@ -7,7 +7,7 @@ import * as _$class_variance_authority_types0 from "class-variance-authority/typ
7
7
  //#region src/components/toggle-group.d.ts
8
8
  declare const toggleGroupItemVariants: (props?: ({
9
9
  variant?: "default" | "outline" | null | undefined;
10
- size?: "sm" | "default" | null | undefined;
10
+ size?: "default" | "sm" | null | undefined;
11
11
  } & _$class_variance_authority_types0.ClassProp) | undefined) => string;
12
12
  type ToggleGroupContextValue = VariantProps<typeof toggleGroupItemVariants>;
13
13
  declare function ToggleGroup({
package/package.json CHANGED
@@ -1,53 +1,35 @@
1
1
  {
2
2
  "name": "@alpic-ai/ui",
3
- "version": "0.0.0",
3
+ "version": "1.111.0",
4
4
  "description": "Alpic design system — shared UI components",
5
5
  "type": "module",
6
6
  "exports": {
7
- "./components/*": "./src/components/*.tsx",
8
- "./hooks/*": "./src/hooks/*.ts",
9
- "./lib/cn": "./src/lib/cn.ts",
7
+ "./components/*": {
8
+ "types": "./dist/components/*.d.mts",
9
+ "default": "./dist/components/*.mjs"
10
+ },
11
+ "./hooks/*": {
12
+ "types": "./dist/hooks/*.d.mts",
13
+ "default": "./dist/hooks/*.mjs"
14
+ },
15
+ "./lib/cn": {
16
+ "types": "./dist/lib/cn.d.mts",
17
+ "default": "./dist/lib/cn.mjs"
18
+ },
10
19
  "./theme": "./src/styles/tokens.css"
11
20
  },
12
21
  "files": [
13
22
  "dist",
14
- "src/components",
15
- "src/hooks",
16
- "src/lib",
17
- "src/styles/tokens.css"
23
+ "src"
18
24
  ],
19
- "publishConfig": {
20
- "exports": {
21
- "./components/*": {
22
- "types": "./dist/components/*.d.mts",
23
- "default": "./dist/components/*.mjs"
24
- },
25
- "./hooks/*": {
26
- "types": "./dist/hooks/*.d.mts",
27
- "default": "./dist/hooks/*.mjs"
28
- },
29
- "./lib/cn": {
30
- "types": "./dist/lib/cn.d.mts",
31
- "default": "./dist/lib/cn.mjs"
32
- },
33
- "./theme": "./src/styles/tokens.css"
34
- }
35
- },
36
- "scripts": {
37
- "build": "shx rm -rf dist && tsdown",
38
- "format": "biome check --write --error-on-warnings .",
39
- "test:type": "tsc --noEmit",
40
- "test:format": "biome check --error-on-warnings .",
41
- "dev:ui": "ladle serve",
42
- "publish:npm": "pnpm publish --tag \"${NPM_TAG}\" --access public --no-git-checks"
43
- },
44
25
  "peerDependencies": {
45
- "lucide-react": "^1.0.0",
46
- "react": "^19.0.0",
47
- "react-dom": "^19.0.0",
48
- "sonner": "^2.0.0",
49
- "tailwindcss": "^4.0.0",
50
- "tw-animate-css": "^1.0.0"
26
+ "lucide-react": "^1.7.0",
27
+ "react": "^19.2.4",
28
+ "react-dom": "^19.2.4",
29
+ "react-hook-form": "^7.72.1",
30
+ "sonner": "^2.0.7",
31
+ "tailwindcss": "^4.2.2",
32
+ "tw-animate-css": "^1.4.0"
51
33
  },
52
34
  "dependencies": {
53
35
  "@radix-ui/react-accordion": "^1.2.12",
@@ -78,11 +60,20 @@
78
60
  "@types/react": "19.2.14",
79
61
  "@types/react-dom": "19.2.3",
80
62
  "lucide-react": "^1.7.0",
63
+ "react-hook-form": "^7.72.1",
81
64
  "shx": "^0.4.0",
82
65
  "sonner": "^2.0.7",
83
66
  "tailwindcss": "^4.2.2",
84
67
  "tsdown": "^0.21.7",
85
68
  "tw-animate-css": "^1.4.0",
86
69
  "typescript": "^6.0.2"
70
+ },
71
+ "scripts": {
72
+ "build": "shx rm -rf dist && tsdown",
73
+ "format": "biome check --write --error-on-warnings .",
74
+ "test:type": "tsc --noEmit",
75
+ "test:format": "biome check --error-on-warnings .",
76
+ "dev:ui": "ladle serve",
77
+ "publish:npm": "pnpm publish --tag \"${NPM_TAG}\" --access public --no-git-checks"
87
78
  }
88
- }
79
+ }
@@ -349,7 +349,7 @@ function ComboboxItem({ className, children, itemValue, ...props }: ComboboxItem
349
349
  data-selected-item={selected || undefined}
350
350
  onSelect={() => onSelect(itemValue)}
351
351
  className={cn(
352
- "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-2.5 outline-hidden select-none",
352
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 outline-hidden select-none",
353
353
  "type-text-md font-medium text-foreground mx-1.5 my-px",
354
354
  "data-[selected=true]:bg-background-hover",
355
355
  "data-[selected-item]:bg-accent",
@@ -37,7 +37,7 @@ function DialogOverlay({ className, ...props }: React.ComponentProps<typeof Dial
37
37
  const dialogContentVariants = cva(
38
38
  [
39
39
  "bg-background fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2",
40
- "overflow-y-auto rounded-2xl px-6 shadow-lg outline-none",
40
+ "max-h-[calc(100vh-4rem)] overflow-hidden rounded-2xl px-6 shadow-lg outline-none",
41
41
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
42
42
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
43
43
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
@@ -93,7 +93,7 @@ function DialogContent({ className, children, size, showCloseButton = true, ...p
93
93
  }
94
94
 
95
95
  function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
96
- return <div data-slot="dialog-header" className={cn("flex flex-col gap-0.5 pt-6 pr-10", className)} {...props} />;
96
+ return <div data-slot="dialog-header" className={cn("flex flex-col gap-0.5 py-6 pr-10", className)} {...props} />;
97
97
  }
98
98
 
99
99
  const dialogFooterVariants = cva("pb-6 pt-6", {
@@ -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
+ };
@@ -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
  {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as SelectPrimitive from "@radix-ui/react-select";
4
4
  import type { VariantProps } from "class-variance-authority";
5
- import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
5
+ import { CheckIcon, ChevronDownIcon } from "lucide-react";
6
6
  import type * as React from "react";
7
7
 
8
8
  import { cn } from "../lib/cn";
@@ -75,7 +75,6 @@ function SelectContent({
75
75
  position={position}
76
76
  {...props}
77
77
  >
78
- <SelectScrollUpButton />
79
78
  <SelectPrimitive.Viewport
80
79
  className={cn(
81
80
  "p-1",
@@ -85,7 +84,6 @@ function SelectContent({
85
84
  >
86
85
  {children}
87
86
  </SelectPrimitive.Viewport>
88
- <SelectScrollDownButton />
89
87
  </SelectPrimitive.Content>
90
88
  </SelectPrimitive.Portal>
91
89
  );
@@ -110,7 +108,7 @@ function SelectItem({ className, children, ...props }: React.ComponentProps<type
110
108
  <SelectPrimitive.Item
111
109
  data-slot="select-item"
112
110
  className={cn(
113
- "relative flex w-full cursor-default items-center gap-2 rounded-sm py-2 pr-8 pl-2.5 outline-hidden select-none",
111
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pl-2 outline-hidden select-none",
114
112
  "type-text-md font-medium text-foreground mx-1 my-px",
115
113
  "[@media(hover:hover)]:hover:bg-background-hover",
116
114
  "focus:bg-background-hover",
@@ -142,43 +140,12 @@ function SelectSeparator({ className, ...props }: React.ComponentProps<typeof Se
142
140
  );
143
141
  }
144
142
 
145
- /* ── Scroll buttons ──────────────────────────────────────────────────────── */
146
-
147
- function SelectScrollUpButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
148
- return (
149
- <SelectPrimitive.ScrollUpButton
150
- data-slot="select-scroll-up-button"
151
- className={cn("flex cursor-default items-center justify-center py-1", className)}
152
- {...props}
153
- >
154
- <ChevronUpIcon className="size-4" />
155
- </SelectPrimitive.ScrollUpButton>
156
- );
157
- }
158
-
159
- function SelectScrollDownButton({
160
- className,
161
- ...props
162
- }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
163
- return (
164
- <SelectPrimitive.ScrollDownButton
165
- data-slot="select-scroll-down-button"
166
- className={cn("flex cursor-default items-center justify-center py-1", className)}
167
- {...props}
168
- >
169
- <ChevronDownIcon className="size-4" />
170
- </SelectPrimitive.ScrollDownButton>
171
- );
172
- }
173
-
174
143
  export {
175
144
  Select,
176
145
  SelectContent,
177
146
  SelectGroup,
178
147
  SelectItem,
179
148
  SelectLabel,
180
- SelectScrollDownButton,
181
- SelectScrollUpButton,
182
149
  SelectSeparator,
183
150
  SelectTrigger,
184
151
  SelectValue,