@cero-base/panel 0.0.1
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/README.md +42 -0
- package/package.json +41 -0
- package/src/components/data-table.jsx +245 -0
- package/src/components/document-form.jsx +211 -0
- package/src/components/panel.jsx +1131 -0
- package/src/components/sidebar.jsx +151 -0
- package/src/components/ui/avatar.jsx +38 -0
- package/src/components/ui/badge.jsx +26 -0
- package/src/components/ui/button.jsx +51 -0
- package/src/components/ui/checkbox.jsx +43 -0
- package/src/components/ui/dialog.jsx +126 -0
- package/src/components/ui/dropdown-menu.jsx +213 -0
- package/src/components/ui/input.jsx +19 -0
- package/src/components/ui/resizable.jsx +34 -0
- package/src/components/ui/scroll-area.jsx +32 -0
- package/src/components/ui/select.jsx +169 -0
- package/src/components/ui/table.jsx +88 -0
- package/src/components/ui/tabs.jsx +43 -0
- package/src/components/ui/tooltip.jsx +33 -0
- package/src/global.css +86 -0
- package/src/hooks/index.js +3 -0
- package/src/hooks/use-collection.js +27 -0
- package/src/hooks/use-rooms.js +80 -0
- package/src/hooks/use-schema.js +71 -0
- package/src/hooks/use-system.js +23 -0
- package/src/hooks/util.js +10 -0
- package/src/index.js +5 -0
- package/src/lib/field-types.js +148 -0
- package/src/lib/utils.js +19 -0
- package/src/open-panel.js +30 -0
- package/src/panel-window.html +24 -0
- package/src/panel-window.jsx +195 -0
- package/src/theme.css +57 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { useSchemaByScope } from '../hooks/use-schema'
|
|
2
|
+
import { useRoomSelection } from '../hooks/use-rooms'
|
|
3
|
+
import { ScrollArea } from './ui/scroll-area'
|
|
4
|
+
import { Badge } from './ui/badge'
|
|
5
|
+
import { Button } from './ui/button'
|
|
6
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'
|
|
7
|
+
import { cn } from '../lib/utils'
|
|
8
|
+
import { Database, Lock, Users, Plus } from 'lucide-react'
|
|
9
|
+
|
|
10
|
+
function CollectionItem({ collection, selected, onSelect }) {
|
|
11
|
+
const isSelected = selected?.name === collection.name
|
|
12
|
+
return (
|
|
13
|
+
<button
|
|
14
|
+
onClick={() => onSelect(collection)}
|
|
15
|
+
className={cn(
|
|
16
|
+
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs transition-colors',
|
|
17
|
+
'hover:bg-accent hover:text-accent-foreground',
|
|
18
|
+
isSelected && 'bg-accent text-accent-foreground'
|
|
19
|
+
)}
|
|
20
|
+
>
|
|
21
|
+
<span className='flex-1 truncate'>{collection.name}</span>
|
|
22
|
+
<Badge variant='outline' className='text-[10px] px-1 py-0'>
|
|
23
|
+
{collection.key.length > 0 ? collection.key.join('+') : 'auto'}
|
|
24
|
+
</Badge>
|
|
25
|
+
</button>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function CollectionSection({ title, icon: Icon, collections, selected, onSelect }) {
|
|
30
|
+
if (collections.length === 0) return null
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className='space-y-1'>
|
|
34
|
+
<div className='flex items-center gap-2 px-2 py-1 text-xs font-medium text-muted-foreground'>
|
|
35
|
+
<Icon className='size-3.5' />
|
|
36
|
+
<span>{title}</span>
|
|
37
|
+
<span className='ml-auto text-[10px]'>{collections.length}</span>
|
|
38
|
+
</div>
|
|
39
|
+
<div className='space-y-0.5'>
|
|
40
|
+
{collections.map((c) => (
|
|
41
|
+
<CollectionItem key={c.name} collection={c} selected={selected} onSelect={onSelect} />
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function RoomPicker({ rooms, selected, onSelect, onCreate, loading }) {
|
|
49
|
+
if (loading) {
|
|
50
|
+
return <div className='px-2 py-1.5 text-xs text-muted-foreground'>Loading rooms...</div>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className='flex items-center gap-1 px-2 py-1'>
|
|
55
|
+
<Select value={selected?.key?.toString('hex') || ''} onValueChange={(key) => onSelect(key)}>
|
|
56
|
+
<SelectTrigger size='sm' className='flex-1 h-6 text-[11px]'>
|
|
57
|
+
<SelectValue placeholder='Select room...' />
|
|
58
|
+
</SelectTrigger>
|
|
59
|
+
<SelectContent>
|
|
60
|
+
{rooms.map((room) => (
|
|
61
|
+
<SelectItem
|
|
62
|
+
key={room.key?.toString('hex') || room.id}
|
|
63
|
+
value={room.key?.toString('hex') || room.id}
|
|
64
|
+
>
|
|
65
|
+
{room.name || room.key?.toString('hex').slice(0, 8) || 'Unnamed'}
|
|
66
|
+
</SelectItem>
|
|
67
|
+
))}
|
|
68
|
+
</SelectContent>
|
|
69
|
+
</Select>
|
|
70
|
+
<Button
|
|
71
|
+
variant='ghost'
|
|
72
|
+
size='icon-sm'
|
|
73
|
+
className='size-6'
|
|
74
|
+
onClick={onCreate}
|
|
75
|
+
title='Create room'
|
|
76
|
+
>
|
|
77
|
+
<Plus className='size-3' />
|
|
78
|
+
</Button>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function Sidebar({ db, selected, onSelect, room, onRoomChange }) {
|
|
84
|
+
const { local, private: privateColls, shared } = useSchemaByScope(db)
|
|
85
|
+
const { rooms, loading: roomsLoading, select: selectRoom, createRoom } = useRoomSelection(db)
|
|
86
|
+
|
|
87
|
+
const onRoomSelect = async (key) => {
|
|
88
|
+
const room = await selectRoom(key)
|
|
89
|
+
onRoomChange(room)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const onCreateRoom = async () => {
|
|
93
|
+
const room = await createRoom()
|
|
94
|
+
if (room) {
|
|
95
|
+
onRoomChange(room)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className='flex h-full flex-col border-r border-border/50'>
|
|
101
|
+
<div className='flex items-center gap-2 border-b border-border/50 px-3 py-2'>
|
|
102
|
+
<Database className='size-4 text-muted-foreground' />
|
|
103
|
+
<span className='text-sm font-medium'>Collections</span>
|
|
104
|
+
</div>
|
|
105
|
+
<ScrollArea className='flex-1'>
|
|
106
|
+
<div className='space-y-4 p-2'>
|
|
107
|
+
<CollectionSection
|
|
108
|
+
title='Local'
|
|
109
|
+
icon={Database}
|
|
110
|
+
collections={local}
|
|
111
|
+
selected={selected}
|
|
112
|
+
onSelect={onSelect}
|
|
113
|
+
/>
|
|
114
|
+
<CollectionSection
|
|
115
|
+
title='Private'
|
|
116
|
+
icon={Lock}
|
|
117
|
+
collections={privateColls}
|
|
118
|
+
selected={selected}
|
|
119
|
+
onSelect={onSelect}
|
|
120
|
+
/>
|
|
121
|
+
{shared.length > 0 && (
|
|
122
|
+
<div className='space-y-1'>
|
|
123
|
+
<div className='flex items-center gap-2 px-2 py-1 text-xs font-medium text-muted-foreground'>
|
|
124
|
+
<Users className='size-3.5' />
|
|
125
|
+
<span>Shared</span>
|
|
126
|
+
<span className='ml-auto text-[10px]'>{shared.length}</span>
|
|
127
|
+
</div>
|
|
128
|
+
<RoomPicker
|
|
129
|
+
rooms={rooms}
|
|
130
|
+
selected={room}
|
|
131
|
+
onSelect={onRoomSelect}
|
|
132
|
+
onCreate={onCreateRoom}
|
|
133
|
+
loading={roomsLoading}
|
|
134
|
+
/>
|
|
135
|
+
<div className='space-y-0.5'>
|
|
136
|
+
{shared.map((c) => (
|
|
137
|
+
<CollectionItem
|
|
138
|
+
key={c.name}
|
|
139
|
+
collection={c}
|
|
140
|
+
selected={selected}
|
|
141
|
+
onSelect={onSelect}
|
|
142
|
+
/>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
</div>
|
|
148
|
+
</ScrollArea>
|
|
149
|
+
</div>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cn } from '../../lib/utils'
|
|
2
|
+
|
|
3
|
+
function Avatar({ className, ...props }) {
|
|
4
|
+
return (
|
|
5
|
+
<span
|
|
6
|
+
className={cn('relative flex h-8 w-8 shrink-0 overflow-hidden rounded-full', className)}
|
|
7
|
+
{...props}
|
|
8
|
+
/>
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function AvatarImage({ className, src, alt, ...props }) {
|
|
13
|
+
if (!src) return null
|
|
14
|
+
return (
|
|
15
|
+
<img
|
|
16
|
+
src={src}
|
|
17
|
+
alt={alt}
|
|
18
|
+
className={cn('aspect-square h-full w-full object-cover', className)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function AvatarFallback({ className, children, ...props }) {
|
|
25
|
+
return (
|
|
26
|
+
<span
|
|
27
|
+
className={cn(
|
|
28
|
+
'flex h-full w-full items-center justify-center rounded-full bg-muted text-xs font-medium',
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</span>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { Avatar, AvatarImage, AvatarFallback }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority'
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../lib/utils'
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
"focus:ring-ring/30 inline-flex w-fit shrink-0 items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-colors focus:ring-[2px] focus:outline-hidden [&_svg:not([class*='size-'])]:size-3 gap-1 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: 'bg-primary text-primary-foreground border-transparent',
|
|
11
|
+
secondary: 'bg-secondary text-secondary-foreground border-transparent',
|
|
12
|
+
destructive: 'bg-destructive/10 text-destructive border-transparent',
|
|
13
|
+
outline: 'text-foreground'
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: 'default'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
function Badge({ className, variant, ...props }) {
|
|
23
|
+
return <span data-slot='badge' className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from '@base-ui/react/button'
|
|
2
|
+
import { cva } from 'class-variance-authority'
|
|
3
|
+
|
|
4
|
+
import { cn } from '../../lib/utils'
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-semibold focus-visible:ring-[2px] aria-invalid:ring-[2px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none antialiased",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/80',
|
|
12
|
+
outline:
|
|
13
|
+
'border-border dark:bg-input/30 hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground',
|
|
14
|
+
secondary:
|
|
15
|
+
'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
|
|
16
|
+
ghost:
|
|
17
|
+
'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground',
|
|
18
|
+
destructive:
|
|
19
|
+
'bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 focus-visible:border-destructive/40',
|
|
20
|
+
link: 'text-primary underline-offset-4 hover:underline'
|
|
21
|
+
},
|
|
22
|
+
size: {
|
|
23
|
+
default:
|
|
24
|
+
"h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
25
|
+
xs: "h-5 gap-1 rounded-sm px-2 text-[0.625rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-2.5",
|
|
26
|
+
sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
27
|
+
lg: "h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-4",
|
|
28
|
+
icon: "size-7 [&_svg:not([class*='size-'])]:size-3.5",
|
|
29
|
+
'icon-xs': "size-5 rounded-sm [&_svg:not([class*='size-'])]:size-2.5",
|
|
30
|
+
'icon-sm': "size-6 [&_svg:not([class*='size-'])]:size-3",
|
|
31
|
+
'icon-lg': "size-8 [&_svg:not([class*='size-'])]:size-4"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: {
|
|
35
|
+
variant: 'default',
|
|
36
|
+
size: 'default'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
function Button({ className, variant = 'default', size = 'default', ...props }) {
|
|
42
|
+
return (
|
|
43
|
+
<ButtonPrimitive
|
|
44
|
+
data-slot='button'
|
|
45
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox'
|
|
2
|
+
import { Check, Minus } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
import { cn } from '../../lib/utils'
|
|
5
|
+
|
|
6
|
+
function Checkbox({ className, ...props }) {
|
|
7
|
+
return (
|
|
8
|
+
<CheckboxPrimitive.Root
|
|
9
|
+
data-slot='checkbox'
|
|
10
|
+
className={cn(
|
|
11
|
+
'peer border-input dark:bg-input/30 data-[checked]:bg-primary data-[checked]:text-primary-foreground dark:data-[checked]:bg-primary data-[checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 size-4 shrink-0 rounded-sm border shadow-xs transition-shadow focus-visible:ring-[2px] aria-invalid:ring-[2px] disabled:cursor-not-allowed disabled:opacity-50 outline-none',
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
>
|
|
16
|
+
<CheckboxPrimitive.Indicator className='flex items-center justify-center text-current transition-none'>
|
|
17
|
+
<Check className='size-3.5' />
|
|
18
|
+
</CheckboxPrimitive.Indicator>
|
|
19
|
+
</CheckboxPrimitive.Root>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function CheckboxIndeterminate({ className, ...props }) {
|
|
24
|
+
return (
|
|
25
|
+
<CheckboxPrimitive.Root
|
|
26
|
+
data-slot='checkbox'
|
|
27
|
+
className={cn(
|
|
28
|
+
'peer border-input dark:bg-input/30 data-[checked]:bg-primary data-[checked]:text-primary-foreground dark:data-[checked]:bg-primary data-[checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 size-4 shrink-0 rounded-sm border shadow-xs transition-shadow focus-visible:ring-[2px] aria-invalid:ring-[2px] disabled:cursor-not-allowed disabled:opacity-50 outline-none',
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
<CheckboxPrimitive.Indicator
|
|
34
|
+
className='flex items-center justify-center text-current transition-none'
|
|
35
|
+
keepMounted
|
|
36
|
+
>
|
|
37
|
+
{props.indeterminate ? <Minus className='size-3.5' /> : <Check className='size-3.5' />}
|
|
38
|
+
</CheckboxPrimitive.Indicator>
|
|
39
|
+
</CheckboxPrimitive.Root>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { Checkbox, CheckboxIndeterminate }
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'
|
|
3
|
+
|
|
4
|
+
import { cn } from '../../lib/utils'
|
|
5
|
+
import { Button } from './button'
|
|
6
|
+
import { X } from 'lucide-react'
|
|
7
|
+
|
|
8
|
+
function Dialog({ ...props }) {
|
|
9
|
+
return <DialogPrimitive.Root data-slot='dialog' {...props} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function DialogTrigger({ ...props }) {
|
|
13
|
+
return <DialogPrimitive.Trigger data-slot='dialog-trigger' {...props} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function DialogPortal({ ...props }) {
|
|
17
|
+
return <DialogPrimitive.Portal data-slot='dialog-portal' {...props} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function DialogClose({ ...props }) {
|
|
21
|
+
return <DialogPrimitive.Close data-slot='dialog-close' {...props} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function DialogOverlay({ className, ...props }) {
|
|
25
|
+
return (
|
|
26
|
+
<DialogPrimitive.Backdrop
|
|
27
|
+
data-slot='dialog-overlay'
|
|
28
|
+
className={cn(
|
|
29
|
+
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50',
|
|
30
|
+
className
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function DialogContent({
|
|
38
|
+
className,
|
|
39
|
+
children,
|
|
40
|
+
showCloseButton = true,
|
|
41
|
+
overlayClassName,
|
|
42
|
+
hideOverlay = false,
|
|
43
|
+
...props
|
|
44
|
+
}) {
|
|
45
|
+
return (
|
|
46
|
+
<DialogPortal>
|
|
47
|
+
{!hideOverlay && <DialogOverlay className={overlayClassName} />}
|
|
48
|
+
<DialogPrimitive.Popup
|
|
49
|
+
data-slot='dialog-content'
|
|
50
|
+
className={cn(
|
|
51
|
+
'bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 shadow-2xl dark:border dark:border-white/15 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-6 text-xs/relaxed duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none overflow-hidden',
|
|
52
|
+
className
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
{children}
|
|
57
|
+
{showCloseButton && (
|
|
58
|
+
<DialogPrimitive.Close
|
|
59
|
+
data-slot='dialog-close'
|
|
60
|
+
render={<Button variant='ghost' className='absolute top-4 right-4' size='icon-sm' />}
|
|
61
|
+
>
|
|
62
|
+
<X className='size-4' />
|
|
63
|
+
<span className='sr-only'>Close</span>
|
|
64
|
+
</DialogPrimitive.Close>
|
|
65
|
+
)}
|
|
66
|
+
</DialogPrimitive.Popup>
|
|
67
|
+
</DialogPortal>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function DialogHeader({ className, ...props }) {
|
|
72
|
+
return (
|
|
73
|
+
<div data-slot='dialog-header' className={cn('gap-1 flex flex-col', className)} {...props} />
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function DialogFooter({ className, showCloseButton = false, children, ...props }) {
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
data-slot='dialog-footer'
|
|
81
|
+
className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
|
|
82
|
+
{...props}
|
|
83
|
+
>
|
|
84
|
+
{children}
|
|
85
|
+
{showCloseButton && (
|
|
86
|
+
<DialogPrimitive.Close render={<Button variant='outline' />}>Close</DialogPrimitive.Close>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function DialogTitle({ className, ...props }) {
|
|
93
|
+
return (
|
|
94
|
+
<DialogPrimitive.Title
|
|
95
|
+
data-slot='dialog-title'
|
|
96
|
+
className={cn('text-lg font-bold antialiased', className)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function DialogDescription({ className, ...props }) {
|
|
103
|
+
return (
|
|
104
|
+
<DialogPrimitive.Description
|
|
105
|
+
data-slot='dialog-description'
|
|
106
|
+
className={cn(
|
|
107
|
+
'text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3',
|
|
108
|
+
className
|
|
109
|
+
)}
|
|
110
|
+
{...props}
|
|
111
|
+
/>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
Dialog,
|
|
117
|
+
DialogClose,
|
|
118
|
+
DialogContent,
|
|
119
|
+
DialogDescription,
|
|
120
|
+
DialogFooter,
|
|
121
|
+
DialogHeader,
|
|
122
|
+
DialogOverlay,
|
|
123
|
+
DialogPortal,
|
|
124
|
+
DialogTitle,
|
|
125
|
+
DialogTrigger
|
|
126
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Menu as MenuPrimitive } from '@base-ui/react/menu'
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../lib/utils'
|
|
4
|
+
import { ChevronRightIcon, CheckIcon } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
function DropdownMenu({ ...props }) {
|
|
7
|
+
return <MenuPrimitive.Root data-slot='dropdown-menu' {...props} />
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function DropdownMenuPortal({ ...props }) {
|
|
11
|
+
return <MenuPrimitive.Portal data-slot='dropdown-menu-portal' {...props} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function DropdownMenuTrigger({ ...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
|
+
}) {
|
|
26
|
+
return (
|
|
27
|
+
<MenuPrimitive.Portal>
|
|
28
|
+
<MenuPrimitive.Positioner
|
|
29
|
+
className='isolate z-9999 outline-none'
|
|
30
|
+
align={align}
|
|
31
|
+
alignOffset={alignOffset}
|
|
32
|
+
side={side}
|
|
33
|
+
sideOffset={sideOffset}
|
|
34
|
+
>
|
|
35
|
+
<MenuPrimitive.Popup
|
|
36
|
+
data-slot='dropdown-menu-content'
|
|
37
|
+
className={cn(
|
|
38
|
+
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 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 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-lg p-1 shadow-md ring-1 duration-100 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden',
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
</MenuPrimitive.Positioner>
|
|
44
|
+
</MenuPrimitive.Portal>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function DropdownMenuGroup({ ...props }) {
|
|
49
|
+
return <MenuPrimitive.Group data-slot='dropdown-menu-group' {...props} />
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function DropdownMenuLabel({ className, inset, ...props }) {
|
|
53
|
+
return (
|
|
54
|
+
<MenuPrimitive.GroupLabel
|
|
55
|
+
data-slot='dropdown-menu-label'
|
|
56
|
+
data-inset={inset}
|
|
57
|
+
className={cn('text-muted-foreground px-2 py-1.5 text-xs data-[inset]:pl-8', className)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function DropdownMenuItem({ className, inset, variant = 'default', ...props }) {
|
|
64
|
+
return (
|
|
65
|
+
<MenuPrimitive.Item
|
|
66
|
+
data-slot='dropdown-menu-item'
|
|
67
|
+
data-inset={inset}
|
|
68
|
+
data-variant={variant}
|
|
69
|
+
className={cn(
|
|
70
|
+
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed [&_svg:not([class*='size-'])]:size-3.5 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
71
|
+
className
|
|
72
|
+
)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function DropdownMenuSub({ ...props }) {
|
|
79
|
+
return <MenuPrimitive.SubmenuRoot data-slot='dropdown-menu-sub' {...props} />
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function DropdownMenuSubTrigger({ className, inset, children, hideChevron, ...props }) {
|
|
83
|
+
return (
|
|
84
|
+
<MenuPrimitive.SubmenuTrigger
|
|
85
|
+
data-slot='dropdown-menu-sub-trigger'
|
|
86
|
+
data-inset={inset}
|
|
87
|
+
className={cn(
|
|
88
|
+
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs [&_svg:not([class*='size-'])]:size-3.5 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
89
|
+
className
|
|
90
|
+
)}
|
|
91
|
+
{...props}
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
{!hideChevron && <ChevronRightIcon className='ml-auto' />}
|
|
95
|
+
</MenuPrimitive.SubmenuTrigger>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function DropdownMenuSubContent({
|
|
100
|
+
align = 'start',
|
|
101
|
+
alignOffset = -3,
|
|
102
|
+
side = 'right',
|
|
103
|
+
sideOffset = 0,
|
|
104
|
+
className,
|
|
105
|
+
...props
|
|
106
|
+
}) {
|
|
107
|
+
return (
|
|
108
|
+
<DropdownMenuContent
|
|
109
|
+
data-slot='dropdown-menu-sub-content'
|
|
110
|
+
className={cn(
|
|
111
|
+
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 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 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-lg p-1 shadow-md ring-1 duration-100 w-auto',
|
|
112
|
+
className
|
|
113
|
+
)}
|
|
114
|
+
align={align}
|
|
115
|
+
alignOffset={alignOffset}
|
|
116
|
+
side={side}
|
|
117
|
+
sideOffset={sideOffset}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function DropdownMenuCheckboxItem({ className, children, checked, ...props }) {
|
|
124
|
+
return (
|
|
125
|
+
<MenuPrimitive.CheckboxItem
|
|
126
|
+
data-slot='dropdown-menu-checkbox-item'
|
|
127
|
+
className={cn(
|
|
128
|
+
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
129
|
+
className
|
|
130
|
+
)}
|
|
131
|
+
checked={checked}
|
|
132
|
+
{...props}
|
|
133
|
+
>
|
|
134
|
+
<span
|
|
135
|
+
className='pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none'
|
|
136
|
+
data-slot='dropdown-menu-checkbox-item-indicator'
|
|
137
|
+
>
|
|
138
|
+
<MenuPrimitive.CheckboxItemIndicator>
|
|
139
|
+
<CheckIcon />
|
|
140
|
+
</MenuPrimitive.CheckboxItemIndicator>
|
|
141
|
+
</span>
|
|
142
|
+
{children}
|
|
143
|
+
</MenuPrimitive.CheckboxItem>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function DropdownMenuRadioGroup({ ...props }) {
|
|
148
|
+
return <MenuPrimitive.RadioGroup data-slot='dropdown-menu-radio-group' {...props} />
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function DropdownMenuRadioItem({ className, children, ...props }) {
|
|
152
|
+
return (
|
|
153
|
+
<MenuPrimitive.RadioItem
|
|
154
|
+
data-slot='dropdown-menu-radio-item'
|
|
155
|
+
className={cn(
|
|
156
|
+
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground min-h-7 gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-3.5 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
157
|
+
className
|
|
158
|
+
)}
|
|
159
|
+
{...props}
|
|
160
|
+
>
|
|
161
|
+
<span
|
|
162
|
+
className='pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none'
|
|
163
|
+
data-slot='dropdown-menu-radio-item-indicator'
|
|
164
|
+
>
|
|
165
|
+
<MenuPrimitive.RadioItemIndicator>
|
|
166
|
+
<CheckIcon />
|
|
167
|
+
</MenuPrimitive.RadioItemIndicator>
|
|
168
|
+
</span>
|
|
169
|
+
{children}
|
|
170
|
+
</MenuPrimitive.RadioItem>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function DropdownMenuSeparator({ className, ...props }) {
|
|
175
|
+
return (
|
|
176
|
+
<MenuPrimitive.Separator
|
|
177
|
+
data-slot='dropdown-menu-separator'
|
|
178
|
+
className={cn('bg-border/50 -mx-1 my-1 h-px', className)}
|
|
179
|
+
{...props}
|
|
180
|
+
/>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function DropdownMenuShortcut({ className, ...props }) {
|
|
185
|
+
return (
|
|
186
|
+
<span
|
|
187
|
+
data-slot='dropdown-menu-shortcut'
|
|
188
|
+
className={cn(
|
|
189
|
+
'text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-[0.625rem] tracking-widest',
|
|
190
|
+
className
|
|
191
|
+
)}
|
|
192
|
+
{...props}
|
|
193
|
+
/>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export {
|
|
198
|
+
DropdownMenu,
|
|
199
|
+
DropdownMenuPortal,
|
|
200
|
+
DropdownMenuTrigger,
|
|
201
|
+
DropdownMenuContent,
|
|
202
|
+
DropdownMenuGroup,
|
|
203
|
+
DropdownMenuLabel,
|
|
204
|
+
DropdownMenuItem,
|
|
205
|
+
DropdownMenuCheckboxItem,
|
|
206
|
+
DropdownMenuRadioGroup,
|
|
207
|
+
DropdownMenuRadioItem,
|
|
208
|
+
DropdownMenuSeparator,
|
|
209
|
+
DropdownMenuShortcut,
|
|
210
|
+
DropdownMenuSub,
|
|
211
|
+
DropdownMenuSubTrigger,
|
|
212
|
+
DropdownMenuSubContent
|
|
213
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Input as InputPrimitive } from '@base-ui/react/input'
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../lib/utils'
|
|
4
|
+
|
|
5
|
+
function Input({ className, type, ...props }) {
|
|
6
|
+
return (
|
|
7
|
+
<InputPrimitive
|
|
8
|
+
type={type}
|
|
9
|
+
data-slot='input'
|
|
10
|
+
className={cn(
|
|
11
|
+
'bg-input/20 dark:bg-input/30 border-input focus-visible:border-primary focus-visible:ring-primary/20 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-7 rounded-md border px-2 py-0.5 text-sm transition-colors file:h-6 file:text-xs/relaxed file:font-medium focus-visible:ring-[2px] aria-invalid:ring-[2px] md:text-xs/relaxed file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 dark:[color-scheme:dark]',
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { Input }
|