@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.
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/components/app-header.d.ts +14 -0
- package/dist/components/app-header.d.ts.map +1 -0
- package/dist/components/badge.d.ts +8 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/button.d.ts +9 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/card.d.ts +12 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/collapsible.d.ts +6 -0
- package/dist/components/collapsible.d.ts.map +1 -0
- package/dist/components/dialog.d.ts +18 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dropdown-menu.d.ts +25 -0
- package/dist/components/dropdown-menu.d.ts.map +1 -0
- package/dist/components/input.d.ts +4 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/json-highlight.d.ts +4 -0
- package/dist/components/json-highlight.d.ts.map +1 -0
- package/dist/components/label.d.ts +4 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/modal.d.ts +33 -0
- package/dist/components/modal.d.ts.map +1 -0
- package/dist/components/popover.d.ts +10 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/scroll-area.d.ts +5 -0
- package/dist/components/scroll-area.d.ts.map +1 -0
- package/dist/components/select.d.ts +13 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/separator.d.ts +4 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/slider.d.ts +4 -0
- package/dist/components/slider.d.ts.map +1 -0
- package/dist/components/switch.d.ts +6 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/table.d.ts +11 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/tabs.d.ts +11 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tooltip.d.ts +7 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/index.css +2 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2648 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +49 -0
- package/src/components/app-header.css +40 -0
- package/src/components/app-header.tsx +57 -0
- package/src/components/badge.tsx +52 -0
- package/src/components/button.tsx +58 -0
- package/src/components/card.tsx +103 -0
- package/src/components/collapsible.tsx +21 -0
- package/src/components/dialog.tsx +155 -0
- package/src/components/dropdown-menu.tsx +240 -0
- package/src/components/input.tsx +20 -0
- package/src/components/json-highlight.tsx +84 -0
- package/src/components/label.tsx +21 -0
- package/src/components/modal.tsx +114 -0
- package/src/components/popover.tsx +90 -0
- package/src/components/scroll-area.tsx +52 -0
- package/src/components/select.tsx +145 -0
- package/src/components/separator.tsx +23 -0
- package/src/components/slider.tsx +41 -0
- package/src/components/switch.tsx +30 -0
- package/src/components/table.tsx +114 -0
- package/src/components/tabs.tsx +80 -0
- package/src/components/tooltip.tsx +64 -0
- package/src/index.ts +141 -0
- package/src/tokens.css +397 -0
- 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 }
|