@octanejs/shadcn 0.0.10 → 0.0.11
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 +6 -7
- package/package.json +13 -8
- package/src/bases/base-ui/ui/context-menu.tsrx +277 -0
- package/src/bases/base-ui/ui/hover-card.tsrx +100 -0
- package/src/bases/base-ui/ui/menubar.tsrx +288 -0
- package/src/bases/base-ui/ui/sidebar.tsrx +628 -0
- package/src/bases/base-ui/ui/tabs.tsrx +90 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// Base UI base menubar — the bar runs on @octanejs/base-ui's `Menubar`, and every menu inside it
|
|
2
|
+
// runs on `Menu`, the same primitive `dropdown-menu` uses. Class strings from the maintainer-supplied
|
|
3
|
+
// radix source.
|
|
4
|
+
//
|
|
5
|
+
// PART MAPPING. Radix nests the whole family under one `Menubar` namespace; Base UI has a `Menubar`
|
|
6
|
+
// component for the bar ONLY, and the menus are ordinary `Menu` parts:
|
|
7
|
+
//
|
|
8
|
+
// radix Menubar.Root / .Menu / .Trigger / .Content / .Item / .Sub / …
|
|
9
|
+
// Base UI Menubar + Menu.Root / .Trigger / .Portal > .Positioner > .Popup / .Item / …
|
|
10
|
+
//
|
|
11
|
+
// CARRIED OVER FROM dropdown-menu, re-verified against this composition:
|
|
12
|
+
// - `--radix-menubar-content-transform-origin` -> `--transform-origin`. A utility naming a
|
|
13
|
+
// variable nothing sets does nothing, silently.
|
|
14
|
+
// - The SUBMENU trigger marks itself open with `data-popup-open`, not the `data-open` the popup
|
|
15
|
+
// carries, so radix's `data-open:bg-accent` there would never match.
|
|
16
|
+
// - `Label` and `Separator` are plain host elements: `Menu.GroupLabel` throws outside a
|
|
17
|
+
// `Menu.Group` while shadcn's label is standalone, and the `Menu` namespace ships no Separator
|
|
18
|
+
// part at all (its sibling `ContextMenu` does).
|
|
19
|
+
// - Radix's `focus:` item utilities carry over, because Base UI moves real DOM focus onto the
|
|
20
|
+
// highlighted item.
|
|
21
|
+
// - Positioning props are routed to the Positioner, not swept onto the Popup where they are inert.
|
|
22
|
+
//
|
|
23
|
+
// THE BAR TRIGGER IS THE ONE PLACE THIS FAMILY DIFFERS from the dropdown. Its radix class keys off
|
|
24
|
+
// `aria-expanded:bg-muted` rather than a data attribute, and that carries over UNCHANGED — Base UI's
|
|
25
|
+
// trigger publishes `aria-expanded="true"` when its menu is open (verified by rendering). So the
|
|
26
|
+
// `data-popup-open` rewrite above applies only to the submenu trigger, not to this one.
|
|
27
|
+
import { type OctaneNode } from 'octane';
|
|
28
|
+
import { Menubar as MenubarPrimitive } from '@octanejs/base-ui/menubar';
|
|
29
|
+
import { Menu as MenuPrimitive } from '@octanejs/base-ui/menu';
|
|
30
|
+
import { CheckIcon, ChevronRightIcon } from '@octanejs/lucide';
|
|
31
|
+
|
|
32
|
+
import { cn } from '../../../lib/utils';
|
|
33
|
+
|
|
34
|
+
type Props = { className?: string; children?: OctaneNode } & Record<string, unknown>;
|
|
35
|
+
|
|
36
|
+
export interface BaseUiPositioningProps {
|
|
37
|
+
/** Forwarded to the Positioner; see the note at the top of this file. */
|
|
38
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
39
|
+
sideOffset?: number;
|
|
40
|
+
align?: 'start' | 'center' | 'end';
|
|
41
|
+
alignOffset?: number;
|
|
42
|
+
anchor?: unknown;
|
|
43
|
+
positionMethod?: 'absolute' | 'fixed';
|
|
44
|
+
collisionBoundary?: unknown;
|
|
45
|
+
collisionPadding?: unknown;
|
|
46
|
+
collisionAvoidance?: unknown;
|
|
47
|
+
arrowPadding?: number;
|
|
48
|
+
sticky?: boolean;
|
|
49
|
+
disableAnchorTracking?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function Menubar({ className, ...props }: Props) @{
|
|
53
|
+
<MenubarPrimitive
|
|
54
|
+
data-slot="menubar"
|
|
55
|
+
className={cn('flex h-8 items-center gap-0.5 rounded-lg border p-[3px]', className)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function MenubarMenu(props: Record<string, unknown>) @{
|
|
61
|
+
<MenuPrimitive.Root data-slot="menubar-menu" {...props} />
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function MenubarGroup(props: Record<string, unknown>) @{
|
|
65
|
+
<MenuPrimitive.Group data-slot="menubar-group" {...props} />
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function MenubarPortal(props: Record<string, unknown>) @{
|
|
69
|
+
<MenuPrimitive.Portal data-slot="menubar-portal" {...props} />
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function MenubarRadioGroup(props: Record<string, unknown>) @{
|
|
73
|
+
<MenuPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function MenubarTrigger({ className, ...props }: Props) @{
|
|
77
|
+
<MenuPrimitive.Trigger
|
|
78
|
+
data-slot="menubar-trigger"
|
|
79
|
+
className={cn(
|
|
80
|
+
'flex items-center rounded-sm px-1.5 py-[2px] text-sm font-medium outline-hidden select-none hover:bg-muted aria-expanded:bg-muted',
|
|
81
|
+
className,
|
|
82
|
+
)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function MenubarContent({
|
|
88
|
+
className,
|
|
89
|
+
align = 'start',
|
|
90
|
+
alignOffset = -4,
|
|
91
|
+
sideOffset = 8,
|
|
92
|
+
side,
|
|
93
|
+
anchor,
|
|
94
|
+
positionMethod,
|
|
95
|
+
collisionBoundary,
|
|
96
|
+
collisionPadding,
|
|
97
|
+
collisionAvoidance,
|
|
98
|
+
arrowPadding,
|
|
99
|
+
sticky,
|
|
100
|
+
disableAnchorTracking,
|
|
101
|
+
...props
|
|
102
|
+
}: Props & BaseUiPositioningProps) @{
|
|
103
|
+
<MenuPrimitive.Portal>
|
|
104
|
+
<MenuPrimitive.Positioner
|
|
105
|
+
align={align}
|
|
106
|
+
alignOffset={alignOffset}
|
|
107
|
+
sideOffset={sideOffset}
|
|
108
|
+
side={side}
|
|
109
|
+
anchor={anchor}
|
|
110
|
+
positionMethod={positionMethod}
|
|
111
|
+
collisionBoundary={collisionBoundary}
|
|
112
|
+
collisionPadding={collisionPadding}
|
|
113
|
+
collisionAvoidance={collisionAvoidance}
|
|
114
|
+
arrowPadding={arrowPadding}
|
|
115
|
+
sticky={sticky}
|
|
116
|
+
disableAnchorTracking={disableAnchorTracking}
|
|
117
|
+
>
|
|
118
|
+
<MenuPrimitive.Popup
|
|
119
|
+
data-slot="menubar-content"
|
|
120
|
+
className={cn(
|
|
121
|
+
'z-50 min-w-36 origin-(--transform-origin) overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95',
|
|
122
|
+
className,
|
|
123
|
+
)}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
</MenuPrimitive.Positioner>
|
|
127
|
+
</MenuPrimitive.Portal>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function MenubarItem({ className, inset, variant = 'default', ...props }: Props & {
|
|
131
|
+
inset?: boolean;
|
|
132
|
+
variant?: 'default' | 'destructive';
|
|
133
|
+
}) @{
|
|
134
|
+
<MenuPrimitive.Item
|
|
135
|
+
data-slot="menubar-item"
|
|
136
|
+
data-inset={inset}
|
|
137
|
+
data-variant={variant}
|
|
138
|
+
className={cn(
|
|
139
|
+
'group/menubar-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive!',
|
|
140
|
+
className,
|
|
141
|
+
)}
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function MenubarCheckboxItem(props: Props & {
|
|
147
|
+
checked?: boolean;
|
|
148
|
+
inset?: boolean;
|
|
149
|
+
}) @{
|
|
150
|
+
const { className, children: _children, checked, inset, ...rest } = props;
|
|
151
|
+
|
|
152
|
+
<MenuPrimitive.CheckboxItem
|
|
153
|
+
data-slot="menubar-checkbox-item"
|
|
154
|
+
data-inset={inset}
|
|
155
|
+
className={cn(
|
|
156
|
+
'relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
157
|
+
className,
|
|
158
|
+
)}
|
|
159
|
+
checked={checked}
|
|
160
|
+
{...rest}
|
|
161
|
+
>
|
|
162
|
+
<span
|
|
163
|
+
className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4"
|
|
164
|
+
>
|
|
165
|
+
<MenuPrimitive.CheckboxItemIndicator>
|
|
166
|
+
<CheckIcon />
|
|
167
|
+
</MenuPrimitive.CheckboxItemIndicator>
|
|
168
|
+
</span>
|
|
169
|
+
{props.children}
|
|
170
|
+
</MenuPrimitive.CheckboxItem>
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function MenubarRadioItem(props: Props & { inset?: boolean }) @{
|
|
174
|
+
const { className, children: _children, inset, ...rest } = props;
|
|
175
|
+
|
|
176
|
+
<MenuPrimitive.RadioItem
|
|
177
|
+
data-slot="menubar-radio-item"
|
|
178
|
+
data-inset={inset}
|
|
179
|
+
className={cn(
|
|
180
|
+
'relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
|
181
|
+
className,
|
|
182
|
+
)}
|
|
183
|
+
{...rest}
|
|
184
|
+
>
|
|
185
|
+
<span
|
|
186
|
+
className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4"
|
|
187
|
+
>
|
|
188
|
+
<MenuPrimitive.RadioItemIndicator>
|
|
189
|
+
<CheckIcon />
|
|
190
|
+
</MenuPrimitive.RadioItemIndicator>
|
|
191
|
+
</span>
|
|
192
|
+
{props.children}
|
|
193
|
+
</MenuPrimitive.RadioItem>
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function MenubarLabel({ className, inset, ...props }: Props & { inset?: boolean }) @{
|
|
197
|
+
<div
|
|
198
|
+
data-slot="menubar-label"
|
|
199
|
+
data-inset={inset}
|
|
200
|
+
className={cn('px-1.5 py-1 text-sm font-medium data-inset:pl-7', className)}
|
|
201
|
+
{...props}
|
|
202
|
+
/>
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function MenubarSeparator({ className, ...props }: Props) @{
|
|
206
|
+
<div
|
|
207
|
+
role="separator"
|
|
208
|
+
aria-orientation="horizontal"
|
|
209
|
+
data-slot="menubar-separator"
|
|
210
|
+
className={cn('-mx-1 my-1 h-px bg-border', className)}
|
|
211
|
+
{...props}
|
|
212
|
+
/>
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function MenubarShortcut({ className, ...props }: Props) @{
|
|
216
|
+
<span
|
|
217
|
+
data-slot="menubar-shortcut"
|
|
218
|
+
className={cn(
|
|
219
|
+
'ml-auto text-xs tracking-widest text-muted-foreground group-focus/menubar-item:text-accent-foreground',
|
|
220
|
+
className,
|
|
221
|
+
)}
|
|
222
|
+
{...props}
|
|
223
|
+
/>
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function MenubarSub(props: Record<string, unknown>) @{
|
|
227
|
+
<MenuPrimitive.SubmenuRoot data-slot="menubar-sub" {...props} />
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function MenubarSubTrigger(props: Props & { inset?: boolean }) @{
|
|
231
|
+
const { className, children: _children, inset, ...rest } = props;
|
|
232
|
+
|
|
233
|
+
<MenuPrimitive.SubmenuTrigger
|
|
234
|
+
data-slot="menubar-sub-trigger"
|
|
235
|
+
data-inset={inset}
|
|
236
|
+
className={cn(
|
|
237
|
+
'flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground [&_svg:not([class*=\'size-\'])]:size-4',
|
|
238
|
+
className,
|
|
239
|
+
)}
|
|
240
|
+
{...rest}
|
|
241
|
+
>
|
|
242
|
+
{props.children}
|
|
243
|
+
<ChevronRightIcon className="cn-rtl-flip ml-auto" />
|
|
244
|
+
</MenuPrimitive.SubmenuTrigger>
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function MenubarSubContent({
|
|
248
|
+
className,
|
|
249
|
+
side,
|
|
250
|
+
sideOffset,
|
|
251
|
+
align,
|
|
252
|
+
alignOffset,
|
|
253
|
+
anchor,
|
|
254
|
+
positionMethod,
|
|
255
|
+
collisionBoundary,
|
|
256
|
+
collisionPadding,
|
|
257
|
+
collisionAvoidance,
|
|
258
|
+
arrowPadding,
|
|
259
|
+
sticky,
|
|
260
|
+
disableAnchorTracking,
|
|
261
|
+
...props
|
|
262
|
+
}: Props & BaseUiPositioningProps) @{
|
|
263
|
+
<MenuPrimitive.Portal>
|
|
264
|
+
<MenuPrimitive.Positioner
|
|
265
|
+
side={side}
|
|
266
|
+
sideOffset={sideOffset}
|
|
267
|
+
align={align}
|
|
268
|
+
alignOffset={alignOffset}
|
|
269
|
+
anchor={anchor}
|
|
270
|
+
positionMethod={positionMethod}
|
|
271
|
+
collisionBoundary={collisionBoundary}
|
|
272
|
+
collisionPadding={collisionPadding}
|
|
273
|
+
collisionAvoidance={collisionAvoidance}
|
|
274
|
+
arrowPadding={arrowPadding}
|
|
275
|
+
sticky={sticky}
|
|
276
|
+
disableAnchorTracking={disableAnchorTracking}
|
|
277
|
+
>
|
|
278
|
+
<MenuPrimitive.Popup
|
|
279
|
+
data-slot="menubar-sub-content"
|
|
280
|
+
className={cn(
|
|
281
|
+
'z-50 min-w-32 origin-(--transform-origin) overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 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 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',
|
|
282
|
+
className,
|
|
283
|
+
)}
|
|
284
|
+
{...props}
|
|
285
|
+
/>
|
|
286
|
+
</MenuPrimitive.Positioner>
|
|
287
|
+
</MenuPrimitive.Portal>
|
|
288
|
+
}
|