@redbamboo/ui 0.1.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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -0
  3. package/dist/components/app-header.d.ts +14 -0
  4. package/dist/components/app-header.d.ts.map +1 -0
  5. package/dist/components/badge.d.ts +8 -0
  6. package/dist/components/badge.d.ts.map +1 -0
  7. package/dist/components/button.d.ts +9 -0
  8. package/dist/components/button.d.ts.map +1 -0
  9. package/dist/components/card.d.ts +12 -0
  10. package/dist/components/card.d.ts.map +1 -0
  11. package/dist/components/collapsible.d.ts +6 -0
  12. package/dist/components/collapsible.d.ts.map +1 -0
  13. package/dist/components/dialog.d.ts +18 -0
  14. package/dist/components/dialog.d.ts.map +1 -0
  15. package/dist/components/dropdown-menu.d.ts +25 -0
  16. package/dist/components/dropdown-menu.d.ts.map +1 -0
  17. package/dist/components/input.d.ts +4 -0
  18. package/dist/components/input.d.ts.map +1 -0
  19. package/dist/components/json-highlight.d.ts +4 -0
  20. package/dist/components/json-highlight.d.ts.map +1 -0
  21. package/dist/components/label.d.ts +4 -0
  22. package/dist/components/label.d.ts.map +1 -0
  23. package/dist/components/modal.d.ts +33 -0
  24. package/dist/components/modal.d.ts.map +1 -0
  25. package/dist/components/popover.d.ts +10 -0
  26. package/dist/components/popover.d.ts.map +1 -0
  27. package/dist/components/scroll-area.d.ts +5 -0
  28. package/dist/components/scroll-area.d.ts.map +1 -0
  29. package/dist/components/select.d.ts +13 -0
  30. package/dist/components/select.d.ts.map +1 -0
  31. package/dist/components/separator.d.ts +4 -0
  32. package/dist/components/separator.d.ts.map +1 -0
  33. package/dist/components/slider.d.ts +4 -0
  34. package/dist/components/slider.d.ts.map +1 -0
  35. package/dist/components/switch.d.ts +6 -0
  36. package/dist/components/switch.d.ts.map +1 -0
  37. package/dist/components/table.d.ts +11 -0
  38. package/dist/components/table.d.ts.map +1 -0
  39. package/dist/components/tabs.d.ts +11 -0
  40. package/dist/components/tabs.d.ts.map +1 -0
  41. package/dist/components/tooltip.d.ts +7 -0
  42. package/dist/components/tooltip.d.ts.map +1 -0
  43. package/dist/index.css +2 -0
  44. package/dist/index.d.ts +24 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +2648 -0
  47. package/dist/utils.d.ts +3 -0
  48. package/dist/utils.d.ts.map +1 -0
  49. package/package.json +49 -0
  50. package/src/components/app-header.css +40 -0
  51. package/src/components/app-header.tsx +57 -0
  52. package/src/components/badge.tsx +52 -0
  53. package/src/components/button.tsx +58 -0
  54. package/src/components/card.tsx +103 -0
  55. package/src/components/collapsible.tsx +21 -0
  56. package/src/components/dialog.tsx +155 -0
  57. package/src/components/dropdown-menu.tsx +240 -0
  58. package/src/components/input.tsx +20 -0
  59. package/src/components/json-highlight.tsx +84 -0
  60. package/src/components/label.tsx +21 -0
  61. package/src/components/modal.tsx +114 -0
  62. package/src/components/popover.tsx +90 -0
  63. package/src/components/scroll-area.tsx +52 -0
  64. package/src/components/select.tsx +145 -0
  65. package/src/components/separator.tsx +23 -0
  66. package/src/components/slider.tsx +41 -0
  67. package/src/components/switch.tsx +30 -0
  68. package/src/components/table.tsx +114 -0
  69. package/src/components/tabs.tsx +80 -0
  70. package/src/components/tooltip.tsx +64 -0
  71. package/src/index.ts +141 -0
  72. package/src/tokens.css +397 -0
  73. package/src/utils.ts +6 -0
