@moloodev/ui 0.0.0-stage → 0.2.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 (93) hide show
  1. package/package.json +139 -3
  2. package/registry/components.ts +135 -0
  3. package/registry/fonts.css +81 -0
  4. package/registry/suit-OFL.txt +94 -0
  5. package/registry/wanted-sans-OFL.txt +93 -0
  6. package/src/components/ui/accordion.tsx +74 -0
  7. package/src/components/ui/alert-dialog.tsx +187 -0
  8. package/src/components/ui/alert.tsx +83 -0
  9. package/src/components/ui/aspect-ratio.tsx +22 -0
  10. package/src/components/ui/attachment.tsx +207 -0
  11. package/src/components/ui/avatar.tsx +109 -0
  12. package/src/components/ui/badge.tsx +54 -0
  13. package/src/components/ui/breadcrumb.tsx +126 -0
  14. package/src/components/ui/bubble.tsx +128 -0
  15. package/src/components/ui/button-group.tsx +103 -0
  16. package/src/components/ui/button.tsx +68 -0
  17. package/src/components/ui/calendar.tsx +222 -0
  18. package/src/components/ui/card.tsx +103 -0
  19. package/src/components/ui/carousel.tsx +242 -0
  20. package/src/components/ui/chart.tsx +398 -0
  21. package/src/components/ui/checkbox.tsx +28 -0
  22. package/src/components/ui/collapsible.tsx +40 -0
  23. package/src/components/ui/combobox.tsx +299 -0
  24. package/src/components/ui/command.tsx +218 -0
  25. package/src/components/ui/context-menu.tsx +271 -0
  26. package/src/components/ui/data-table.tsx +919 -0
  27. package/src/components/ui/date-picker.tsx +65 -0
  28. package/src/components/ui/dialog.tsx +224 -0
  29. package/src/components/ui/drawer.tsx +231 -0
  30. package/src/components/ui/dropdown-menu.tsx +268 -0
  31. package/src/components/ui/dropzone.tsx +186 -0
  32. package/src/components/ui/empty.tsx +101 -0
  33. package/src/components/ui/field.tsx +238 -0
  34. package/src/components/ui/form.tsx +83 -0
  35. package/src/components/ui/highlighted-source.tsx +200 -0
  36. package/src/components/ui/hover-card.tsx +51 -0
  37. package/src/components/ui/image-field.tsx +796 -0
  38. package/src/components/ui/input-group.tsx +149 -0
  39. package/src/components/ui/input-otp.tsx +86 -0
  40. package/src/components/ui/input-step.ts +19 -0
  41. package/src/components/ui/input.tsx +100 -0
  42. package/src/components/ui/item.tsx +201 -0
  43. package/src/components/ui/kbd.tsx +91 -0
  44. package/src/components/ui/kind-icon-glob.ts +24 -0
  45. package/src/components/ui/kind-icon-load.ts +52 -0
  46. package/src/components/ui/kind-icon-static.ts +46 -0
  47. package/src/components/ui/kind-icon.tsx +84 -0
  48. package/src/components/ui/kind-mark.tsx +65 -0
  49. package/src/components/ui/label.tsx +29 -0
  50. package/src/components/ui/marker.tsx +71 -0
  51. package/src/components/ui/menubar.tsx +277 -0
  52. package/src/components/ui/message-scroller.tsx +137 -0
  53. package/src/components/ui/message.tsx +92 -0
  54. package/src/components/ui/moloo-logo.tsx +45 -0
  55. package/src/components/ui/native-select.tsx +61 -0
  56. package/src/components/ui/navigation-menu.tsx +168 -0
  57. package/src/components/ui/outline-list.tsx +73 -0
  58. package/src/components/ui/pagination.tsx +130 -0
  59. package/src/components/ui/panel.tsx +108 -0
  60. package/src/components/ui/popover.tsx +90 -0
  61. package/src/components/ui/progress.tsx +117 -0
  62. package/src/components/ui/radio-group.tsx +38 -0
  63. package/src/components/ui/resizable.tsx +52 -0
  64. package/src/components/ui/scroll-area.tsx +54 -0
  65. package/src/components/ui/select.tsx +205 -0
  66. package/src/components/ui/separator.tsx +25 -0
  67. package/src/components/ui/sheet.tsx +140 -0
  68. package/src/components/ui/sidebar.tsx +746 -0
  69. package/src/components/ui/skeleton.tsx +13 -0
  70. package/src/components/ui/slider.tsx +52 -0
  71. package/src/components/ui/spinner.tsx +18 -0
  72. package/src/components/ui/switch.tsx +32 -0
  73. package/src/components/ui/table.tsx +116 -0
  74. package/src/components/ui/tabs.tsx +83 -0
  75. package/src/components/ui/tag-list-input.tsx +194 -0
  76. package/src/components/ui/textarea.tsx +18 -0
  77. package/src/components/ui/timeline.tsx +265 -0
  78. package/src/components/ui/toast.tsx +237 -0
  79. package/src/components/ui/toggle-group.tsx +90 -0
  80. package/src/components/ui/toggle.tsx +45 -0
  81. package/src/components/ui/tooltip.tsx +66 -0
  82. package/src/css.d.ts +1 -0
  83. package/src/hooks/use-clipboard.ts +82 -0
  84. package/src/hooks/use-mobile.ts +21 -0
  85. package/src/index.ts +106 -0
  86. package/src/kind-icon.ts +4 -0
  87. package/src/lib/format-relative-time.ts +41 -0
  88. package/src/lib/list-selection.ts +61 -0
  89. package/src/lib/utils.ts +6 -0
  90. package/src/moloo.css +360 -0
  91. package/src/styles.css +36 -0
  92. package/src/typeset.css +518 -0
  93. package/README.md +0 -3
