@bensdev/react-sidebar 0.1.0
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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +279 -0
- package/dist/index.cjs +1323 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +262 -0
- package/dist/index.d.ts +262 -0
- package/dist/index.js +1287 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +723 -0
- package/package.json +65 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface SidebarBadge {
|
|
4
|
+
content: React.ReactNode;
|
|
5
|
+
tone?: 'default' | 'accent' | 'danger' | 'success';
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
interface SidebarItemBase {
|
|
9
|
+
/** Stable key. Falls back to `href`, then to an index-based key. */
|
|
10
|
+
id?: string;
|
|
11
|
+
label?: React.ReactNode;
|
|
12
|
+
icon?: React.ReactNode;
|
|
13
|
+
/** A number/node renders as-is; a shaped badge gets tone-based coloring. */
|
|
14
|
+
badge?: React.ReactNode | SidebarBadge;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/** Removes the item from render entirely — the standard way to do role gating. */
|
|
17
|
+
hidden?: boolean;
|
|
18
|
+
/** Overrides the tooltip text shown in collapsed mode. `false` disables it. */
|
|
19
|
+
tooltip?: React.ReactNode | false;
|
|
20
|
+
className?: string;
|
|
21
|
+
/** Arbitrary payload echoed back in render callbacks. */
|
|
22
|
+
meta?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
interface ActiveContext {
|
|
25
|
+
currentPath: string;
|
|
26
|
+
item: SidebarLinkItem | SidebarGroupItem;
|
|
27
|
+
defaultIsActive: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface SidebarLinkItem extends SidebarItemBase {
|
|
30
|
+
type?: 'link';
|
|
31
|
+
href: string;
|
|
32
|
+
/** Exact-match only, mirrors react-router's NavLink `end`. */
|
|
33
|
+
end?: boolean;
|
|
34
|
+
isActive?: boolean | ((ctx: ActiveContext) => boolean);
|
|
35
|
+
target?: React.HTMLAttributeAnchorTarget;
|
|
36
|
+
rel?: string;
|
|
37
|
+
/** Extra props forwarded to `linkComponent` (e.g. `{ replace: true }`, `{ prefetch: false }`). */
|
|
38
|
+
linkProps?: Record<string, unknown>;
|
|
39
|
+
onClick?: (e: React.MouseEvent<HTMLElement>) => void;
|
|
40
|
+
}
|
|
41
|
+
interface SidebarActionItem extends SidebarItemBase {
|
|
42
|
+
type: 'action';
|
|
43
|
+
onSelect: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
44
|
+
selected?: boolean;
|
|
45
|
+
}
|
|
46
|
+
interface SidebarGroupItem extends SidebarItemBase {
|
|
47
|
+
type: 'group';
|
|
48
|
+
items: SidebarItem[];
|
|
49
|
+
/** `false` renders a static section: label as heading, children always visible. */
|
|
50
|
+
collapsible?: boolean;
|
|
51
|
+
defaultOpen?: boolean;
|
|
52
|
+
open?: boolean;
|
|
53
|
+
onOpenChange?: (open: boolean) => void;
|
|
54
|
+
href?: string;
|
|
55
|
+
end?: boolean;
|
|
56
|
+
isActive?: boolean | ((ctx: ActiveContext) => boolean);
|
|
57
|
+
}
|
|
58
|
+
interface SidebarHeadingItem {
|
|
59
|
+
type: 'heading';
|
|
60
|
+
id?: string;
|
|
61
|
+
label: React.ReactNode;
|
|
62
|
+
hidden?: boolean;
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
interface SidebarDividerItem {
|
|
66
|
+
type: 'divider';
|
|
67
|
+
id?: string;
|
|
68
|
+
hidden?: boolean;
|
|
69
|
+
className?: string;
|
|
70
|
+
}
|
|
71
|
+
interface SidebarCustomItem {
|
|
72
|
+
type: 'custom';
|
|
73
|
+
id?: string;
|
|
74
|
+
hidden?: boolean;
|
|
75
|
+
render: (ctx: SidebarRenderContext) => React.ReactNode;
|
|
76
|
+
}
|
|
77
|
+
type SidebarItem = SidebarLinkItem | SidebarActionItem | SidebarGroupItem | SidebarHeadingItem | SidebarDividerItem | SidebarCustomItem;
|
|
78
|
+
interface SidebarRenderContext {
|
|
79
|
+
collapsed: boolean;
|
|
80
|
+
isMobile: boolean;
|
|
81
|
+
colorScheme: 'light' | 'dark';
|
|
82
|
+
currentPath: string;
|
|
83
|
+
toggleCollapsed: () => void;
|
|
84
|
+
closeMobile: () => void;
|
|
85
|
+
}
|
|
86
|
+
interface RenderLinkArgs {
|
|
87
|
+
item: SidebarLinkItem;
|
|
88
|
+
href: string;
|
|
89
|
+
isActive: boolean;
|
|
90
|
+
className: string;
|
|
91
|
+
children: React.ReactNode;
|
|
92
|
+
props: React.AnchorHTMLAttributes<HTMLAnchorElement> & Record<string, unknown>;
|
|
93
|
+
ctx: SidebarRenderContext;
|
|
94
|
+
}
|
|
95
|
+
type SidebarSlot = 'root' | 'aside' | 'header' | 'brand' | 'logo' | 'title' | 'subtitle' | 'headerBadge' | 'nav' | 'list' | 'item' | 'itemActive' | 'itemDisabled' | 'itemIcon' | 'itemLabel' | 'badge' | 'group' | 'groupTrigger' | 'groupPanel' | 'groupChevron' | 'heading' | 'divider' | 'tooltip' | 'footer' | 'user' | 'avatar' | 'userName' | 'userMeta' | 'footerAction' | 'toggle' | 'backdrop' | 'drawer' | 'drawerClose';
|
|
96
|
+
type SidebarClassNames = Partial<Record<SidebarSlot, string>>;
|
|
97
|
+
interface SidebarSlots {
|
|
98
|
+
header?: React.ReactNode | ((ctx: SidebarRenderContext) => React.ReactNode);
|
|
99
|
+
footer?: React.ReactNode | ((ctx: SidebarRenderContext) => React.ReactNode);
|
|
100
|
+
navTop?: React.ReactNode | ((ctx: SidebarRenderContext) => React.ReactNode);
|
|
101
|
+
navBottom?: React.ReactNode | ((ctx: SidebarRenderContext) => React.ReactNode);
|
|
102
|
+
collapseIcon?: (collapsed: boolean) => React.ReactNode;
|
|
103
|
+
groupChevron?: (open: boolean) => React.ReactNode;
|
|
104
|
+
closeIcon?: React.ReactNode;
|
|
105
|
+
tooltip?: (item: SidebarItem) => React.ReactNode;
|
|
106
|
+
badge?: (badge: SidebarBadge, item: SidebarItem) => React.ReactNode;
|
|
107
|
+
}
|
|
108
|
+
interface StorageAdapter {
|
|
109
|
+
getItem: (key: string) => string | null;
|
|
110
|
+
setItem: (key: string, value: string) => void;
|
|
111
|
+
subscribe?: (cb: () => void) => () => void;
|
|
112
|
+
}
|
|
113
|
+
interface SidebarBrand {
|
|
114
|
+
/** Node placed inside the default 32px logo tile (e.g. a single letter). */
|
|
115
|
+
logo?: React.ReactNode;
|
|
116
|
+
/** Replaces the logo tile entirely with a custom node (e.g. an <img>). */
|
|
117
|
+
logoNode?: React.ReactNode;
|
|
118
|
+
title?: React.ReactNode;
|
|
119
|
+
subtitle?: React.ReactNode;
|
|
120
|
+
badge?: React.ReactNode | SidebarBadge;
|
|
121
|
+
href?: string;
|
|
122
|
+
onClick?: () => void;
|
|
123
|
+
}
|
|
124
|
+
interface SidebarUser {
|
|
125
|
+
name?: React.ReactNode;
|
|
126
|
+
email?: React.ReactNode;
|
|
127
|
+
avatarUrl?: string;
|
|
128
|
+
avatar?: React.ReactNode;
|
|
129
|
+
/** Initials shown when there's no avatar and `name` isn't derivable (e.g. a ReactNode name). */
|
|
130
|
+
fallbackInitials?: string;
|
|
131
|
+
}
|
|
132
|
+
interface SidebarFooterAction {
|
|
133
|
+
icon: React.ReactNode;
|
|
134
|
+
label: string;
|
|
135
|
+
onClick: () => void;
|
|
136
|
+
}
|
|
137
|
+
type SidebarTokens = Partial<Record<string, string | number>>;
|
|
138
|
+
interface SidebarTheme {
|
|
139
|
+
light?: SidebarTokens;
|
|
140
|
+
dark?: SidebarTokens;
|
|
141
|
+
[key: string]: string | number | SidebarTokens | undefined;
|
|
142
|
+
}
|
|
143
|
+
interface SidebarHandle {
|
|
144
|
+
collapse(): void;
|
|
145
|
+
expand(): void;
|
|
146
|
+
toggleCollapsed(): void;
|
|
147
|
+
openMobile(): void;
|
|
148
|
+
closeMobile(): void;
|
|
149
|
+
element: HTMLElement | null;
|
|
150
|
+
}
|
|
151
|
+
interface SidebarProps {
|
|
152
|
+
items: SidebarItem[];
|
|
153
|
+
/** Full per-item override. Return `undefined` to fall through to the default row. */
|
|
154
|
+
renderItem?: (item: SidebarItem, ctx: SidebarRenderContext, defaultNode: React.ReactNode) => React.ReactNode | undefined;
|
|
155
|
+
slots?: SidebarSlots;
|
|
156
|
+
classNames?: SidebarClassNames;
|
|
157
|
+
currentPath?: string;
|
|
158
|
+
linkComponent?: React.ElementType;
|
|
159
|
+
hrefProp?: string;
|
|
160
|
+
renderLink?: (args: RenderLinkArgs) => React.ReactNode;
|
|
161
|
+
isItemActive?: (item: SidebarLinkItem, ctx: ActiveContext) => boolean | undefined;
|
|
162
|
+
exact?: boolean;
|
|
163
|
+
onItemClick?: (item: SidebarItem, e: React.MouseEvent) => void;
|
|
164
|
+
closeOnNavigate?: boolean;
|
|
165
|
+
collapsible?: boolean;
|
|
166
|
+
collapsed?: boolean;
|
|
167
|
+
defaultCollapsed?: boolean;
|
|
168
|
+
onCollapsedChange?: (collapsed: boolean) => void;
|
|
169
|
+
persistCollapse?: boolean | string | StorageAdapter;
|
|
170
|
+
expandOnHover?: boolean;
|
|
171
|
+
showCollapseToggle?: boolean;
|
|
172
|
+
collapsedIconAlign?: 'start' | 'center';
|
|
173
|
+
collapsedFooter?: 'avatar' | 'stack' | 'hidden';
|
|
174
|
+
openGroups?: string[];
|
|
175
|
+
defaultOpenGroups?: string[];
|
|
176
|
+
onOpenGroupsChange?: (ids: string[]) => void;
|
|
177
|
+
accordion?: boolean;
|
|
178
|
+
autoOpenActiveGroup?: boolean;
|
|
179
|
+
collapsedGroupBehavior?: 'flyout' | 'expand' | 'ignore';
|
|
180
|
+
mobileOpen?: boolean;
|
|
181
|
+
onMobileClose?: () => void;
|
|
182
|
+
onMobileOpenChange?: (open: boolean) => void;
|
|
183
|
+
breakpoint?: number | string;
|
|
184
|
+
responsive?: boolean;
|
|
185
|
+
drawerWidth?: number | string;
|
|
186
|
+
closeOnEscape?: boolean;
|
|
187
|
+
closeOnBackdropClick?: boolean;
|
|
188
|
+
trapFocus?: boolean;
|
|
189
|
+
lockScroll?: boolean;
|
|
190
|
+
restoreFocus?: boolean;
|
|
191
|
+
portalTarget?: Element | (() => Element | null) | null;
|
|
192
|
+
disablePortal?: boolean;
|
|
193
|
+
brand?: SidebarBrand | React.ReactNode | ((ctx: SidebarRenderContext) => React.ReactNode);
|
|
194
|
+
user?: SidebarUser;
|
|
195
|
+
footerAction?: SidebarFooterAction | SidebarFooterAction[] | null;
|
|
196
|
+
header?: React.ReactNode | ((ctx: SidebarRenderContext) => React.ReactNode);
|
|
197
|
+
footer?: React.ReactNode | ((ctx: SidebarRenderContext) => React.ReactNode);
|
|
198
|
+
theme?: SidebarTheme;
|
|
199
|
+
colorScheme?: 'light' | 'dark' | 'auto';
|
|
200
|
+
width?: number | string;
|
|
201
|
+
collapsedWidth?: number | string;
|
|
202
|
+
position?: 'left' | 'right';
|
|
203
|
+
bordered?: boolean;
|
|
204
|
+
tooltips?: boolean;
|
|
205
|
+
reduceMotion?: boolean | 'auto';
|
|
206
|
+
toggleStrategy?: 'absolute' | 'fixed';
|
|
207
|
+
as?: React.ElementType;
|
|
208
|
+
id?: string;
|
|
209
|
+
className?: string;
|
|
210
|
+
style?: React.CSSProperties;
|
|
211
|
+
'aria-label'?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
declare const Sidebar: React.ForwardRefExoticComponent<SidebarProps & React.RefAttributes<SidebarHandle>>;
|
|
215
|
+
|
|
216
|
+
declare function normalizePath(path: string): string;
|
|
217
|
+
/** Reproduces react-router NavLink's `end` semantics without depending on react-router. */
|
|
218
|
+
declare function defaultIsActive(href: string, currentPath: string, end?: boolean): boolean;
|
|
219
|
+
/**
|
|
220
|
+
* Resolves whether a link or group item is active, honoring (in order):
|
|
221
|
+
* 1. `item.isActive` (boolean or predicate) — always wins.
|
|
222
|
+
* 2. `isItemActive` prop, if it returns something other than `undefined`.
|
|
223
|
+
* 3. `defaultIsActive(item.href, currentPath, item.end ?? exact)`.
|
|
224
|
+
*/
|
|
225
|
+
declare function resolveIsActive(item: SidebarLinkItem | SidebarGroupItem, currentPath: string, exact: boolean, isItemActive?: (item: SidebarLinkItem, ctx: ActiveContext) => boolean | undefined): boolean;
|
|
226
|
+
|
|
227
|
+
/** Flattens a `theme` prop (with optional `light`/`dark` overrides) into inline CSS custom properties. */
|
|
228
|
+
declare function themeToStyle(theme: SidebarTheme | undefined, scheme: 'light' | 'dark'): React.CSSProperties;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* `slate` is the identity theme — it matches the stylesheet's built-in defaults exactly.
|
|
232
|
+
* Useful for explicitness (`theme={slate}`) or as a base to spread over (`{ ...slate, ... }`).
|
|
233
|
+
*/
|
|
234
|
+
declare const slate: SidebarTheme;
|
|
235
|
+
/**
|
|
236
|
+
* `greenMist` reproduces the original B-Lines sidebar palette pixel-for-pixel, including
|
|
237
|
+
* the app-wide dark-mode rule that softened `text-white` to `#e2e2e2`, and the project's
|
|
238
|
+
* custom border-radius scale (`rounded-lg` = 16px, `rounded-md` = 10px here, not Tailwind's
|
|
239
|
+
* defaults).
|
|
240
|
+
*/
|
|
241
|
+
declare const greenMist: SidebarTheme;
|
|
242
|
+
/** A neutral dark theme — same shape language, palette independent of B-Lines. */
|
|
243
|
+
declare const midnight: SidebarTheme;
|
|
244
|
+
declare const themes: {
|
|
245
|
+
readonly slate: SidebarTheme;
|
|
246
|
+
readonly greenMist: SidebarTheme;
|
|
247
|
+
readonly midnight: SidebarTheme;
|
|
248
|
+
};
|
|
249
|
+
type ThemeName = keyof typeof themes;
|
|
250
|
+
/** Identity helper — gives autocomplete/type-checking to a hand-written theme object. */
|
|
251
|
+
declare function defineTheme(theme: SidebarTheme): SidebarTheme;
|
|
252
|
+
|
|
253
|
+
interface IconProps extends React.SVGProps<SVGSVGElement> {
|
|
254
|
+
size?: number;
|
|
255
|
+
}
|
|
256
|
+
declare function ChevronLeftIcon({ size, ...rest }: IconProps): React.JSX.Element;
|
|
257
|
+
declare function ChevronRightIcon({ size, ...rest }: IconProps): React.JSX.Element;
|
|
258
|
+
declare function ChevronDownIcon({ size, ...rest }: IconProps): React.JSX.Element;
|
|
259
|
+
declare function CloseIcon({ size, ...rest }: IconProps): React.JSX.Element;
|
|
260
|
+
declare function MenuIcon({ size, ...rest }: IconProps): React.JSX.Element;
|
|
261
|
+
|
|
262
|
+
export { type ActiveContext, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, CloseIcon, MenuIcon, type RenderLinkArgs, Sidebar, type SidebarActionItem, type SidebarBadge, type SidebarBrand, type SidebarClassNames, type SidebarCustomItem, type SidebarDividerItem, type SidebarFooterAction, type SidebarGroupItem, type SidebarHandle, type SidebarHeadingItem, type SidebarItem, type SidebarLinkItem, type SidebarProps, type SidebarRenderContext, type SidebarSlot, type SidebarSlots, type SidebarTheme, type SidebarTokens, type SidebarUser, type StorageAdapter, type ThemeName, defaultIsActive, defineTheme, greenMist, midnight, normalizePath, resolveIsActive, slate, themeToStyle, themes };
|