@alpic-ai/ui 0.0.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 (134) hide show
  1. package/dist/components/accordion-card.d.mts +41 -0
  2. package/dist/components/accordion-card.mjs +61 -0
  3. package/dist/components/accordion.d.mts +29 -0
  4. package/dist/components/accordion.mjs +48 -0
  5. package/dist/components/alert.d.mts +40 -0
  6. package/dist/components/alert.mjs +63 -0
  7. package/dist/components/attachment-tile.d.mts +26 -0
  8. package/dist/components/attachment-tile.mjs +35 -0
  9. package/dist/components/avatar.d.mts +52 -0
  10. package/dist/components/avatar.mjs +81 -0
  11. package/dist/components/badge.d.mts +20 -0
  12. package/dist/components/badge.mjs +36 -0
  13. package/dist/components/breadcrumb.d.mts +42 -0
  14. package/dist/components/breadcrumb.mjs +79 -0
  15. package/dist/components/button.d.mts +29 -0
  16. package/dist/components/button.mjs +67 -0
  17. package/dist/components/card.d.mts +37 -0
  18. package/dist/components/card.mjs +54 -0
  19. package/dist/components/checkbox.d.mts +11 -0
  20. package/dist/components/checkbox.mjs +26 -0
  21. package/dist/components/collapsible.d.mts +16 -0
  22. package/dist/components/collapsible.mjs +24 -0
  23. package/dist/components/combobox.d.mts +86 -0
  24. package/dist/components/combobox.mjs +207 -0
  25. package/dist/components/command.d.mts +38 -0
  26. package/dist/components/command.mjs +68 -0
  27. package/dist/components/copyable.d.mts +22 -0
  28. package/dist/components/copyable.mjs +33 -0
  29. package/dist/components/description-list.d.mts +22 -0
  30. package/dist/components/description-list.mjs +34 -0
  31. package/dist/components/dialog.d.mts +61 -0
  32. package/dist/components/dialog.mjs +110 -0
  33. package/dist/components/dropdown-menu.d.mts +72 -0
  34. package/dist/components/dropdown-menu.mjs +122 -0
  35. package/dist/components/input-group.d.mts +25 -0
  36. package/dist/components/input-group.mjs +43 -0
  37. package/dist/components/input.d.mts +27 -0
  38. package/dist/components/input.mjs +90 -0
  39. package/dist/components/label.d.mts +11 -0
  40. package/dist/components/label.mjs +14 -0
  41. package/dist/components/pagination.d.mts +18 -0
  42. package/dist/components/pagination.mjs +42 -0
  43. package/dist/components/popover.d.mts +22 -0
  44. package/dist/components/popover.mjs +34 -0
  45. package/dist/components/radio-group.d.mts +15 -0
  46. package/dist/components/radio-group.mjs +26 -0
  47. package/dist/components/scroll-area.d.mts +17 -0
  48. package/dist/components/scroll-area.mjs +35 -0
  49. package/dist/components/select-trigger-variants.d.mts +13 -0
  50. package/dist/components/select-trigger-variants.mjs +23 -0
  51. package/dist/components/select.d.mts +51 -0
  52. package/dist/components/select.mjs +95 -0
  53. package/dist/components/separator.d.mts +13 -0
  54. package/dist/components/separator.mjs +16 -0
  55. package/dist/components/sheet.d.mts +43 -0
  56. package/dist/components/sheet.mjs +74 -0
  57. package/dist/components/sidebar.d.mts +163 -0
  58. package/dist/components/sidebar.mjs +378 -0
  59. package/dist/components/skeleton.d.mts +16 -0
  60. package/dist/components/skeleton.mjs +21 -0
  61. package/dist/components/sonner.d.mts +29 -0
  62. package/dist/components/sonner.mjs +76 -0
  63. package/dist/components/spinner.d.mts +30 -0
  64. package/dist/components/spinner.mjs +46 -0
  65. package/dist/components/status-dot.d.mts +19 -0
  66. package/dist/components/status-dot.mjs +34 -0
  67. package/dist/components/switch.d.mts +11 -0
  68. package/dist/components/switch.mjs +18 -0
  69. package/dist/components/table.d.mts +38 -0
  70. package/dist/components/table.mjs +65 -0
  71. package/dist/components/tabs.d.mts +58 -0
  72. package/dist/components/tabs.mjs +119 -0
  73. package/dist/components/tag.d.mts +35 -0
  74. package/dist/components/tag.mjs +65 -0
  75. package/dist/components/textarea.d.mts +21 -0
  76. package/dist/components/textarea.mjs +44 -0
  77. package/dist/components/toggle-group.d.mts +28 -0
  78. package/dist/components/toggle-group.mjs +72 -0
  79. package/dist/components/tooltip-icon-button.d.mts +12 -0
  80. package/dist/components/tooltip-icon-button.mjs +27 -0
  81. package/dist/components/tooltip.d.mts +23 -0
  82. package/dist/components/tooltip.mjs +35 -0
  83. package/dist/hooks/use-copy-to-clipboard.d.mts +11 -0
  84. package/dist/hooks/use-copy-to-clipboard.mjs +24 -0
  85. package/dist/hooks/use-mobile.d.mts +4 -0
  86. package/dist/hooks/use-mobile.mjs +16 -0
  87. package/dist/lib/cn.d.mts +6 -0
  88. package/dist/lib/cn.mjs +8 -0
  89. package/package.json +88 -0
  90. package/src/components/accordion-card.tsx +103 -0
  91. package/src/components/accordion.tsx +63 -0
  92. package/src/components/alert.tsx +74 -0
  93. package/src/components/attachment-tile.tsx +68 -0
  94. package/src/components/avatar.tsx +127 -0
  95. package/src/components/badge.tsx +41 -0
  96. package/src/components/breadcrumb.tsx +98 -0
  97. package/src/components/button.tsx +106 -0
  98. package/src/components/card.tsx +62 -0
  99. package/src/components/checkbox.tsx +35 -0
  100. package/src/components/collapsible.tsx +18 -0
  101. package/src/components/combobox.tsx +393 -0
  102. package/src/components/command.tsx +112 -0
  103. package/src/components/copyable.tsx +47 -0
  104. package/src/components/description-list.tsx +36 -0
  105. package/src/components/dialog.tsx +161 -0
  106. package/src/components/dropdown-menu.tsx +234 -0
  107. package/src/components/input-group.tsx +97 -0
  108. package/src/components/input.tsx +145 -0
  109. package/src/components/label.tsx +20 -0
  110. package/src/components/pagination.tsx +53 -0
  111. package/src/components/popover.tsx +49 -0
  112. package/src/components/radio-group.tsx +33 -0
  113. package/src/components/scroll-area.tsx +48 -0
  114. package/src/components/select-trigger-variants.ts +28 -0
  115. package/src/components/select.tsx +186 -0
  116. package/src/components/separator.tsx +30 -0
  117. package/src/components/sheet.tsx +112 -0
  118. package/src/components/sidebar.tsx +682 -0
  119. package/src/components/skeleton.tsx +24 -0
  120. package/src/components/sonner.tsx +91 -0
  121. package/src/components/spinner.tsx +62 -0
  122. package/src/components/status-dot.tsx +33 -0
  123. package/src/components/switch.tsx +33 -0
  124. package/src/components/table.tsx +89 -0
  125. package/src/components/tabs.tsx +226 -0
  126. package/src/components/tag.tsx +82 -0
  127. package/src/components/textarea.tsx +70 -0
  128. package/src/components/toggle-group.tsx +96 -0
  129. package/src/components/tooltip-icon-button.tsx +33 -0
  130. package/src/components/tooltip.tsx +54 -0
  131. package/src/hooks/use-copy-to-clipboard.ts +27 -0
  132. package/src/hooks/use-mobile.ts +17 -0
  133. package/src/lib/cn.ts +6 -0
  134. package/src/styles/tokens.css +352 -0
