@allbluecn/admin-app 0.3.3 → 0.3.4

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 (41) hide show
  1. package/allblue-ui/package.json +6 -0
  2. package/allblue-ui/src/components/confirm-dialog.tsx +94 -0
  3. package/allblue-ui/src/components/infinite-scroll-sentinel.tsx +37 -0
  4. package/allblue-ui/src/components/notion-avatar.tsx +126 -0
  5. package/allblue-ui/src/components/user-avatar.tsx +82 -0
  6. package/allblue-ui/src/data-table/data-table-pagination.tsx +121 -0
  7. package/allblue-ui/src/data-table/data-table.tsx +218 -0
  8. package/allblue-ui/src/data-table/table-detail-layout.tsx +97 -0
  9. package/allblue-ui/src/hooks/use-load-more.ts +57 -0
  10. package/allblue-ui/src/hooks/use-mobile.ts +19 -0
  11. package/allblue-ui/src/index.ts +31 -0
  12. package/allblue-ui/src/lib/avatar.ts +41 -0
  13. package/allblue-ui/src/lib/datetime.ts +226 -0
  14. package/allblue-ui/src/lib/sanitize.ts +18 -0
  15. package/allblue-ui/src/lib/utils.ts +1 -0
  16. package/allblue-ui/src/ui/alert-dialog.tsx +185 -0
  17. package/allblue-ui/src/ui/alert.tsx +75 -0
  18. package/allblue-ui/src/ui/avatar.tsx +106 -0
  19. package/allblue-ui/src/ui/badge.tsx +51 -0
  20. package/allblue-ui/src/ui/button.tsx +57 -0
  21. package/allblue-ui/src/ui/card.tsx +102 -0
  22. package/allblue-ui/src/ui/checkbox.tsx +27 -0
  23. package/allblue-ui/src/ui/collapsible.tsx +19 -0
  24. package/allblue-ui/src/ui/dialog.tsx +155 -0
  25. package/allblue-ui/src/ui/dropdown-menu.tsx +265 -0
  26. package/allblue-ui/src/ui/field.tsx +236 -0
  27. package/allblue-ui/src/ui/input-group.tsx +157 -0
  28. package/allblue-ui/src/ui/input.tsx +19 -0
  29. package/allblue-ui/src/ui/label.tsx +17 -0
  30. package/allblue-ui/src/ui/pagination.tsx +130 -0
  31. package/allblue-ui/src/ui/select.tsx +232 -0
  32. package/allblue-ui/src/ui/separator.tsx +23 -0
  33. package/allblue-ui/src/ui/sheet.tsx +134 -0
  34. package/allblue-ui/src/ui/sidebar.tsx +721 -0
  35. package/allblue-ui/src/ui/skeleton.tsx +13 -0
  36. package/allblue-ui/src/ui/switch.tsx +30 -0
  37. package/allblue-ui/src/ui/table.tsx +113 -0
  38. package/allblue-ui/src/ui/textarea.tsx +17 -0
  39. package/allblue-ui/src/ui/toast.tsx +308 -0
  40. package/allblue-ui/src/ui/tooltip.tsx +64 -0
  41. package/package.json +3 -2
