@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.
Files changed (103) hide show
  1. package/components.css +880 -243
  2. package/dist/cjs/components/_internal/shell-bottom.d.ts +31 -5
  3. package/dist/cjs/components/_internal/shell-bottom.d.ts.map +1 -1
  4. package/dist/cjs/components/_internal/shell-bottom.js +1 -1
  5. package/dist/cjs/components/_internal/shell-bottom.js.map +3 -3
  6. package/dist/cjs/components/_internal/shell-handles.d.ts.map +1 -1
  7. package/dist/cjs/components/_internal/shell-handles.js +1 -1
  8. package/dist/cjs/components/_internal/shell-handles.js.map +3 -3
  9. package/dist/cjs/components/_internal/shell-inspector.d.ts +23 -5
  10. package/dist/cjs/components/_internal/shell-inspector.d.ts.map +1 -1
  11. package/dist/cjs/components/_internal/shell-inspector.js +1 -1
  12. package/dist/cjs/components/_internal/shell-inspector.js.map +3 -3
  13. package/dist/cjs/components/_internal/shell-sidebar.d.ts +24 -6
  14. package/dist/cjs/components/_internal/shell-sidebar.d.ts.map +1 -1
  15. package/dist/cjs/components/_internal/shell-sidebar.js +1 -1
  16. package/dist/cjs/components/_internal/shell-sidebar.js.map +3 -3
  17. package/dist/cjs/components/chatbar.d.ts +9 -2
  18. package/dist/cjs/components/chatbar.d.ts.map +1 -1
  19. package/dist/cjs/components/chatbar.js +1 -1
  20. package/dist/cjs/components/chatbar.js.map +3 -3
  21. package/dist/cjs/components/shell.context.d.ts +88 -0
  22. package/dist/cjs/components/shell.context.d.ts.map +1 -1
  23. package/dist/cjs/components/shell.context.js +1 -1
  24. package/dist/cjs/components/shell.context.js.map +3 -3
  25. package/dist/cjs/components/shell.d.ts +51 -13
  26. package/dist/cjs/components/shell.d.ts.map +1 -1
  27. package/dist/cjs/components/shell.hooks.d.ts +7 -1
  28. package/dist/cjs/components/shell.hooks.d.ts.map +1 -1
  29. package/dist/cjs/components/shell.hooks.js +1 -1
  30. package/dist/cjs/components/shell.hooks.js.map +3 -3
  31. package/dist/cjs/components/shell.js +1 -1
  32. package/dist/cjs/components/shell.js.map +3 -3
  33. package/dist/cjs/components/shell.types.d.ts +1 -0
  34. package/dist/cjs/components/shell.types.d.ts.map +1 -1
  35. package/dist/cjs/components/shell.types.js +1 -1
  36. package/dist/cjs/components/shell.types.js.map +2 -2
  37. package/dist/cjs/components/sidebar.d.ts +7 -1
  38. package/dist/cjs/components/sidebar.d.ts.map +1 -1
  39. package/dist/cjs/components/sidebar.js +1 -1
  40. package/dist/cjs/components/sidebar.js.map +3 -3
  41. package/dist/esm/components/_internal/shell-bottom.d.ts +31 -5
  42. package/dist/esm/components/_internal/shell-bottom.d.ts.map +1 -1
  43. package/dist/esm/components/_internal/shell-bottom.js +1 -1
  44. package/dist/esm/components/_internal/shell-bottom.js.map +3 -3
  45. package/dist/esm/components/_internal/shell-handles.d.ts.map +1 -1
  46. package/dist/esm/components/_internal/shell-handles.js +1 -1
  47. package/dist/esm/components/_internal/shell-handles.js.map +3 -3
  48. package/dist/esm/components/_internal/shell-inspector.d.ts +23 -5
  49. package/dist/esm/components/_internal/shell-inspector.d.ts.map +1 -1
  50. package/dist/esm/components/_internal/shell-inspector.js +1 -1
  51. package/dist/esm/components/_internal/shell-inspector.js.map +3 -3
  52. package/dist/esm/components/_internal/shell-sidebar.d.ts +24 -6
  53. package/dist/esm/components/_internal/shell-sidebar.d.ts.map +1 -1
  54. package/dist/esm/components/_internal/shell-sidebar.js +1 -1
  55. package/dist/esm/components/_internal/shell-sidebar.js.map +3 -3
  56. package/dist/esm/components/chatbar.d.ts +9 -2
  57. package/dist/esm/components/chatbar.d.ts.map +1 -1
  58. package/dist/esm/components/chatbar.js +1 -1
  59. package/dist/esm/components/chatbar.js.map +3 -3
  60. package/dist/esm/components/shell.context.d.ts +88 -0
  61. package/dist/esm/components/shell.context.d.ts.map +1 -1
  62. package/dist/esm/components/shell.context.js +1 -1
  63. package/dist/esm/components/shell.context.js.map +3 -3
  64. package/dist/esm/components/shell.d.ts +51 -13
  65. package/dist/esm/components/shell.d.ts.map +1 -1
  66. package/dist/esm/components/shell.hooks.d.ts +7 -1
  67. package/dist/esm/components/shell.hooks.d.ts.map +1 -1
  68. package/dist/esm/components/shell.hooks.js +1 -1
  69. package/dist/esm/components/shell.hooks.js.map +3 -3
  70. package/dist/esm/components/shell.js +1 -1
  71. package/dist/esm/components/shell.js.map +3 -3
  72. package/dist/esm/components/shell.types.d.ts +1 -0
  73. package/dist/esm/components/shell.types.d.ts.map +1 -1
  74. package/dist/esm/components/shell.types.js.map +2 -2
  75. package/dist/esm/components/sidebar.d.ts +7 -1
  76. package/dist/esm/components/sidebar.d.ts.map +1 -1
  77. package/dist/esm/components/sidebar.js +1 -1
  78. package/dist/esm/components/sidebar.js.map +3 -3
  79. package/package.json +14 -3
  80. package/schemas/base-button.json +1 -1
  81. package/schemas/button.json +1 -1
  82. package/schemas/icon-button.json +1 -1
  83. package/schemas/index.json +6 -6
  84. package/schemas/toggle-button.json +1 -1
  85. package/schemas/toggle-icon-button.json +1 -1
  86. package/src/components/_internal/base-menu.css +17 -18
  87. package/src/components/_internal/base-sidebar-menu.css +23 -21
  88. package/src/components/_internal/base-sidebar.css +20 -0
  89. package/src/components/_internal/shell-bottom.tsx +176 -49
  90. package/src/components/_internal/shell-handles.tsx +29 -4
  91. package/src/components/_internal/shell-inspector.tsx +175 -43
  92. package/src/components/_internal/shell-sidebar.tsx +176 -69
  93. package/src/components/chatbar.css +240 -21
  94. package/src/components/chatbar.tsx +246 -290
  95. package/src/components/sheet.css +8 -16
  96. package/src/components/shell.context.tsx +79 -0
  97. package/src/components/shell.css +28 -2
  98. package/src/components/shell.hooks.ts +35 -0
  99. package/src/components/shell.tsx +574 -214
  100. package/src/components/shell.types.ts +2 -0
  101. package/src/components/sidebar.css +233 -33
  102. package/src/components/sidebar.tsx +247 -213
  103. 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