@@ -0,0 +1,13 @@
1
+ import { cn } from "../../lib/utils"
2
+
3
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4
+ return (
5
+ <div
6
+ data-slot="skeleton"
7
+ className={cn("animate-pulse rounded-md bg-muted", className)}
8
+ {...props}
9
+ />
10
+ )
11
+ }
12
+
13
+ export { Skeleton }
@@ -0,0 +1,52 @@
1
+ import { Slider as SliderPrimitive } from "@base-ui/react/slider"
2
+
3
+ import { cn } from "../../lib/utils"
4
+
5
+ function Slider({
6
+ className,
7
+ defaultValue,
8
+ value,
9
+ min = 0,
10
+ max = 100,
11
+ ...props
12
+ }: SliderPrimitive.Root.Props) {
13
+ const _values = Array.isArray(value)
14
+ ? value
15
+ : Array.isArray(defaultValue)
16
+ ? defaultValue
17
+ : [min, max]
18
+
19
+ return (
20
+ <SliderPrimitive.Root
21
+ className={cn("data-horizontal:w-full data-vertical:h-full", className)}
22
+ data-slot="slider"
23
+ defaultValue={defaultValue}
24
+ value={value}
25
+ min={min}
26
+ max={max}
27
+ thumbAlignment="edge"
28
+ {...props}
29
+ >
30
+ <SliderPrimitive.Control className="relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col">
31
+ <SliderPrimitive.Track
32
+ data-slot="slider-track"
33
+ className="relative grow overflow-hidden rounded-md bg-muted select-none data-horizontal:h-1 data-horizontal:w-full data-vertical:h-full data-vertical:w-1"
34
+ >
35
+ <SliderPrimitive.Indicator
36
+ data-slot="slider-range"
37
+ className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"
38
+ />
39
+ </SliderPrimitive.Track>
40
+ {Array.from({ length: _values.length }, (_, index) => (
41
+ <SliderPrimitive.Thumb
42
+ data-slot="slider-thumb"
43
+ key={index}
44
+ className="moloo-focus-within relative block size-3.5 shrink-0 rounded-md border-2 border-border bg-primary shadow-moloo-sm transition-[color,box-shadow] select-none after:absolute after:-inset-2 disabled:pointer-events-none disabled:opacity-50"
45
+ />
46
+ ))}
47
+ </SliderPrimitive.Control>
48
+ </SliderPrimitive.Root>
49
+ )
50
+ }
51
+
52
+ export { Slider }
@@ -0,0 +1,18 @@
1
+ import { cn } from "../../lib/utils"
2
+
3
+ function Spinner({ className, ...props }: React.ComponentProps<"div">) {
4
+ return (
5
+ <div
6
+ data-slot="spinner"
7
+ role="status"
8
+ aria-label="Loading"
9
+ className={cn(
10
+ "size-[1.35rem] shrink-0 animate-spin rounded-full border-[3px] border-secondary border-t-primary border-r-primary",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export { Spinner }
@@ -0,0 +1,32 @@
1
+ "use client"
2
+
3
+ import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ function Switch({
8
+ className,
9
+ size = "default",
10
+ ...props
11
+ }: SwitchPrimitive.Root.Props & {
12
+ size?: "sm" | "default"
13
+ }) {
14
+ return (
15
+ <SwitchPrimitive.Root
16
+ data-slot="switch"
17
+ data-size={size}
18
+ className={cn(
19
+ "moloo-focus peer group/switch relative inline-flex shrink-0 items-center rounded-md border-2 border-input bg-[var(--field-bg)] p-0.5 transition-colors outline-none after:absolute after:-inset-x-3 after:-inset-y-2 aria-invalid:border-destructive aria-invalid:aria-checked:border-primary data-[size=default]:h-6 data-[size=default]:w-10 data-[size=sm]:h-5 data-[size=sm]:w-8 data-checked:border-primary data-checked:bg-primary dark:bg-input/30 dark:data-checked:bg-primary data-disabled:cursor-not-allowed data-disabled:opacity-50",
20
+ className
21
+ )}
22
+ {...props}
23
+ >
24
+ <SwitchPrimitive.Thumb
25
+ data-slot="switch-thumb"
26
+ className="pointer-events-none block shrink-0 rounded-sm bg-foreground transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-checked:translate-x-full data-checked:bg-primary-foreground"
27
+ />
28
+ </SwitchPrimitive.Root>
29
+ )
30
+ }
31
+
32
+ export { Switch }
@@ -0,0 +1,116 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ function Table({ className, ...props }: React.ComponentProps<"table">) {
8
+ return (
9
+ <div
10
+ data-slot="table-container"
11
+ className="relative w-full overflow-x-auto"
12
+ >
13
+ <table
14
+ data-slot="table"
15
+ className={cn("w-full caption-bottom text-sm", className)}
16
+ {...props}
17
+ />
18
+ </div>
19
+ )
20
+ }
21
+
22
+ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
23
+ return (
24
+ <thead
25
+ data-slot="table-header"
26
+ className={cn("[&_tr]:border-b", className)}
27
+ {...props}
28
+ />
29
+ )
30
+ }
31
+
32
+ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
33
+ return (
34
+ <tbody
35
+ data-slot="table-body"
36
+ className={cn("[&_tr:last-child]:border-0", className)}
37
+ {...props}
38
+ />
39
+ )
40
+ }
41
+
42
+ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
43
+ return (
44
+ <tfoot
45
+ data-slot="table-footer"
46
+ className={cn(
47
+ "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ )
53
+ }
54
+
55
+ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
56
+ return (
57
+ <tr
58
+ data-slot="table-row"
59
+ className={cn(
60
+ "border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted",
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
69
+ return (
70
+ <th
71
+ data-slot="table-head"
72
+ className={cn(
73
+ "label h-10 px-2 text-left align-middle whitespace-nowrap text-muted-foreground [&:has([role=checkbox])]:pr-0",
74
+ className
75
+ )}
76
+ {...props}
77
+ />
78
+ )
79
+ }
80
+
81
+ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
82
+ return (
83
+ <td
84
+ data-slot="table-cell"
85
+ className={cn(
86
+ "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
87
+ className
88
+ )}
89
+ {...props}
90
+ />
91
+ )
92
+ }
93
+
94
+ function TableCaption({
95
+ className,
96
+ ...props
97
+ }: React.ComponentProps<"caption">) {
98
+ return (
99
+ <caption
100
+ data-slot="table-caption"
101
+ className={cn("mt-4 text-sm text-muted-foreground", className)}
102
+ {...props}
103
+ />
104
+ )
105
+ }
106
+
107
+ export {
108
+ Table,
109
+ TableHeader,
110
+ TableBody,
111
+ TableFooter,
112
+ TableHead,
113
+ TableRow,
114
+ TableCell,
115
+ TableCaption,
116
+ }
@@ -0,0 +1,83 @@
1
+ "use client"
2
+
3
+ import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ function Tabs({
9
+ className,
10
+ orientation = "horizontal",
11
+ ...props
12
+ }: TabsPrimitive.Root.Props) {
13
+ return (
14
+ <TabsPrimitive.Root
15
+ data-slot="tabs"
16
+ data-orientation={orientation}
17
+ orientation={orientation}
18
+ className={cn(
19
+ "group/tabs flex gap-2 data-horizontal:flex-col",
20
+ className
21
+ )}
22
+ {...props}
23
+ />
24
+ )
25
+ }
26
+
27
+ const tabsListVariants = cva(
28
+ "group/tabs-list inline-flex w-fit items-center justify-center gap-1.5 text-muted-foreground group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col",
29
+ {
30
+ variants: {
31
+ variant: {
32
+ default: "bg-transparent p-0",
33
+ line: "gap-1 rounded-md bg-transparent p-0",
34
+ },
35
+ },
36
+ defaultVariants: {
37
+ variant: "default",
38
+ },
39
+ }
40
+ )
41
+
42
+ function TabsList({
43
+ className,
44
+ variant = "default",
45
+ ...props
46
+ }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
47
+ return (
48
+ <TabsPrimitive.List
49
+ data-slot="tabs-list"
50
+ data-variant={variant}
51
+ className={cn(tabsListVariants({ variant }), className)}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
58
+ return (
59
+ <TabsPrimitive.Tab
60
+ data-slot="tabs-trigger"
61
+ className={cn(
62
+ "moloo-focus relative inline-flex items-center justify-center gap-1.5 rounded-md border-2 border-transparent bg-card px-3 py-1.5 font-display text-[0.8rem] font-bold tracking-[-0.02em] whitespace-nowrap text-muted-foreground shadow-moloo-sm transition-all outline-none group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
63
+ "data-active:bg-accent data-active:text-accent-foreground data-active:shadow-moloo data-active:rotate-[-1.2deg]",
64
+ "group-data-[variant=line]/tabs-list:rounded-none group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:px-2 group-data-[variant=line]/tabs-list:shadow-none group-data-[variant=line]/tabs-list:data-active:rotate-0 group-data-[variant=line]/tabs-list:data-active:bg-transparent group-data-[variant=line]/tabs-list:data-active:text-foreground group-data-[variant=line]/tabs-list:data-active:shadow-none",
65
+ "after:absolute after:bg-accent after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-4px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
66
+ className
67
+ )}
68
+ {...props}
69
+ />
70
+ )
71
+ }
72
+
73
+ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
74
+ return (
75
+ <TabsPrimitive.Panel
76
+ data-slot="tabs-content"
77
+ className={cn("flex-1 text-sm outline-none", className)}
78
+ {...props}
79
+ />
80
+ )
81
+ }
82
+
83
+ export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
@@ -0,0 +1,194 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { IconPlus, IconX } from "@tabler/icons-react"
5
+
6
+ import { Badge } from "./badge"
7
+ import { Button } from "./button"
8
+ import { Input } from "./input"
9
+ import { cn } from "../../lib/utils"
10
+
11
+ export type TagListInputParseResult =
12
+ | { ok: true; value: string }
13
+ | { ok: false; error: string }
14
+
15
+ export type TagListInputProps = {
16
+ values: readonly string[]
17
+ onChange: (values: string[]) => void
18
+ parse?: (raw: string) => TagListInputParseResult
19
+ placeholder?: string
20
+ addLabel?: string
21
+ removeLabel?: (value: string) => string
22
+ empty?: React.ReactNode
23
+ disabled?: boolean
24
+ id?: string
25
+ className?: string
26
+ "aria-label"?: string
27
+ "aria-invalid"?: React.AriaAttributes["aria-invalid"]
28
+ "aria-describedby"?: string
29
+ }
30
+
31
+ function defaultParse(raw: string): TagListInputParseResult {
32
+ const value = raw.trim()
33
+ if (value.length === 0) {
34
+ return { ok: false, error: "Enter a value." }
35
+ }
36
+ return { ok: true, value }
37
+ }
38
+
39
+ function defaultRemoveLabel(value: string) {
40
+ return `Remove ${value}`
41
+ }
42
+
43
+ function commitTag(
44
+ values: readonly string[],
45
+ raw: string,
46
+ parse: (raw: string) => TagListInputParseResult,
47
+ ):
48
+ | { type: "invalid"; error: string }
49
+ | { type: "unchanged" }
50
+ | { type: "added"; values: string[] } {
51
+ const parsed = parse(raw)
52
+ if (!parsed.ok) return { type: "invalid", error: parsed.error }
53
+ if (values.includes(parsed.value)) return { type: "unchanged" }
54
+ return { type: "added", values: [...values, parsed.value] }
55
+ }
56
+
57
+ function markedInvalid(
58
+ ariaInvalid: React.AriaAttributes["aria-invalid"],
59
+ error: string | null,
60
+ ) {
61
+ if (error !== null) return true
62
+ return (
63
+ ariaInvalid === true ||
64
+ ariaInvalid === "true" ||
65
+ ariaInvalid === "grammar" ||
66
+ ariaInvalid === "spelling"
67
+ )
68
+ }
69
+
70
+ function TagListInput({
71
+ values,
72
+ onChange,
73
+ parse = defaultParse,
74
+ placeholder,
75
+ addLabel = "Add",
76
+ removeLabel = defaultRemoveLabel,
77
+ empty,
78
+ disabled = false,
79
+ id,
80
+ className,
81
+ "aria-label": ariaLabel,
82
+ "aria-invalid": ariaInvalid,
83
+ "aria-describedby": ariaDescribedBy,
84
+ }: TagListInputProps) {
85
+ const [draft, setDraft] = React.useState("")
86
+ const [error, setError] = React.useState<string | null>(null)
87
+ const inputRef = React.useRef<HTMLInputElement>(null)
88
+ const errorId = React.useId()
89
+ const invalid = markedInvalid(ariaInvalid, error)
90
+ const describedBy =
91
+ [ariaDescribedBy, error ? errorId : undefined].filter(Boolean).join(" ") ||
92
+ undefined
93
+
94
+ function add() {
95
+ const result = commitTag(values, draft, parse)
96
+ if (result.type === "invalid") {
97
+ setError(result.error)
98
+ return
99
+ }
100
+ setError(null)
101
+ setDraft("")
102
+ if (result.type === "added") onChange(result.values)
103
+ }
104
+
105
+ function remove(value: string) {
106
+ onChange(values.filter((item) => item !== value))
107
+ inputRef.current?.focus()
108
+ }
109
+
110
+ function onDraftKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
111
+ if (event.nativeEvent.isComposing) return
112
+ if (event.key === "Enter") {
113
+ event.preventDefault()
114
+ add()
115
+ return
116
+ }
117
+ if (
118
+ (event.key === "Backspace" || event.key === "Delete") &&
119
+ draft.length === 0 &&
120
+ values.length > 0
121
+ ) {
122
+ event.preventDefault()
123
+ onChange(values.slice(0, -1))
124
+ }
125
+ }
126
+
127
+ return (
128
+ <div
129
+ data-slot="tag-list-input"
130
+ data-disabled={disabled ? "true" : undefined}
131
+ className={cn("flex w-full flex-col gap-2", className)}
132
+ >
133
+ <div className="flex min-h-7 flex-wrap items-center gap-1.5">
134
+ {values.length === 0
135
+ ? empty
136
+ : values.map((value) => (
137
+ <Badge
138
+ key={value}
139
+ variant="outline"
140
+ className="h-auto max-w-full gap-1 overflow-visible py-0.5 pr-0.5"
141
+ >
142
+ <span className="min-w-0 truncate" title={value}>
143
+ {value}
144
+ </span>
145
+ <Button
146
+ type="button"
147
+ variant="ghost"
148
+ size="icon-xs"
149
+ className="size-5"
150
+ aria-label={removeLabel(value)}
151
+ disabled={disabled}
152
+ onClick={() => remove(value)}
153
+ >
154
+ <IconX aria-hidden />
155
+ </Button>
156
+ </Badge>
157
+ ))}
158
+ </div>
159
+ <div className="flex gap-2">
160
+ <Input
161
+ ref={inputRef}
162
+ id={id}
163
+ value={draft}
164
+ placeholder={placeholder}
165
+ disabled={disabled}
166
+ aria-label={ariaLabel}
167
+ aria-invalid={invalid || undefined}
168
+ aria-describedby={describedBy}
169
+ onChange={(event) => {
170
+ setDraft(event.currentTarget.value)
171
+ if (error) setError(null)
172
+ }}
173
+ onKeyDown={onDraftKeyDown}
174
+ />
175
+ <Button
176
+ type="button"
177
+ variant="outline"
178
+ disabled={disabled || draft.trim().length === 0}
179
+ onClick={add}
180
+ >
181
+ <IconPlus aria-hidden />
182
+ {addLabel}
183
+ </Button>
184
+ </div>
185
+ {error ? (
186
+ <p id={errorId} role="alert" className="text-sm font-normal text-destructive">
187
+ {error}
188
+ </p>
189
+ ) : null}
190
+ </div>
191
+ )
192
+ }
193
+
194
+ export { TagListInput }
@@ -0,0 +1,18 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+
5
+ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6
+ return (
7
+ <textarea
8
+ data-slot="textarea"
9
+ className={cn(
10
+ "moloo-field moloo-focus flex field-sizing-content min-h-16 w-full px-2.5 py-2 text-sm font-semibold text-foreground transition-colors outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:shadow-[var(--moloo-invalid-shadow)]",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export { Textarea }