@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,34 @@
|
|
|
1
|
+
import { GripVertical } from 'lucide-react'
|
|
2
|
+
import { PanelGroup, Panel, PanelResizeHandle } from 'react-resizable-panels'
|
|
3
|
+
|
|
4
|
+
import { cn } from '../../lib/utils'
|
|
5
|
+
|
|
6
|
+
function ResizablePanelGroup({ className, direction, ...props }) {
|
|
7
|
+
return (
|
|
8
|
+
<PanelGroup orientation={direction} className={cn('h-full w-full', className)} {...props} />
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function ResizablePanel({ className, ...props }) {
|
|
13
|
+
return <Panel className={cn('h-full', className)} {...props} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function ResizableHandle({ withHandle, className, ...props }) {
|
|
17
|
+
return (
|
|
18
|
+
<PanelResizeHandle
|
|
19
|
+
className={cn(
|
|
20
|
+
'bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[orientation=vertical]:h-px data-[orientation=vertical]:w-full data-[orientation=vertical]:after:left-0 data-[orientation=vertical]:after:h-1 data-[orientation=vertical]:after:w-full data-[orientation=vertical]:after:-translate-y-1/2 data-[orientation=vertical]:after:translate-x-0 [&[data-orientation=vertical]>div]:rotate-90',
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
{withHandle && (
|
|
26
|
+
<div className='bg-border z-10 flex h-4 w-3 items-center justify-center rounded-sm border'>
|
|
27
|
+
<GripVertical className='size-2.5' />
|
|
28
|
+
</div>
|
|
29
|
+
)}
|
|
30
|
+
</PanelResizeHandle>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ScrollArea as ScrollAreaPrimitive } from '@base-ui/react/scroll-area'
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../lib/utils'
|
|
4
|
+
|
|
5
|
+
function ScrollArea({ className, children, ...props }) {
|
|
6
|
+
return (
|
|
7
|
+
<ScrollAreaPrimitive.Root
|
|
8
|
+
data-slot='scroll-area'
|
|
9
|
+
className={cn('relative', className)}
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<ScrollAreaPrimitive.Viewport className='size-full overflow-scroll'>
|
|
13
|
+
{children}
|
|
14
|
+
</ScrollAreaPrimitive.Viewport>
|
|
15
|
+
<ScrollAreaPrimitive.Scrollbar
|
|
16
|
+
orientation='vertical'
|
|
17
|
+
className='flex h-full w-2.5 touch-none border-l border-l-transparent p-px select-none transition-colors'
|
|
18
|
+
>
|
|
19
|
+
<ScrollAreaPrimitive.Thumb className='bg-border relative flex-1 rounded-full' />
|
|
20
|
+
</ScrollAreaPrimitive.Scrollbar>
|
|
21
|
+
<ScrollAreaPrimitive.Scrollbar
|
|
22
|
+
orientation='horizontal'
|
|
23
|
+
className='flex h-2.5 touch-none flex-col border-t border-t-transparent p-px select-none transition-colors'
|
|
24
|
+
>
|
|
25
|
+
<ScrollAreaPrimitive.Thumb className='bg-border relative flex-1 rounded-full' />
|
|
26
|
+
</ScrollAreaPrimitive.Scrollbar>
|
|
27
|
+
<ScrollAreaPrimitive.Corner />
|
|
28
|
+
</ScrollAreaPrimitive.Root>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { ScrollArea }
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Select as SelectPrimitive } from '@base-ui/react/select'
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../lib/utils'
|
|
4
|
+
import { ChevronsUpDown, Check, ChevronUp, ChevronDown } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
const Select = SelectPrimitive.Root
|
|
7
|
+
|
|
8
|
+
function SelectGroup({ className, ...props }) {
|
|
9
|
+
return (
|
|
10
|
+
<SelectPrimitive.Group
|
|
11
|
+
data-slot='select-group'
|
|
12
|
+
className={cn('scroll-my-1 p-1', className)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function SelectValue({ className, ...props }) {
|
|
19
|
+
return (
|
|
20
|
+
<SelectPrimitive.Value
|
|
21
|
+
data-slot='select-value'
|
|
22
|
+
className={cn('flex flex-1 text-left', className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function SelectTrigger({ className, size = 'default', children, ...props }) {
|
|
29
|
+
return (
|
|
30
|
+
<SelectPrimitive.Trigger
|
|
31
|
+
data-slot='select-trigger'
|
|
32
|
+
data-size={size}
|
|
33
|
+
className={cn(
|
|
34
|
+
"border-input data-[placeholder]:text-muted-foreground bg-input/20 dark:bg-input/30 dark:hover:bg-input/50 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 gap-1.5 rounded-md border px-2 py-1.5 text-xs/relaxed transition-colors focus-visible:ring-[2px] aria-invalid:ring-[2px] data-[size=default]:h-7 data-[size=sm]:h-6 *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-3.5 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
<SelectPrimitive.Icon
|
|
41
|
+
render={<ChevronsUpDown className='text-muted-foreground size-3.5 pointer-events-none' />}
|
|
42
|
+
/>
|
|
43
|
+
</SelectPrimitive.Trigger>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function SelectContent({
|
|
48
|
+
className,
|
|
49
|
+
children,
|
|
50
|
+
side = 'bottom',
|
|
51
|
+
sideOffset = 4,
|
|
52
|
+
align = 'center',
|
|
53
|
+
alignOffset = 0,
|
|
54
|
+
alignItemWithTrigger = true,
|
|
55
|
+
...props
|
|
56
|
+
}) {
|
|
57
|
+
return (
|
|
58
|
+
<SelectPrimitive.Portal>
|
|
59
|
+
<SelectPrimitive.Positioner
|
|
60
|
+
side={side}
|
|
61
|
+
sideOffset={sideOffset}
|
|
62
|
+
align={align}
|
|
63
|
+
alignOffset={alignOffset}
|
|
64
|
+
alignItemWithTrigger={alignItemWithTrigger}
|
|
65
|
+
className='isolate z-50'
|
|
66
|
+
>
|
|
67
|
+
<SelectPrimitive.Popup
|
|
68
|
+
data-slot='select-content'
|
|
69
|
+
className={cn(
|
|
70
|
+
'bg-popover text-popover-foreground 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 min-w-32 rounded-lg shadow-md ring-1 duration-100 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto',
|
|
71
|
+
className
|
|
72
|
+
)}
|
|
73
|
+
{...props}
|
|
74
|
+
>
|
|
75
|
+
<SelectScrollUpButton />
|
|
76
|
+
<SelectPrimitive.List>{children}</SelectPrimitive.List>
|
|
77
|
+
<SelectScrollDownButton />
|
|
78
|
+
</SelectPrimitive.Popup>
|
|
79
|
+
</SelectPrimitive.Positioner>
|
|
80
|
+
</SelectPrimitive.Portal>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function SelectLabel({ className, ...props }) {
|
|
85
|
+
return (
|
|
86
|
+
<SelectPrimitive.GroupLabel
|
|
87
|
+
data-slot='select-label'
|
|
88
|
+
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function SelectItem({ className, children, ...props }) {
|
|
95
|
+
return (
|
|
96
|
+
<SelectPrimitive.Item
|
|
97
|
+
data-slot='select-item'
|
|
98
|
+
className={cn(
|
|
99
|
+
"focus:bg-accent focus:text-accent-foreground 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 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
100
|
+
className
|
|
101
|
+
)}
|
|
102
|
+
{...props}
|
|
103
|
+
>
|
|
104
|
+
<SelectPrimitive.ItemText className='flex flex-1 gap-2 shrink-0 whitespace-nowrap'>
|
|
105
|
+
{children}
|
|
106
|
+
</SelectPrimitive.ItemText>
|
|
107
|
+
<SelectPrimitive.ItemIndicator
|
|
108
|
+
render={
|
|
109
|
+
<span className='pointer-events-none absolute right-2 flex items-center justify-center' />
|
|
110
|
+
}
|
|
111
|
+
>
|
|
112
|
+
<Check className='pointer-events-none' />
|
|
113
|
+
</SelectPrimitive.ItemIndicator>
|
|
114
|
+
</SelectPrimitive.Item>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function SelectSeparator({ className, ...props }) {
|
|
119
|
+
return (
|
|
120
|
+
<SelectPrimitive.Separator
|
|
121
|
+
data-slot='select-separator'
|
|
122
|
+
className={cn('bg-border/50 -mx-1 my-1 h-px pointer-events-none', className)}
|
|
123
|
+
{...props}
|
|
124
|
+
/>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function SelectScrollUpButton({ className, ...props }) {
|
|
129
|
+
return (
|
|
130
|
+
<SelectPrimitive.ScrollUpArrow
|
|
131
|
+
data-slot='select-scroll-up-button'
|
|
132
|
+
className={cn(
|
|
133
|
+
"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5 top-0 w-full",
|
|
134
|
+
className
|
|
135
|
+
)}
|
|
136
|
+
{...props}
|
|
137
|
+
>
|
|
138
|
+
<ChevronUp />
|
|
139
|
+
</SelectPrimitive.ScrollUpArrow>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function SelectScrollDownButton({ className, ...props }) {
|
|
144
|
+
return (
|
|
145
|
+
<SelectPrimitive.ScrollDownArrow
|
|
146
|
+
data-slot='select-scroll-down-button'
|
|
147
|
+
className={cn(
|
|
148
|
+
"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5 bottom-0 w-full",
|
|
149
|
+
className
|
|
150
|
+
)}
|
|
151
|
+
{...props}
|
|
152
|
+
>
|
|
153
|
+
<ChevronDown />
|
|
154
|
+
</SelectPrimitive.ScrollDownArrow>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export {
|
|
159
|
+
Select,
|
|
160
|
+
SelectContent,
|
|
161
|
+
SelectGroup,
|
|
162
|
+
SelectItem,
|
|
163
|
+
SelectLabel,
|
|
164
|
+
SelectScrollDownButton,
|
|
165
|
+
SelectScrollUpButton,
|
|
166
|
+
SelectSeparator,
|
|
167
|
+
SelectTrigger,
|
|
168
|
+
SelectValue
|
|
169
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { cn } from '../../lib/utils'
|
|
2
|
+
|
|
3
|
+
function Table({ className, ...props }) {
|
|
4
|
+
return (
|
|
5
|
+
<div data-slot='table-container' className='relative w-full overflow-x-auto'>
|
|
6
|
+
<table
|
|
7
|
+
data-slot='table'
|
|
8
|
+
className={cn('w-full caption-bottom text-xs', className)}
|
|
9
|
+
{...props}
|
|
10
|
+
/>
|
|
11
|
+
</div>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function TableHeader({ className, ...props }) {
|
|
16
|
+
return <thead data-slot='table-header' className={cn('[&_tr]:border-b', className)} {...props} />
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function TableBody({ className, ...props }) {
|
|
20
|
+
return (
|
|
21
|
+
<tbody
|
|
22
|
+
data-slot='table-body'
|
|
23
|
+
className={cn('[&_tr:last-child]:border-0', className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function TableFooter({ className, ...props }) {
|
|
30
|
+
return (
|
|
31
|
+
<tfoot
|
|
32
|
+
data-slot='table-footer'
|
|
33
|
+
className={cn('bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', className)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function TableRow({ className, ...props }) {
|
|
40
|
+
return (
|
|
41
|
+
<tr
|
|
42
|
+
data-slot='table-row'
|
|
43
|
+
className={cn(
|
|
44
|
+
'border-b border-border/50 transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function TableHead({ className, ...props }) {
|
|
53
|
+
return (
|
|
54
|
+
<th
|
|
55
|
+
data-slot='table-head'
|
|
56
|
+
className={cn(
|
|
57
|
+
'text-muted-foreground h-9 px-3 text-left align-middle text-xs font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
|
|
58
|
+
className
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function TableCell({ className, ...props }) {
|
|
66
|
+
return (
|
|
67
|
+
<td
|
|
68
|
+
data-slot='table-cell'
|
|
69
|
+
className={cn(
|
|
70
|
+
'px-3 py-1.5 align-middle max-w-xs truncate [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
|
|
71
|
+
className
|
|
72
|
+
)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function TableCaption({ className, ...props }) {
|
|
79
|
+
return (
|
|
80
|
+
<caption
|
|
81
|
+
data-slot='table-caption'
|
|
82
|
+
className={cn('text-muted-foreground mt-4 text-xs', className)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption }
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Tabs as TabsPrimitive } from '@base-ui/react/tabs'
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../lib/utils'
|
|
4
|
+
|
|
5
|
+
const Tabs = TabsPrimitive.Root
|
|
6
|
+
|
|
7
|
+
function TabsList({ className, ...props }) {
|
|
8
|
+
return (
|
|
9
|
+
<TabsPrimitive.List
|
|
10
|
+
data-slot='tabs-list'
|
|
11
|
+
className={cn(
|
|
12
|
+
'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-1',
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function TabsTrigger({ className, ...props }) {
|
|
21
|
+
return (
|
|
22
|
+
<TabsPrimitive.Tab
|
|
23
|
+
data-slot='tabs-trigger'
|
|
24
|
+
className={cn(
|
|
25
|
+
"inline-flex h-7 flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 text-xs font-medium whitespace-nowrap transition-all focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50 data-active:border-primary/50 dark:data-active:border-primary/30 data-active:bg-primary/10 data-active:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
|
26
|
+
className
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function TabsContent({ className, ...props }) {
|
|
34
|
+
return (
|
|
35
|
+
<TabsPrimitive.Panel
|
|
36
|
+
data-slot='tabs-content'
|
|
37
|
+
className={cn('flex-1 outline-none mt-2', className)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { Tabs, TabsContent, TabsList, TabsTrigger }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip'
|
|
2
|
+
import { cn } from '../../lib/utils'
|
|
3
|
+
|
|
4
|
+
function Tooltip({ children, ...props }) {
|
|
5
|
+
return <TooltipPrimitive.Provider {...props}>{children}</TooltipPrimitive.Provider>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function TooltipRoot({ ...props }) {
|
|
9
|
+
return <TooltipPrimitive.Root {...props} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function TooltipTrigger({ className, ...props }) {
|
|
13
|
+
return <TooltipPrimitive.Trigger className={cn('cursor-default', className)} {...props} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function TooltipContent({ className, sideOffset = 4, ...props }) {
|
|
17
|
+
return (
|
|
18
|
+
<TooltipPrimitive.Portal>
|
|
19
|
+
<TooltipPrimitive.Positioner sideOffset={sideOffset}>
|
|
20
|
+
<TooltipPrimitive.Popup
|
|
21
|
+
className={cn(
|
|
22
|
+
'bg-popover text-popover-foreground z-50 rounded-md border px-2.5 py-1 text-xs shadow-md',
|
|
23
|
+
'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',
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
</TooltipPrimitive.Positioner>
|
|
29
|
+
</TooltipPrimitive.Portal>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { Tooltip, TooltipRoot, TooltipTrigger, TooltipContent }
|
package/src/global.css
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
@import 'tailwindcss' theme(static);
|
|
2
|
+
|
|
3
|
+
@custom-variant dark (&:is(.dark *));
|
|
4
|
+
|
|
5
|
+
@theme inline {
|
|
6
|
+
--font-sans: system-ui, sans-serif;
|
|
7
|
+
--color-background: var(--background);
|
|
8
|
+
--color-foreground: var(--foreground);
|
|
9
|
+
--color-card: var(--card);
|
|
10
|
+
--color-card-foreground: var(--card-foreground);
|
|
11
|
+
--color-popover: var(--popover);
|
|
12
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
13
|
+
--color-primary: var(--primary);
|
|
14
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
15
|
+
--color-secondary: var(--secondary);
|
|
16
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
17
|
+
--color-muted: var(--muted);
|
|
18
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
19
|
+
--color-accent: var(--accent);
|
|
20
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
21
|
+
--color-destructive: var(--destructive);
|
|
22
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
23
|
+
--color-border: var(--border);
|
|
24
|
+
--color-input: var(--input);
|
|
25
|
+
--color-ring: var(--ring);
|
|
26
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
27
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
28
|
+
--radius-lg: var(--radius);
|
|
29
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
:root {
|
|
33
|
+
--background: oklch(0.145 0 0);
|
|
34
|
+
--foreground: oklch(0.985 0 0);
|
|
35
|
+
--card: oklch(0.205 0 0);
|
|
36
|
+
--card-foreground: oklch(0.985 0 0);
|
|
37
|
+
--popover: oklch(0.205 0 0);
|
|
38
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
39
|
+
--primary: oklch(0.7 0.12 183);
|
|
40
|
+
--primary-foreground: oklch(0.28 0.04 193);
|
|
41
|
+
--secondary: oklch(0.274 0.006 286.033);
|
|
42
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
43
|
+
--muted: oklch(0.269 0 0);
|
|
44
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
45
|
+
--accent: oklch(0.7 0.12 183);
|
|
46
|
+
--accent-foreground: oklch(0.28 0.04 193);
|
|
47
|
+
--destructive: oklch(0.604 0.191 22.216);
|
|
48
|
+
--destructive-foreground: oklch(1 0 0);
|
|
49
|
+
--border: oklch(1 0 0 / 10%);
|
|
50
|
+
--input: oklch(1 0 0 / 10%);
|
|
51
|
+
--ring: oklch(0.556 0 0);
|
|
52
|
+
--radius: 0.625rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@layer base {
|
|
56
|
+
* {
|
|
57
|
+
@apply border-border outline-ring/50;
|
|
58
|
+
}
|
|
59
|
+
body {
|
|
60
|
+
@apply font-sans bg-background text-foreground;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Scrollbar styling */
|
|
64
|
+
* {
|
|
65
|
+
scrollbar-width: thin;
|
|
66
|
+
scrollbar-color: oklch(0.5 0 0 / 30%) transparent;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
*::-webkit-scrollbar {
|
|
70
|
+
width: 4px !important;
|
|
71
|
+
height: 4px !important;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
*::-webkit-scrollbar-track {
|
|
75
|
+
background: transparent;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
*::-webkit-scrollbar-thumb {
|
|
79
|
+
background: oklch(0.5 0 0 / 30%);
|
|
80
|
+
border-radius: 3px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
*::-webkit-scrollbar-thumb:hover {
|
|
84
|
+
background: oklch(0.5 0 0 / 50%);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useMemo, useCallback } from 'react'
|
|
2
|
+
import { useQuery } from '@cero-base/react'
|
|
3
|
+
import { empty } from './util.js'
|
|
4
|
+
|
|
5
|
+
export function useCollection(db, collectionName, room = null) {
|
|
6
|
+
const col = useMemo(() => {
|
|
7
|
+
if (!db || !collectionName) return null
|
|
8
|
+
try {
|
|
9
|
+
const scope = db.schema.getScope(collectionName)
|
|
10
|
+
if (scope === 'shared') return room?.collection(collectionName) ?? null
|
|
11
|
+
return db.collection(collectionName)
|
|
12
|
+
} catch {
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
}, [db, collectionName, room])
|
|
16
|
+
|
|
17
|
+
const { data, busy, error } = useQuery(() => (col ? col.sub() : empty([])), [col])
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
data: data || [],
|
|
21
|
+
loading: busy,
|
|
22
|
+
error,
|
|
23
|
+
put: useCallback((doc) => col?.put(doc), [col]),
|
|
24
|
+
del: useCallback((query) => col?.del(query), [col]),
|
|
25
|
+
get: useCallback((query) => col?.get(query) ?? null, [col])
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook to manage rooms for shared collections
|
|
5
|
+
*/
|
|
6
|
+
export function useRooms(db) {
|
|
7
|
+
const [rooms, setRooms] = useState([])
|
|
8
|
+
const [loading, setLoading] = useState(true)
|
|
9
|
+
const [error, setError] = useState(null)
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!db?.rooms) {
|
|
13
|
+
setRooms([])
|
|
14
|
+
setLoading(false)
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const loadRooms = async () => {
|
|
19
|
+
setLoading(true)
|
|
20
|
+
try {
|
|
21
|
+
const list = await db.rooms.list()
|
|
22
|
+
setRooms(list || [])
|
|
23
|
+
} catch (err) {
|
|
24
|
+
setError(err)
|
|
25
|
+
} finally {
|
|
26
|
+
setLoading(false)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
loadRooms()
|
|
31
|
+
|
|
32
|
+
const onUpdate = () => loadRooms()
|
|
33
|
+
if (db.on) db.on('update', onUpdate)
|
|
34
|
+
|
|
35
|
+
return () => {
|
|
36
|
+
if (db.off) db.off('update', onUpdate)
|
|
37
|
+
}
|
|
38
|
+
}, [db])
|
|
39
|
+
|
|
40
|
+
const openRoom = useCallback(
|
|
41
|
+
async (roomKey) => {
|
|
42
|
+
if (!db?.rooms) return null
|
|
43
|
+
return db.rooms.open(roomKey)
|
|
44
|
+
},
|
|
45
|
+
[db]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const createRoom = useCallback(
|
|
49
|
+
async (opts = {}) => {
|
|
50
|
+
if (!db?.rooms) return null
|
|
51
|
+
return db.rooms.create(opts)
|
|
52
|
+
},
|
|
53
|
+
[db]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return { rooms, loading, error, openRoom, createRoom }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Hook for room selection state
|
|
61
|
+
*/
|
|
62
|
+
export function useRoomSelection(db) {
|
|
63
|
+
const { rooms, loading, error, openRoom, createRoom } = useRooms(db)
|
|
64
|
+
const [selected, setSelected] = useState(null)
|
|
65
|
+
|
|
66
|
+
const select = useCallback(
|
|
67
|
+
async (roomKey) => {
|
|
68
|
+
if (!roomKey) {
|
|
69
|
+
setSelected(null)
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
const room = await openRoom(roomKey)
|
|
73
|
+
setSelected(room)
|
|
74
|
+
return room
|
|
75
|
+
},
|
|
76
|
+
[openRoom]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return { rooms, loading, error, selected, select, createRoom }
|
|
80
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook to introspect the cero-base schema
|
|
5
|
+
* Returns collection metadata including fields, key, indexes, and scope
|
|
6
|
+
*/
|
|
7
|
+
export function useSchema(db) {
|
|
8
|
+
return useMemo(() => {
|
|
9
|
+
if (!db?.schema?.collections) return []
|
|
10
|
+
|
|
11
|
+
const { collections } = db.schema
|
|
12
|
+
|
|
13
|
+
return Object.entries(collections).map(([name, def]) => {
|
|
14
|
+
// Include timestamp fields if timestamps is enabled
|
|
15
|
+
const fields = { ...def.fields }
|
|
16
|
+
if (def.timestamps) {
|
|
17
|
+
fields.createdAt = { type: 'uint', required: true }
|
|
18
|
+
fields.updatedAt = { type: 'uint', required: false }
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
name,
|
|
22
|
+
scope: db.schema.getScope(name),
|
|
23
|
+
fields,
|
|
24
|
+
key: def.key || [],
|
|
25
|
+
indexes: def.indexes || [],
|
|
26
|
+
timestamps: def.timestamps || false
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
}, [db])
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get collections grouped by scope
|
|
34
|
+
*/
|
|
35
|
+
export function useSchemaByScope(db) {
|
|
36
|
+
const collections = useSchema(db)
|
|
37
|
+
|
|
38
|
+
return useMemo(
|
|
39
|
+
() => ({
|
|
40
|
+
local: collections.filter((c) => c.scope === 'local'),
|
|
41
|
+
private: collections.filter((c) => c.scope === 'private'),
|
|
42
|
+
shared: collections.filter((c) => c.scope === 'shared')
|
|
43
|
+
}),
|
|
44
|
+
[collections]
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get schema for a specific collection
|
|
50
|
+
*/
|
|
51
|
+
export function useCollectionSchema(db, collectionName) {
|
|
52
|
+
return useMemo(() => {
|
|
53
|
+
if (!db?.schema?.collections?.[collectionName]) return null
|
|
54
|
+
|
|
55
|
+
const def = db.schema.collections[collectionName]
|
|
56
|
+
// Include timestamp fields if timestamps is enabled
|
|
57
|
+
const fields = { ...def.fields }
|
|
58
|
+
if (def.timestamps) {
|
|
59
|
+
fields.createdAt = { type: 'uint', required: true }
|
|
60
|
+
fields.updatedAt = { type: 'uint', required: false }
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
name: collectionName,
|
|
64
|
+
scope: db.schema.getScope(collectionName),
|
|
65
|
+
fields,
|
|
66
|
+
key: def.key || [],
|
|
67
|
+
indexes: def.indexes || [],
|
|
68
|
+
timestamps: def.timestamps || false
|
|
69
|
+
}
|
|
70
|
+
}, [db, collectionName])
|
|
71
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useQuery } from '@cero-base/react'
|
|
2
|
+
import { empty } from './util.js'
|
|
3
|
+
|
|
4
|
+
function toStream(facade) {
|
|
5
|
+
if (!facade) return empty(null)
|
|
6
|
+
if (facade.sub) return facade.sub()
|
|
7
|
+
if (facade.subscribe) return facade.subscribe()
|
|
8
|
+
return empty(null)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useIdentityData(db, type) {
|
|
12
|
+
const { data, busy } = useQuery(() => {
|
|
13
|
+
const facade = db?.identity?.[type] || db?.[type]
|
|
14
|
+
return toStream(facade)
|
|
15
|
+
}, [db, type])
|
|
16
|
+
return { data, loading: busy }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useRoomData(db, room, type) {
|
|
20
|
+
const target = room?.room || room
|
|
21
|
+
const { data, busy } = useQuery(() => toStream(target?.[type]), [target, type])
|
|
22
|
+
return { data, loading: busy }
|
|
23
|
+
}
|