@arbor-education/design-system.components 0.23.2 → 0.24.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 +6 -0
- package/dist/components/sidebarNav/SidebarNav.d.ts +46 -0
- package/dist/components/sidebarNav/SidebarNav.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNav.js +102 -0
- package/dist/components/sidebarNav/SidebarNav.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNav.stories.d.ts +61 -0
- package/dist/components/sidebarNav/SidebarNav.stories.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNav.stories.js +253 -0
- package/dist/components/sidebarNav/SidebarNav.stories.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNav.test.d.ts +2 -0
- package/dist/components/sidebarNav/SidebarNav.test.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNav.test.js +240 -0
- package/dist/components/sidebarNav/SidebarNav.test.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavContext.d.ts +13 -0
- package/dist/components/sidebarNav/SidebarNavContext.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavContext.js +15 -0
- package/dist/components/sidebarNav/SidebarNavContext.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavGroup.d.ts +10 -0
- package/dist/components/sidebarNav/SidebarNavGroup.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavGroup.js +16 -0
- package/dist/components/sidebarNav/SidebarNavGroup.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavItem.d.ts +32 -0
- package/dist/components/sidebarNav/SidebarNavItem.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavItem.js +43 -0
- package/dist/components/sidebarNav/SidebarNavItem.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavItemFavourite.d.ts +8 -0
- package/dist/components/sidebarNav/SidebarNavItemFavourite.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavItemFavourite.js +14 -0
- package/dist/components/sidebarNav/SidebarNavItemFavourite.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavPanel.d.ts +4 -0
- package/dist/components/sidebarNav/SidebarNavPanel.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavPanel.js +9 -0
- package/dist/components/sidebarNav/SidebarNavPanel.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavPanelNav.d.ts +10 -0
- package/dist/components/sidebarNav/SidebarNavPanelNav.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavPanelNav.js +21 -0
- package/dist/components/sidebarNav/SidebarNavPanelNav.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavRail.d.ts +6 -0
- package/dist/components/sidebarNav/SidebarNavRail.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavRail.js +7 -0
- package/dist/components/sidebarNav/SidebarNavRail.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavRailItem.d.ts +10 -0
- package/dist/components/sidebarNav/SidebarNavRailItem.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavRailItem.js +24 -0
- package/dist/components/sidebarNav/SidebarNavRailItem.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavRailList.d.ts +4 -0
- package/dist/components/sidebarNav/SidebarNavRailList.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavRailList.js +7 -0
- package/dist/components/sidebarNav/SidebarNavRailList.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavRailSlot.d.ts +6 -0
- package/dist/components/sidebarNav/SidebarNavRailSlot.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavRailSlot.js +7 -0
- package/dist/components/sidebarNav/SidebarNavRailSlot.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavSeparator.d.ts +6 -0
- package/dist/components/sidebarNav/SidebarNavSeparator.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavSeparator.js +8 -0
- package/dist/components/sidebarNav/SidebarNavSeparator.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavTitle.d.ts +4 -0
- package/dist/components/sidebarNav/SidebarNavTitle.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavTitle.js +7 -0
- package/dist/components/sidebarNav/SidebarNavTitle.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavTooltip.d.ts +7 -0
- package/dist/components/sidebarNav/SidebarNavTooltip.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavTooltip.js +9 -0
- package/dist/components/sidebarNav/SidebarNavTooltip.js.map +1 -0
- package/dist/components/sidebarNav/SidebarNavTrigger.d.ts +8 -0
- package/dist/components/sidebarNav/SidebarNavTrigger.d.ts.map +1 -0
- package/dist/components/sidebarNav/SidebarNavTrigger.js +15 -0
- package/dist/components/sidebarNav/SidebarNavTrigger.js.map +1 -0
- package/dist/components/sidebarNav/index.d.ts +4 -0
- package/dist/components/sidebarNav/index.d.ts.map +1 -0
- package/dist/components/sidebarNav/index.js +3 -0
- package/dist/components/sidebarNav/index.js.map +1 -0
- package/dist/components/sidebarNav/resolvePanelItemProps.d.ts +4 -0
- package/dist/components/sidebarNav/resolvePanelItemProps.d.ts.map +1 -0
- package/dist/components/sidebarNav/resolvePanelItemProps.js +43 -0
- package/dist/components/sidebarNav/resolvePanelItemProps.js.map +1 -0
- package/dist/components/sidebarNav/resolvePanelItemProps.test.d.ts +2 -0
- package/dist/components/sidebarNav/resolvePanelItemProps.test.d.ts.map +1 -0
- package/dist/components/sidebarNav/resolvePanelItemProps.test.js +52 -0
- package/dist/components/sidebarNav/resolvePanelItemProps.test.js.map +1 -0
- package/dist/components/sidebarNav/types.d.ts +100 -0
- package/dist/components/sidebarNav/types.d.ts.map +1 -0
- package/dist/components/sidebarNav/types.js +4 -0
- package/dist/components/sidebarNav/types.js.map +1 -0
- package/dist/components/sidebarNav/useControllableBoolean.d.ts +9 -0
- package/dist/components/sidebarNav/useControllableBoolean.d.ts.map +1 -0
- package/dist/components/sidebarNav/useControllableBoolean.js +14 -0
- package/dist/components/sidebarNav/useControllableBoolean.js.map +1 -0
- package/dist/index.css +275 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/sidebarNav/SidebarNav.stories.tsx +484 -0
- package/src/components/sidebarNav/SidebarNav.test.tsx +611 -0
- package/src/components/sidebarNav/SidebarNav.tsx +230 -0
- package/src/components/sidebarNav/SidebarNavContext.tsx +28 -0
- package/src/components/sidebarNav/SidebarNavGroup.tsx +59 -0
- package/src/components/sidebarNav/SidebarNavItem.tsx +160 -0
- package/src/components/sidebarNav/SidebarNavItemFavourite.tsx +49 -0
- package/src/components/sidebarNav/SidebarNavPanel.tsx +20 -0
- package/src/components/sidebarNav/SidebarNavPanelNav.tsx +55 -0
- package/src/components/sidebarNav/SidebarNavRail.tsx +20 -0
- package/src/components/sidebarNav/SidebarNavRailItem.tsx +84 -0
- package/src/components/sidebarNav/SidebarNavRailList.tsx +11 -0
- package/src/components/sidebarNav/SidebarNavRailSlot.tsx +15 -0
- package/src/components/sidebarNav/SidebarNavSeparator.tsx +19 -0
- package/src/components/sidebarNav/SidebarNavTitle.tsx +13 -0
- package/src/components/sidebarNav/SidebarNavTooltip.tsx +24 -0
- package/src/components/sidebarNav/SidebarNavTrigger.tsx +52 -0
- package/src/components/sidebarNav/index.ts +6 -0
- package/src/components/sidebarNav/resolvePanelItemProps.test.ts +57 -0
- package/src/components/sidebarNav/resolvePanelItemProps.ts +50 -0
- package/src/components/sidebarNav/sidebarNav.scss +283 -0
- package/src/components/sidebarNav/types.ts +126 -0
- package/src/components/sidebarNav/useControllableBoolean.ts +20 -0
- package/src/index.scss +1 -0
- package/src/index.ts +12 -0
- package/src/tokens.scss +14 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Controls,
|
|
3
|
+
Heading as DocHeading,
|
|
4
|
+
Primary as DocPrimary,
|
|
5
|
+
Markdown,
|
|
6
|
+
Stories,
|
|
7
|
+
Subtitle,
|
|
8
|
+
Title,
|
|
9
|
+
} from '@storybook/addon-docs/blocks';
|
|
10
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
11
|
+
import { Badge } from 'Components/badge/Badge';
|
|
12
|
+
import { Icon } from 'Components/icon/Icon';
|
|
13
|
+
import { useMemo, useState } from 'react';
|
|
14
|
+
import { fn } from 'storybook/test';
|
|
15
|
+
import { SidebarNav } from './SidebarNav.js';
|
|
16
|
+
import type { SidebarNavNode, SidebarNavRailEntry } from './types.js';
|
|
17
|
+
|
|
18
|
+
const DESCRIPTION_INTRO = [
|
|
19
|
+
'SidebarNav is a two-part navigation pattern: a fixed icon rail for global shortcuts and an expandable',
|
|
20
|
+
'panel for hierarchical section links. Place a `<Separator />` after the burger to divide the panel toggle',
|
|
21
|
+
'from the rest of the rail icons.',
|
|
22
|
+
].join('\n');
|
|
23
|
+
|
|
24
|
+
const USAGE_GUIDANCE = [
|
|
25
|
+
'### When to use',
|
|
26
|
+
'',
|
|
27
|
+
'- **Application shell navigation** — persistent rail icons (home, favourites, calendar) plus contextual section nav',
|
|
28
|
+
'- **MIS module navigation** — attendance, students, or reporting areas with grouped links',
|
|
29
|
+
'- **Favourites on leaf links** — `canFavourite` on panel items for user-pinned pages',
|
|
30
|
+
'',
|
|
31
|
+
'---',
|
|
32
|
+
'',
|
|
33
|
+
'### When NOT to use',
|
|
34
|
+
'',
|
|
35
|
+
'| Situation | Use instead |',
|
|
36
|
+
'|---|---|',
|
|
37
|
+
'| Horizontal top-level product nav | [`Menubar`](?path=/docs/components-menubar--docs) or app header patterns |',
|
|
38
|
+
'| Single flat list of links | [`Tabs`](?path=/docs/components-tabs--docs) or plain links |',
|
|
39
|
+
'| Account / settings menu | [`Dropdown`](?path=/docs/components-dropdown--docs) |',
|
|
40
|
+
].join('\n');
|
|
41
|
+
|
|
42
|
+
const DEVELOPER_NOTES = [
|
|
43
|
+
'### Rail layout',
|
|
44
|
+
'',
|
|
45
|
+
'The rail is a single vertical column. Compose **`Trigger` → `SidebarNav.Separator` → `RailList`** (icon rows).',
|
|
46
|
+
'Insert additional `<SidebarNav.Separator />` siblings between `RailList` blocks when Figma shows a break',
|
|
47
|
+
'(e.g. main shortcuts, emergency alert, help). `RailItem` and `RailSlot` each render their own',
|
|
48
|
+
'`<li>` row wrapper — do not add `ds-sidebar-nav__*` layout classes in consumer code. Use `RailSlot` for other',
|
|
49
|
+
'custom rail rows inside `RailList`.',
|
|
50
|
+
'',
|
|
51
|
+
'The rail `<nav>` defaults to `aria-label="Sidebar rail"`; the panel links nav defaults to `"Sidebar links"`.',
|
|
52
|
+
'Override via `ariaLabel` on `SidebarNav.Rail` or `SidebarNav.PanelNav` when needed.',
|
|
53
|
+
'',
|
|
54
|
+
'### Config-driven API',
|
|
55
|
+
'',
|
|
56
|
+
'Pass `railItems` as an ordered array of item configs and `{ type: \'separator\', id }` entries. Use',
|
|
57
|
+
'`opensPanel: true` on the burger item to render the panel trigger. Use `renderItem` for custom rail rows.',
|
|
58
|
+
'',
|
|
59
|
+
'### Favourites',
|
|
60
|
+
'',
|
|
61
|
+
'Panel leaf items support `canFavourite`, `isPressed`, `onFavouriteClick`, and required `favouriteTooltip` when',
|
|
62
|
+
'`canFavourite` is true (sets `aria-label` and shows a tooltip on hover/focus). The favourite `<button>` is a',
|
|
63
|
+
'sibling of the link (not inside the `<a>`), with `aria-pressed` reflecting state. Persistence is the',
|
|
64
|
+
'consumer\'s responsibility.',
|
|
65
|
+
'',
|
|
66
|
+
'The panel toggle (`SidebarNav.Trigger`) always shows tooltip **Show/hide sub navigation** (override via `tooltip` prop).',
|
|
67
|
+
'',
|
|
68
|
+
'### `renderLink`',
|
|
69
|
+
'',
|
|
70
|
+
'Provide `renderLink` at the root to integrate with your router. Panel items also support `linkElement="button"`',
|
|
71
|
+
'with `linkElementProps` for SPA actions without a URL. Data-driven items accept `href` and/or `onClick`.',
|
|
72
|
+
'Top-level `onClick` takes precedence over `linkElementProps.onClick` when both are set.',
|
|
73
|
+
'When `renderLink` is set, both `linkProps` and `linkElementProps` are forwarded in the args — spread them onto your router link (same order as the default `<a>`: `linkProps` then `linkElementProps`).',
|
|
74
|
+
'Also spread `ariaLabel`, `ariaCurrent`, and `onClick` from the args as appropriate.',
|
|
75
|
+
'',
|
|
76
|
+
'Pass `railItems={[]}` to render an intentionally empty data-driven rail. Omit `railItems` to compose the rail with JSX children.',
|
|
77
|
+
'',
|
|
78
|
+
'For data-driven rail items, pass `iconColor` as a design-token CSS value (e.g. `var(--icons-icon-default)` or `var(--color-semantic-warning-600)`), not hardcoded hex.',
|
|
79
|
+
'',
|
|
80
|
+
'The root provider passes shared state (panel open/closed, `renderLink`) to rail and panel subcomponents.',
|
|
81
|
+
'',
|
|
82
|
+
'---',
|
|
83
|
+
'',
|
|
84
|
+
'### Test IDs',
|
|
85
|
+
'',
|
|
86
|
+
'| Element | `data-testid` |',
|
|
87
|
+
'|---|---|',
|
|
88
|
+
'| Root | `sidebar-nav` |',
|
|
89
|
+
'| Rail | `sidebar-nav-rail` |',
|
|
90
|
+
'| Panel / trigger | `sidebar-nav-panel`, `sidebar-nav-panel-trigger`, `sidebar-nav-panel-trigger-{id}` |',
|
|
91
|
+
'| Rail item | `sidebar-nav-rail-item-{id}` |',
|
|
92
|
+
'| Rail separator | `sidebar-nav-rail-separator-{id}` |',
|
|
93
|
+
'| Panel item / favourite | `sidebar-nav-panel-item-{itemId}`, `sidebar-nav-panel-item-favourite-{itemId}` — `itemId` is required on composable `SidebarNav.Item` |',
|
|
94
|
+
].join('\n');
|
|
95
|
+
|
|
96
|
+
const RELATED_COMPONENTS = [
|
|
97
|
+
'## Related components',
|
|
98
|
+
'',
|
|
99
|
+
'[Dropdown](?path=/docs/components-dropdown--docs) · [Tooltip](?path=/docs/components-tooltip--docs) ·',
|
|
100
|
+
'[Badge](?path=/docs/components-badge--docs) · [Separator](?path=/docs/components-separator--docs) ·',
|
|
101
|
+
'[IconText](?path=/docs/components-icontext--docs)',
|
|
102
|
+
].join('\n');
|
|
103
|
+
|
|
104
|
+
const PROPS_INTRO = 'The preview below is wired to the **Controls** panel where applicable.';
|
|
105
|
+
|
|
106
|
+
function SidebarNavDocsPage() {
|
|
107
|
+
return (
|
|
108
|
+
<>
|
|
109
|
+
<Title />
|
|
110
|
+
<Subtitle />
|
|
111
|
+
<Markdown>{DESCRIPTION_INTRO}</Markdown>
|
|
112
|
+
<DocHeading>Interactive example</DocHeading>
|
|
113
|
+
<Markdown>{PROPS_INTRO}</Markdown>
|
|
114
|
+
<DocPrimary />
|
|
115
|
+
<Controls />
|
|
116
|
+
<DocHeading>Usage guidance</DocHeading>
|
|
117
|
+
<Markdown>{USAGE_GUIDANCE}</Markdown>
|
|
118
|
+
<DocHeading>Developer notes</DocHeading>
|
|
119
|
+
<Markdown>{DEVELOPER_NOTES}</Markdown>
|
|
120
|
+
<DocHeading>Examples</DocHeading>
|
|
121
|
+
<Stories title="" />
|
|
122
|
+
<Markdown>{RELATED_COMPONENTS}</Markdown>
|
|
123
|
+
</>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const meta = {
|
|
128
|
+
title: 'Components/SidebarNav',
|
|
129
|
+
component: SidebarNav,
|
|
130
|
+
parameters: {
|
|
131
|
+
layout: 'padded',
|
|
132
|
+
docs: {
|
|
133
|
+
page: SidebarNavDocsPage,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
tags: ['autodocs'],
|
|
137
|
+
args: {
|
|
138
|
+
onExpandedChange: fn(),
|
|
139
|
+
},
|
|
140
|
+
argTypes: {
|
|
141
|
+
defaultExpanded: {
|
|
142
|
+
control: 'boolean',
|
|
143
|
+
description: 'Initial panel open state when uncontrolled.',
|
|
144
|
+
table: { defaultValue: { summary: 'true' } },
|
|
145
|
+
},
|
|
146
|
+
children: { control: false },
|
|
147
|
+
railItems: { control: false },
|
|
148
|
+
panelNavItems: { control: false },
|
|
149
|
+
renderLink: { control: false },
|
|
150
|
+
},
|
|
151
|
+
} satisfies Meta<typeof SidebarNav>;
|
|
152
|
+
|
|
153
|
+
export default meta;
|
|
154
|
+
type Story = StoryObj<typeof SidebarNav>;
|
|
155
|
+
|
|
156
|
+
const withDescription = (story: Story, description: string): Story => ({
|
|
157
|
+
...story,
|
|
158
|
+
parameters: {
|
|
159
|
+
...story.parameters,
|
|
160
|
+
docs: {
|
|
161
|
+
...story.parameters?.docs,
|
|
162
|
+
description: { story: description },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const storyWrapperStyle = { height: 640 };
|
|
168
|
+
|
|
169
|
+
const basePanelNavItems: SidebarNavNode[] = [
|
|
170
|
+
{
|
|
171
|
+
type: 'group',
|
|
172
|
+
id: 'registers',
|
|
173
|
+
label: 'Registers',
|
|
174
|
+
defaultExpanded: true,
|
|
175
|
+
children: [
|
|
176
|
+
{ type: 'item', id: 'covid', label: 'Covid-19 Dashboard', href: '/covid' },
|
|
177
|
+
{
|
|
178
|
+
type: 'item',
|
|
179
|
+
id: 'daily',
|
|
180
|
+
label: 'Daily Attendance',
|
|
181
|
+
href: '/daily',
|
|
182
|
+
current: true,
|
|
183
|
+
canFavourite: true as const,
|
|
184
|
+
isPressed: false,
|
|
185
|
+
favouriteTooltip: 'Add to favourites',
|
|
186
|
+
},
|
|
187
|
+
{ type: 'item', id: 'quick', label: 'Quick Follow-Up', href: '/quick' },
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
type: 'group',
|
|
192
|
+
id: 'absentees',
|
|
193
|
+
label: 'Absentees',
|
|
194
|
+
children: [
|
|
195
|
+
{ type: 'item', id: 'late', label: 'Latecomers', href: '/latecomers' },
|
|
196
|
+
{ type: 'item', id: 'stats', label: 'Statistics', href: '/statistics' },
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
const figmaRailItems: SidebarNavRailEntry[] = [
|
|
202
|
+
{
|
|
203
|
+
id: 'burger',
|
|
204
|
+
label: 'Show/hide sub navigation',
|
|
205
|
+
iconName: 'menu',
|
|
206
|
+
opensPanel: true,
|
|
207
|
+
},
|
|
208
|
+
{ type: 'separator', id: 'after-burger' },
|
|
209
|
+
{ id: 'favourites', label: 'Favourites', iconName: 'favourite-outline', href: '/favourites' },
|
|
210
|
+
{
|
|
211
|
+
id: 'notifications',
|
|
212
|
+
label: 'Notifications',
|
|
213
|
+
iconName: 'message-square-more',
|
|
214
|
+
href: '/notifications',
|
|
215
|
+
badgeContent: <Badge size="sm" colour="salmon">6</Badge>,
|
|
216
|
+
},
|
|
217
|
+
{ id: 'calendar', label: 'My Calendar', iconName: 'date', href: '/calendar' },
|
|
218
|
+
{ type: 'separator', id: 'before-emergency' },
|
|
219
|
+
{
|
|
220
|
+
id: 'emergency',
|
|
221
|
+
label: 'Emergency Alerts',
|
|
222
|
+
iconName: 'triangle-alert',
|
|
223
|
+
href: '/emergency',
|
|
224
|
+
},
|
|
225
|
+
{ type: 'separator', id: 'before-help' },
|
|
226
|
+
{
|
|
227
|
+
id: 'help',
|
|
228
|
+
label: 'Help & Learn with Arbor',
|
|
229
|
+
iconName: 'circle-help',
|
|
230
|
+
href: '/help',
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
const SidebarNavShell = ({ children }: { children: React.ReactNode }) => (
|
|
235
|
+
<div style={storyWrapperStyle}>{children}</div>
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
/** Figma rail: burger → separator → shortcuts → separator → emergency → separator → help. */
|
|
239
|
+
const FigmaRailComposition = () => (
|
|
240
|
+
<>
|
|
241
|
+
<SidebarNav.Trigger>
|
|
242
|
+
<Icon name="menu" size={24} />
|
|
243
|
+
</SidebarNav.Trigger>
|
|
244
|
+
<SidebarNav.Separator id="after-burger" />
|
|
245
|
+
<SidebarNav.RailList>
|
|
246
|
+
<SidebarNav.RailItem href="/favourites" aria-label="Favourites">
|
|
247
|
+
<Icon name="favourite-outline" size={24} />
|
|
248
|
+
</SidebarNav.RailItem>
|
|
249
|
+
<SidebarNav.RailItem href="/notifications" aria-label="Notifications">
|
|
250
|
+
<Icon name="message-square-more" size={24} />
|
|
251
|
+
<Badge size="sm" colour="salmon">6</Badge>
|
|
252
|
+
</SidebarNav.RailItem>
|
|
253
|
+
<SidebarNav.RailItem href="/calendar" aria-label="My Calendar">
|
|
254
|
+
<Icon name="date" size={24} />
|
|
255
|
+
</SidebarNav.RailItem>
|
|
256
|
+
</SidebarNav.RailList>
|
|
257
|
+
<SidebarNav.Separator id="before-emergency" />
|
|
258
|
+
<SidebarNav.RailList>
|
|
259
|
+
<SidebarNav.RailItem href="/emergency" aria-label="Emergency Alerts">
|
|
260
|
+
<Icon name="triangle-alert" size={24} />
|
|
261
|
+
</SidebarNav.RailItem>
|
|
262
|
+
</SidebarNav.RailList>
|
|
263
|
+
<SidebarNav.Separator id="before-help" />
|
|
264
|
+
<SidebarNav.RailList>
|
|
265
|
+
<SidebarNav.RailItem href="/help" aria-label="Help & Learn with Arbor">
|
|
266
|
+
<Icon name="circle-help" size={24} />
|
|
267
|
+
</SidebarNav.RailItem>
|
|
268
|
+
</SidebarNav.RailList>
|
|
269
|
+
</>
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const DefaultTemplate = () => (
|
|
273
|
+
<SidebarNavShell>
|
|
274
|
+
<SidebarNav>
|
|
275
|
+
<SidebarNav.Rail>
|
|
276
|
+
<FigmaRailComposition />
|
|
277
|
+
</SidebarNav.Rail>
|
|
278
|
+
<SidebarNav.Panel>
|
|
279
|
+
<SidebarNav.Title>Attendance</SidebarNav.Title>
|
|
280
|
+
<SidebarNav.PanelNav items={basePanelNavItems} />
|
|
281
|
+
</SidebarNav.Panel>
|
|
282
|
+
</SidebarNav>
|
|
283
|
+
</SidebarNavShell>
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const CollapsedPanelTemplate = () => (
|
|
287
|
+
<SidebarNavShell>
|
|
288
|
+
<SidebarNav defaultExpanded={false}>
|
|
289
|
+
<SidebarNav.Rail>
|
|
290
|
+
<SidebarNav.Trigger>
|
|
291
|
+
<Icon name="menu" size={24} />
|
|
292
|
+
</SidebarNav.Trigger>
|
|
293
|
+
<SidebarNav.Separator id="after-burger" />
|
|
294
|
+
<SidebarNav.RailList>
|
|
295
|
+
<SidebarNav.RailItem href="/favourites" aria-label="Favourites">
|
|
296
|
+
<Icon name="favourite-outline" size={24} />
|
|
297
|
+
</SidebarNav.RailItem>
|
|
298
|
+
</SidebarNav.RailList>
|
|
299
|
+
</SidebarNav.Rail>
|
|
300
|
+
<SidebarNav.Panel>
|
|
301
|
+
<SidebarNav.Title>Attendance</SidebarNav.Title>
|
|
302
|
+
<SidebarNav.PanelNav items={[]} />
|
|
303
|
+
</SidebarNav.Panel>
|
|
304
|
+
</SidebarNav>
|
|
305
|
+
</SidebarNavShell>
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const ConfigDrivenTemplate = () => (
|
|
309
|
+
<SidebarNavShell>
|
|
310
|
+
<SidebarNav
|
|
311
|
+
railItems={figmaRailItems}
|
|
312
|
+
panelTitle="Attendance"
|
|
313
|
+
panelNavItems={basePanelNavItems}
|
|
314
|
+
/>
|
|
315
|
+
</SidebarNavShell>
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const WithFavouritesTemplate = () => {
|
|
319
|
+
const [favourites, setFavourites] = useState<Record<string, boolean>>({ daily: true });
|
|
320
|
+
|
|
321
|
+
const toggle = (id: string) => {
|
|
322
|
+
setFavourites(prev => ({ ...prev, [id]: !prev[id] }));
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<SidebarNavShell>
|
|
327
|
+
<SidebarNav>
|
|
328
|
+
<SidebarNav.Rail>
|
|
329
|
+
<SidebarNav.Trigger>
|
|
330
|
+
<Icon name="menu" size={24} />
|
|
331
|
+
</SidebarNav.Trigger>
|
|
332
|
+
<SidebarNav.Separator id="after-burger" />
|
|
333
|
+
</SidebarNav.Rail>
|
|
334
|
+
<SidebarNav.Panel>
|
|
335
|
+
<SidebarNav.Title>Attendance</SidebarNav.Title>
|
|
336
|
+
<SidebarNav.PanelNav>
|
|
337
|
+
<SidebarNav.Item
|
|
338
|
+
href="/daily"
|
|
339
|
+
itemId="daily"
|
|
340
|
+
current
|
|
341
|
+
canFavourite
|
|
342
|
+
isPressed={Boolean(favourites.daily)}
|
|
343
|
+
favouriteTooltip={favourites.daily ? 'Remove from favourites' : 'Add to favourites'}
|
|
344
|
+
onFavouriteClick={() => toggle('daily')}
|
|
345
|
+
>
|
|
346
|
+
Daily Attendance
|
|
347
|
+
</SidebarNav.Item>
|
|
348
|
+
<SidebarNav.Item href="/quick" itemId="quick">
|
|
349
|
+
Quick Follow-Up
|
|
350
|
+
</SidebarNav.Item>
|
|
351
|
+
</SidebarNav.PanelNav>
|
|
352
|
+
</SidebarNav.Panel>
|
|
353
|
+
</SidebarNav>
|
|
354
|
+
</SidebarNavShell>
|
|
355
|
+
);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const ConfigDrivenWithFavouritesTemplate = () => {
|
|
359
|
+
const [favourites, setFavourites] = useState<Record<string, boolean>>({ daily: true });
|
|
360
|
+
|
|
361
|
+
const panelNavItems = useMemo(
|
|
362
|
+
(): SidebarNavNode[] => basePanelNavItems.map(node => (
|
|
363
|
+
node.type === 'group'
|
|
364
|
+
? {
|
|
365
|
+
...node,
|
|
366
|
+
children: node.children.map((child) => {
|
|
367
|
+
if (child.id !== 'daily') {
|
|
368
|
+
return child;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
...child,
|
|
373
|
+
canFavourite: true as const,
|
|
374
|
+
isPressed: Boolean(favourites.daily),
|
|
375
|
+
favouriteTooltip: favourites.daily ? 'Remove from favourites' : 'Add to favourites',
|
|
376
|
+
onFavouriteClick: () => setFavourites(prev => ({ ...prev, daily: !prev.daily })),
|
|
377
|
+
};
|
|
378
|
+
}),
|
|
379
|
+
}
|
|
380
|
+
: node
|
|
381
|
+
)),
|
|
382
|
+
[favourites.daily],
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
return (
|
|
386
|
+
<SidebarNavShell>
|
|
387
|
+
<SidebarNav
|
|
388
|
+
railItems={figmaRailItems}
|
|
389
|
+
panelTitle="Attendance"
|
|
390
|
+
panelNavItems={panelNavItems}
|
|
391
|
+
/>
|
|
392
|
+
</SidebarNavShell>
|
|
393
|
+
);
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const WithRenderLinkTemplate = () => (
|
|
397
|
+
<SidebarNavShell>
|
|
398
|
+
<SidebarNav
|
|
399
|
+
renderLink={({
|
|
400
|
+
href,
|
|
401
|
+
className,
|
|
402
|
+
children,
|
|
403
|
+
ariaCurrent,
|
|
404
|
+
ariaLabel,
|
|
405
|
+
onClick,
|
|
406
|
+
linkProps,
|
|
407
|
+
linkElementProps,
|
|
408
|
+
}) => (
|
|
409
|
+
<a
|
|
410
|
+
href={href}
|
|
411
|
+
className={className}
|
|
412
|
+
aria-current={ariaCurrent}
|
|
413
|
+
aria-label={ariaLabel}
|
|
414
|
+
data-router-link="true"
|
|
415
|
+
onClick={onClick ?? (e => e.preventDefault())}
|
|
416
|
+
{...linkProps}
|
|
417
|
+
{...linkElementProps}
|
|
418
|
+
>
|
|
419
|
+
{children}
|
|
420
|
+
</a>
|
|
421
|
+
)}
|
|
422
|
+
>
|
|
423
|
+
<SidebarNav.Rail>
|
|
424
|
+
<SidebarNav.Trigger>
|
|
425
|
+
<Icon name="menu" size={24} />
|
|
426
|
+
</SidebarNav.Trigger>
|
|
427
|
+
<SidebarNav.Separator id="after-burger" />
|
|
428
|
+
</SidebarNav.Rail>
|
|
429
|
+
<SidebarNav.Panel>
|
|
430
|
+
<SidebarNav.PanelNav
|
|
431
|
+
items={[{ type: 'item', id: 'home', label: 'Home', href: '/' }]}
|
|
432
|
+
/>
|
|
433
|
+
</SidebarNav.Panel>
|
|
434
|
+
</SidebarNav>
|
|
435
|
+
</SidebarNavShell>
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
export const Default: Story = withDescription(
|
|
439
|
+
{
|
|
440
|
+
render: DefaultTemplate,
|
|
441
|
+
parameters: { controls: { disable: true } },
|
|
442
|
+
},
|
|
443
|
+
'Composed rail: `Trigger`, separators between `RailList` sections (favourites/notifications/calendar, emergency, help). Panel uses `Trigger` ↔ `Panel` via context (`aria-controls` / `aria-expanded`).',
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
export const CollapsedPanel: Story = withDescription(
|
|
447
|
+
{
|
|
448
|
+
render: CollapsedPanelTemplate,
|
|
449
|
+
parameters: { controls: { disable: true } },
|
|
450
|
+
},
|
|
451
|
+
'Panel hidden by default (`defaultExpanded={false}`). Use the burger trigger to open the panel.',
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
export const ConfigDriven: Story = withDescription(
|
|
455
|
+
{
|
|
456
|
+
render: ConfigDrivenTemplate,
|
|
457
|
+
parameters: { controls: { disable: true } },
|
|
458
|
+
},
|
|
459
|
+
'Same Figma rail order via `railItems` (burger, `{ type: \'separator\' }` entries, then icons). Compare with **Default** for the composed `RailList` + `Separator` pattern.',
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
export const WithFavourites: Story = withDescription(
|
|
463
|
+
{
|
|
464
|
+
render: WithFavouritesTemplate,
|
|
465
|
+
parameters: { controls: { disable: true } },
|
|
466
|
+
},
|
|
467
|
+
'Composable panel items with `canFavourite`, `isPressed`, and `onFavouriteClick`. State is mocked in the story — wire to your store or API in MIS.',
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
export const ConfigDrivenWithFavourites: Story = withDescription(
|
|
471
|
+
{
|
|
472
|
+
render: ConfigDrivenWithFavouritesTemplate,
|
|
473
|
+
parameters: { controls: { disable: true } },
|
|
474
|
+
},
|
|
475
|
+
'Config-driven rail (`figmaRailItems`) and `panelNavItems` with favourite props merged from local state.',
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
export const WithRenderLink: Story = withDescription(
|
|
479
|
+
{
|
|
480
|
+
render: WithRenderLinkTemplate,
|
|
481
|
+
parameters: { controls: { disable: true } },
|
|
482
|
+
},
|
|
483
|
+
'Root `renderLink` replaces default `<a>` elements for router integration.',
|
|
484
|
+
);
|