@kushagradhawan/kookie-ui 0.1.15 → 0.1.16

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 (49) hide show
  1. package/components.css +1321 -131
  2. package/dist/cjs/components/dropdown-menu.js +1 -1
  3. package/dist/cjs/components/dropdown-menu.js.map +2 -2
  4. package/dist/cjs/components/icons.d.ts +2 -1
  5. package/dist/cjs/components/icons.d.ts.map +1 -1
  6. package/dist/cjs/components/icons.js +1 -1
  7. package/dist/cjs/components/icons.js.map +3 -3
  8. package/dist/cjs/components/image.d.ts +4 -0
  9. package/dist/cjs/components/image.d.ts.map +1 -1
  10. package/dist/cjs/components/image.js +1 -1
  11. package/dist/cjs/components/image.js.map +3 -3
  12. package/dist/cjs/components/sidebar.d.ts +117 -42
  13. package/dist/cjs/components/sidebar.d.ts.map +1 -1
  14. package/dist/cjs/components/sidebar.js +1 -1
  15. package/dist/cjs/components/sidebar.js.map +3 -3
  16. package/dist/cjs/components/sidebar.props.d.ts +17 -10
  17. package/dist/cjs/components/sidebar.props.d.ts.map +1 -1
  18. package/dist/cjs/components/sidebar.props.js +1 -1
  19. package/dist/cjs/components/sidebar.props.js.map +3 -3
  20. package/dist/esm/components/dropdown-menu.js +1 -1
  21. package/dist/esm/components/dropdown-menu.js.map +3 -3
  22. package/dist/esm/components/icons.d.ts +2 -1
  23. package/dist/esm/components/icons.d.ts.map +1 -1
  24. package/dist/esm/components/icons.js +1 -1
  25. package/dist/esm/components/icons.js.map +3 -3
  26. package/dist/esm/components/image.d.ts +4 -0
  27. package/dist/esm/components/image.d.ts.map +1 -1
  28. package/dist/esm/components/image.js +1 -1
  29. package/dist/esm/components/image.js.map +3 -3
  30. package/dist/esm/components/sidebar.d.ts +117 -42
  31. package/dist/esm/components/sidebar.d.ts.map +1 -1
  32. package/dist/esm/components/sidebar.js +1 -1
  33. package/dist/esm/components/sidebar.js.map +3 -3
  34. package/dist/esm/components/sidebar.props.d.ts +17 -10
  35. package/dist/esm/components/sidebar.props.d.ts.map +1 -1
  36. package/dist/esm/components/sidebar.props.js +1 -1
  37. package/dist/esm/components/sidebar.props.js.map +3 -3
  38. package/package.json +1 -1
  39. package/src/components/_internal/base-button.css +2 -9
  40. package/src/components/_internal/base-menu.css +2 -2
  41. package/src/components/button.css +2 -2
  42. package/src/components/dropdown-menu.tsx +2 -2
  43. package/src/components/icon-button.css +2 -2
  44. package/src/components/icons.tsx +18 -1
  45. package/src/components/image.tsx +48 -9
  46. package/src/components/sidebar.css +850 -54
  47. package/src/components/sidebar.props.tsx +15 -11
  48. package/src/components/sidebar.tsx +500 -367
  49. package/styles.css +1321 -131
@@ -2,29 +2,35 @@
2
2
 
3
3
  import * as React from 'react';
4
4
  import classNames from 'classnames';
5
+ import { Slot } from './slot';
6
+ import { Accordion } from 'radix-ui';
5
7
 
6
- import { sidebarPropDefs } from './sidebar.props.js';
8
+ import { sidebarPropDefs } from './sidebar.props';
9
+ import { Theme, useThemeContext } from './theme';
10
+ import { IconButton } from './icon-button';
11
+ import { ScrollArea } from './scroll-area';
12
+ import { Separator } from './separator';
13
+ import { ChevronDownIcon, ThickChevronRightIcon } from './icons';
14
+ import { extractProps } from '../helpers/extract-props';
7
15
 
8
- import { IconButton } from './icon-button.js';
9
- import { ScrollArea } from './scroll-area.js';
10
- import { Separator } from './separator.js';
11
- import { Theme, useThemeContext } from './theme.js';
12
- import { ChevronDownIcon } from './icons.js';
13
- import { extractProps } from '../helpers/extract-props.js';
16
+ import type { ComponentPropsWithout, RemovedProps } from '../helpers/component-props';
17
+ import type { GetPropDefTypes } from '../props/prop-def';
14
18
 
15
-
16
- // Import base menu styling and components
17
- import { baseMenuItemPropDefs } from './_internal/base-menu.props.js';
18
- import { Slot } from 'radix-ui';
19
-
20
- import type { ComponentPropsWithout, RemovedProps } from '../helpers/component-props.js';
21
- import type { GetPropDefTypes } from '../props/prop-def.js';
22
-
23
- // Context for sidebar state
19
+ // Sidebar context for state management
24
20
  type SidebarContextProps = {
21
+ state: 'expanded' | 'collapsed';
25
22
  open: boolean;
26
23
  setOpen: (open: boolean) => void;
27
- collapsible: 'icon' | 'offcanvas' | 'none';
24
+ openMobile: boolean;
25
+ setOpenMobile: (open: boolean) => void;
26
+ isMobile: boolean;
27
+ toggleSidebar: () => void;
28
+ side: 'left' | 'right';
29
+ type: 'sidebar' | 'floating';
30
+ variant: 'soft' | 'surface' | 'ghost';
31
+ menuVariant: 'solid' | 'soft';
32
+ collapsible: 'offcanvas' | 'icon' | 'none';
33
+ size: '1' | '2';
28
34
  };