@@ -0,0 +1,265 @@
1
+ import * as React from "react"
2
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu"
3
+ import { cn } from "cn"
4
+ import { ChevronRightIcon, CheckIcon } from "lucide-react"
5
+
6
+ function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
7
+ return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />
8
+ }
9
+
10
+ function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
11
+ return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
12
+ }
13
+
14
+ function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
15
+ return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
16
+ }
17
+
18
+ function DropdownMenuContent({
19
+ align = "start",
20
+ alignOffset = 0,
21
+ side = "bottom",
22
+ sideOffset = 4,
23
+ className,
24
+ ...props
25
+ }: MenuPrimitive.Popup.Props &
26
+ Pick<
27
+ MenuPrimitive.Positioner.Props,
28
+ "align" | "alignOffset" | "side" | "sideOffset"
29
+ >) {
30
+ return (
31
+ <MenuPrimitive.Portal>
32
+ <MenuPrimitive.Positioner
33
+ className="isolate z-50 outline-none"
34
+ align={align}
35
+ alignOffset={alignOffset}
36
+ side={side}
37
+ sideOffset={sideOffset}
38
+ >
39
+ <MenuPrimitive.Popup
40
+ data-slot="dropdown-menu-content"
41
+ className={cn("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", className )}
42
+ {...props}
43
+ />
44
+ </MenuPrimitive.Positioner>
45
+ </MenuPrimitive.Portal>
46
+ )
47
+ }
48
+
49
+ function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
50
+ return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
51
+ }
52
+
53
+ function DropdownMenuLabel({
54
+ className,
55
+ inset,
56
+ ...props
57
+ }: MenuPrimitive.GroupLabel.Props & {
58
+ inset?: boolean
59
+ }) {
60
+ return (
61
+ <MenuPrimitive.GroupLabel
62
+ data-slot="dropdown-menu-label"
63
+ data-inset={inset}
64
+ className={cn(
65
+ "px-2 py-1.5 text-xs font-medium text-muted-foreground data-inset:pl-8",
66
+ className
67
+ )}
68
+ {...props}
69
+ />
70
+ )
71
+ }
72
+
73
+ function DropdownMenuItem({
74
+ className,
75
+ inset,
76
+ variant = "default",
77
+ ...props
78
+ }: MenuPrimitive.Item.Props & {
79
+ inset?: boolean
80
+ variant?: "default" | "destructive"
81
+ }) {
82
+ return (
83
+ <MenuPrimitive.Item
84
+ data-slot="dropdown-menu-item"
85
+ data-inset={inset}
86
+ data-variant={variant}
87
+ className={cn(
88
+ "group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
89
+ className
90
+ )}
91
+ {...props}
92
+ />
93
+ )
94
+ }
95
+
96
+ function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
97
+ return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />
98
+ }
99
+
100
+ function DropdownMenuSubTrigger({
101
+ className,
102
+ inset,
103
+ children,
104
+ ...props
105
+ }: MenuPrimitive.SubmenuTrigger.Props & {
106
+ inset?: boolean
107
+ }) {
108
+ return (
109
+ <MenuPrimitive.SubmenuTrigger
110
+ data-slot="dropdown-menu-sub-trigger"
111
+ data-inset={inset}
112
+ className={cn(
113
+ "flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
114
+ className
115
+ )}
116
+ {...props}
117
+ >
118
+ {children}
119
+ <ChevronRightIcon className="ml-auto" />
120
+ </MenuPrimitive.SubmenuTrigger>
121
+ )
122
+ }
123
+
124
+ function DropdownMenuSubContent({
125
+ align = "start",
126
+ alignOffset = -3,
127
+ side = "right",
128
+ sideOffset = 0,
129
+ className,
130
+ ...props
131
+ }: React.ComponentProps<typeof DropdownMenuContent>) {
132
+ return (
133
+ <DropdownMenuContent
134
+ data-slot="dropdown-menu-sub-content"
135
+ className={cn("w-auto min-w-[96px] rounded-md bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
136
+ align={align}
137
+ alignOffset={alignOffset}
138
+ side={side}
139
+ sideOffset={sideOffset}
140
+ {...props}
141
+ />
142
+ )
143
+ }
144
+
145
+ function DropdownMenuCheckboxItem({
146
+ className,
147
+ children,
148
+ checked,
149
+ inset,
150
+ ...props
151
+ }: MenuPrimitive.CheckboxItem.Props & {
152
+ inset?: boolean
153
+ }) {
154
+ return (
155
+ <MenuPrimitive.CheckboxItem
156
+ data-slot="dropdown-menu-checkbox-item"
157
+ data-inset={inset}
158
+ className={cn(
159
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-8 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
160
+ className
161
+ )}
162
+ checked={checked}
163
+ {...props}
164
+ >
165
+ <span
166
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
167
+ data-slot="dropdown-menu-checkbox-item-indicator"
168
+ >
169
+ <MenuPrimitive.CheckboxItemIndicator>
170
+ <CheckIcon
171
+ />
172
+ </MenuPrimitive.CheckboxItemIndicator>
173
+ </span>
174
+ {children}
175
+ </MenuPrimitive.CheckboxItem>
176
+ )
177
+ }
178
+
179
+ function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
180
+ return (
181
+ <MenuPrimitive.RadioGroup
182
+ data-slot="dropdown-menu-radio-group"
183
+ {...props}
184
+ />
185
+ )
186
+ }
187
+
188
+ function DropdownMenuRadioItem({
189
+ className,
190
+ children,
191
+ inset,
192
+ ...props
193
+ }: MenuPrimitive.RadioItem.Props & {
194
+ inset?: boolean
195
+ }) {
196
+ return (
197
+ <MenuPrimitive.RadioItem
198
+ data-slot="dropdown-menu-radio-item"
199
+ data-inset={inset}
200
+ className={cn(
201
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-8 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
202
+ className
203
+ )}
204
+ {...props}
205
+ >
206
+ <span
207
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
208
+ data-slot="dropdown-menu-radio-item-indicator"
209
+ >
210
+ <MenuPrimitive.RadioItemIndicator>
211
+ <CheckIcon
212
+ />
213
+ </MenuPrimitive.RadioItemIndicator>
214
+ </span>
215
+ {children}
216
+ </MenuPrimitive.RadioItem>
217
+ )
218
+ }
219
+
220
+ function DropdownMenuSeparator({
221
+ className,
222
+ ...props
223
+ }: MenuPrimitive.Separator.Props) {
224
+ return (
225
+ <MenuPrimitive.Separator
226
+ data-slot="dropdown-menu-separator"
227
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
228
+ {...props}
229
+ />
230
+ )
231
+ }
232
+
233
+ function DropdownMenuShortcut({
234
+ className,
235
+ ...props
236
+ }: React.ComponentProps<"span">) {
237
+ return (
238
+ <span
239
+ data-slot="dropdown-menu-shortcut"
240
+ className={cn(
241
+ "ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground",
242
+ className
243
+ )}
244
+ {...props}
245
+ />
246
+ )
247
+ }
248
+
249
+ export {
250
+ DropdownMenu,
251
+ DropdownMenuPortal,
252
+ DropdownMenuTrigger,
253
+ DropdownMenuContent,
254
+ DropdownMenuGroup,
255
+ DropdownMenuLabel,
256
+ DropdownMenuItem,
257
+ DropdownMenuCheckboxItem,
258
+ DropdownMenuRadioGroup,
259
+ DropdownMenuRadioItem,
260
+ DropdownMenuSeparator,
261
+ DropdownMenuShortcut,
262
+ DropdownMenuSub,
263
+ DropdownMenuSubTrigger,
264
+ DropdownMenuSubContent,
265
+ }
@@ -0,0 +1,236 @@
1
+ import { useMemo } from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "cn"
4
+
5
+ import { Label } from "./label"
6
+ import { Separator } from "./separator"
7
+
8
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
9
+ return (
10
+ <fieldset
11
+ data-slot="field-set"
12
+ className={cn(
13
+ "flex flex-col gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ function FieldLegend({
22
+ className,
23
+ variant = "legend",
24
+ ...props
25
+ }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
26
+ return (
27
+ <legend
28
+ data-slot="field-legend"
29
+ data-variant={variant}
30
+ className={cn(
31
+ "mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
32
+ className
33
+ )}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
40
+ return (
41
+ <div
42
+ data-slot="field-group"
43
+ className={cn(
44
+ "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",
45
+ className
46
+ )}
47
+ {...props}
48
+ />
49
+ )
50
+ }
51
+
52
+ const fieldVariants = cva(
53
+ "group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
54
+ {
55
+ variants: {
56
+ orientation: {
57
+ vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
58
+ horizontal:
59
+ "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
60
+ responsive:
61
+ "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
62
+ },
63
+ },
64
+ defaultVariants: {
65
+ orientation: "vertical",
66
+ },
67
+ }
68
+ )
69
+
70
+ function Field({
71
+ className,
72
+ orientation = "vertical",
73
+ ...props
74
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
75
+ return (
76
+ <div
77
+ role="group"
78
+ data-slot="field"
79
+ data-orientation={orientation}
80
+ className={cn(fieldVariants({ orientation }), className)}
81
+ {...props}
82
+ />
83
+ )
84
+ }
85
+
86
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
87
+ return (
88
+ <div
89
+ data-slot="field-content"
90
+ className={cn(
91
+ "group/field-content flex flex-1 flex-col gap-1 leading-snug",
92
+ className
93
+ )}
94
+ {...props}
95
+ />
96
+ )
97
+ }
98
+
99
+ function FieldLabel({
100
+ className,
101
+ ...props
102
+ }: React.ComponentProps<typeof Label>) {
103
+ return (
104
+ <Label
105
+ data-slot="field-label"
106
+ className={cn(
107
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 *:data-[slot=field]:p-3 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
108
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
109
+ className
110
+ )}
111
+ {...props}
112
+ />
113
+ )
114
+ }
115
+
116
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
117
+ return (
118
+ <div
119
+ data-slot="field-label"
120
+ className={cn(
121
+ "flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50",
122
+ className
123
+ )}
124
+ {...props}
125
+ />
126
+ )
127
+ }
128
+
129
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
130
+ return (
131
+ <p
132
+ data-slot="field-description"
133
+ className={cn(
134
+ "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
135
+ "last:mt-0 nth-last-2:-mt-1",
136
+ "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
137
+ className
138
+ )}
139
+ {...props}
140
+ />
141
+ )
142
+ }
143
+
144
+ function FieldSeparator({
145
+ children,
146
+ className,
147
+ ...props
148
+ }: React.ComponentProps<"div"> & {
149
+ children?: React.ReactNode
150
+ }) {
151
+ return (
152
+ <div
153
+ data-slot="field-separator"
154
+ data-content={!!children}
155
+ className={cn(
156
+ "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
157
+ className
158
+ )}
159
+ {...props}
160
+ >
161
+ <Separator className="absolute inset-0 top-1/2" />
162
+ {children && (
163
+ <span
164
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
165
+ data-slot="field-separator-content"
166
+ >
167
+ {children}
168
+ </span>
169
+ )}
170
+ </div>
171
+ )
172
+ }
173
+
174
+ function FieldError({
175
+ className,
176
+ children,
177
+ errors,
178
+ ...props
179
+ }: React.ComponentProps<"div"> & {
180
+ errors?: Array<{ message?: string } | undefined>
181
+ }) {
182
+ const content = useMemo(() => {
183
+ if (children) {
184
+ return children
185
+ }
186
+
187
+ if (!errors?.length) {
188
+ return null
189
+ }
190
+
191
+ const uniqueErrors = [
192
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
193
+ ]
194
+
195
+ if (uniqueErrors?.length == 1) {
196
+ return uniqueErrors[0]?.message
197
+ }
198
+
199
+ return (
200
+ <ul className="ml-4 flex list-disc flex-col gap-1">
201
+ {uniqueErrors.map(
202
+ (error, index) =>
203
+ error?.message && <li key={index}>{error.message}</li>
204
+ )}
205
+ </ul>
206
+ )
207
+ }, [children, errors])
208
+
209
+ if (!content) {
210
+ return null
211
+ }
212
+
213
+ return (
214
+ <div
215
+ role="alert"
216
+ data-slot="field-error"
217
+ className={cn("text-sm font-normal text-destructive", className)}
218
+ {...props}
219
+ >
220
+ {content}
221
+ </div>
222
+ )
223
+ }
224
+
225
+ export {
226
+ Field,
227
+ FieldLabel,
228
+ FieldDescription,
229
+ FieldError,
230
+ FieldGroup,
231
+ FieldLegend,
232
+ FieldSeparator,
233
+ FieldSet,
234
+ FieldContent,
235
+ FieldTitle,
236
+ }
@@ -0,0 +1,157 @@
1
+
2
+ import * as React from "react"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "cn"
5
+
6
+ import { Button } from "./button"
7
+ import { Input } from "./input"
8
+ import { Textarea } from "./textarea"
9
+
10
+ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
11
+ return (
12
+ <div
13
+ data-slot="input-group"
14
+ role="group"
15
+ className={cn(
16
+ "group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ const inputGroupAddonVariants = cva(
25
+ "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
26
+ {
27
+ variants: {
28
+ align: {
29
+ "inline-start":
30
+ "order-first pl-2 has-[>button]:-ml-1 has-[>kbd]:ml-[-0.15rem]",
31
+ "inline-end":
32
+ "order-last pr-2 has-[>button]:-mr-1 has-[>kbd]:mr-[-0.15rem]",
33
+ "block-start":
34
+ "order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
35
+ "block-end":
36
+ "order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2",
37
+ },
38
+ },
39
+ defaultVariants: {
40
+ align: "inline-start",
41
+ },
42
+ }
43
+ )
44
+
45
+ function InputGroupAddon({
46
+ className,
47
+ align = "inline-start",
48
+ ...props
49
+ }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
50
+ return (
51
+ <div
52
+ role="group"
53
+ data-slot="input-group-addon"
54
+ data-align={align}
55
+ className={cn(inputGroupAddonVariants({ align }), className)}
56
+ onClick={(e) => {
57
+ if ((e.target as HTMLElement).closest("button")) {
58
+ return
59
+ }
60
+ e.currentTarget.parentElement?.querySelector("input")?.focus()
61
+ }}
62
+ {...props}
63
+ />
64
+ )
65
+ }
66
+
67
+ const inputGroupButtonVariants = cva(
68
+ "flex items-center gap-2 text-sm shadow-none",
69
+ {
70
+ variants: {
71
+ size: {
72
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
73
+ sm: "",
74
+ "icon-xs":
75
+ "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
76
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0",
77
+ },
78
+ },
79
+ defaultVariants: {
80
+ size: "xs",
81
+ },
82
+ }
83
+ )
84
+
85
+ function InputGroupButton({
86
+ className,
87
+ type = "button",
88
+ variant = "ghost",
89
+ size = "xs",
90
+ ...props
91
+ }: Omit<React.ComponentProps<typeof Button>, "size" | "type"> &
92
+ VariantProps<typeof inputGroupButtonVariants> & {
93
+ type?: "button" | "submit" | "reset"
94
+ }) {
95
+ return (
96
+ <Button
97
+ type={type}
98
+ data-size={size}
99
+ variant={variant}
100
+ className={cn(inputGroupButtonVariants({ size }), className)}
101
+ {...props}
102
+ />
103
+ )
104
+ }
105
+
106
+ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
107
+ return (
108
+ <span
109
+ className={cn(
110
+ "flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
111
+ className
112
+ )}
113
+ {...props}
114
+ />
115
+ )
116
+ }
117
+
118
+ function InputGroupInput({
119
+ className,
120
+ ...props
121
+ }: React.ComponentProps<"input">) {
122
+ return (
123
+ <Input
124
+ data-slot="input-group-control"
125
+ className={cn(
126
+ "flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
127
+ className
128
+ )}
129
+ {...props}
130
+ />
131
+ )
132
+ }
133
+
134
+ function InputGroupTextarea({
135
+ className,
136
+ ...props
137
+ }: React.ComponentProps<"textarea">) {
138
+ return (
139
+ <Textarea
140
+ data-slot="input-group-control"
141
+ className={cn(
142
+ "flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
143
+ className
144
+ )}
145
+ {...props}
146
+ />
147
+ )
148
+ }
149
+
150
+ export {
151
+ InputGroup,
152
+ InputGroupAddon,
153
+ InputGroupButton,
154
+ InputGroupText,
155
+ InputGroupInput,
156
+ InputGroupTextarea,
157
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from "react"
2
+ import { Input as InputPrimitive } from "@base-ui/react/input"
3
+ import { cn } from "cn"
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
+ return (
7
+ <InputPrimitive
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-2.5 py-1 text-base transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
12
+ className
13
+ )}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ export { Input }
@@ -0,0 +1,17 @@
1
+ import * as React from "react"
2
+ import { cn } from "cn"
3
+
4
+ function Label({ className, ...props }: React.ComponentProps<"label">) {
5
+ return (
6
+ <label
7
+ data-slot="label"
8
+ className={cn(
9
+ "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",
10
+ className
11
+ )}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
17
+ export { Label }