@xsolla/xui-b2b-sidebar 0.148.0 → 0.148.1
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/README.md +280 -0
- package/package.json +6 -6
package/README.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# B2B Sidebar
|
|
2
|
+
|
|
3
|
+
A composable navigation sidebar for B2B admin surfaces. Renders an expanded panel with grouped menu items and a parallel collapsed icon strip with hover-popovers, both driven by the same `SidebarProvider` so the active route, link component, and collapsed state stay in sync.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @xsolla/xui-b2b-sidebar
|
|
9
|
+
# or
|
|
10
|
+
yarn add @xsolla/xui-b2b-sidebar
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Demo
|
|
14
|
+
|
|
15
|
+
### Basic usage
|
|
16
|
+
|
|
17
|
+
Wrap the sidebar in `SidebarProvider`, supplying the current `pathname`, the controlled `collapsed` state, and your app's link component (React Router, Next.js `Link`, etc.). The default link is a plain `<a>` if you omit `linkComponent`.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { useState } from 'react';
|
|
21
|
+
import { Home, Wallet, Settings } from '@xsolla/xui-icons-base';
|
|
22
|
+
import {
|
|
23
|
+
Sidebar,
|
|
24
|
+
SidebarProvider,
|
|
25
|
+
SidebarContent,
|
|
26
|
+
SidebarFooter,
|
|
27
|
+
SidebarGroup,
|
|
28
|
+
SidebarMenu,
|
|
29
|
+
SidebarMenuItem,
|
|
30
|
+
SidebarMenuCollapsible,
|
|
31
|
+
SidebarMenuSub,
|
|
32
|
+
SidebarTrigger,
|
|
33
|
+
} from '@xsolla/xui-b2b-sidebar';
|
|
34
|
+
|
|
35
|
+
export default function AppShell({ pathname }: { pathname: string }) {
|
|
36
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<SidebarProvider
|
|
40
|
+
collapsed={collapsed}
|
|
41
|
+
onCollapsedChange={setCollapsed}
|
|
42
|
+
pathname={pathname}
|
|
43
|
+
linkComponent={MyRouterLink}
|
|
44
|
+
>
|
|
45
|
+
<Sidebar
|
|
46
|
+
collapsedItems={mainNavItems}
|
|
47
|
+
collapsedBottomItems={workspaceNavItems}
|
|
48
|
+
>
|
|
49
|
+
<SidebarContent>
|
|
50
|
+
<SidebarGroup label="Main">
|
|
51
|
+
<SidebarMenu>
|
|
52
|
+
<SidebarMenuItem
|
|
53
|
+
to="/dashboard"
|
|
54
|
+
icon={<Home size={18} variant="line" />}
|
|
55
|
+
label="Dashboard"
|
|
56
|
+
/>
|
|
57
|
+
<SidebarMenuCollapsible
|
|
58
|
+
icon={<Wallet size={18} variant="line" />}
|
|
59
|
+
label="Finance"
|
|
60
|
+
matchPaths={['/finance']}
|
|
61
|
+
>
|
|
62
|
+
<SidebarMenuSub>
|
|
63
|
+
<SidebarMenuItem to="/finance/payouts" label="Payouts" isNested />
|
|
64
|
+
<SidebarMenuItem to="/finance/reports" label="Reports" isNested />
|
|
65
|
+
</SidebarMenuSub>
|
|
66
|
+
</SidebarMenuCollapsible>
|
|
67
|
+
</SidebarMenu>
|
|
68
|
+
</SidebarGroup>
|
|
69
|
+
<SidebarGroup label="Workspace">
|
|
70
|
+
<SidebarMenu>
|
|
71
|
+
<SidebarMenuItem
|
|
72
|
+
to="/settings"
|
|
73
|
+
icon={<Settings size={18} variant="line" />}
|
|
74
|
+
label="Settings"
|
|
75
|
+
/>
|
|
76
|
+
</SidebarMenu>
|
|
77
|
+
</SidebarGroup>
|
|
78
|
+
</SidebarContent>
|
|
79
|
+
<SidebarFooter>
|
|
80
|
+
<SidebarTrigger />
|
|
81
|
+
</SidebarFooter>
|
|
82
|
+
</Sidebar>
|
|
83
|
+
</SidebarProvider>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Custom link component
|
|
89
|
+
|
|
90
|
+
Supply your router's link via the `linkComponent` prop on `SidebarProvider`. The component receives `to`, `className`, `activeClassName`, `onClick`, `external`, `target`, `dataId`, and renders children.
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { NavLink } from 'react-router-dom';
|
|
94
|
+
import type { SidebarLinkProps } from '@xsolla/xui-b2b-sidebar';
|
|
95
|
+
|
|
96
|
+
const RouterLink: React.FC<SidebarLinkProps> = ({
|
|
97
|
+
to,
|
|
98
|
+
exact,
|
|
99
|
+
className,
|
|
100
|
+
activeClassName,
|
|
101
|
+
children,
|
|
102
|
+
onClick,
|
|
103
|
+
dataId,
|
|
104
|
+
}) => (
|
|
105
|
+
<NavLink
|
|
106
|
+
to={to ?? '#'}
|
|
107
|
+
end={exact}
|
|
108
|
+
className={className}
|
|
109
|
+
activeClassName={activeClassName}
|
|
110
|
+
data-id={dataId}
|
|
111
|
+
onClick={onClick}
|
|
112
|
+
>
|
|
113
|
+
{children}
|
|
114
|
+
</NavLink>
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
<SidebarProvider linkComponent={RouterLink} pathname={location.pathname} ...>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Auto-expanding sections
|
|
121
|
+
|
|
122
|
+
Pass `matchPaths` to `SidebarMenuCollapsible`. When the current `pathname` starts with any of the listed prefixes, the section auto-expands and stays expanded. Only one collapsible can be open at a time (Radix-style accordion), so navigating between sections collapses the others.
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
<SidebarMenuCollapsible
|
|
126
|
+
icon={<Wallet size={18} variant="line" />}
|
|
127
|
+
label="Finance"
|
|
128
|
+
matchPaths={['/finance', '/finance/payouts', '/finance/reports']}
|
|
129
|
+
>
|
|
130
|
+
<SidebarMenuSub>{/* ... */}</SidebarMenuSub>
|
|
131
|
+
</SidebarMenuCollapsible>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Item flags (pin / badge / beta / external)
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
<SidebarMenuItem to="/pinned" label="Pinned report" icon={<Pin size={18} />} isPinned showBadge />
|
|
138
|
+
<SidebarMenuItem to="/labs" label="New analytics" icon={<Graph size={18} />} beta />
|
|
139
|
+
<SidebarMenuItem
|
|
140
|
+
to="https://docs.example.com"
|
|
141
|
+
label="Documentation"
|
|
142
|
+
icon={<Layer size={18} />}
|
|
143
|
+
external
|
|
144
|
+
hasExternalIcon
|
|
145
|
+
target="_blank"
|
|
146
|
+
/>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Collapsed icon strip
|
|
150
|
+
|
|
151
|
+
Because the collapsed layout is fundamentally different (icons only, hover popovers), pass the icon-strip items separately as `collapsedItems`, `collapsedToolItems` (rendered after a spacer in the main scroll area), and `collapsedBottomItems` (rendered above the chat/toggle footer). Items with `children` open a hover popover listing the children; items without children open a single-link popover.
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
const mainNavItems: SidebarItemType[] = [
|
|
155
|
+
{ to: '/dashboard', label: 'Dashboard', icon: <Home size={18} variant="line" /> },
|
|
156
|
+
{
|
|
157
|
+
label: 'Finance',
|
|
158
|
+
icon: <Wallet size={18} variant="line" />,
|
|
159
|
+
children: [
|
|
160
|
+
{ to: '/finance/payouts', label: 'Payouts' },
|
|
161
|
+
{ to: '/finance/reports', label: 'Reports' },
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
<Sidebar
|
|
167
|
+
collapsedItems={mainNavItems}
|
|
168
|
+
collapsedBottomItems={[{ to: '/settings', label: 'Settings', icon: <Settings size={18} /> }]}
|
|
169
|
+
onChatClick={() => openSupportChat()}
|
|
170
|
+
chatBadge={hasUnreadMessages}
|
|
171
|
+
/>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## API Reference
|
|
175
|
+
|
|
176
|
+
### SidebarProvider
|
|
177
|
+
|
|
178
|
+
| Prop | Type | Default | Description |
|
|
179
|
+
| :--- | :--- | :------ | :---------- |
|
|
180
|
+
| `collapsed` | `boolean` | `false` | Controlled collapsed state. |
|
|
181
|
+
| `onCollapsedChange` | `(collapsed: boolean) => void` | - | Called when the user toggles collapsed via the trigger button. |
|
|
182
|
+
| `pathname` | `string` | `""` | Current route, used for active-state matching and auto-expand. |
|
|
183
|
+
| `linkComponent` | `ComponentType<SidebarLinkProps>` | plain `<a>` | Router link component. Receives `to`, `className`, `activeClassName`, `onClick`, `external`, `target`, `dataId`, `children`. |
|
|
184
|
+
| `children` | `ReactNode` | - | Sidebar tree. |
|
|
185
|
+
|
|
186
|
+
### Sidebar
|
|
187
|
+
|
|
188
|
+
| Prop | Type | Default | Description |
|
|
189
|
+
| :--- | :--- | :------ | :---------- |
|
|
190
|
+
| `collapsedItems` | `SidebarItemType[]` | `[]` | Top icons in the collapsed strip. |
|
|
191
|
+
| `collapsedToolItems` | `SidebarItemType[]` | `[]` | Tool icons rendered after a spacer. |
|
|
192
|
+
| `collapsedBottomItems` | `SidebarItemType[]` | `[]` | Bottom icons (e.g. Settings, Billing). |
|
|
193
|
+
| `showChat` | `boolean` | `true` | Render the chat button in the collapsed footer. |
|
|
194
|
+
| `onChatClick` | `() => void` | - | Click handler for the chat button. |
|
|
195
|
+
| `chatBadge` | `boolean` | `false` | Show an unread-indicator dot on the chat button. |
|
|
196
|
+
| `children` | `ReactNode` | - | The expanded tree (`SidebarContent`, `SidebarFooter`). |
|
|
197
|
+
|
|
198
|
+
### SidebarMenuItem
|
|
199
|
+
|
|
200
|
+
| Prop | Type | Default | Description |
|
|
201
|
+
| :--- | :--- | :------ | :---------- |
|
|
202
|
+
| `to` | `string` | - | Route URL passed through to `linkComponent`. |
|
|
203
|
+
| `label` | `ReactNode` | - | Text label. |
|
|
204
|
+
| `icon` | `ReactNode` | - | Leading icon (hidden when `isNested`). |
|
|
205
|
+
| `exact` | `boolean` | - | Exact-match flag forwarded to the link component. |
|
|
206
|
+
| `external` | `boolean` | - | Marks the link as external (sets `target`/`rel`). |
|
|
207
|
+
| `hasExternalIcon` | `boolean` | - | Shows the external-link icon at the trailing edge. |
|
|
208
|
+
| `target` | `string \| null` | - | Anchor target. |
|
|
209
|
+
| `onClick` | `(event?) => void` | - | Click handler. |
|
|
210
|
+
| `dataId` | `string` | - | `data-id` attribute for tests/analytics. |
|
|
211
|
+
| `isActive` | `(match, location) => boolean` | - | Custom active-check forwarded to the link. |
|
|
212
|
+
| `isPinned` | `boolean` | - | Renders the icon at 12px to indicate a pinned item. |
|
|
213
|
+
| `showBadge` | `boolean` | - | Shows a small alert dot next to the label. |
|
|
214
|
+
| `hasTooltip` | `boolean` | - | Truncates labels longer than 20 chars and shows the full text on hover. |
|
|
215
|
+
| `beta` | `boolean` | - | Renders a "Beta" tag at the trailing edge. |
|
|
216
|
+
| `multiLine` | `boolean` | - | Allows the label to wrap to multiple lines. |
|
|
217
|
+
| `extra` | `ReactNode` | - | Slot rendered between the icon and the label. |
|
|
218
|
+
| `isNested` | `boolean` | `false` | Set when rendered inside `SidebarMenuSub` (drops the icon, indents under the rail). |
|
|
219
|
+
|
|
220
|
+
### SidebarMenuCollapsible
|
|
221
|
+
|
|
222
|
+
| Prop | Type | Default | Description |
|
|
223
|
+
| :--- | :--- | :------ | :---------- |
|
|
224
|
+
| `icon` | `ReactNode` | - | Leading icon. |
|
|
225
|
+
| `label` | `ReactNode` | - | Section label. |
|
|
226
|
+
| `dataId` | `string` | - | `data-id` attribute. |
|
|
227
|
+
| `matchPaths` | `string[]` | `[]` | Route prefixes that auto-expand this section. |
|
|
228
|
+
| `children` | `ReactNode` | - | Nested items, typically a `SidebarMenuSub` containing `SidebarMenuItem isNested`. |
|
|
229
|
+
|
|
230
|
+
### SidebarTrigger / SidebarChatButton / SidebarFooter / SidebarGroup / SidebarMenu / SidebarMenuSub
|
|
231
|
+
|
|
232
|
+
Layout primitives:
|
|
233
|
+
|
|
234
|
+
- `<SidebarTrigger />` — collapse/expand toggle button (reads state from the provider).
|
|
235
|
+
- `<SidebarChatButton onClick badge />` — branded chat button for the footer.
|
|
236
|
+
- `<SidebarFooter>{...}</SidebarFooter>` — bordered footer row, typically holding the chat button and trigger.
|
|
237
|
+
- `<SidebarGroup label="...">` — section container with optional uppercase label.
|
|
238
|
+
- `<SidebarMenu>` — flex column wrapper around items.
|
|
239
|
+
- `<SidebarMenuSub>` — fragment used as a structural marker inside collapsibles.
|
|
240
|
+
|
|
241
|
+
### Types
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
type SidebarItemType = {
|
|
245
|
+
to?: string;
|
|
246
|
+
label: ReactNode;
|
|
247
|
+
icon?: ReactNode;
|
|
248
|
+
dataId?: string;
|
|
249
|
+
exact?: boolean;
|
|
250
|
+
external?: boolean;
|
|
251
|
+
hasExternalIcon?: boolean;
|
|
252
|
+
target?: string | null;
|
|
253
|
+
onClick?: (event?: MouseEvent) => void;
|
|
254
|
+
hasTooltip?: boolean;
|
|
255
|
+
beta?: boolean;
|
|
256
|
+
multiLine?: boolean;
|
|
257
|
+
isPinned?: boolean;
|
|
258
|
+
showBadge?: boolean;
|
|
259
|
+
isActive?: (match: unknown, location: { pathname: string }) => boolean;
|
|
260
|
+
children?: SidebarItemType[];
|
|
261
|
+
privileges?: string[];
|
|
262
|
+
visibility?: string;
|
|
263
|
+
disallowedIntegrationTypes?: string[];
|
|
264
|
+
};
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Behaviour
|
|
268
|
+
|
|
269
|
+
- Expanded width is 280px, collapsed width is 48px; the wrapper animates `width` over 250ms and cross-fades the two views via opacity.
|
|
270
|
+
- Only one `SidebarMenuCollapsible` is open at a time; the section auto-expands when `pathname` matches one of `matchPaths`.
|
|
271
|
+
- Hover popovers in collapsed mode are rendered into a portal on `document.body` and positioned with viewport-clamped `position: fixed` math.
|
|
272
|
+
- Custom scrollbar styling on the expanded scroll area; thumb fades in on sidebar hover.
|
|
273
|
+
- Active route detection lives in the consumer's `linkComponent` — apply `activeClassName` when the link matches the current route.
|
|
274
|
+
|
|
275
|
+
## Accessibility
|
|
276
|
+
|
|
277
|
+
- Collapse/expand button has `aria-pressed` and a context-aware `aria-label` (`"Expand sidebar"` / `"Collapse sidebar"`).
|
|
278
|
+
- Each `SidebarMenuCollapsible` header is a `<button>` with `aria-expanded` and `aria-controls` linking to the region.
|
|
279
|
+
- Collapsed icon items expose `aria-haspopup="menu"` when they have children; popover is keyboard-reachable and closes on Escape.
|
|
280
|
+
- Truncated labels (`hasTooltip`) fall back to a Tooltip with the full text.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xsolla/xui-b2b-sidebar",
|
|
3
|
-
"version": "0.148.
|
|
3
|
+
"version": "0.148.1",
|
|
4
4
|
"main": "./web/index.js",
|
|
5
5
|
"module": "./web/index.mjs",
|
|
6
6
|
"types": "./web/index.d.ts",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"test:coverage": "vitest run --coverage"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@xsolla/xui-core": "0.148.
|
|
17
|
-
"@xsolla/xui-icons-base": "0.148.
|
|
18
|
-
"@xsolla/xui-primitives-core": "0.148.
|
|
19
|
-
"@xsolla/xui-tooltip": "0.148.
|
|
20
|
-
"@xsolla/xui-typography": "0.148.
|
|
16
|
+
"@xsolla/xui-core": "0.148.1",
|
|
17
|
+
"@xsolla/xui-icons-base": "0.148.1",
|
|
18
|
+
"@xsolla/xui-primitives-core": "0.148.1",
|
|
19
|
+
"@xsolla/xui-tooltip": "0.148.1",
|
|
20
|
+
"@xsolla/xui-typography": "0.148.1"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"react": ">=16.8.0",
|