29
35
 
30
36
  const SidebarContext = React.createContext<SidebarContextProps | null>(null);
@@ -32,37 +38,87 @@ const SidebarContext = React.createContext<SidebarContextProps | null>(null);
32
38
  function useSidebar() {
33
39
  const context = React.useContext(SidebarContext);
34
40
  if (!context) {
35
- throw new Error('useSidebar must be used within a Sidebar.Provider');
41
+ throw new Error('useSidebar must be used within a SidebarProvider.');
36
42
  }
37
43
  return context;
38
44
  }
39
45
 
46
+ // Hook to detect mobile (simplified version)
47
+ function useIsMobile() {
48
+ const [isMobile, setIsMobile] = React.useState(false);
49
+
50
+ React.useEffect(() => {
51
+ const checkIsMobile = () => {
52
+ setIsMobile(window.innerWidth < 768);
53
+ };
54
+
55
+ checkIsMobile();
56
+ window.addEventListener('resize', checkIsMobile);
57
+ return () => window.removeEventListener('resize', checkIsMobile);
58
+ }, []);
59
+
60
+ return isMobile;
61
+ }
62
+
40
63
  // Provider component
41
64
  interface SidebarProviderProps extends React.ComponentPropsWithoutRef<'div'> {
42
65
  defaultOpen?: boolean;
43
66
  open?: boolean;
44
67
  onOpenChange?: (open: boolean) => void;
68
+ side?: 'left' | 'right';
45
69
  }
46
70
 