@@ -0,0 +1,161 @@
1
+ "use client";
2
+
3
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+ import { XIcon } from "lucide-react";
6
+ import type * as React from "react";
7
+ import { cn } from "../lib/cn";
8
+ import { Button, type ButtonProps } from "./button";
9
+
10
+ function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
11
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
12
+ }
13
+
14
+ function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
15
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
16
+ }
17
+
18
+ function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
19
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
20
+ }
21
+
22
+ function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
23
+ return (
24
+ <DialogPrimitive.Overlay
25
+ data-slot="dialog-overlay"
26
+ className={cn(
27
+ "fixed inset-0 z-50 bg-overlay",
28
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
29
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ const dialogContentVariants = cva(
38
+ [
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",
41
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
42
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
43
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
44
+ "duration-200",
45
+ ].join(" "),
46
+ {
47
+ variants: {
48
+ size: {
49
+ sm: "max-w-[400px]",
50
+ lg: "max-w-[544px]",
51
+ },
52
+ },
53
+ defaultVariants: {
54
+ size: "sm",
55
+ },
56
+ },
57
+ );
58
+
59
+ interface DialogContentProps
60
+ extends React.ComponentProps<typeof DialogPrimitive.Content>,
61
+ VariantProps<typeof dialogContentVariants> {
62
+ showCloseButton?: boolean;
63
+ }
64
+
65
+ function DialogContent({ className, children, size, showCloseButton = true, ...props }: DialogContentProps) {
66
+ return (
67
+ <DialogPortal>
68
+ <DialogOverlay />
69
+ <DialogPrimitive.Content
70
+ data-slot="dialog-content"
71
+ className={cn(dialogContentVariants({ size }), className)}
72
+ {...props}
73
+ >
74
+ {children}
75
+ {showCloseButton && (
76
+ <DialogPrimitive.Close
77
+ data-slot="dialog-close"
78
+ className={cn(
79
+ "absolute top-3 right-4 flex size-8 items-center justify-center rounded-md",
80
+ "text-subtle-foreground transition-colors",
81
+ "[@media(hover:hover)]:hover:text-foreground",
82
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
83
+ "disabled:pointer-events-none",
84
+ )}
85
+ >
86
+ <XIcon className="size-4" />
87
+ <span className="sr-only">Close</span>
88
+ </DialogPrimitive.Close>
89
+ )}
90
+ </DialogPrimitive.Content>
91
+ </DialogPortal>
92
+ );
93
+ }
94
+
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} />;
97
+ }
98
+
99
+ const dialogFooterVariants = cva("pb-6 pt-6", {
100
+ variants: {
101
+ layout: {
102
+ horizontal: "flex gap-3 [&>*]:flex-1",
103
+ vertical: "flex flex-col gap-3",
104
+ },
105
+ },
106
+ defaultVariants: {
107
+ layout: "horizontal",
108
+ },
109
+ });
110
+
111
+ interface DialogFooterProps extends React.ComponentProps<"div">, VariantProps<typeof dialogFooterVariants> {}
112
+
113
+ function DialogFooter({ className, layout, ...props }: DialogFooterProps) {
114
+ return <div data-slot="dialog-footer" className={cn(dialogFooterVariants({ layout }), className)} {...props} />;
115
+ }
116
+
117
+ function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
118
+ return (
119
+ <DialogPrimitive.Title
120
+ data-slot="dialog-title"
121
+ className={cn("type-text-md font-semibold text-foreground", className)}
122
+ {...props}
123
+ />
124
+ );
125
+ }
126
+
127
+ function DialogClose({ children = "Cancel", ...props }: Omit<ButtonProps, "variant" | "asChild">) {
128
+ return (
129
+ <DialogPrimitive.Close data-slot="dialog-cancel" asChild>
130
+ <Button variant="secondary" {...props}>
131
+ {children}
132
+ </Button>
133
+ </DialogPrimitive.Close>
134
+ );
135
+ }
136
+
137
+ function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
138
+ return (
139
+ <DialogPrimitive.Description
140
+ data-slot="dialog-description"
141
+ className={cn("type-text-sm text-subtle-foreground", className)}
142
+ {...props}
143
+ />
144
+ );
145
+ }
146
+
147
+ export type { DialogContentProps, DialogFooterProps };
148
+ export {
149
+ Dialog,
150
+ DialogClose,
151
+ DialogContent,
152
+ DialogDescription,
153
+ DialogFooter,
154
+ DialogHeader,
155
+ DialogOverlay,
156
+ DialogPortal,
157
+ DialogTitle,
158
+ DialogTrigger,
159
+ dialogContentVariants,
160
+ dialogFooterVariants,
161
+ };
@@ -0,0 +1,234 @@
1
+ "use client";
2
+
3
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+ import { ChevronRightIcon } from "lucide-react";
6
+ import type * as React from "react";
7
+
8
+ import { cn } from "../lib/cn";
9
+
10
+ function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
11
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
12
+ }
13
+
14
+ function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
15
+ return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
16
+ }
17
+
18
+ function DropdownMenuTrigger({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
19
+ return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
20
+ }
21
+
22
+ function DropdownMenuContent({
23
+ className,
24
+ sideOffset = 4,
25
+ ...props
26
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
27
+ return (
28
+ <DropdownMenuPrimitive.Portal>
29
+ <DropdownMenuPrimitive.Content
30
+ data-slot="dropdown-menu-content"
31
+ sideOffset={sideOffset}
32
+ className={cn(
33
+ "bg-popover text-popover-foreground",
34
+ "z-50 min-w-60 overflow-x-hidden overflow-y-auto rounded-md border border-border-secondary py-1 shadow-lg",
35
+ "max-h-(--radix-dropdown-menu-content-available-height)",
36
+ "origin-(--radix-dropdown-menu-content-transform-origin)",
37
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
38
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
39
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
40
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
41
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
42
+ className,
43
+ )}
44
+ {...props}
45
+ />
46
+ </DropdownMenuPrimitive.Portal>
47
+ );
48
+ }
49
+
50
+ function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
51
+ return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
52
+ }
53
+
54
+ /* ── Item ─────────────────────────────────────────────────────────────────── */
55
+
56
+ const dropdownMenuItemVariants = cva(
57
+ [
58
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2.5 py-2 outline-hidden select-none",
59
+ "type-text-sm font-semibold",
60
+ "mx-1.5 my-px",
61
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
62
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
63
+ ].join(" "),
64
+ {
65
+ variants: {
66
+ variant: {
67
+ default: [
68
+ "text-muted-foreground",
69
+ "[&_svg:not([class*='text-'])]:text-muted-foreground",
70
+ "[@media(hover:hover)]:hover:bg-background-hover [@media(hover:hover)]:hover:text-muted-foreground-hover",
71
+ "focus:bg-background-hover focus:text-muted-foreground-hover",
72
+ ].join(" "),
73
+ destructive: [
74
+ "text-destructive",
75
+ "[&_svg]:!text-destructive",
76
+ "[@media(hover:hover)]:hover:bg-destructive/10 dark:[@media(hover:hover)]:hover:bg-destructive/20",
77
+ "focus:bg-destructive/10 dark:focus:bg-destructive/20",
78
+ ].join(" "),
79
+ },
80
+ },
81
+ defaultVariants: {
82
+ variant: "default",
83
+ },
84
+ },
85
+ );
86
+
87
+ function DropdownMenuItem({
88
+ className,
89
+ inset,
90
+ variant = "default",
91
+ ...props
92
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> &
93
+ VariantProps<typeof dropdownMenuItemVariants> & {
94
+ inset?: boolean;
95
+ }) {
96
+ return (
97
+ <DropdownMenuPrimitive.Item
98
+ data-slot="dropdown-menu-item"
99
+ data-inset={inset}
100
+ className={cn(dropdownMenuItemVariants({ variant }), inset && "pl-8", className)}
101
+ {...props}
102
+ />
103
+ );
104
+ }
105
+
106
+ /* ── Label ────────────────────────────────────────────────────────────────── */
107
+
108
+ function DropdownMenuLabel({
109
+ className,
110
+ inset,
111
+ ...props
112
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
113
+ inset?: boolean;
114
+ }) {
115
+ return (
116
+ <DropdownMenuPrimitive.Label
117
+ data-slot="dropdown-menu-label"
118
+ className={cn("px-2.5 py-1.5 type-text-sm font-semibold text-foreground", inset && "pl-8", className)}
119
+ {...props}
120
+ />
121
+ );
122
+ }
123
+
124
+ /* ── Header (avatar group) ────────────────────────────────────────────────── */
125
+
126
+ function DropdownMenuHeader({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
127
+ return (
128
+ <div
129
+ data-slot="dropdown-menu-header"
130
+ className={cn("flex items-center gap-2 border-b border-border-secondary p-3", className)}
131
+ {...props}
132
+ >
133
+ {children}
134
+ </div>
135
+ );
136
+ }
137
+
138
+ /* ── Separator ────────────────────────────────────────────────────────────── */
139
+
140
+ function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
141
+ return (
142
+ <DropdownMenuPrimitive.Separator
143
+ data-slot="dropdown-menu-separator"
144
+ className={cn("bg-border -mx-1 my-1 h-px", className)}
145
+ {...props}
146
+ />
147
+ );
148
+ }
149
+
150
+ /* ── Shortcut ─────────────────────────────────────────────────────────────── */
151
+
152
+ function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
153
+ return (
154
+ <span
155
+ data-slot="dropdown-menu-shortcut"
156
+ className={cn(
157
+ "ml-auto shrink-0 rounded-xs border border-border px-1 py-px type-text-xs font-medium text-subtle-foreground",
158
+ className,
159
+ )}
160
+ {...props}
161
+ />
162
+ );
163
+ }
164
+
165
+ /* ── Sub menu ─────────────────────────────────────────────────────────────── */
166
+
167
+ function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
168
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
169
+ }
170
+
171
+ function DropdownMenuSubTrigger({
172
+ className,
173
+ inset,
174
+ children,
175
+ ...props
176
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
177
+ inset?: boolean;
178
+ }) {
179
+ return (
180
+ <DropdownMenuPrimitive.SubTrigger
181
+ data-slot="dropdown-menu-sub-trigger"
182
+ className={cn(
183
+ dropdownMenuItemVariants({ variant: "default" }),
184
+ "data-[state=open]:bg-background-hover data-[state=open]:text-muted-foreground-hover",
185
+ inset && "pl-8",
186
+ className,
187
+ )}
188
+ {...props}
189
+ >
190
+ {children}
191
+ <ChevronRightIcon className="ml-auto size-4" />
192
+ </DropdownMenuPrimitive.SubTrigger>
193
+ );
194
+ }
195
+
196
+ function DropdownMenuSubContent({
197
+ className,
198
+ ...props
199
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
200
+ return (
201
+ <DropdownMenuPrimitive.SubContent
202
+ data-slot="dropdown-menu-sub-content"
203
+ className={cn(
204
+ "bg-popover text-popover-foreground",
205
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border border-border-secondary py-1 shadow-lg",
206
+ "origin-(--radix-dropdown-menu-content-transform-origin)",
207
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
208
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
209
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
210
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
211
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
212
+ className,
213
+ )}
214
+ {...props}
215
+ />
216
+ );
217
+ }
218
+
219
+ export {
220
+ DropdownMenu,
221
+ DropdownMenuContent,
222
+ DropdownMenuGroup,
223
+ DropdownMenuHeader,
224
+ DropdownMenuItem,
225
+ DropdownMenuLabel,
226
+ DropdownMenuPortal,
227
+ DropdownMenuSeparator,
228
+ DropdownMenuShortcut,
229
+ DropdownMenuSub,
230
+ DropdownMenuSubContent,
231
+ DropdownMenuSubTrigger,
232
+ DropdownMenuTrigger,
233
+ dropdownMenuItemVariants,
234
+ };
@@ -0,0 +1,97 @@
1
+ "use client";
2
+
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import type * as React from "react";
5
+ import { cn } from "../lib/cn";
6
+
7
+ function InputGroup({ className, children, ...props }: React.ComponentProps<"fieldset">) {
8
+ return (
9
+ <fieldset
10
+ data-slot="input-group"
11
+ className={cn(
12
+ "group/input-group relative flex w-full items-center rounded-md border bg-background",
13
+ "h-9 min-w-0 has-[>textarea]:h-auto",
14
+ "transition-colors",
15
+
16
+ // Variants based on alignment.
17
+ "has-[>[data-align=inline-start]]:[&>input]:pl-2",
18
+ "has-[>[data-align=inline-end]]:[&>input]:pr-2",
19
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
20
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
21
+
22
+ // Focus state — matches Input component pattern.
23
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:border-2",
24
+
25
+ // Error state.
26
+ "has-[[data-slot][aria-invalid=true]]:border-border-error",
27
+
28
+ // Disabled state.
29
+ "has-[[data-slot=input-group-control]:disabled]:bg-disabled has-[[data-slot=input-group-control]:disabled]:text-disabled-foreground has-[[data-slot=input-group-control]:disabled]:cursor-not-allowed",
30
+
31
+ className,
32
+ )}
33
+ {...props}
34
+ >
35
+ {children}
36
+ </fieldset>
37
+ );
38
+ }
39
+
40
+ const inputGroupAddonVariants = cva(
41
+ "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 type-text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 group-disabled/input-group:opacity-50",
42
+ {
43
+ variants: {
44
+ align: {
45
+ "inline-start": "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
46
+ "inline-end": "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
47
+ "block-start":
48
+ "order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
49
+ "block-end": "order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5",
50
+ },
51
+ },
52
+ defaultVariants: {
53
+ align: "inline-start",
54
+ },
55
+ },
56
+ );
57
+
58
+ function InputGroupAddon({
59
+ className,
60
+ align = "inline-start",
61
+ ...props
62
+ }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
63
+ return (
64
+ // biome-ignore lint/a11y/noStaticElementInteractions: click delegates focus to the associated input control
65
+ // biome-ignore lint/a11y/useKeyWithClickEvents: click delegates focus to the associated input control
66
+ <div
67
+ data-slot="input-group-addon"
68
+ data-align={align}
69
+ className={cn(inputGroupAddonVariants({ align }), className)}
70
+ onClick={(event) => {
71
+ if ((event.target as HTMLElement).closest("button")) {
72
+ return;
73
+ }
74
+ const control = event.currentTarget.parentElement?.querySelector<HTMLElement>("textarea, input");
75
+ control?.focus();
76
+ }}
77
+ {...props}
78
+ />
79
+ );
80
+ }
81
+
82
+ function InputGroupTextarea({ className, ...props }: React.ComponentProps<"textarea">) {
83
+ return (
84
+ <textarea
85
+ data-slot="input-group-control"
86
+ className={cn(
87
+ "w-full flex-1 resize-none rounded-none border-0 bg-transparent px-3 py-3 shadow-none outline-none",
88
+ "type-text-md text-foreground placeholder:text-placeholder",
89
+ "focus-visible:ring-0",
90
+ className,
91
+ )}
92
+ {...props}
93
+ />
94
+ );
95
+ }
96
+
97
+ export { InputGroup, InputGroupAddon, InputGroupTextarea };
@@ -0,0 +1,145 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ import { cn } from "../lib/cn";
6
+ import { Label } from "./label";
7
+
8
+ const inputSizeStyles = {
9
+ sm: {
10
+ standalone: "h-8 px-2 py-1.5 type-text-sm",
11
+ wrapper: "h-8",
12
+ inner: "px-2 py-1.5",
13
+ text: "type-text-sm",
14
+ leadingText: "pl-2 pr-2 type-text-sm",
15
+ leadingIcon: "pl-2 [&_svg]:size-4",
16
+ },
17
+ md: {
18
+ standalone: "h-8 px-2.5 py-1.5 type-text-md",
19
+ wrapper: "h-8",
20
+ inner: "px-2.5 py-1.5",
21
+ text: "type-text-md",
22
+ leadingText: "pl-2.5 pr-2 type-text-md",
23
+ leadingIcon: "pl-2.5 [&_svg]:size-5",
24
+ },
25
+ };
26
+
27
+ interface InputProps extends Omit<React.ComponentProps<"input">, "size"> {
28
+ label?: string;
29
+ required?: boolean;
30
+ hint?: string;
31
+ error?: string;
32
+ leadingText?: string;
33
+ leadingIcon?: React.ReactNode;
34
+ size?: "sm" | "md";
35
+ }
36
+
37
+ function Input({
38
+ className,
39
+ id,
40
+ label,
41
+ required,
42
+ hint,
43
+ error,
44
+ leadingText,
45
+ leadingIcon,
46
+ size = "md",
47
+ ...props
48
+ }: InputProps) {
49
+ const generatedId = React.useId();
50
+ const fieldId = id ?? generatedId;
51
+
52
+ const hasAddons = leadingText || leadingIcon;
53
+ const sizes = inputSizeStyles[size];
54
+
55
+ const inputElement = (
56
+ <input
57
+ id={fieldId}
58
+ data-slot="input"
59
+ className={cn(
60
+ "w-full min-w-0 bg-transparent outline-none",
61
+ sizes.text,
62
+ "text-foreground placeholder:text-placeholder",
63
+ "disabled:cursor-not-allowed",
64
+ !hasAddons && [
65
+ "flex",
66
+ sizes.standalone,
67
+ "bg-background border border-border rounded-md",
68
+ "transition-colors",
69
+ "focus-visible:border-ring focus-visible:border-2",
70
+ "disabled:bg-disabled disabled:text-disabled-foreground",
71
+ "aria-invalid:border-border-error",
72
+ error && "border-border-error",
73
+ ],
74
+ hasAddons && "flex-1 min-h-0",
75
+ className,
76
+ )}
77
+ required={required}
78
+ aria-invalid={error ? true : undefined}
79
+ aria-describedby={fieldId && (hint || error) ? `${fieldId}-description` : undefined}
80
+ {...props}
81
+ />
82
+ );
83
+
84
+ const wrappedInput = hasAddons ? (
85
+ <div
86
+ className={cn(
87
+ "flex items-center w-full overflow-hidden",
88
+ sizes.wrapper,
89
+ "bg-background border border-border rounded-md",
90
+ "transition-colors",
91
+ "has-[input:focus-visible]:border-ring has-[input:focus-visible]:border-2",
92
+ "has-[input:disabled]:bg-disabled has-[input:disabled]:text-disabled-foreground has-[input:disabled]:cursor-not-allowed",
93
+ error && "border-border-error",
94
+ )}
95
+ >
96
+ {leadingIcon && (
97
+ <span className={cn("flex items-center text-subtle-foreground [&_svg]:shrink-0", sizes.leadingIcon)}>
98
+ {leadingIcon}
99
+ </span>
100
+ )}
101
+ {leadingText && (
102
+ <span
103
+ className={cn(
104
+ "flex items-center text-subtle-foreground whitespace-nowrap border-r border-border self-stretch",
105
+ sizes.leadingText,
106
+ )}
107
+ >
108
+ {leadingText}
109
+ </span>
110
+ )}
111
+ <div className={cn("flex flex-1 items-center min-w-0", sizes.inner)}>{inputElement}</div>
112
+ </div>
113
+ ) : (
114
+ inputElement
115
+ );
116
+
117
+ return (
118
+ <div className="flex flex-col gap-1.5">
119
+ {label && (
120
+ <div className="flex items-center gap-0.5">
121
+ <Label htmlFor={fieldId} className="type-text-sm font-medium text-muted-foreground">
122
+ {label}
123
+ </Label>
124
+ {required && (
125
+ <span aria-hidden className="type-text-sm font-medium text-required">
126
+ *
127
+ </span>
128
+ )}
129
+ </div>
130
+ )}
131
+ {wrappedInput}
132
+ {(hint || error) && (
133
+ <p
134
+ id={fieldId ? `${fieldId}-description` : undefined}
135
+ className={cn("type-text-sm", error ? "text-destructive" : "text-subtle-foreground")}
136
+ >
137
+ {error ?? hint}
138
+ </p>
139
+ )}
140
+ </div>
141
+ );
142
+ }
143
+
144
+ export type { InputProps };
145
+ export { Input };
@@ -0,0 +1,20 @@
1
+ "use client";
2
+
3
+ import { cn } from "@alpic-ai/ui/lib/cn";
4
+ import * as LabelPrimitive from "@radix-ui/react-label";
5
+ import type * as React from "react";
6
+
7
+ function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
8
+ return (
9
+ <LabelPrimitive.Root
10
+ data-slot="label"
11
+ className={cn(
12
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
13
+ className,
14
+ )}
15
+ {...props}
16
+ />
17
+ );
18
+ }
19
+
20
+ export { Label };