@kushagradhawan/kookie-ui 0.1.11 → 0.1.13
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 +164 -0
- package/dist/cjs/components/card.props.d.ts +1 -1
- package/dist/cjs/components/card.props.js +1 -1
- package/dist/cjs/components/card.props.js.map +2 -2
- package/dist/cjs/components/image.js.map +3 -3
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.d.ts.map +1 -1
- package/dist/cjs/components/index.js +1 -1
- package/dist/cjs/components/index.js.map +3 -3
- package/dist/cjs/components/sidebar.d.ts +71 -0
- package/dist/cjs/components/sidebar.d.ts.map +1 -0
- package/dist/cjs/components/sidebar.js +2 -0
- package/dist/cjs/components/sidebar.js.map +7 -0
- package/dist/cjs/components/sidebar.props.d.ts +48 -0
- package/dist/cjs/components/sidebar.props.d.ts.map +1 -0
- package/dist/cjs/components/sidebar.props.js +2 -0
- package/dist/cjs/components/sidebar.props.js.map +7 -0
- package/dist/esm/components/card.props.d.ts +1 -1
- package/dist/esm/components/card.props.js +1 -1
- package/dist/esm/components/card.props.js.map +2 -2
- package/dist/esm/components/image.js.map +3 -3
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/components/index.js +1 -1
- package/dist/esm/components/index.js.map +3 -3
- package/dist/esm/components/sidebar.d.ts +71 -0
- package/dist/esm/components/sidebar.d.ts.map +1 -0
- package/dist/esm/components/sidebar.js +2 -0
- package/dist/esm/components/sidebar.js.map +7 -0
- package/dist/esm/components/sidebar.props.d.ts +48 -0
- package/dist/esm/components/sidebar.props.d.ts.map +1 -0
- package/dist/esm/components/sidebar.props.js +2 -0
- package/dist/esm/components/sidebar.props.js.map +7 -0
- package/package.json +2 -2
- package/src/components/card.css +46 -0
- package/src/components/card.props.tsx +1 -1
- package/src/components/image.tsx +1 -1
- package/src/components/index.css +1 -0
- package/src/components/index.tsx +1 -0
- package/src/components/sidebar.css +132 -0
- package/src/components/sidebar.props.tsx +38 -0
- package/src/components/sidebar.tsx +536 -0
- package/styles.css +164 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* Sidebar Root Container */
|
|
2
|
+
.rt-SidebarRoot {
|
|
3
|
+
--sidebar-width: 16rem;
|
|
4
|
+
--sidebar-width-icon: 3rem;
|
|
5
|
+
|
|
6
|
+
position: fixed;
|
|
7
|
+
top: 0;
|
|
8
|
+
bottom: 0;
|
|
9
|
+
left: 0;
|
|
10
|
+
z-index: 30;
|
|
11
|
+
width: var(--sidebar-width);
|
|
12
|
+
transition: transform 0.2s ease, width 0.2s ease;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Sidebar Container */
|
|
16
|
+
.rt-SidebarContainer {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
height: 100%;
|
|
20
|
+
width: 100%;
|
|
21
|
+
background-color: var(--color-panel-solid);
|
|
22
|
+
border-right: 1px solid var(--gray-a5);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Sidebar Header */
|
|
26
|
+
.rt-SidebarHeader {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
padding: var(--space-3);
|
|
30
|
+
border-bottom: 1px solid var(--gray-a5);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Sidebar Content */
|
|
34
|
+
.rt-SidebarContent {
|
|
35
|
+
flex: 1;
|
|
36
|
+
padding: var(--space-2);
|
|
37
|
+
min-height: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Sidebar Footer */
|
|
41
|
+
.rt-SidebarFooter {
|
|
42
|
+
padding: var(--space-3);
|
|
43
|
+
border-top: 1px solid var(--gray-a5);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Sidebar Inset (main content area) */
|
|
47
|
+
.rt-SidebarInset {
|
|
48
|
+
flex: 1;
|
|
49
|
+
margin-left: var(--sidebar-width);
|
|
50
|
+
transition: margin-left 0.2s ease;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/***************************************************************************************************
|
|
54
|
+
* *
|
|
55
|
+
* SIZES *
|
|
56
|
+
* *
|
|
57
|
+
***************************************************************************************************/
|
|
58
|
+
|
|
59
|
+
@breakpoints {
|
|
60
|
+
.rt-SidebarRoot {
|
|
61
|
+
&:where(.rt-r-size-1) {
|
|
62
|
+
--sidebar-width: 14rem;
|
|
63
|
+
--sidebar-width-icon: 2.5rem;
|
|
64
|
+
}
|
|
65
|
+
&:where(.rt-r-size-3) {
|
|
66
|
+
--sidebar-width: 18rem;
|
|
67
|
+
--sidebar-width-icon: 3.5rem;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Size-specific padding adjustments */
|
|
73
|
+
.rt-SidebarHeader {
|
|
74
|
+
&:where(.rt-r-size-1) {
|
|
75
|
+
padding: var(--space-2);
|
|
76
|
+
}
|
|
77
|
+
&:where(.rt-r-size-3) {
|
|
78
|
+
padding: var(--space-4);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.rt-SidebarFooter {
|
|
83
|
+
&:where(.rt-r-size-1) {
|
|
84
|
+
padding: var(--space-2);
|
|
85
|
+
}
|
|
86
|
+
&:where(.rt-r-size-3) {
|
|
87
|
+
padding: var(--space-4);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.rt-SidebarContent {
|
|
92
|
+
&:where(.rt-r-size-1) {
|
|
93
|
+
padding: var(--space-1);
|
|
94
|
+
}
|
|
95
|
+
&:where(.rt-r-size-3) {
|
|
96
|
+
padding: var(--space-3);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/***************************************************************************************************
|
|
101
|
+
* *
|
|
102
|
+
* VARIANTS *
|
|
103
|
+
* *
|
|
104
|
+
***************************************************************************************************/
|
|
105
|
+
|
|
106
|
+
.rt-SidebarContainer {
|
|
107
|
+
&:where(.rt-variant-classic) {
|
|
108
|
+
background-color: var(--color-surface);
|
|
109
|
+
border: 1px solid var(--gray-a5);
|
|
110
|
+
}
|
|
111
|
+
&:where(.rt-variant-ghost) {
|
|
112
|
+
background-color: transparent;
|
|
113
|
+
border: none;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Ensure menu items inherit proper theming and spacing for sidebar */
|
|
118
|
+
.rt-SidebarContent :where(.rt-BaseMenuItem) {
|
|
119
|
+
border-radius: var(--radius-2);
|
|
120
|
+
margin-bottom: var(--space-1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Responsive behavior */
|
|
124
|
+
@media (max-width: 768px) {
|
|
125
|
+
.rt-SidebarRoot {
|
|
126
|
+
display: none;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.rt-SidebarInset {
|
|
130
|
+
margin-left: 0;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { asChildPropDef } from '../props/as-child.prop.js';
|
|
2
|
+
import { accentColorPropDef } from '../props/color.prop.js';
|
|
3
|
+
import { highContrastPropDef } from '../props/high-contrast.prop.js';
|
|
4
|
+
|
|
5
|
+
import type { PropDef } from '../props/prop-def.js';
|
|
6
|
+
|
|
7
|
+
// Re-export base menu props for sidebar menu components
|
|
8
|
+
export {
|
|
9
|
+
baseMenuContentPropDefs as sidebarContentPropDefs,
|
|
10
|
+
baseMenuItemPropDefs as sidebarItemPropDefs,
|
|
11
|
+
baseMenuCheckboxItemPropDefs as sidebarCheckboxItemPropDefs,
|
|
12
|
+
baseMenuRadioItemPropDefs as sidebarRadioItemPropDefs,
|
|
13
|
+
} from './_internal/base-menu.props.js';
|
|
14
|
+
|
|
15
|
+
// Sidebar container props
|
|
16
|
+
const sizes = ['1', '2', '3'] as const;
|
|
17
|
+
const variants = ['classic', 'surface', 'ghost'] as const;
|
|
18
|
+
const sides = ['left', 'right'] as const;
|
|
19
|
+
const collapsibleModes = ['icon', 'offcanvas', 'none'] as const;
|
|
20
|
+
|
|
21
|
+
const sidebarPropDefs = {
|
|
22
|
+
...asChildPropDef,
|
|
23
|
+
size: { type: 'enum', className: 'rt-r-size', values: sizes, default: '2', responsive: true },
|
|
24
|
+
variant: { type: 'enum', className: 'rt-variant', values: variants, default: 'surface' },
|
|
25
|
+
side: { type: 'enum', className: 'rt-side', values: sides, default: 'left' },
|
|
26
|
+
collapsible: { type: 'enum', className: 'rt-collapsible', values: collapsibleModes, default: 'icon' },
|
|
27
|
+
floating: { type: 'boolean', className: 'rt-floating', default: false },
|
|
28
|
+
...accentColorPropDef,
|
|
29
|
+
...highContrastPropDef,
|
|
30
|
+
} satisfies {
|
|
31
|
+
size: PropDef<(typeof sizes)[number]>;
|
|
32
|
+
variant: PropDef<(typeof variants)[number]>;
|
|
33
|
+
side: PropDef<(typeof sides)[number]>;
|
|
34
|
+
collapsible: PropDef<(typeof collapsibleModes)[number]>;
|
|
35
|
+
floating: PropDef<boolean>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export { sidebarPropDefs };
|
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
|
|
6
|
+
import { sidebarPropDefs } from './sidebar.props.js';
|
|
7
|
+
|
|
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';
|
|
14
|
+
|
|
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
|
|
24
|
+
type SidebarContextProps = {
|
|
25
|
+
open: boolean;
|
|
26
|
+
setOpen: (open: boolean) => void;
|
|
27
|
+
collapsible: 'icon' | 'offcanvas' | 'none';
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const SidebarContext = React.createContext<SidebarContextProps | null>(null);
|
|
31
|
+
|
|
32
|
+
function useSidebar() {
|
|
33
|
+
const context = React.useContext(SidebarContext);
|
|
34
|
+
if (!context) {
|
|
35
|
+
throw new Error('useSidebar must be used within a Sidebar.Provider');
|
|
36
|
+
}
|
|
37
|
+
return context;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Provider component
|
|
41
|
+
interface SidebarProviderProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
42
|
+
defaultOpen?: boolean;
|
|
43
|
+
open?: boolean;
|
|
44
|
+
onOpenChange?: (open: boolean) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const SidebarProvider = React.forwardRef<HTMLDivElement, SidebarProviderProps>(
|
|
48
|
+
({ defaultOpen = true, open: openProp, onOpenChange: setOpenProp, children, ...props }, forwardedRef) => {
|
|
49
|
+
// Internal state for uncontrolled mode
|
|
50
|
+
const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
|
|
51
|
+
|
|
52
|
+
// Use controlled state if provided, otherwise internal state
|
|
53
|
+
const open = openProp ?? internalOpen;
|
|
54
|
+
|
|
55
|
+
const setOpen = React.useCallback((value: boolean) => {
|
|
56
|
+
if (setOpenProp) {
|
|
57
|
+
setOpenProp(value); // Controlled mode
|
|
58
|
+
} else {
|
|
59
|
+
setInternalOpen(value); // Uncontrolled mode
|
|
60
|
+
}
|
|
61
|
+
}, [setOpenProp]);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div {...props} ref={forwardedRef}>
|
|
65
|
+
<SidebarContext.Provider value={{ open, setOpen, collapsible: 'icon' }}>
|
|
66
|
+
{children}
|
|
67
|
+
</SidebarContext.Provider>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
SidebarProvider.displayName = 'Sidebar.Provider';
|
|
73
|
+
|
|
74
|
+
// Root sidebar container
|
|
75
|
+
type SidebarRootOwnProps = GetPropDefTypes<typeof sidebarPropDefs>;
|
|
76
|
+
type SidebarRootElement = HTMLDivElement;
|
|
77
|
+
interface SidebarRootProps
|
|
78
|
+
extends ComponentPropsWithout<'div', RemovedProps>,
|
|
79
|
+
SidebarRootOwnProps {}
|
|
80
|
+
|
|
81
|
+
const SidebarRoot = React.forwardRef<SidebarRootElement, SidebarRootProps>(
|
|
82
|
+
(props, forwardedRef) => {
|
|
83
|
+
const themeContext = useThemeContext();
|
|
84
|
+
const { open } = useSidebar();
|
|
85
|
+
|
|
86
|
+
const {
|
|
87
|
+
size = sidebarPropDefs.size.default,
|
|
88
|
+
variant = sidebarPropDefs.variant.default,
|
|
89
|
+
side = sidebarPropDefs.side.default,
|
|
90
|
+
collapsible = sidebarPropDefs.collapsible.default,
|
|
91
|
+
floating: _floating = sidebarPropDefs.floating.default,
|
|
92
|
+
color,
|
|
93
|
+
highContrast = sidebarPropDefs.highContrast.default,
|
|
94
|
+
} = props;
|
|
95
|
+
|
|
96
|
+
const { className, children, ...rootProps } = extractProps(props, sidebarPropDefs);
|
|
97
|
+
const resolvedColor = color || themeContext.accentColor;
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
{...rootProps}
|
|
102
|
+
ref={forwardedRef}
|
|
103
|
+
data-accent-color={resolvedColor}
|
|
104
|
+
data-state={open ? 'expanded' : 'collapsed'}
|
|
105
|
+
data-side={side}
|
|
106
|
+
data-collapsible={collapsible}
|
|
107
|
+
className={classNames(
|
|
108
|
+
'rt-SidebarRoot',
|
|
109
|
+
className
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
<Theme asChild>
|
|
113
|
+
<div
|
|
114
|
+
className={classNames('rt-SidebarContainer', `rt-variant-${variant}`, `rt-r-size-${size}`)}
|
|
115
|
+
data-accent-color={resolvedColor}
|
|
116
|
+
data-high-contrast={highContrast || undefined}
|
|
117
|
+
>
|
|
118
|
+
{children}
|
|
119
|
+
</div>
|
|
120
|
+
</Theme>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
SidebarRoot.displayName = 'Sidebar.Root';
|
|
126
|
+
|
|
127
|
+
// Sidebar content area
|
|
128
|
+
type SidebarContentElement = HTMLDivElement;
|
|
129
|
+
interface SidebarContentProps extends ComponentPropsWithout<'div', RemovedProps> {}
|
|
130
|
+
|
|
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
|
+
)
|
|
143
|
+
);
|
|
144
|
+
SidebarContent.displayName = 'Sidebar.Content';
|
|
145
|
+
|
|
146
|
+
// Sidebar header
|
|
147
|
+
type SidebarHeaderElement = HTMLDivElement;
|
|
148
|
+
interface SidebarHeaderProps extends ComponentPropsWithout<'div', RemovedProps> {}
|
|
149
|
+
|
|
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
|
+
)
|
|
158
|
+
);
|
|
159
|
+
SidebarHeader.displayName = 'Sidebar.Header';
|
|
160
|
+
|
|
161
|
+
// 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> {}
|
|
179
|
+
|
|
180
|
+
const SidebarTrigger = React.forwardRef<SidebarTriggerElement, SidebarTriggerProps>(
|
|
181
|
+
({ onClick, ...props }, forwardedRef) => {
|
|
182
|
+
const { setOpen, open } = useSidebar();
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<IconButton
|
|
186
|
+
{...props}
|
|
187
|
+
ref={forwardedRef}
|
|
188
|
+
variant="ghost"
|
|
189
|
+
onClick={(event) => {
|
|
190
|
+
onClick?.(event);
|
|
191
|
+
setOpen(!open);
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
<ChevronDownIcon />
|
|
195
|
+
</IconButton>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
SidebarTrigger.displayName = 'Sidebar.Trigger';
|
|
200
|
+
|
|
201
|
+
// Main content area (pushes to make room for sidebar)
|
|
202
|
+
type SidebarInsetElement = HTMLDivElement;
|
|
203
|
+
interface SidebarInsetProps extends ComponentPropsWithout<'main', RemovedProps> {}
|
|
204
|
+
|
|
205
|
+
const SidebarInset = React.forwardRef<SidebarInsetElement, SidebarInsetProps>(
|
|
206
|
+
({ className, ...props }, forwardedRef) => (
|
|
207
|
+
<main
|
|
208
|
+
{...props}
|
|
209
|
+
ref={forwardedRef}
|
|
210
|
+
className={classNames('rt-SidebarInset', className)}
|
|
211
|
+
/>
|
|
212
|
+
)
|
|
213
|
+
);
|
|
214
|
+
SidebarInset.displayName = 'Sidebar.Inset';
|
|
215
|
+
|
|
216
|
+
// Create sidebar-specific menu components that don't require DropdownMenu context
|
|
217
|
+
interface SidebarLabelProps extends React.ComponentPropsWithoutRef<'div'> {}
|
|
218
|
+
|
|
219
|
+
const SidebarLabel = React.forwardRef<HTMLDivElement, SidebarLabelProps>(
|
|
220
|
+
({ className, ...props }, ref) => (
|
|
221
|
+
<div
|
|
222
|
+
ref={ref}
|
|
223
|
+
className={classNames('rt-BaseMenuLabel', className)}
|
|
224
|
+
{...props}
|
|
225
|
+
/>
|
|
226
|
+
)
|
|
227
|
+
);
|
|
228
|
+
SidebarLabel.displayName = 'Sidebar.Label';
|
|
229
|
+
|
|
230
|
+
type SidebarItemOwnProps = GetPropDefTypes<typeof baseMenuItemPropDefs>;
|
|
231
|
+
interface SidebarItemProps
|
|
232
|
+
extends ComponentPropsWithout<'div', RemovedProps>,
|
|
233
|
+
SidebarItemOwnProps {}
|
|
234
|
+
|
|
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
|
+
}
|
|
289
|
+
|
|
290
|
+
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>}
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
SidebarItem.displayName = 'Sidebar.Item';
|
|
311
|
+
|
|
312
|
+
interface SidebarGroupProps extends React.ComponentPropsWithoutRef<'div'> {}
|
|
313
|
+
|
|
314
|
+
const SidebarGroup = React.forwardRef<HTMLDivElement, SidebarGroupProps>(
|
|
315
|
+
({ className, ...props }, ref) => (
|
|
316
|
+
<div
|
|
317
|
+
ref={ref}
|
|
318
|
+
className={classNames('rt-BaseMenuGroup', className)}
|
|
319
|
+
{...props}
|
|
320
|
+
/>
|
|
321
|
+
)
|
|
322
|
+
);
|
|
323
|
+
SidebarGroup.displayName = 'Sidebar.Group';
|
|
324
|
+
|
|
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;
|
|
343
|
+
}
|
|
344
|
+
|
|
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]);
|
|
386
|
+
|
|
387
|
+
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}
|
|
412
|
+
{...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>
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
SidebarCheckboxItem.displayName = 'Sidebar.CheckboxItem';
|
|
422
|
+
|
|
423
|
+
// Sidebar radio group with proper prop filtering
|
|
424
|
+
interface SidebarRadioGroupProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
425
|
+
value?: string;
|
|
426
|
+
onValueChange?: (value: string) => void;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const SidebarRadioGroup = React.forwardRef<HTMLDivElement, SidebarRadioGroupProps>(
|
|
430
|
+
({ className, value, onValueChange, children, ...props }, ref) => (
|
|
431
|
+
<div
|
|
432
|
+
ref={ref}
|
|
433
|
+
className={classNames('rt-BaseMenuGroup', className)}
|
|
434
|
+
{...props}
|
|
435
|
+
>
|
|
436
|
+
{children}
|
|
437
|
+
</div>
|
|
438
|
+
)
|
|
439
|
+
);
|
|
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
|
+
}
|
|
448
|
+
|
|
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
|
|
508
|
+
export {
|
|
509
|
+
SidebarProvider as Provider,
|
|
510
|
+
SidebarRoot as Root,
|
|
511
|
+
SidebarContent as Content,
|
|
512
|
+
SidebarHeader as Header,
|
|
513
|
+
SidebarFooter as Footer,
|
|
514
|
+
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
|
+
SidebarSeparator as Separator,
|
|
521
|
+
SidebarCheckboxItem as CheckboxItem,
|
|
522
|
+
SidebarRadioGroup as RadioGroup,
|
|
523
|
+
SidebarRadioItem as RadioItem,
|
|
524
|
+
// Export hook
|
|
525
|
+
useSidebar,
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
export type {
|
|
529
|
+
SidebarProviderProps as ProviderProps,
|
|
530
|
+
SidebarRootProps as RootProps,
|
|
531
|
+
SidebarContentProps as ContentProps,
|
|
532
|
+
SidebarHeaderProps as HeaderProps,
|
|
533
|
+
SidebarFooterProps as FooterProps,
|
|
534
|
+
SidebarTriggerProps as TriggerProps,
|
|
535
|
+
SidebarInsetProps as InsetProps,
|
|
536
|
+
};
|