- const { asChild: _, panelBackground: __, ...safeRootProps } = rootProps; // Remove asChild and panelBackground from DOM props
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
- {...safeRootProps}
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
- ({ className, asContainer = true, ...props }, forwardedRef) => {
160
- const visual = useSidebarVisual();
161
- const size = visual?.size ?? '2';
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
- return (
165
- <div
166
- {...props}
167
- ref={forwardedRef}
168
- className={classNames(
169
- 'rt-SidebarHeader',
170
- `rt-r-size-${size}`,
171
- `rt-menu-variant-${menuVariant}`,
172
- {
173
- 'rt-SidebarHeader--container': asContainer,
174
- },
175
- className,
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
- ({ className, asContainer = true, ...props }, forwardedRef) => {
194
- const visual = useSidebarVisual();
195
- const size = visual?.size ?? '2';
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
- return (
199
- <div
200
- {...props}
201
- ref={forwardedRef}
202
- className={classNames(
203
- 'rt-SidebarFooter',
204
- `rt-r-size-${size}`,
205
- `rt-menu-variant-${menuVariant}`,
206
- {
207
- 'rt-SidebarFooter--container': asContainer,
208
- },
209
- className,
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
- React.ComponentRef<typeof Separator>,
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
- ({ className, ...props }, forwardedRef) => (
242
- <ul
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
- ({ className, ...props }, forwardedRef) => (
256
- <li {...props} ref={forwardedRef} className={classNames('rt-SidebarMenuItem', className)} />
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
- children
349
+ slottedChildren
344
350
  ) : (
345
351
  <>
346
- {children}
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 using Radix Accordion
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
- ({ defaultOpen = false, children, ...props }, forwardedRef) => {
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
- </div>
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
- React.ElementRef<typeof Accordion.Trigger>,
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
- extends React.ComponentPropsWithoutRef<typeof Accordion.Content> {}
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
- ({ className, ...props }, forwardedRef) => (
484
- <div
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
- ({ asChild = false, className, ...props }, forwardedRef) => {
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
- return (
502
- <Comp
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
- ({ className, ...props }, forwardedRef) => (
517
- <div
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 };