47
71
  const SidebarProvider = React.forwardRef<HTMLDivElement, SidebarProviderProps>(
48
- ({ defaultOpen = true, open: openProp, onOpenChange: setOpenProp, children, ...props }, forwardedRef) => {
72
+ ({ defaultOpen = true, open: openProp, onOpenChange: setOpenProp, side = 'left', className, children, ...props }, forwardedRef) => {
73
+ const isMobile = useIsMobile();
74
+ const [openMobile, setOpenMobile] = React.useState(false);
75
+
49
76
  // Internal state for uncontrolled mode
50
77
  const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
51
78
 
52
79
  // Use controlled state if provided, otherwise internal state
53
80
  const open = openProp ?? internalOpen;
54
81
 
55
- const setOpen = React.useCallback((value: boolean) => {
82
+ const setOpen = React.useCallback((value: boolean | ((value: boolean) => boolean)) => {
83
+ const openState = typeof value === 'function' ? value(open) : value;
56
84
  if (setOpenProp) {
57
- setOpenProp(value); // Controlled mode
85
+ setOpenProp(openState);
58
86
  } else {
59
- setInternalOpen(value); // Uncontrolled mode
87
+ setInternalOpen(openState);
60
88
  }
61
- }, [setOpenProp]);
89
+ }, [setOpenProp, open]);
90
+
91
+ // Helper to toggle the sidebar
92
+ const toggleSidebar = React.useCallback(() => {
93
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
94
+ }, [isMobile, setOpen, setOpenMobile]);
95
+
96
+ // State for data attributes
97
+ const state = open ? 'expanded' : 'collapsed';
98
+
99
+ const contextValue = React.useMemo<Partial<SidebarContextProps>>(
100
+ () => ({
101
+ state,
102
+ open,
103
+ setOpen,
104
+ isMobile,
105
+ openMobile,
106
+ setOpenMobile,
107
+ toggleSidebar,
108
+ side,
109
+ }),
110
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar, side]
111
+ );
62
112
 
63
113
  return (
64
- <div {...props} ref={forwardedRef}>
65
- <SidebarContext.Provider value={{ open, setOpen, collapsible: 'icon' }}>
114
+ <div
115
+ {...props}
116
+ ref={forwardedRef}
117
+ className={classNames('rt-SidebarProvider', className)}
118
+ data-state={state}
119
+ data-side={side}
120
+ >
121
+ <SidebarContext.Provider value={contextValue as SidebarContextProps}>
66
122
  {children}
67
123
  </SidebarContext.Provider>
68
124
  </div>
@@ -71,49 +127,112 @@ const SidebarProvider = React.forwardRef<HTMLDivElement, SidebarProviderProps>(
71
127
  );
72
128
  SidebarProvider.displayName = 'Sidebar.Provider';
73
129
 
74
- // Root sidebar container
75
- type SidebarRootOwnProps = GetPropDefTypes<typeof sidebarPropDefs>;
76
- type SidebarRootElement = HTMLDivElement;
77
- interface SidebarRootProps
78
- extends ComponentPropsWithout<'div', RemovedProps>,
79
- SidebarRootOwnProps {}
130
+ // Main Sidebar component
131
+ type SidebarOwnProps = GetPropDefTypes<typeof sidebarPropDefs>;
132
+ interface SidebarProps extends ComponentPropsWithout<'div', RemovedProps>, SidebarOwnProps {}
80
133
 
81
- const SidebarRoot = React.forwardRef<SidebarRootElement, SidebarRootProps>(
134
+ const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>(
82
135
  (props, forwardedRef) => {
83
136
  const themeContext = useThemeContext();
84
- const { open } = useSidebar();
85
-
137
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
138
+
86
139
  const {
87
140
  size = sidebarPropDefs.size.default,
88
141
  variant = sidebarPropDefs.variant.default,
142
+ menuVariant = sidebarPropDefs.menuVariant.default,
143
+ type = sidebarPropDefs.type.default,
89
144
  side = sidebarPropDefs.side.default,
90
145
  collapsible = sidebarPropDefs.collapsible.default,
91
- floating: _floating = sidebarPropDefs.floating.default,
92
146
  color,
93
147
  highContrast = sidebarPropDefs.highContrast.default,
148
+ asChild,
94
149
  } = props;
95
150
 
96
151
  const { className, children, ...rootProps } = extractProps(props, sidebarPropDefs);
152
+ const { asChild: _, ...safeRootProps } = rootProps; // Remove asChild from DOM props
97
153
  const resolvedColor = color || themeContext.accentColor;
98
154
 
155
+ // Update context with current props - we'll pass the resolved values
156
+ const resolvedSize = typeof size === 'object' ? size.initial || '2' : size;
157
+ const context = React.useContext(SidebarContext);
158
+ if (context) {
159
+ context.side = side;
160
+ context.type = type;
161
+ context.variant = variant;
162
+ context.menuVariant = menuVariant;
163
+ context.collapsible = collapsible;
164
+ context.size = resolvedSize;
165
+ }
166
+
167
+ if (collapsible === 'none') {
168
+ return (
169
+ <div
170
+ {...safeRootProps}
171
+ ref={forwardedRef}
172
+ data-accent-color={resolvedColor}
173
+ data-state={state}
174
+ data-side={side}
175
+ data-type={type}
176
+ data-collapsible={collapsible}
177
+ className={classNames('rt-SidebarRoot', `rt-r-size-${size}`, className)}
178
+ >
179
+ <Theme>
180
+ <div
181
+ className={classNames('rt-SidebarContainer', `rt-variant-${variant}`)}
182
+ data-accent-color={resolvedColor}
183
+ data-high-contrast={highContrast || undefined}
184
+ data-side={side}
185
+ >
186
+ {children}
187
+ </div>
188
+ </Theme>
189
+ </div>
190
+ );
191
+ }
192
+
193
+ if (isMobile) {
194
+ return (
195
+ <div
196
+ {...safeRootProps}
197
+ ref={forwardedRef}
198
+ data-accent-color={resolvedColor}
199
+ data-state={openMobile ? 'open' : 'closed'}
200
+ data-side={side}
201
+ data-type={type}
202
+ data-collapsible={collapsible}
203
+ className={classNames('rt-SidebarRoot', 'rt-SidebarRoot--mobile', className)}
204
+ >
205
+ <Theme>
206
+ <div
207
+ className={classNames('rt-SidebarContainer', `rt-variant-${variant}`, `rt-r-size-${size}`)}
208
+ data-accent-color={resolvedColor}
209
+ data-high-contrast={highContrast || undefined}
210
+ data-side={side}
211
+ >
212
+ {children}
213
+ </div>
214
+ </Theme>
215
+ </div>
216
+ );
217
+ }
218
+
99
219
  return (
100
220
  <div
101
- {...rootProps}
221
+ {...safeRootProps}
102
222
  ref={forwardedRef}
103
223
  data-accent-color={resolvedColor}
104
- data-state={open ? 'expanded' : 'collapsed'}
224
+ data-state={state}
105
225
  data-side={side}
226
+ data-type={type}
106
227
  data-collapsible={collapsible}
107
- className={classNames(
108
- 'rt-SidebarRoot',
109
- className
110
- )}
228
+ className={classNames('rt-SidebarRoot', className)}
111
229
  >
112
- <Theme asChild>
230
+ <Theme>
113
231
  <div
114
232
  className={classNames('rt-SidebarContainer', `rt-variant-${variant}`, `rt-r-size-${size}`)}
115
233
  data-accent-color={resolvedColor}
116
234
  data-high-contrast={highContrast || undefined}
235
+ data-side={side}
117
236
  >
118
237
  {children}
119
238
  </div>
@@ -122,415 +241,429 @@ const SidebarRoot = React.forwardRef<SidebarRootElement, SidebarRootProps>(
122
241
  );
123
242
  }
124
243
  );
125
- SidebarRoot.displayName = 'Sidebar.Root';
244
+ Sidebar.displayName = 'Sidebar.Root';
126
245
 
127
246
  // Sidebar content area
128
- type SidebarContentElement = HTMLDivElement;
129
- interface SidebarContentProps extends ComponentPropsWithout<'div', RemovedProps> {}
247
+ interface SidebarContentProps extends React.ComponentPropsWithoutRef<'div'> {}
130
248
 
131
- const SidebarContent = React.forwardRef<SidebarContentElement, SidebarContentProps>(
132
- ({ className, children, ...props }, forwardedRef) => (
133
- <ScrollArea type="auto">
134
- <div
135
- {...props}
136
- ref={forwardedRef}
137
- className={classNames('rt-SidebarContent', 'rt-BaseMenuContent', className)}
138
- >
139
- {children}
140
- </div>
141
- </ScrollArea>
142
- )
249
+ const SidebarContent = React.forwardRef<HTMLDivElement, SidebarContentProps>(
250
+ ({ className, children, ...props }, forwardedRef) => {
251
+ const context = React.useContext(SidebarContext);
252
+ const { size = '2', menuVariant = 'soft' } = context || {};
253
+
254
+ return (
255
+ <ScrollArea type="auto">
256
+ <div
257
+ {...props}
258
+ ref={forwardedRef}
259
+ className={classNames(
260
+ 'rt-SidebarContent',
261
+ `rt-r-size-${size}`,
262
+ `rt-menu-variant-${menuVariant}`,
263
+ className
264
+ )}
265
+ >
266
+ {children}
267
+ </div>
268
+ </ScrollArea>
269
+ );
270
+ }
143
271
  );
144
272
  SidebarContent.displayName = 'Sidebar.Content';
145
273
 
146
274
  // Sidebar header
147
- type SidebarHeaderElement = HTMLDivElement;
148
- interface SidebarHeaderProps extends ComponentPropsWithout<'div', RemovedProps> {}
275
+ interface SidebarHeaderProps extends React.ComponentPropsWithoutRef<'div'> {
276
+ /**
277
+ * Whether to use the default flex container layout.
278
+ * @default true
279
+ */
280
+ asContainer?: boolean;
281
+ }
149
282
 
150
- const SidebarHeader = React.forwardRef<SidebarHeaderElement, SidebarHeaderProps>(
151
- ({ className, ...props }, forwardedRef) => (
152
- <div
153
- {...props}
154
- ref={forwardedRef}
155
- className={classNames('rt-SidebarHeader', 'rt-BaseMenuContent', className)}
156
- />
157
- )
283
+ const SidebarHeader = React.forwardRef<HTMLDivElement, SidebarHeaderProps>(
284
+ ({ className, asContainer = true, ...props }, forwardedRef) => {
285
+ const context = React.useContext(SidebarContext);
286
+ const { size = '2', menuVariant = 'soft' } = context || {};
287
+
288
+ return (
289
+ <div
290
+ {...props}
291
+ ref={forwardedRef}
292
+ className={classNames(
293
+ 'rt-SidebarHeader',
294
+ `rt-r-size-${size}`,
295
+ `rt-menu-variant-${menuVariant}`,
296
+ {
297
+ 'rt-SidebarHeader--container': asContainer,
298
+ },
299
+ className
300
+ )}
301
+ />
302
+ );
303
+ }
158
304
  );
159
305
  SidebarHeader.displayName = 'Sidebar.Header';
160
306
 
161
307
  // Sidebar footer
162
- type SidebarFooterElement = HTMLDivElement;
163
- interface SidebarFooterProps extends ComponentPropsWithout<'div', RemovedProps> {}
164
-
165
- const SidebarFooter = React.forwardRef<SidebarFooterElement, SidebarFooterProps>(
166
- ({ className, ...props }, forwardedRef) => (
167
- <div
168
- {...props}
169
- ref={forwardedRef}
170
- className={classNames('rt-SidebarFooter', 'rt-BaseMenuContent', className)}
171
- />
172
- )
173
- );
174
- SidebarFooter.displayName = 'Sidebar.Footer';
175
-
176
- // Sidebar trigger button
177
- type SidebarTriggerElement = React.ElementRef<typeof IconButton>;
178
- interface SidebarTriggerProps extends ComponentPropsWithout<typeof IconButton, RemovedProps> {}
308
+ interface SidebarFooterProps extends React.ComponentPropsWithoutRef<'div'> {
309
+ /**
310
+ * Whether to use the default flex container layout.
311
+ * @default true
312
+ */
313
+ asContainer?: boolean;
314
+ }
179
315
 
180
- const SidebarTrigger = React.forwardRef<SidebarTriggerElement, SidebarTriggerProps>(
181
- ({ onClick, ...props }, forwardedRef) => {
182
- const { setOpen, open } = useSidebar();
316
+ const SidebarFooter = React.forwardRef<HTMLDivElement, SidebarFooterProps>(
317
+ ({ className, asContainer = true, ...props }, forwardedRef) => {
318
+ const context = React.useContext(SidebarContext);
319
+ const { size = '2', menuVariant = 'soft' } = context || {};
183
320
 
184
321
  return (
185
- <IconButton
322
+ <div
186
323
  {...props}
187
324
  ref={forwardedRef}
188
- variant="ghost"
189
- onClick={(event) => {
190
- onClick?.(event);
191
- setOpen(!open);
192
- }}
193
- >
194
- <ChevronDownIcon />
195
- </IconButton>
325
+ className={classNames(
326
+ 'rt-SidebarFooter',
327
+ `rt-r-size-${size}`,
328
+ `rt-menu-variant-${menuVariant}`,
329
+ {
330
+ 'rt-SidebarFooter--container': asContainer,
331
+ },
332
+ className
333
+ )}
334
+ />
196
335
  );
197
336
  }
198
337
  );
338
+ SidebarFooter.displayName = 'Sidebar.Footer';
339
+
340
+ // Sidebar trigger button
341
+ interface SidebarTriggerProps extends ComponentPropsWithout<typeof IconButton, RemovedProps> {}
342
+
343
+ const SidebarTrigger = React.forwardRef<
344
+ React.ElementRef<typeof IconButton>,
345
+ SidebarTriggerProps
346
+ >(({ onClick, children, ...props }, forwardedRef) => {
347
+ const { toggleSidebar } = useSidebar();
348
+
349
+ return (
350
+ <IconButton
351
+ {...props}
352
+ ref={forwardedRef}
353
+ variant="ghost"
354
+ onClick={(event) => {
355
+ onClick?.(event);
356
+ toggleSidebar();
357
+ }}
358
+ >
359
+ {children || <ChevronDownIcon />}
360
+ </IconButton>
361
+ );
362
+ });
199
363
  SidebarTrigger.displayName = 'Sidebar.Trigger';
200
364
 
201
- // Main content area (pushes to make room for sidebar)
202
- type SidebarInsetElement = HTMLDivElement;
203
- interface SidebarInsetProps extends ComponentPropsWithout<'main', RemovedProps> {}
365
+ // Removed SidebarInset - not needed
366
+
367
+ // Sidebar separator
368
+ interface SidebarSeparatorProps extends ComponentPropsWithout<typeof Separator, RemovedProps> {}
369
+
370
+ const SidebarSeparator = React.forwardRef<
371
+ React.ComponentRef<typeof Separator>,
372
+ SidebarSeparatorProps
373
+ >(({ className, ...props }, forwardedRef) => (
374
+ <Separator
375
+ {...props}
376
+ ref={forwardedRef}
377
+ className={classNames('rt-SidebarSeparator', className)}
378
+ />
379
+ ));
380
+ SidebarSeparator.displayName = 'Sidebar.Separator';
204
381
 
205
- const SidebarInset = React.forwardRef<SidebarInsetElement, SidebarInsetProps>(
382
+ // Menu components - reusing dropdown menu structure
383
+ interface SidebarMenuProps extends React.ComponentPropsWithoutRef<'ul'> {}
384
+
385
+ const SidebarMenu = React.forwardRef<HTMLUListElement, SidebarMenuProps>(
206
386
  ({ className, ...props }, forwardedRef) => (
207
- <main
387
+ <ul
208
388
  {...props}
209
389
  ref={forwardedRef}
210
- className={classNames('rt-SidebarInset', className)}
390
+ className={classNames('rt-SidebarMenu', className)}
211
391
  />
212
392
  )
213
393
  );
214
- SidebarInset.displayName = 'Sidebar.Inset';
394
+ SidebarMenu.displayName = 'Sidebar.Menu';
215
395
 
216
- // Create sidebar-specific menu components that don't require DropdownMenu context
217
- interface SidebarLabelProps extends React.ComponentPropsWithoutRef<'div'> {}
396
+ interface SidebarMenuItemProps extends React.ComponentPropsWithoutRef<'li'> {}
218
397
 
219
- const SidebarLabel = React.forwardRef<HTMLDivElement, SidebarLabelProps>(
220
- ({ className, ...props }, ref) => (
221
- <div
222
- ref={ref}
223
- className={classNames('rt-BaseMenuLabel', className)}
398
+ const SidebarMenuItem = React.forwardRef<HTMLLIElement, SidebarMenuItemProps>(
399
+ ({ className, ...props }, forwardedRef) => (
400
+ <li
224
401
  {...props}
402
+ ref={forwardedRef}
403
+ className={classNames('rt-SidebarMenuItem', className)}
225
404
  />
226
405
  )
227
406
  );
228
- SidebarLabel.displayName = 'Sidebar.Label';
407
+ SidebarMenuItem.displayName = 'Sidebar.MenuItem';
229
408
 
230
- type SidebarItemOwnProps = GetPropDefTypes<typeof baseMenuItemPropDefs>;
231
- interface SidebarItemProps
232
- extends ComponentPropsWithout<'div', RemovedProps>,
233
- SidebarItemOwnProps {}
409
+ interface SidebarMenuButtonProps extends React.ComponentPropsWithoutRef<'button'> {
410
+ asChild?: boolean;
411
+ isActive?: boolean;
412
+ }
234
413
 
235
- const SidebarItem = React.forwardRef<HTMLDivElement, SidebarItemProps>(
236
- (props, ref) => {
237
- const {
238
- className,
239
- children,
240
- color = baseMenuItemPropDefs.color.default,
241
- shortcut,
242
- asChild = false,
243
- onMouseEnter,
244
- onMouseLeave,
245
- onFocus,
246
- onBlur,
247
- ...itemProps
248
- } = props;
249
-
250
- const [highlighted, setHighlighted] = React.useState(false);
251
-
252
- const handleMouseEnter = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
253
- setHighlighted(true);
254
- onMouseEnter?.(e);
255
- }, [onMouseEnter]);
256
-
257
- const handleMouseLeave = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
258
- setHighlighted(false);
259
- onMouseLeave?.(e);
260
- }, [onMouseLeave]);
261
-
262
- const handleFocus = React.useCallback((e: React.FocusEvent<HTMLDivElement>) => {
263
- setHighlighted(true);
264
- onFocus?.(e);
265
- }, [onFocus]);
266
-
267
- const handleBlur = React.useCallback((e: React.FocusEvent<HTMLDivElement>) => {
268
- setHighlighted(false);
269
- onBlur?.(e);
270
- }, [onBlur]);
271
-
272
- if (asChild) {
273
- return (
274
- <Slot.Root
275
- ref={ref}
276
- data-accent-color={color}
277
- data-highlighted={highlighted || undefined}
278
- className={classNames('rt-reset', 'rt-BaseMenuItem', className)}
279
- onMouseEnter={handleMouseEnter}
280
- onMouseLeave={handleMouseLeave}
281
- onFocus={handleFocus}
282
- onBlur={handleBlur}
283
- {...itemProps}
284
- >
285
- {children}
286
- </Slot.Root>
287
- );
288
- }
414
+ const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(
415
+ ({ asChild = false, isActive = false, className, onMouseEnter, onMouseLeave, ...props }, forwardedRef) => {
416
+ const [isHighlighted, setIsHighlighted] = React.useState(false);
417
+ const Comp = asChild ? Slot : 'button';
289
418
 
290
419
  return (
291
- <div
292
- ref={ref}
293
- data-accent-color={color}
294
- data-highlighted={highlighted || undefined}
295
- className={classNames('rt-reset', 'rt-BaseMenuItem', className)}
296
- onMouseEnter={handleMouseEnter}
297
- onMouseLeave={handleMouseLeave}
298
- onFocus={handleFocus}
299
- onBlur={handleBlur}
300
- tabIndex={0}
301
- role="menuitem"
302
- {...itemProps}
303
- >
304
- <Slot.Slottable>{children}</Slot.Slottable>
305
- {shortcut && <div className="rt-BaseMenuShortcut">{shortcut}</div>}
420
+ <Comp
421
+ {...props}
422
+ ref={forwardedRef}
423
+ className={classNames(
424
+ 'rt-reset',
425
+ 'rt-SidebarMenuButton',
426
+ className
427
+ )}
428
+ data-active={isActive || undefined}
429
+ data-highlighted={isHighlighted || undefined}
430
+ onMouseEnter={(event) => {
431
+ setIsHighlighted(true);
432
+ onMouseEnter?.(event);
433
+ }}
434
+ onMouseLeave={(event) => {
435
+ setIsHighlighted(false);
436
+ onMouseLeave?.(event);
437
+ }}
438
+ />
439
+ );
440
+ }
441
+ );
442
+ SidebarMenuButton.displayName = 'Sidebar.MenuButton';
443
+
444
+ // Sub-menu components using Radix Accordion
445
+ interface SidebarMenuSubProps extends React.ComponentPropsWithoutRef<'div'> {
446
+ defaultOpen?: boolean;
447
+ }
448
+
449
+ const SidebarMenuSub = React.forwardRef<HTMLDivElement, SidebarMenuSubProps>(
450
+ ({ defaultOpen = false, children, ...props }, forwardedRef) => {
451
+ return (
452
+ <div {...props} ref={forwardedRef}>
453
+ <Accordion.Root
454
+ type="single"
455
+ collapsible
456
+ defaultValue={defaultOpen ? 'item' : undefined}
457
+ >
458
+ <Accordion.Item value="item">
459
+ {children}
460
+ </Accordion.Item>
461
+ </Accordion.Root>
306
462
  </div>
307
463
  );
308
464
  }
309
465
  );
310
- SidebarItem.displayName = 'Sidebar.Item';
466
+ SidebarMenuSub.displayName = 'Sidebar.MenuSub';
467
+
468
+ interface SidebarMenuSubTriggerProps extends React.ComponentPropsWithoutRef<typeof Accordion.Trigger> {
469
+ asChild?: boolean;
470
+ }
471
+
472
+ const SidebarMenuSubTrigger = React.forwardRef<
473
+ React.ElementRef<typeof Accordion.Trigger>,
474
+ SidebarMenuSubTriggerProps
475
+ >(({ asChild = false, className, children, onMouseEnter, onMouseLeave, ...props }, forwardedRef) => {
476
+ const [isHighlighted, setIsHighlighted] = React.useState(false);
477
+
478
+ return (
479
+ <Accordion.Header asChild>
480
+ <div>
481
+ <Accordion.Trigger
482
+ {...props}
483
+ ref={forwardedRef}
484
+ asChild={asChild}
485
+ className={classNames(
486
+ 'rt-reset',
487
+ 'rt-SidebarMenuButton',
488
+ 'rt-SidebarMenuSubTrigger',
489
+ className
490
+ )}
491
+ data-highlighted={isHighlighted || undefined}
492
+ onMouseEnter={(event) => {
493
+ setIsHighlighted(true);
494
+ onMouseEnter?.(event);
495
+ }}
496
+ onMouseLeave={(event) => {
497
+ setIsHighlighted(false);
498
+ onMouseLeave?.(event);
499
+ }}
500
+ >
501
+ {asChild ? (
502
+ children
503
+ ) : (
504
+ <>
505
+ {children}
506
+ <ThickChevronRightIcon
507
+ className={classNames(
508
+ 'rt-BaseMenuSubTriggerIcon',
509
+ 'rt-SidebarMenuSubTriggerIcon'
510
+ )}
511
+ />
512
+ </>
513
+ )}
514
+ </Accordion.Trigger>
515
+ </div>
516
+ </Accordion.Header>
517
+ );
518
+ });
519
+ SidebarMenuSubTrigger.displayName = 'Sidebar.MenuSubTrigger';
520
+
521
+ interface SidebarMenuSubContentProps extends React.ComponentPropsWithoutRef<typeof Accordion.Content> {}
522
+
523
+ const SidebarMenuSubContent = React.forwardRef<
524
+ React.ElementRef<typeof Accordion.Content>,
525
+ SidebarMenuSubContentProps
526
+ >(({ className, children, ...props }, forwardedRef) => {
527
+ return (
528
+ <Accordion.Content
529
+ {...props}
530
+ ref={forwardedRef}
531
+ className={classNames('rt-SidebarMenuSubContent', className)}
532
+ >
533
+ <div className="rt-SidebarMenuSubList">
534
+ {children}
535
+ </div>
536
+ </Accordion.Content>
537
+ );
538
+ });
539
+ SidebarMenuSubContent.displayName = 'Sidebar.MenuSubContent';
311
540
 
541
+ // Group components
312
542
  interface SidebarGroupProps extends React.ComponentPropsWithoutRef<'div'> {}
313
543
 
314
544
  const SidebarGroup = React.forwardRef<HTMLDivElement, SidebarGroupProps>(
315
- ({ className, ...props }, ref) => (
545
+ ({ className, ...props }, forwardedRef) => (
316
546
  <div
317
- ref={ref}
318
- className={classNames('rt-BaseMenuGroup', className)}
319
547
  {...props}
548
+ ref={forwardedRef}
549
+ className={classNames('rt-SidebarGroup', className)}
320
550
  />
321
551
  )
322
552
  );
323
553
  SidebarGroup.displayName = 'Sidebar.Group';
324
554
 
325
- interface SidebarSeparatorProps extends React.ComponentPropsWithoutRef<'div'> {}
326
-
327
- const SidebarSeparator = React.forwardRef<HTMLDivElement, SidebarSeparatorProps>(
328
- ({ className, ..._props }, ref) => (
329
- <Separator
330
- ref={ref}
331
- className={classNames('rt-BaseMenuSeparator', className)}
332
- />
333
- )
334
- );
335
- SidebarSeparator.displayName = 'Sidebar.Separator';
336
-
337
- // Sidebar checkbox item with proper prop filtering
338
- interface SidebarCheckboxItemProps extends React.ComponentPropsWithoutRef<'div'> {
339
- checked?: boolean;
340
- onCheckedChange?: (checked: boolean) => void;
341
- color?: string;
342
- shortcut?: string;
555
+ interface SidebarGroupLabelProps extends React.ComponentPropsWithoutRef<'div'> {
556
+ asChild?: boolean;
343
557
  }
344
558
 
345
- const SidebarCheckboxItem = React.forwardRef<HTMLDivElement, SidebarCheckboxItemProps>(
346
- ({
347
- className,
348
- checked,
349
- onCheckedChange,
350
- children,
351
- color,
352
- shortcut,
353
- onMouseEnter,
354
- onMouseLeave,
355
- onFocus,
356
- onBlur,
357
- onClick,
358
- ...props
359
- }, ref) => {
360
- const [highlighted, setHighlighted] = React.useState(false);
361
-
362
- const handleMouseEnter = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
363
- setHighlighted(true);
364
- onMouseEnter?.(e);
365
- }, [onMouseEnter]);
366
-
367
- const handleMouseLeave = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
368
- setHighlighted(false);
369
- onMouseLeave?.(e);
370
- }, [onMouseLeave]);
371
-
372
- const handleFocus = React.useCallback((e: React.FocusEvent<HTMLDivElement>) => {
373
- setHighlighted(true);
374
- onFocus?.(e);
375
- }, [onFocus]);
376
-
377
- const handleBlur = React.useCallback((e: React.FocusEvent<HTMLDivElement>) => {
378
- setHighlighted(false);
379
- onBlur?.(e);
380
- }, [onBlur]);
381
-
382
- const handleClick = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
383
- onCheckedChange?.(!checked);
384
- onClick?.(e);
385
- }, [checked, onCheckedChange, onClick]);
559
+ const SidebarGroupLabel = React.forwardRef<HTMLDivElement, SidebarGroupLabelProps>(
560
+ ({ asChild = false, className, ...props }, forwardedRef) => {
561
+ const Comp = asChild ? Slot : 'div';
386
562
 
387
563
  return (
388
- <div
389
- ref={ref}
390
- data-accent-color={color}
391
- data-highlighted={highlighted || undefined}
392
- className={classNames(
393
- 'rt-reset',
394
- 'rt-BaseMenuItem',
395
- 'rt-BaseMenuCheckboxItem',
396
- className
397
- )}
398
- onClick={handleClick}
399
- onKeyDown={(e) => {
400
- if (e.key === 'Enter' || e.key === ' ') {
401
- e.preventDefault();
402
- handleClick(e as any);
403
- }
404
- }}
405
- onMouseEnter={handleMouseEnter}
406
- onMouseLeave={handleMouseLeave}
407
- onFocus={handleFocus}
408
- onBlur={handleBlur}
409
- tabIndex={0}
410
- role="menuitemcheckbox"
411
- aria-checked={checked}
564
+ <Comp
412
565
  {...props}
413
- >
414
- <Slot.Slottable>{children}</Slot.Slottable>
415
- {checked && <div className="rt-BaseMenuItemIndicator">✓</div>}
416
- {shortcut && <div className="rt-BaseMenuShortcut">{shortcut}</div>}
417
- </div>
566
+ ref={forwardedRef}
567
+ className={classNames('rt-SidebarGroupLabel', className)}
568
+ />
418
569
  );
419
570
  }
420
571
  );
421
- SidebarCheckboxItem.displayName = 'Sidebar.CheckboxItem';
572
+ SidebarGroupLabel.displayName = 'Sidebar.GroupLabel';
422
573
 
423
- // Sidebar radio group with proper prop filtering
424
- interface SidebarRadioGroupProps extends React.ComponentPropsWithoutRef<'div'> {
425
- value?: string;
426
- onValueChange?: (value: string) => void;
427
- }
574
+ interface SidebarGroupContentProps extends React.ComponentPropsWithoutRef<'div'> {}
428
575
 
429
- const SidebarRadioGroup = React.forwardRef<HTMLDivElement, SidebarRadioGroupProps>(
430
- ({ className, value, onValueChange, children, ...props }, ref) => (
576
+ const SidebarGroupContent = React.forwardRef<HTMLDivElement, SidebarGroupContentProps>(
577
+ ({ className, ...props }, forwardedRef) => (
431
578
  <div
432
- ref={ref}
433
- className={classNames('rt-BaseMenuGroup', className)}
434
579
  {...props}
435
- >
436
- {children}
437
- </div>
580
+ ref={forwardedRef}
581
+ className={classNames('rt-SidebarGroupContent', className)}
582
+ />
438
583
  )
439
584
  );
440
- SidebarRadioGroup.displayName = 'Sidebar.RadioGroup';
441
-
442
- // Sidebar radio item with proper prop filtering
443
- interface SidebarRadioItemProps extends React.ComponentPropsWithoutRef<'div'> {
444
- value?: string;
445
- color?: string;
446
- shortcut?: string;
447
- }
585
+ SidebarGroupContent.displayName = 'Sidebar.GroupContent';
448
586
 
449
- const SidebarRadioItem = React.forwardRef<HTMLDivElement, SidebarRadioItemProps>(
450
- ({
451
- className,
452
- value,
453
- children,
454
- color,
455
- shortcut,
456
- onMouseEnter,
457
- onMouseLeave,
458
- onFocus,
459
- onBlur,
460
- ...props
461
- }, ref) => {
462
- const [highlighted, setHighlighted] = React.useState(false);
463
-
464
- const handleMouseEnter = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
465
- setHighlighted(true);
466
- onMouseEnter?.(e);
467
- }, [onMouseEnter]);
468
-
469
- const handleMouseLeave = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
470
- setHighlighted(false);
471
- onMouseLeave?.(e);
472
- }, [onMouseLeave]);
473
-
474
- const handleFocus = React.useCallback((e: React.FocusEvent<HTMLDivElement>) => {
475
- setHighlighted(true);
476
- onFocus?.(e);
477
- }, [onFocus]);
478
-
479
- const handleBlur = React.useCallback((e: React.FocusEvent<HTMLDivElement>) => {
480
- setHighlighted(false);
481
- onBlur?.(e);
482
- }, [onBlur]);
483
-
484
- return (
485
- <div
486
- ref={ref}
487
- data-accent-color={color}
488
- data-highlighted={highlighted || undefined}
489
- className={classNames('rt-reset', 'rt-BaseMenuItem', className)}
490
- onMouseEnter={handleMouseEnter}
491
- onMouseLeave={handleMouseLeave}
492
- onFocus={handleFocus}
493
- onBlur={handleBlur}
494
- tabIndex={0}
495
- role="menuitemradio"
496
- aria-checked={false}
497
- {...props}
498
- >
499
- <Slot.Slottable>{children}</Slot.Slottable>
500
- {shortcut && <div className="rt-BaseMenuShortcut">{shortcut}</div>}
501
- </div>
502
- );
503
- }
504
- );
505
- SidebarRadioItem.displayName = 'Sidebar.RadioItem';
506
-
507
- // Export all components
587
+ // Export all components following shadcn's pattern
508
588
  export {
509
589
  SidebarProvider as Provider,
510
- SidebarRoot as Root,
590
+ Sidebar as Root,
511
591
  SidebarContent as Content,
512
592
  SidebarHeader as Header,
513
593
  SidebarFooter as Footer,
514
594
  SidebarTrigger as Trigger,
515
- SidebarInset as Inset,
516
- // Re-export DropdownMenu components as sidebar menu components
517
- SidebarLabel as Label,
518
- SidebarItem as Item,
519
- SidebarGroup as Group,
520
595
  SidebarSeparator as Separator,
521
- SidebarCheckboxItem as CheckboxItem,
522
- SidebarRadioGroup as RadioGroup,
523
- SidebarRadioItem as RadioItem,
596
+ SidebarMenu as Menu,
597
+ SidebarMenuItem as MenuItem,
598
+ SidebarMenuButton as MenuButton,
599
+ SidebarMenuSub as MenuSub,
600
+ SidebarMenuSubTrigger as MenuSubTrigger,
601
+ SidebarMenuSubContent as MenuSubContent,
602
+ SidebarGroup as Group,
603
+ SidebarGroupLabel as GroupLabel,
604
+ SidebarGroupContent as GroupContent,
524
605
  // Export hook
525
606
  useSidebar,
526
607
  };
527
608
 
609
+ /**
610
+ * Enhanced Sidebar Header and Footer Usage Examples:
611
+ *
612
+ * 1. Simple default container (backwards compatible):
613
+ * <Sidebar.Header>
614
+ * <Logo />
615
+ * <span>App Name</span>
616
+ * </Sidebar.Header>
617
+ *
618
+ * 2. Custom flex layout:
619
+ * <Sidebar.Header className="rt-justify-between rt-gap-3">
620
+ * <Logo />
621
+ * <Sidebar.MenuButton>
622
+ * <SettingsIcon />
623
+ * </Sidebar.MenuButton>
624
+ * </Sidebar.Header>
625
+ *
626
+ * 3. Column layout for multiple rows:
627
+ * <Sidebar.Header className="rt-flex-col rt-gap-2" asContainer={false}>
628
+ * <div className="rt-flex rt-items-center rt-gap-2">
629
+ * <Logo />
630
+ * <span>App Name</span>
631
+ * </div>
632
+ * <Sidebar.MenuButton>
633
+ * <UserAvatar />
634
+ * <span>John Doe</span>
635
+ * </Sidebar.MenuButton>
636
+ * </Sidebar.Header>
637
+ *
638
+ * 4. Interactive footer with menu button:
639
+ * <Sidebar.Footer>
640
+ * <Sidebar.MenuButton>
641
+ * <UserIcon />
642
+ * <span>Settings</span>
643
+ * <ChevronUpIcon />
644
+ * </Sidebar.MenuButton>
645
+ * </Sidebar.Footer>
646
+ *
647
+ * 5. Custom footer layout:
648
+ * <Sidebar.Footer className="rt-justify-between">
649
+ * <span>v1.0.0</span>
650
+ * <Sidebar.MenuButton>
651
+ * <HelpIcon />
652
+ * </Sidebar.MenuButton>
653
+ * </Sidebar.Footer>
654
+ *
655
+ * Available utility classes:
656
+ * - Layout: rt-flex-row, rt-flex-col
657
+ * - Alignment: rt-items-center, rt-items-start, rt-items-end
658
+ * - Justification: rt-justify-between, rt-justify-center, rt-justify-start, rt-justify-end
659
+ * - Gap: rt-gap-1, rt-gap-2, rt-gap-3, rt-gap-4
660
+ */
661
+
528
662
  export type {
529
663
  SidebarProviderProps as ProviderProps,
530
- SidebarRootProps as RootProps,
664
+ SidebarProps as RootProps,
531
665
  SidebarContentProps as ContentProps,
532
666
  SidebarHeaderProps as HeaderProps,
533
667
  SidebarFooterProps as FooterProps,
534
668
  SidebarTriggerProps as TriggerProps,
535
- SidebarInsetProps as InsetProps,
536
669
  };