@@ -0,0 +1,240 @@
1
+ import * as React from "react"
2
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu"
3
+ import { cn } from "../utils"
4
+
5
+ function DropdownMenu({
6
+ ...props
7
+ }: MenuPrimitive.Root.Props) {
8
+ return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />
9
+ }
10
+
11
+ function DropdownMenuTrigger({
12
+ ...props
13
+ }: MenuPrimitive.Trigger.Props) {
14
+ return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
15
+ }
16
+
17
+ function DropdownMenuContent({
18
+ className,
19
+ sideOffset = 4,
20
+ side,
21
+ align,
22
+ ...props
23
+ }: MenuPrimitive.Popup.Props & Pick<MenuPrimitive.Positioner.Props, "sideOffset" | "side" | "align">) {
24
+ return (
25
+ <MenuPrimitive.Portal>
26
+ <MenuPrimitive.Positioner sideOffset={sideOffset} side={side} align={align} className="z-50">
27
+ <MenuPrimitive.Popup
28
+ data-slot="dropdown-menu-content"
29
+ className={cn(
30
+ "min-w-[8rem] overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-md duration-100 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",
31
+ className
32
+ )}
33
+ {...props}
34
+ />
35
+ </MenuPrimitive.Positioner>
36
+ </MenuPrimitive.Portal>
37
+ )
38
+ }
39
+
40
+ function DropdownMenuGroup({
41
+ ...props
42
+ }: MenuPrimitive.Group.Props) {
43
+ return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
44
+ }
45
+
46
+ function DropdownMenuItem({
47
+ className,
48
+ inset,
49
+ variant = "default",
50
+ ...props
51
+ }: MenuPrimitive.Item.Props & {
52
+ inset?: boolean
53
+ variant?: "default" | "destructive"
54
+ }) {
55
+ return (
56
+ <MenuPrimitive.Item
57
+ data-slot="dropdown-menu-item"
58
+ data-inset={inset}
59
+ data-variant={variant}
60
+ className={cn(
61
+ "relative flex cursor-pointer select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
62
+ inset && "pl-8",
63
+ variant === "destructive" && "text-destructive focus:bg-destructive/10 focus:text-destructive",
64
+ className
65
+ )}
66
+ {...props}
67
+ />
68
+ )
69
+ }
70
+
71
+ function DropdownMenuCheckboxItem({
72
+ className,
73
+ children,
74
+ checked,
75
+ ...props
76
+ }: MenuPrimitive.CheckboxItem.Props) {
77
+ return (
78
+ <MenuPrimitive.CheckboxItem
79
+ data-slot="dropdown-menu-checkbox-item"
80
+ className={cn(
81
+ "relative flex cursor-pointer select-none items-center gap-2 rounded-md py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
82
+ className
83
+ )}
84
+ checked={checked}
85
+ {...props}
86
+ >
87
+ <span className="pointer-events-none absolute left-2 flex size-4 items-center justify-center">
88
+ <MenuPrimitive.CheckboxItemIndicator>
89
+ <i className="fa-solid fa-check size-4" />
90
+ </MenuPrimitive.CheckboxItemIndicator>
91
+ </span>
92
+ {children}
93
+ </MenuPrimitive.CheckboxItem>
94
+ )
95
+ }
96
+
97
+ function DropdownMenuRadioGroup({
98
+ ...props
99
+ }: MenuPrimitive.RadioGroup.Props) {
100
+ return <MenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
101
+ }
102
+
103
+ function DropdownMenuRadioItem({
104
+ className,
105
+ children,
106
+ ...props
107
+ }: MenuPrimitive.RadioItem.Props) {
108
+ return (
109
+ <MenuPrimitive.RadioItem
110
+ data-slot="dropdown-menu-radio-item"
111
+ className={cn(
112
+ "relative flex cursor-pointer select-none items-center gap-2 rounded-md py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
113
+ className
114
+ )}
115
+ {...props}
116
+ >
117
+ <span className="pointer-events-none absolute left-2 flex size-4 items-center justify-center">
118
+ <MenuPrimitive.RadioItemIndicator>
119
+ <i className="fa-solid fa-circle size-2" />
120
+ </MenuPrimitive.RadioItemIndicator>
121
+ </span>
122
+ {children}
123
+ </MenuPrimitive.RadioItem>
124
+ )
125
+ }
126
+
127
+ function DropdownMenuLabel({
128
+ className,
129
+ inset,
130
+ ...props
131
+ }: MenuPrimitive.GroupLabel.Props & {
132
+ inset?: boolean
133
+ }) {
134
+ return (
135
+ <MenuPrimitive.GroupLabel
136
+ data-slot="dropdown-menu-label"
137
+ data-inset={inset}
138
+ className={cn(
139
+ "px-2 py-1.5 text-xs font-medium text-muted-foreground",
140
+ inset && "pl-8",
141
+ className
142
+ )}
143
+ {...props}
144
+ />
145
+ )
146
+ }
147
+
148
+ function DropdownMenuSeparator({
149
+ className,
150
+ ...props
151
+ }: MenuPrimitive.Separator.Props) {
152
+ return (
153
+ <MenuPrimitive.Separator
154
+ data-slot="dropdown-menu-separator"
155
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
156
+ {...props}
157
+ />
158
+ )
159
+ }
160
+
161
+ function DropdownMenuShortcut({
162
+ className,
163
+ ...props
164
+ }: React.ComponentProps<"span">) {
165
+ return (
166
+ <span
167
+ data-slot="dropdown-menu-shortcut"
168
+ className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
169
+ {...props}
170
+ />
171
+ )
172
+ }
173
+
174
+ function DropdownMenuSub({
175
+ ...props
176
+ }: MenuPrimitive.Root.Props) {
177
+ return <MenuPrimitive.Root data-slot="dropdown-menu-sub" {...props} />
178
+ }
179
+
180
+ function DropdownMenuSubTrigger({
181
+ className,
182
+ inset,
183
+ children,
184
+ ...props
185
+ }: MenuPrimitive.SubmenuTrigger.Props & {
186
+ inset?: boolean
187
+ }) {
188
+ return (
189
+ <MenuPrimitive.SubmenuTrigger
190
+ data-slot="dropdown-menu-sub-trigger"
191
+ data-inset={inset}
192
+ className={cn(
193
+ "flex cursor-pointer select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none focus:bg-accent data-[popup-open]:bg-accent [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
194
+ inset && "pl-8",
195
+ className
196
+ )}
197
+ {...props}
198
+ >
199
+ {children}
200
+ <i className="fa-solid fa-chevron-right ml-auto size-4" />
201
+ </MenuPrimitive.SubmenuTrigger>
202
+ )
203
+ }
204
+
205
+ function DropdownMenuSubContent({
206
+ className,
207
+ ...props
208
+ }: MenuPrimitive.Popup.Props) {
209
+ return (
210
+ <MenuPrimitive.Portal>
211
+ <MenuPrimitive.Positioner className="z-50">
212
+ <MenuPrimitive.Popup
213
+ data-slot="dropdown-menu-sub-content"
214
+ className={cn(
215
+ "min-w-[8rem] overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-lg duration-100 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",
216
+ className
217
+ )}
218
+ {...props}
219
+ />
220
+ </MenuPrimitive.Positioner>
221
+ </MenuPrimitive.Portal>
222
+ )
223
+ }
224
+
225
+ export {
226
+ DropdownMenu,
227
+ DropdownMenuTrigger,
228
+ DropdownMenuContent,
229
+ DropdownMenuGroup,
230
+ DropdownMenuItem,
231
+ DropdownMenuCheckboxItem,
232
+ DropdownMenuRadioGroup,
233
+ DropdownMenuRadioItem,
234
+ DropdownMenuLabel,
235
+ DropdownMenuSeparator,
236
+ DropdownMenuShortcut,
237
+ DropdownMenuSub,
238
+ DropdownMenuSubTrigger,
239
+ DropdownMenuSubContent,
240
+ }
@@ -0,0 +1,20 @@
1
+ import * as React from "react"
2
+ import { Input as InputPrimitive } from "@base-ui/react/input"
3
+
4
+ import { cn } from "../utils"
5
+
6
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
7
+ return (
8
+ <InputPrimitive
9
+ type={type}
10
+ data-slot="input"
11
+ className={cn(
12
+ "h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 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:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+
20
+ export { Input }
@@ -0,0 +1,84 @@
1
+ import * as React from "react"
2
+
3
+ const C = {
4
+ key: "#26A69A",
5
+ string: "#CE9178",
6
+ number: "#B5CEA8",
7
+ bool: "#569CD6",
8
+ null: "#569CD6",
9
+ punct: "#6B6F77",
10
+ }
11
+
12
+ export function JsonHighlight({ json }: { json: string }) {
13
+ let parsed: unknown
14
+ try {
15
+ parsed = JSON.parse(json)
16
+ } catch {
17
+ return <pre className="text-xs font-mono whitespace-pre-wrap break-all text-text-muted">{json}</pre>
18
+ }
19
+
20
+ return (
21
+ <pre className="text-xs font-mono whitespace-pre-wrap break-all">
22
+ {renderValue(parsed, 0)}
23
+ </pre>
24
+ )
25
+ }
26
+
27
+ function renderValue(val: unknown, depth: number): React.ReactNode {
28
+ if (val === null) return <span style={{ color: C.null }}>null</span>
29
+ if (typeof val === "boolean") return <span style={{ color: C.bool }}>{String(val)}</span>
30
+ if (typeof val === "number") return <span style={{ color: C.number }}>{String(val)}</span>
31
+ if (typeof val === "string") return <span style={{ color: C.string }}>"{escapeStr(val)}"</span>
32
+
33
+ const indent = " ".repeat(depth)
34
+ const innerIndent = " ".repeat(depth + 1)
35
+
36
+ if (Array.isArray(val)) {
37
+ if (val.length === 0) return <span style={{ color: C.punct }}>[]</span>
38
+ return (
39
+ <>
40
+ <span style={{ color: C.punct }}>{"["}</span>
41
+ {"\n"}
42
+ {val.map((item, i) => (
43
+ <span key={i}>
44
+ {innerIndent}
45
+ {renderValue(item, depth + 1)}
46
+ {i < val.length - 1 && <span style={{ color: C.punct }}>,</span>}
47
+ {"\n"}
48
+ </span>
49
+ ))}
50
+ {indent}
51
+ <span style={{ color: C.punct }}>{"]"}</span>
52
+ </>
53
+ )
54
+ }
55
+
56
+ if (typeof val === "object") {
57
+ const entries = Object.entries(val as Record<string, unknown>)
58
+ if (entries.length === 0) return <span style={{ color: C.punct }}>{"{}"}</span>
59
+ return (
60
+ <>
61
+ <span style={{ color: C.punct }}>{"{"}</span>
62
+ {"\n"}
63
+ {entries.map(([k, v], i) => (
64
+ <span key={k}>
65
+ {innerIndent}
66
+ <span style={{ color: C.key }}>"{k}"</span>
67
+ <span style={{ color: C.punct }}>: </span>
68
+ {renderValue(v, depth + 1)}
69
+ {i < entries.length - 1 && <span style={{ color: C.punct }}>,</span>}
70
+ {"\n"}
71
+ </span>
72
+ ))}
73
+ {indent}
74
+ <span style={{ color: C.punct }}>{"}"}</span>
75
+ </>
76
+ )
77
+ }
78
+
79
+ return String(val)
80
+ }
81
+
82
+ function escapeStr(s: string): string {
83
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t")
84
+ }
@@ -0,0 +1,21 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../utils"
4
+
5
+ function Label({
6
+ className,
7
+ ...props
8
+ }: React.ComponentProps<"label">) {
9
+ return (
10
+ <label
11
+ data-slot="label"
12
+ className={cn(
13
+ "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",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ export { Label }
@@ -0,0 +1,114 @@
1
+ import { useEffect } from "react"
2
+ import { cn } from "../utils"
3
+ import { Card, CardHeader } from "./card"
4
+ import { Button } from "./button"
5
+
6
+ export type ModalSize = "sm" | "md" | "lg" | "xl"
7
+
8
+ const SIZE_MAP: Record<ModalSize, string> = {
9
+ sm: "max-w-lg",
10
+ md: "max-w-xl",
11
+ lg: "max-w-2xl",
12
+ xl: "max-w-3xl",
13
+ }
14
+
15
+ export interface ModalBaseProps {
16
+ dataModal: string
17
+ ariaLabel: string
18
+ onClose: () => void
19
+ size?: ModalSize
20
+ dataAttrs?: Record<string, string>
21
+ children: React.ReactNode
22
+ }
23
+
24
+ export function ModalBase({ dataModal, ariaLabel, onClose, size = "xl", dataAttrs, children }: ModalBaseProps) {
25
+ useEffect(() => {
26
+ const handler = (e: KeyboardEvent) => { if (e.key === "Escape") onClose() }
27
+ document.addEventListener("keydown", handler)
28
+ return () => document.removeEventListener("keydown", handler)
29
+ }, [onClose])
30
+
31
+ return (
32
+ <div
33
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm animate-in fade-in duration-300"
34
+ onClick={(e) => { if (e.target === e.currentTarget) onClose() }}
35
+ role="dialog"
36
+ aria-modal="true"
37
+ data-modal={dataModal}
38
+ aria-label={ariaLabel}
39
+ {...dataAttrs}
40
+ >
41
+ <Card className={cn("w-full mx-4 max-h-[90vh] overflow-y-auto animate-in fade-in zoom-in-98 slide-in-from-bottom-8 duration-200", SIZE_MAP[size])}>
42
+ {children}
43
+ </Card>
44
+ </div>
45
+ )
46
+ }
47
+
48
+ export interface ModalHeaderProps {
49
+ icon?: React.ReactNode
50
+ title: React.ReactNode
51
+ badges?: React.ReactNode
52
+ subtitle?: React.ReactNode
53
+ onClose: () => void
54
+ closeLabel?: string
55
+ }
56
+
57
+ export function ModalHeader({ icon, title, badges, subtitle, onClose, closeLabel }: ModalHeaderProps) {
58
+ return (
59
+ <CardHeader className="pb-3">
60
+ <div className="flex items-center justify-between">
61
+ <div className="flex items-center gap-3">
62
+ {icon}
63
+ <div>
64
+ <div className="flex items-center gap-2">
65
+ {title}
66
+ {badges}
67
+ </div>
68
+ {subtitle && <p className="text-xs text-muted-foreground mt-0.5">{subtitle}</p>}
69
+ </div>
70
+ </div>
71
+ <Button variant="ghost" size="icon-xs" onClick={onClose} aria-label={closeLabel ?? "Close"}>
72
+ <i className="fa-solid fa-xmark w-4 h-4" />
73
+ </Button>
74
+ </div>
75
+ </CardHeader>
76
+ )
77
+ }
78
+
79
+ export interface ModalSectionProps {
80
+ section: string
81
+ heading?: string
82
+ ariaLabel?: string
83
+ dataAttrs?: Record<string, string>
84
+ children: React.ReactNode
85
+ }
86
+
87
+ export function ModalSection({ section, heading, ariaLabel, dataAttrs, children }: ModalSectionProps) {
88
+ return (
89
+ <div data-section={section} aria-label={ariaLabel} {...dataAttrs}>
90
+ {heading && <h3 className="text-sm font-semibold text-foreground mb-2">{heading}</h3>}
91
+ {children}
92
+ </div>
93
+ )
94
+ }
95
+
96
+ export interface ModalFooterProps {
97
+ children: React.ReactNode
98
+ align?: "start" | "end" | "between"
99
+ }
100
+
101
+ export function ModalFooter({ children, align }: ModalFooterProps) {
102
+ return (
103
+ <div
104
+ className={cn(
105
+ "flex items-center gap-2 pt-4 mt-2 border-t border-border/50",
106
+ align === "end" && "justify-end",
107
+ align === "between" && "justify-between",
108
+ )}
109
+ data-section="actions"
110
+ >
111
+ {children}
112
+ </div>
113
+ )
114
+ }
@@ -0,0 +1,90 @@
1
+ import * as React from "react"
2
+ import { Popover as PopoverPrimitive } from "@base-ui/react/popover"
3
+
4
+ import { cn } from "../utils"
5
+
6
+ function Popover({
7
+ ...props
8
+ }: PopoverPrimitive.Root.Props) {
9
+ return <PopoverPrimitive.Root data-slot="popover" {...props} />
10
+ }
11
+
12
+ function PopoverTrigger({
13
+ ...props
14
+ }: PopoverPrimitive.Trigger.Props) {
15
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
16
+ }
17
+
18
+ function PopoverContent({
19
+ className,
20
+ side = "bottom",
21
+ sideOffset = 4,
22
+ align = "center",
23
+ children,
24
+ ...props
25
+ }: PopoverPrimitive.Popup.Props &
26
+ Pick<PopoverPrimitive.Positioner.Props, "align" | "side" | "sideOffset">) {
27
+ return (
28
+ <PopoverPrimitive.Portal>
29
+ <PopoverPrimitive.Positioner
30
+ align={align}
31
+ side={side}
32
+ sideOffset={sideOffset}
33
+ className="z-50"
34
+ >
35
+ <PopoverPrimitive.Popup
36
+ data-slot="popover-content"
37
+ className={cn(
38
+ "flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 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",
39
+ className
40
+ )}
41
+ {...props}
42
+ >
43
+ {children}
44
+ </PopoverPrimitive.Popup>
45
+ </PopoverPrimitive.Positioner>
46
+ </PopoverPrimitive.Portal>
47
+ )
48
+ }
49
+
50
+ function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
51
+ return (
52
+ <div
53
+ data-slot="popover-header"
54
+ className={cn("flex flex-col gap-0.5 text-sm", className)}
55
+ {...props}
56
+ />
57
+ )
58
+ }
59
+
60
+ function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
61
+ return (
62
+ <div
63
+ data-slot="popover-title"
64
+ className={cn("font-heading font-medium", className)}
65
+ {...props}
66
+ />
67
+ )
68
+ }
69
+
70
+ function PopoverDescription({
71
+ className,
72
+ ...props
73
+ }: React.ComponentProps<"p">) {
74
+ return (
75
+ <p
76
+ data-slot="popover-description"
77
+ className={cn("text-muted-foreground", className)}
78
+ {...props}
79
+ />
80
+ )
81
+ }
82
+
83
+ export {
84
+ Popover,
85
+ PopoverContent,
86
+ PopoverDescription,
87
+ PopoverHeader,
88
+ PopoverTitle,
89
+ PopoverTrigger,
90
+ }
@@ -0,0 +1,52 @@
1
+ import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"
2
+
3
+ import { cn } from "../utils"
4
+
5
+ function ScrollArea({
6
+ className,
7
+ children,
8
+ ...props
9
+ }: ScrollAreaPrimitive.Root.Props) {
10
+ return (
11
+ <ScrollAreaPrimitive.Root
12
+ data-slot="scroll-area"
13
+ className={cn("relative", className)}
14
+ {...props}
15
+ >
16
+ <ScrollAreaPrimitive.Viewport
17
+ data-slot="scroll-area-viewport"
18
+ className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
19
+ >
20
+ {children}
21
+ </ScrollAreaPrimitive.Viewport>
22
+ <ScrollBar />
23
+ <ScrollAreaPrimitive.Corner />
24
+ </ScrollAreaPrimitive.Root>
25
+ )
26
+ }
27
+
28
+ function ScrollBar({
29
+ className,
30
+ orientation = "vertical",
31
+ ...props
32
+ }: ScrollAreaPrimitive.Scrollbar.Props) {
33
+ return (
34
+ <ScrollAreaPrimitive.Scrollbar
35
+ data-slot="scroll-area-scrollbar"
36
+ data-orientation={orientation}
37
+ orientation={orientation}
38
+ className={cn(
39
+ "flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent",
40
+ className
41
+ )}
42
+ {...props}
43
+ >
44
+ <ScrollAreaPrimitive.Thumb
45
+ data-slot="scroll-area-thumb"
46
+ className="relative flex-1 rounded-full bg-border"
47
+ />
48
+ </ScrollAreaPrimitive.Scrollbar>
49
+ )
50
+ }
51
+
52
+ export { ScrollArea, ScrollBar }