@kushagradhawan/kookie-ui 0.1.49 → 0.1.51
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/components.css +880 -243
- package/dist/cjs/components/_internal/shell-bottom.d.ts +31 -5
- package/dist/cjs/components/_internal/shell-bottom.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-bottom.js +1 -1
- package/dist/cjs/components/_internal/shell-bottom.js.map +3 -3
- package/dist/cjs/components/_internal/shell-handles.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-handles.js +1 -1
- package/dist/cjs/components/_internal/shell-handles.js.map +3 -3
- package/dist/cjs/components/_internal/shell-inspector.d.ts +23 -5
- package/dist/cjs/components/_internal/shell-inspector.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-inspector.js +1 -1
- package/dist/cjs/components/_internal/shell-inspector.js.map +3 -3
- package/dist/cjs/components/_internal/shell-sidebar.d.ts +24 -6
- package/dist/cjs/components/_internal/shell-sidebar.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-sidebar.js +1 -1
- package/dist/cjs/components/_internal/shell-sidebar.js.map +3 -3
- package/dist/cjs/components/chatbar.d.ts +9 -2
- package/dist/cjs/components/chatbar.d.ts.map +1 -1
- package/dist/cjs/components/chatbar.js +1 -1
- package/dist/cjs/components/chatbar.js.map +3 -3
- package/dist/cjs/components/shell.context.d.ts +88 -0
- package/dist/cjs/components/shell.context.d.ts.map +1 -1
- package/dist/cjs/components/shell.context.js +1 -1
- package/dist/cjs/components/shell.context.js.map +3 -3
- package/dist/cjs/components/shell.d.ts +51 -13
- package/dist/cjs/components/shell.d.ts.map +1 -1
- package/dist/cjs/components/shell.hooks.d.ts +7 -1
- package/dist/cjs/components/shell.hooks.d.ts.map +1 -1
- package/dist/cjs/components/shell.hooks.js +1 -1
- package/dist/cjs/components/shell.hooks.js.map +3 -3
- package/dist/cjs/components/shell.js +1 -1
- package/dist/cjs/components/shell.js.map +3 -3
- package/dist/cjs/components/shell.types.d.ts +1 -0
- package/dist/cjs/components/shell.types.d.ts.map +1 -1
- package/dist/cjs/components/shell.types.js +1 -1
- package/dist/cjs/components/shell.types.js.map +2 -2
- package/dist/cjs/components/sidebar.d.ts +7 -1
- package/dist/cjs/components/sidebar.d.ts.map +1 -1
- package/dist/cjs/components/sidebar.js +1 -1
- package/dist/cjs/components/sidebar.js.map +3 -3
- package/dist/esm/components/_internal/shell-bottom.d.ts +31 -5
- package/dist/esm/components/_internal/shell-bottom.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-bottom.js +1 -1
- package/dist/esm/components/_internal/shell-bottom.js.map +3 -3
- package/dist/esm/components/_internal/shell-handles.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-handles.js +1 -1
- package/dist/esm/components/_internal/shell-handles.js.map +3 -3
- package/dist/esm/components/_internal/shell-inspector.d.ts +23 -5
- package/dist/esm/components/_internal/shell-inspector.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-inspector.js +1 -1
- package/dist/esm/components/_internal/shell-inspector.js.map +3 -3
- package/dist/esm/components/_internal/shell-sidebar.d.ts +24 -6
- package/dist/esm/components/_internal/shell-sidebar.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-sidebar.js +1 -1
- package/dist/esm/components/_internal/shell-sidebar.js.map +3 -3
- package/dist/esm/components/chatbar.d.ts +9 -2
- package/dist/esm/components/chatbar.d.ts.map +1 -1
- package/dist/esm/components/chatbar.js +1 -1
- package/dist/esm/components/chatbar.js.map +3 -3
- package/dist/esm/components/shell.context.d.ts +88 -0
- package/dist/esm/components/shell.context.d.ts.map +1 -1
- package/dist/esm/components/shell.context.js +1 -1
- package/dist/esm/components/shell.context.js.map +3 -3
- package/dist/esm/components/shell.d.ts +51 -13
- package/dist/esm/components/shell.d.ts.map +1 -1
- package/dist/esm/components/shell.hooks.d.ts +7 -1
- package/dist/esm/components/shell.hooks.d.ts.map +1 -1
- package/dist/esm/components/shell.hooks.js +1 -1
- package/dist/esm/components/shell.hooks.js.map +3 -3
- package/dist/esm/components/shell.js +1 -1
- package/dist/esm/components/shell.js.map +3 -3
- package/dist/esm/components/shell.types.d.ts +1 -0
- package/dist/esm/components/shell.types.d.ts.map +1 -1
- package/dist/esm/components/shell.types.js.map +2 -2
- package/dist/esm/components/sidebar.d.ts +7 -1
- package/dist/esm/components/sidebar.d.ts.map +1 -1
- package/dist/esm/components/sidebar.js +1 -1
- package/dist/esm/components/sidebar.js.map +3 -3
- package/package.json +14 -3
- package/schemas/base-button.json +1 -1
- package/schemas/button.json +1 -1
- package/schemas/icon-button.json +1 -1
- package/schemas/index.json +6 -6
- package/schemas/toggle-button.json +1 -1
- package/schemas/toggle-icon-button.json +1 -1
- package/src/components/_internal/base-menu.css +17 -18
- package/src/components/_internal/base-sidebar-menu.css +23 -21
- package/src/components/_internal/base-sidebar.css +20 -0
- package/src/components/_internal/shell-bottom.tsx +176 -49
- package/src/components/_internal/shell-handles.tsx +29 -4
- package/src/components/_internal/shell-inspector.tsx +175 -43
- package/src/components/_internal/shell-sidebar.tsx +176 -69
- package/src/components/chatbar.css +240 -21
- package/src/components/chatbar.tsx +246 -290
- package/src/components/sheet.css +8 -16
- package/src/components/shell.context.tsx +79 -0
- package/src/components/shell.css +28 -2
- package/src/components/shell.hooks.ts +35 -0
- package/src/components/shell.tsx +574 -214
- package/src/components/shell.types.ts +2 -0
- package/src/components/sidebar.css +233 -33
- package/src/components/sidebar.tsx +247 -213
- package/styles.css +841 -204
|
@@ -4,6 +4,7 @@ import * as React from 'react';
|
|
|
4
4
|
import classNames from 'classnames';
|
|
5
5
|
import { Slot } from './slot.js';
|
|
6
6
|
import * as Accordion from '@radix-ui/react-accordion';
|
|
7
|
+
import * as DropdownMenu from './dropdown-menu.js';
|
|
7
8
|
|
|
8
9
|
import { sidebarPropDefs } from './sidebar.props.js';
|
|
9
10
|
import { useThemeContext } from './theme.js';
|
|
@@ -33,6 +34,8 @@ type BadgeConfig = {
|
|
|
33
34
|
type SidebarVisualContextValue = {
|
|
34
35
|
size: '1' | '2';
|
|
35
36
|
menuVariant: 'solid' | 'soft';
|
|
37
|
+
presentation?: 'thin' | 'expanded';
|
|
38
|
+
color?: string;
|
|
36
39
|
};
|
|
37
40
|
const SidebarVisualContext = React.createContext<SidebarVisualContextValue | null>(null);
|
|
38
41
|
function useSidebarVisual() {
|
|
@@ -43,7 +46,14 @@ function useSidebarVisual() {
|
|
|
43
46
|
|
|
44
47
|
// Main Sidebar component
|
|
45
48
|
type SidebarOwnProps = GetPropDefTypes<typeof sidebarPropDefs>;
|
|
46
|
-
interface SidebarProps extends ComponentPropsWithout<'div', RemovedProps>, SidebarOwnProps {
|
|
49
|
+
interface SidebarProps extends ComponentPropsWithout<'div', RemovedProps>, SidebarOwnProps {
|
|
50
|
+
/**
|
|
51
|
+
* Presentational mode independent of Shell.
|
|
52
|
+
* 'thin' renders a rail-style sidebar, 'expanded' renders a panel-style sidebar.
|
|
53
|
+
* If both `presentation` and `layout` are provided, `presentation` takes precedence.
|
|
54
|
+
*/
|
|
55
|
+
presentation?: 'thin' | 'expanded';
|
|
56
|
+
}
|
|
47
57
|
|
|
48
58
|
const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>((props, forwardedRef) => {
|
|
49
59
|
const themeContext = useThemeContext();
|
|
@@ -53,6 +63,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>((props, forwarded
|
|
|
53
63
|
variant = sidebarPropDefs.variant.default,
|
|
54
64
|
menuVariant = sidebarPropDefs.menuVariant.default,
|
|
55
65
|
layout = sidebarPropDefs.layout.default,
|
|
66
|
+
presentation,
|
|
56
67
|
// type = sidebarPropDefs.type.default,
|
|
57
68
|
// side = sidebarPropDefs.side.default,
|
|
58
69
|
// collapsible = sidebarPropDefs.collapsible.default,
|
|
@@ -62,33 +73,24 @@ const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>((props, forwarded
|
|
|
62
73
|
} = props;
|
|
63
74
|
|
|
64
75
|
const { className, children, ...rootProps } = extractProps(props, sidebarPropDefs);
|
|
65
|
-
|
|
76
|
+
// Remove internal-only props from DOM
|
|
77
|
+
const { asChild: _, panelBackground: __, presentation: ___, ...safeRootProps } = rootProps;
|
|
66
78
|
const resolvedColor = color || themeContext.accentColor;
|
|
67
79
|
|
|
68
|
-
// Resolve layout (default to 'panel')
|
|
69
|
-
const resolvedLayout = layout || 'panel';
|
|
80
|
+
// Resolve layout (default to 'panel'). `presentation` takes precedence over `layout`.
|
|
81
|
+
const resolvedLayout = presentation === 'thin' ? 'rail' : presentation === 'expanded' ? 'panel' : layout || 'panel';
|
|
70
82
|
|
|
71
83
|
// Update context with current props - we'll pass the resolved values
|
|
72
84
|
const resolvedSize = typeof size === 'object' ? size.initial || '2' : size;
|
|
73
85
|
return (
|
|
74
|
-
<div
|
|
75
|
-
{
|
|
76
|
-
ref={forwardedRef}
|
|
77
|
-
data-accent-color={resolvedColor}
|
|
78
|
-
className={classNames('rt-SidebarRoot', className)}
|
|
79
|
-
>
|
|
80
|
-
<SidebarVisualContext.Provider value={{ size: resolvedSize, menuVariant }}>
|
|
86
|
+
<div {...safeRootProps} ref={forwardedRef} data-accent-color={resolvedColor} className={classNames('rt-SidebarRoot', className)}>
|
|
87
|
+
<SidebarVisualContext.Provider value={{ size: resolvedSize, menuVariant, presentation, color: resolvedColor }}>
|
|
81
88
|
<div
|
|
82
|
-
className={classNames(
|
|
83
|
-
'rt-SidebarContainer',
|
|
84
|
-
`rt-variant-${variant}`,
|
|
85
|
-
`rt-r-size-${resolvedSize}`,
|
|
86
|
-
`rt-menu-variant-${menuVariant}`,
|
|
87
|
-
resolvedLayout && `rt-layout-${resolvedLayout}`,
|
|
88
|
-
)}
|
|
89
|
+
className={classNames('rt-SidebarContainer', `rt-variant-${variant}`, `rt-r-size-${resolvedSize}`, `rt-menu-variant-${menuVariant}`, resolvedLayout && `rt-layout-${resolvedLayout}`)}
|
|
89
90
|
data-accent-color={resolvedColor}
|
|
90
91
|
data-high-contrast={highContrast || undefined}
|
|
91
92
|
data-panel-background={panelBackground}
|
|
93
|
+
data-presentation={presentation}
|
|
92
94
|
data-layout={resolvedLayout}
|
|
93
95
|
>
|
|
94
96
|
{children}
|
|
@@ -107,17 +109,7 @@ interface SidebarContentProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
const SidebarContent = React.forwardRef<HTMLDivElement, SidebarContentProps>(
|
|
110
|
-
(
|
|
111
|
-
{
|
|
112
|
-
className,
|
|
113
|
-
children,
|
|
114
|
-
role = 'navigation',
|
|
115
|
-
'aria-label': ariaLabel = 'Main navigation',
|
|
116
|
-
id,
|
|
117
|
-
...props
|
|
118
|
-
},
|
|
119
|
-
forwardedRef,
|
|
120
|
-
) => {
|
|
112
|
+
({ className, children, role = 'navigation', 'aria-label': ariaLabel = 'Main navigation', id, ...props }, forwardedRef) => {
|
|
121
113
|
const visual = useSidebarVisual();
|
|
122
114
|
const size = visual?.size ?? '2';
|
|
123
115
|
const menuVariant = visual?.menuVariant ?? 'soft';
|
|
@@ -130,13 +122,7 @@ const SidebarContent = React.forwardRef<HTMLDivElement, SidebarContentProps>(
|
|
|
130
122
|
id={id}
|
|
131
123
|
role={role}
|
|
132
124
|
aria-label={ariaLabel}
|
|
133
|
-
className={classNames(
|
|
134
|
-
'rt-BaseMenuContent',
|
|
135
|
-
'rt-SidebarContent',
|
|
136
|
-
`rt-r-size-${size}`,
|
|
137
|
-
`rt-menu-variant-${menuVariant}`,
|
|
138
|
-
className,
|
|
139
|
-
)}
|
|
125
|
+
className={classNames('rt-BaseMenuContent', 'rt-SidebarContent', `rt-r-size-${size}`, `rt-menu-variant-${menuVariant}`, className)}
|
|
140
126
|
>
|
|
141
127
|
<div className="rt-BaseMenuViewport">{children}</div>
|
|
142
128
|
</div>
|
|
@@ -155,29 +141,27 @@ interface SidebarHeaderProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
|
155
141
|
asContainer?: boolean;
|
|
156
142
|
}
|
|
157
143
|
|
|
158
|
-
const SidebarHeader = React.forwardRef<HTMLDivElement, SidebarHeaderProps>(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const menuVariant = visual?.menuVariant ?? 'soft';
|
|
144
|
+
const SidebarHeader = React.forwardRef<HTMLDivElement, SidebarHeaderProps>(({ className, asContainer = true, ...props }, forwardedRef) => {
|
|
145
|
+
const visual = useSidebarVisual();
|
|
146
|
+
const size = visual?.size ?? '2';
|
|
147
|
+
const menuVariant = visual?.menuVariant ?? 'soft';
|
|
163
148
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
);
|
|
149
|
+
return (
|
|
150
|
+
<div
|
|
151
|
+
{...props}
|
|
152
|
+
ref={forwardedRef}
|
|
153
|
+
className={classNames(
|
|
154
|
+
'rt-SidebarHeader',
|
|
155
|
+
`rt-r-size-${size}`,
|
|
156
|
+
`rt-menu-variant-${menuVariant}`,
|
|
157
|
+
{
|
|
158
|
+
'rt-SidebarHeader--container': asContainer,
|
|
159
|
+
},
|
|
160
|
+
className,
|
|
161
|
+
)}
|
|
162
|
+
/>
|
|
163
|
+
);
|
|
164
|
+
});
|
|
181
165
|
SidebarHeader.displayName = 'Sidebar.Header';
|
|
182
166
|
|
|
183
167
|
// Sidebar footer
|
|
@@ -189,29 +173,27 @@ interface SidebarFooterProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
|
189
173
|
asContainer?: boolean;
|
|
190
174
|
}
|
|
191
175
|
|
|
192
|
-
const SidebarFooter = React.forwardRef<HTMLDivElement, SidebarFooterProps>(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const menuVariant = visual?.menuVariant ?? 'soft';
|
|
176
|
+
const SidebarFooter = React.forwardRef<HTMLDivElement, SidebarFooterProps>(({ className, asContainer = true, ...props }, forwardedRef) => {
|
|
177
|
+
const visual = useSidebarVisual();
|
|
178
|
+
const size = visual?.size ?? '2';
|
|
179
|
+
const menuVariant = visual?.menuVariant ?? 'soft';
|
|
197
180
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
);
|
|
181
|
+
return (
|
|
182
|
+
<div
|
|
183
|
+
{...props}
|
|
184
|
+
ref={forwardedRef}
|
|
185
|
+
className={classNames(
|
|
186
|
+
'rt-SidebarFooter',
|
|
187
|
+
`rt-r-size-${size}`,
|
|
188
|
+
`rt-menu-variant-${menuVariant}`,
|
|
189
|
+
{
|
|
190
|
+
'rt-SidebarFooter--container': asContainer,
|
|
191
|
+
},
|
|
192
|
+
className,
|
|
193
|
+
)}
|
|
194
|
+
/>
|
|
195
|
+
);
|
|
196
|
+
});
|
|
215
197
|
SidebarFooter.displayName = 'Sidebar.Footer';
|
|
216
198
|
|
|
217
199
|
// Sidebar trigger button
|
|
@@ -222,40 +204,24 @@ SidebarFooter.displayName = 'Sidebar.Footer';
|
|
|
222
204
|
// Sidebar separator
|
|
223
205
|
interface SidebarSeparatorProps extends ComponentPropsWithout<typeof Separator, RemovedProps> {}
|
|
224
206
|
|
|
225
|
-
const SidebarSeparator = React.forwardRef<
|
|
226
|
-
|
|
227
|
-
SidebarSeparatorProps
|
|
228
|
-
>(({ className, ...props }, forwardedRef) => (
|
|
229
|
-
<Separator
|
|
230
|
-
{...props}
|
|
231
|
-
ref={forwardedRef}
|
|
232
|
-
className={classNames('rt-SidebarSeparator', className)}
|
|
233
|
-
/>
|
|
207
|
+
const SidebarSeparator = React.forwardRef<React.ComponentRef<typeof Separator>, SidebarSeparatorProps>(({ className, ...props }, forwardedRef) => (
|
|
208
|
+
<Separator {...props} ref={forwardedRef} className={classNames('rt-SidebarSeparator', className)} />
|
|
234
209
|
));
|
|
235
210
|
SidebarSeparator.displayName = 'Sidebar.Separator';
|
|
236
211
|
|
|
237
212
|
// Menu components - reusing dropdown menu structure
|
|
238
213
|
interface SidebarMenuProps extends React.ComponentPropsWithoutRef<'ul'> {}
|
|
239
214
|
|
|
240
|
-
const SidebarMenu = React.forwardRef<HTMLUListElement, SidebarMenuProps>(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
{...props}
|
|
244
|
-
ref={forwardedRef}
|
|
245
|
-
role="menu"
|
|
246
|
-
className={classNames('rt-SidebarMenu', className)}
|
|
247
|
-
/>
|
|
248
|
-
),
|
|
249
|
-
);
|
|
215
|
+
const SidebarMenu = React.forwardRef<HTMLUListElement, SidebarMenuProps>(({ className, ...props }, forwardedRef) => (
|
|
216
|
+
<ul {...props} ref={forwardedRef} role="menu" className={classNames('rt-SidebarMenu', className)} />
|
|
217
|
+
));
|
|
250
218
|
SidebarMenu.displayName = 'Sidebar.Menu';
|
|
251
219
|
|
|
252
220
|
interface SidebarMenuItemProps extends React.ComponentPropsWithoutRef<'li'> {}
|
|
253
221
|
|
|
254
|
-
const SidebarMenuItem = React.forwardRef<HTMLLIElement, SidebarMenuItemProps>(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
),
|
|
258
|
-
);
|
|
222
|
+
const SidebarMenuItem = React.forwardRef<HTMLLIElement, SidebarMenuItemProps>(({ className, ...props }, forwardedRef) => (
|
|
223
|
+
<li {...props} ref={forwardedRef} className={classNames('rt-SidebarMenuItem', className)} />
|
|
224
|
+
));
|
|
259
225
|
SidebarMenuItem.displayName = 'Sidebar.MenuItem';
|
|
260
226
|
|
|
261
227
|
interface SidebarMenuButtonProps extends React.ComponentPropsWithoutRef<'button'> {
|
|
@@ -266,21 +232,7 @@ interface SidebarMenuButtonProps extends React.ComponentPropsWithoutRef<'button'
|
|
|
266
232
|
}
|
|
267
233
|
|
|
268
234
|
const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(
|
|
269
|
-
(
|
|
270
|
-
{
|
|
271
|
-
asChild = false,
|
|
272
|
-
isActive = false,
|
|
273
|
-
shortcut,
|
|
274
|
-
badge,
|
|
275
|
-
className,
|
|
276
|
-
children,
|
|
277
|
-
onMouseEnter,
|
|
278
|
-
onMouseLeave,
|
|
279
|
-
onKeyDown,
|
|
280
|
-
...props
|
|
281
|
-
},
|
|
282
|
-
forwardedRef,
|
|
283
|
-
) => {
|
|
235
|
+
({ asChild = false, isActive = false, shortcut, badge, className, children, onMouseEnter, onMouseLeave, onKeyDown, ...props }, forwardedRef) => {
|
|
284
236
|
const [isHighlighted, setIsHighlighted] = React.useState(false);
|
|
285
237
|
const visual = useSidebarVisual();
|
|
286
238
|
const sidebarSize = visual?.size ?? '2';
|
|
@@ -299,18 +251,14 @@ const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonP
|
|
|
299
251
|
case 'ArrowDown': {
|
|
300
252
|
event.preventDefault();
|
|
301
253
|
// Focus next menu item
|
|
302
|
-
const nextItem = (event.currentTarget as HTMLElement).nextElementSibling?.querySelector(
|
|
303
|
-
'[role="menuitem"]',
|
|
304
|
-
) as HTMLElement;
|
|
254
|
+
const nextItem = (event.currentTarget as HTMLElement).nextElementSibling?.querySelector('[role="menuitem"]') as HTMLElement;
|
|
305
255
|
if (nextItem) nextItem.focus();
|
|
306
256
|
break;
|
|
307
257
|
}
|
|
308
258
|
case 'ArrowUp': {
|
|
309
259
|
event.preventDefault();
|
|
310
260
|
// Focus previous menu item
|
|
311
|
-
const prevItem = (
|
|
312
|
-
event.currentTarget as HTMLElement
|
|
313
|
-
).previousElementSibling?.querySelector('[role="menuitem"]') as HTMLElement;
|
|
261
|
+
const prevItem = (event.currentTarget as HTMLElement).previousElementSibling?.querySelector('[role="menuitem"]') as HTMLElement;
|
|
314
262
|
if (prevItem) prevItem.focus();
|
|
315
263
|
break;
|
|
316
264
|
}
|
|
@@ -320,6 +268,64 @@ const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonP
|
|
|
320
268
|
[onClick, onKeyDown],
|
|
321
269
|
);
|
|
322
270
|
|
|
271
|
+
// Wrap bare text nodes so CSS can target labels (e.g., for truncation in thin mode)
|
|
272
|
+
const wrapTextNodes = (node: React.ReactNode): React.ReactNode => {
|
|
273
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
|
274
|
+
return <span className="rt-SidebarMenuLabel">{node}</span>;
|
|
275
|
+
}
|
|
276
|
+
if (Array.isArray(node)) {
|
|
277
|
+
return node.map((child, index) => <React.Fragment key={index}>{wrapTextNodes(child)}</React.Fragment>);
|
|
278
|
+
}
|
|
279
|
+
if (React.isValidElement(node)) {
|
|
280
|
+
const el = node as React.ReactElement<any>;
|
|
281
|
+
const className: string = (el.props && (el.props as any).className) || '';
|
|
282
|
+
const isAlreadyLabel = typeof el.type === 'string' && className.split(' ').includes('rt-SidebarMenuLabel');
|
|
283
|
+
if (isAlreadyLabel) return el;
|
|
284
|
+
const newChildren = wrapTextNodes((el.props as any)?.children);
|
|
285
|
+
return React.cloneElement(el, { ...(el.props as any) }, newChildren);
|
|
286
|
+
}
|
|
287
|
+
return node;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const processedChildren = wrapTextNodes(children);
|
|
291
|
+
|
|
292
|
+
// When rendering asChild, Slot expects a single child element. We still want to
|
|
293
|
+
// append optional badge/shortcut inside that element so they render with Link.
|
|
294
|
+
const slottedChildren = React.useMemo(() => {
|
|
295
|
+
if (!asChild) return null;
|
|
296
|
+
try {
|
|
297
|
+
const onlyChild = React.Children.only(processedChildren as React.ReactElement<any>) as React.ReactElement<any>;
|
|
298
|
+
const originalInnerChildren = (onlyChild.props as any)?.children;
|
|
299
|
+
const enhancedInnerChildren = (
|
|
300
|
+
<>
|
|
301
|
+
{originalInnerChildren}
|
|
302
|
+
{badge && (
|
|
303
|
+
<div className="rt-SidebarMenuBadge">
|
|
304
|
+
{typeof badge === 'string' ? (
|
|
305
|
+
<Badge size={sidebarSize} variant="soft">
|
|
306
|
+
{badge}
|
|
307
|
+
</Badge>
|
|
308
|
+
) : (
|
|
309
|
+
<Badge size={badge.size || sidebarSize} variant={badge.variant || 'soft'} color={badge.color} highContrast={badge.highContrast} radius={badge.radius}>
|
|
310
|
+
{badge.content}
|
|
311
|
+
</Badge>
|
|
312
|
+
)}
|
|
313
|
+
</div>
|
|
314
|
+
)}
|
|
315
|
+
{shortcut && (
|
|
316
|
+
<div className="rt-BaseMenuShortcut rt-SidebarMenuShortcut">
|
|
317
|
+
<Kbd size={sidebarSize}>{shortcut}</Kbd>
|
|
318
|
+
</div>
|
|
319
|
+
)}
|
|
320
|
+
</>
|
|
321
|
+
);
|
|
322
|
+
return React.cloneElement(onlyChild, { ...(onlyChild.props as any) }, enhancedInnerChildren);
|
|
323
|
+
} catch {
|
|
324
|
+
// Fallback: if multiple children were passed incorrectly, just return processedChildren
|
|
325
|
+
return processedChildren as React.ReactNode;
|
|
326
|
+
}
|
|
327
|
+
}, [asChild, processedChildren, badge, shortcut, sidebarSize]);
|
|
328
|
+
|
|
323
329
|
return (
|
|
324
330
|
<Comp
|
|
325
331
|
{...props}
|
|
@@ -340,10 +346,10 @@ const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonP
|
|
|
340
346
|
}}
|
|
341
347
|
>
|
|
342
348
|
{asChild ? (
|
|
343
|
-
|
|
349
|
+
slottedChildren
|
|
344
350
|
) : (
|
|
345
351
|
<>
|
|
346
|
-
{
|
|
352
|
+
{processedChildren}
|
|
347
353
|
{/* Badge with soft variant default and size mapping to sidebar size */}
|
|
348
354
|
{badge && (
|
|
349
355
|
<div className="rt-SidebarMenuBadge">
|
|
@@ -352,13 +358,7 @@ const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonP
|
|
|
352
358
|
{badge}
|
|
353
359
|
</Badge>
|
|
354
360
|
) : (
|
|
355
|
-
<Badge
|
|
356
|
-
size={badge.size || sidebarSize}
|
|
357
|
-
variant={badge.variant || 'soft'}
|
|
358
|
-
color={badge.color}
|
|
359
|
-
highContrast={badge.highContrast}
|
|
360
|
-
radius={badge.radius}
|
|
361
|
-
>
|
|
361
|
+
<Badge size={badge.size || sidebarSize} variant={badge.variant || 'soft'} color={badge.color} highContrast={badge.highContrast} radius={badge.radius}>
|
|
362
362
|
{badge.content}
|
|
363
363
|
</Badge>
|
|
364
364
|
)}
|
|
@@ -377,38 +377,72 @@ const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonP
|
|
|
377
377
|
);
|
|
378
378
|
SidebarMenuButton.displayName = 'Sidebar.MenuButton';
|
|
379
379
|
|
|
380
|
-
// Sub-menu components
|
|
380
|
+
// Sub-menu components: Accordion in expanded, Dropdown in thin
|
|
381
|
+
const SidebarSubMenuModeContext = React.createContext<'accordion' | 'dropdown'>('accordion');
|
|
381
382
|
interface SidebarMenuSubProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
382
383
|
defaultOpen?: boolean;
|
|
383
384
|
}
|
|
384
385
|
|
|
385
|
-
const SidebarMenuSub = React.forwardRef<HTMLDivElement, SidebarMenuSubProps>(
|
|
386
|
-
|
|
386
|
+
const SidebarMenuSub = React.forwardRef<HTMLDivElement, SidebarMenuSubProps>(({ defaultOpen = false, children, ...props }, forwardedRef) => {
|
|
387
|
+
const visual = useSidebarVisual();
|
|
388
|
+
const mode: 'accordion' | 'dropdown' = visual?.presentation === 'thin' ? 'dropdown' : 'accordion';
|
|
389
|
+
|
|
390
|
+
if (mode === 'dropdown') {
|
|
387
391
|
return (
|
|
388
392
|
<div {...props} ref={forwardedRef}>
|
|
393
|
+
<DropdownMenu.Root>
|
|
394
|
+
<SidebarSubMenuModeContext.Provider value="dropdown">{children}</SidebarSubMenuModeContext.Provider>
|
|
395
|
+
</DropdownMenu.Root>
|
|
396
|
+
</div>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return (
|
|
401
|
+
<div {...props} ref={forwardedRef}>
|
|
402
|
+
<SidebarSubMenuModeContext.Provider value="accordion">
|
|
389
403
|
<Accordion.Root type="single" collapsible defaultValue={defaultOpen ? 'item' : undefined}>
|
|
390
404
|
<Accordion.Item value="item">{children}</Accordion.Item>
|
|
391
405
|
</Accordion.Root>
|
|
392
|
-
</
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
);
|
|
406
|
+
</SidebarSubMenuModeContext.Provider>
|
|
407
|
+
</div>
|
|
408
|
+
);
|
|
409
|
+
});
|
|
396
410
|
SidebarMenuSub.displayName = 'Sidebar.MenuSub';
|
|
397
411
|
|
|
398
|
-
interface SidebarMenuSubTriggerProps
|
|
399
|
-
extends React.ComponentPropsWithoutRef<typeof Accordion.Trigger> {
|
|
412
|
+
interface SidebarMenuSubTriggerProps extends React.ComponentPropsWithoutRef<typeof Accordion.Trigger> {
|
|
400
413
|
asChild?: boolean;
|
|
401
414
|
}
|
|
402
415
|
|
|
403
|
-
const SidebarMenuSubTrigger = React.forwardRef<
|
|
404
|
-
|
|
405
|
-
SidebarMenuSubTriggerProps
|
|
406
|
-
>(
|
|
407
|
-
(
|
|
408
|
-
{ asChild = false, className, children, onMouseEnter, onMouseLeave, ...props },
|
|
409
|
-
forwardedRef,
|
|
410
|
-
) => {
|
|
416
|
+
const SidebarMenuSubTrigger = React.forwardRef<React.ElementRef<typeof Accordion.Trigger>, SidebarMenuSubTriggerProps>(
|
|
417
|
+
({ asChild = false, className, children, onMouseEnter, onMouseLeave, ...props }, forwardedRef) => {
|
|
411
418
|
const [isHighlighted, setIsHighlighted] = React.useState(false);
|
|
419
|
+
const mode = React.useContext(SidebarSubMenuModeContext);
|
|
420
|
+
|
|
421
|
+
if (mode === 'dropdown') {
|
|
422
|
+
return (
|
|
423
|
+
<DropdownMenu.Trigger>
|
|
424
|
+
<button
|
|
425
|
+
{...(props as any)}
|
|
426
|
+
ref={forwardedRef as any}
|
|
427
|
+
type="button"
|
|
428
|
+
role="menuitem"
|
|
429
|
+
aria-haspopup="menu"
|
|
430
|
+
className={classNames('rt-reset', 'rt-BaseMenuItem', 'rt-SidebarMenuButton', 'rt-SidebarMenuSubTrigger', className)}
|
|
431
|
+
data-highlighted={isHighlighted || undefined}
|
|
432
|
+
onMouseEnter={(event) => {
|
|
433
|
+
setIsHighlighted(true);
|
|
434
|
+
onMouseEnter?.(event as any);
|
|
435
|
+
}}
|
|
436
|
+
onMouseLeave={(event) => {
|
|
437
|
+
setIsHighlighted(false);
|
|
438
|
+
onMouseLeave?.(event as any);
|
|
439
|
+
}}
|
|
440
|
+
>
|
|
441
|
+
{children}
|
|
442
|
+
</button>
|
|
443
|
+
</DropdownMenu.Trigger>
|
|
444
|
+
);
|
|
445
|
+
}
|
|
412
446
|
|
|
413
447
|
return (
|
|
414
448
|
<Accordion.Header asChild>
|
|
@@ -419,13 +453,7 @@ const SidebarMenuSubTrigger = React.forwardRef<
|
|
|
419
453
|
asChild={asChild}
|
|
420
454
|
role="menuitem"
|
|
421
455
|
aria-haspopup="true"
|
|
422
|
-
className={classNames(
|
|
423
|
-
'rt-reset',
|
|
424
|
-
'rt-BaseMenuItem',
|
|
425
|
-
'rt-SidebarMenuButton',
|
|
426
|
-
'rt-SidebarMenuSubTrigger',
|
|
427
|
-
className,
|
|
428
|
-
)}
|
|
456
|
+
className={classNames('rt-reset', 'rt-BaseMenuItem', 'rt-SidebarMenuButton', 'rt-SidebarMenuSubTrigger', className)}
|
|
429
457
|
data-highlighted={isHighlighted || undefined}
|
|
430
458
|
onMouseEnter={(event) => {
|
|
431
459
|
setIsHighlighted(true);
|
|
@@ -441,12 +469,7 @@ const SidebarMenuSubTrigger = React.forwardRef<
|
|
|
441
469
|
) : (
|
|
442
470
|
<>
|
|
443
471
|
{children}
|
|
444
|
-
<ThickChevronRightIcon
|
|
445
|
-
className={classNames(
|
|
446
|
-
'rt-BaseMenuSubTriggerIcon',
|
|
447
|
-
'rt-SidebarMenuSubTriggerIcon',
|
|
448
|
-
)}
|
|
449
|
-
/>
|
|
472
|
+
<ThickChevronRightIcon className={classNames('rt-BaseMenuSubTriggerIcon', 'rt-SidebarMenuSubTriggerIcon')} />
|
|
450
473
|
</>
|
|
451
474
|
)}
|
|
452
475
|
</Accordion.Trigger>
|
|
@@ -457,19 +480,57 @@ const SidebarMenuSubTrigger = React.forwardRef<
|
|
|
457
480
|
);
|
|
458
481
|
SidebarMenuSubTrigger.displayName = 'Sidebar.MenuSubTrigger';
|
|
459
482
|
|
|
460
|
-
interface SidebarMenuSubContentProps
|
|
461
|
-
|
|
483
|
+
interface SidebarMenuSubContentProps extends React.ComponentPropsWithoutRef<typeof Accordion.Content> {}
|
|
484
|
+
|
|
485
|
+
const SidebarMenuSubContent = React.forwardRef<React.ElementRef<typeof Accordion.Content>, SidebarMenuSubContentProps>(({ className, children, ...props }, forwardedRef) => {
|
|
486
|
+
const visual = useSidebarVisual();
|
|
487
|
+
const mode = React.useContext(SidebarSubMenuModeContext);
|
|
488
|
+
|
|
489
|
+
if (mode === 'dropdown') {
|
|
490
|
+
const unwrapMenuButton = (node: React.ReactNode): React.ReactNode => {
|
|
491
|
+
if (Array.isArray(node)) {
|
|
492
|
+
return node.map((n, i) => <React.Fragment key={i}>{unwrapMenuButton(n)}</React.Fragment>);
|
|
493
|
+
}
|
|
494
|
+
if (React.isValidElement(node)) {
|
|
495
|
+
const typeDisplay = (node.type as any)?.displayName;
|
|
496
|
+
if (typeDisplay === 'Sidebar.MenuButton') {
|
|
497
|
+
return (node.props as any)?.children;
|
|
498
|
+
}
|
|
499
|
+
const child = (node.props as any)?.children;
|
|
500
|
+
if (child !== undefined) {
|
|
501
|
+
return React.cloneElement(node as any, { ...(node.props as any), children: unwrapMenuButton(child) });
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return node;
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const normalized = React.Children.map(children as React.ReactNode, (child, index) => {
|
|
508
|
+
if (React.isValidElement(child) && (child.type as any)?.displayName === 'Sidebar.MenuItem') {
|
|
509
|
+
const itemChildren = (child.props as any)?.children;
|
|
510
|
+
const content = unwrapMenuButton(itemChildren);
|
|
511
|
+
return (
|
|
512
|
+
<DropdownMenu.Item key={index} asChild>
|
|
513
|
+
{content as any}
|
|
514
|
+
</DropdownMenu.Item>
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
// Fallback: wrap raw nodes too for consistent menu styling
|
|
518
|
+
return (
|
|
519
|
+
<DropdownMenu.Item key={index} asChild>
|
|
520
|
+
{unwrapMenuButton(child) as any}
|
|
521
|
+
</DropdownMenu.Item>
|
|
522
|
+
);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<DropdownMenu.Content size={visual?.size} variant={visual?.menuVariant} className={classNames(className)}>
|
|
527
|
+
<DropdownMenu.Group>{normalized}</DropdownMenu.Group>
|
|
528
|
+
</DropdownMenu.Content>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
462
531
|
|
|
463
|
-
const SidebarMenuSubContent = React.forwardRef<
|
|
464
|
-
React.ElementRef<typeof Accordion.Content>,
|
|
465
|
-
SidebarMenuSubContentProps
|
|
466
|
-
>(({ className, children, ...props }, forwardedRef) => {
|
|
467
532
|
return (
|
|
468
|
-
<Accordion.Content
|
|
469
|
-
{...props}
|
|
470
|
-
ref={forwardedRef}
|
|
471
|
-
className={classNames('rt-SidebarMenuSubContent', className)}
|
|
472
|
-
>
|
|
533
|
+
<Accordion.Content {...props} ref={forwardedRef} className={classNames('rt-SidebarMenuSubContent', className)}>
|
|
473
534
|
<div className="rt-SidebarMenuSubList">{children}</div>
|
|
474
535
|
</Accordion.Content>
|
|
475
536
|
);
|
|
@@ -479,48 +540,27 @@ SidebarMenuSubContent.displayName = 'Sidebar.MenuSubContent';
|
|
|
479
540
|
// Group components
|
|
480
541
|
interface SidebarGroupProps extends React.ComponentPropsWithoutRef<'div'> {}
|
|
481
542
|
|
|
482
|
-
const SidebarGroup = React.forwardRef<HTMLDivElement, SidebarGroupProps>(
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
{...props}
|
|
486
|
-
ref={forwardedRef}
|
|
487
|
-
className={classNames('rt-BaseMenuGroup', 'rt-SidebarGroup', className)}
|
|
488
|
-
/>
|
|
489
|
-
),
|
|
490
|
-
);
|
|
543
|
+
const SidebarGroup = React.forwardRef<HTMLDivElement, SidebarGroupProps>(({ className, ...props }, forwardedRef) => (
|
|
544
|
+
<div {...props} ref={forwardedRef} className={classNames('rt-BaseMenuGroup', 'rt-SidebarGroup', className)} />
|
|
545
|
+
));
|
|
491
546
|
SidebarGroup.displayName = 'Sidebar.Group';
|
|
492
547
|
|
|
493
548
|
interface SidebarGroupLabelProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
494
549
|
asChild?: boolean;
|
|
495
550
|
}
|
|
496
551
|
|
|
497
|
-
const SidebarGroupLabel = React.forwardRef<HTMLDivElement, SidebarGroupLabelProps>(
|
|
498
|
-
|
|
499
|
-
const Comp = asChild ? Slot : 'div';
|
|
552
|
+
const SidebarGroupLabel = React.forwardRef<HTMLDivElement, SidebarGroupLabelProps>(({ asChild = false, className, ...props }, forwardedRef) => {
|
|
553
|
+
const Comp = asChild ? Slot : 'div';
|
|
500
554
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
{...props}
|
|
504
|
-
ref={forwardedRef}
|
|
505
|
-
role="group"
|
|
506
|
-
className={classNames('rt-BaseMenuLabel', 'rt-SidebarGroupLabel', className)}
|
|
507
|
-
/>
|
|
508
|
-
);
|
|
509
|
-
},
|
|
510
|
-
);
|
|
555
|
+
return <Comp {...props} ref={forwardedRef} role="group" className={classNames('rt-BaseMenuLabel', 'rt-SidebarGroupLabel', className)} />;
|
|
556
|
+
});
|
|
511
557
|
SidebarGroupLabel.displayName = 'Sidebar.GroupLabel';
|
|
512
558
|
|
|
513
559
|
interface SidebarGroupContentProps extends React.ComponentPropsWithoutRef<'div'> {}
|
|
514
560
|
|
|
515
|
-
const SidebarGroupContent = React.forwardRef<HTMLDivElement, SidebarGroupContentProps>(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
{...props}
|
|
519
|
-
ref={forwardedRef}
|
|
520
|
-
className={classNames('rt-SidebarGroupContent', className)}
|
|
521
|
-
/>
|
|
522
|
-
),
|
|
523
|
-
);
|
|
561
|
+
const SidebarGroupContent = React.forwardRef<HTMLDivElement, SidebarGroupContentProps>(({ className, ...props }, forwardedRef) => (
|
|
562
|
+
<div {...props} ref={forwardedRef} className={classNames('rt-SidebarGroupContent', className)} />
|
|
563
|
+
));
|
|
524
564
|
SidebarGroupContent.displayName = 'Sidebar.GroupContent';
|
|
525
565
|
|
|
526
566
|
// Export all components following shadcn's pattern
|
|
@@ -594,10 +634,4 @@ export {
|
|
|
594
634
|
* - Gap: rt-gap-1, rt-gap-2, rt-gap-3, rt-gap-4
|
|
595
635
|
*/
|
|
596
636
|
|
|
597
|
-
export type {
|
|
598
|
-
SidebarProps as RootProps,
|
|
599
|
-
SidebarContentProps as ContentProps,
|
|
600
|
-
SidebarHeaderProps as HeaderProps,
|
|
601
|
-
SidebarFooterProps as FooterProps,
|
|
602
|
-
BadgeConfig,
|
|
603
|
-
};
|
|
637
|
+
export type { SidebarProps as RootProps, SidebarContentProps as ContentProps, SidebarHeaderProps as HeaderProps, SidebarFooterProps as FooterProps, BadgeConfig };
|