@aiquants/menu-bar 1.18.2 → 1.19.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/README.md +159 -30
- package/dist/MenuBar.d.ts +2 -29
- package/dist/components/SessionInfo.d.ts +1 -26
- package/dist/contents/MenuContent.d.ts +3 -14
- package/dist/contents/MenuList.d.ts +3 -38
- package/dist/contents/OverlayShadow.d.ts +1 -20
- package/dist/elements/MenuSwitch.d.ts +8 -22
- package/dist/events/pinEvent.d.ts +2 -37
- package/dist/hooks/useMenuPinState.d.ts +1 -16
- package/dist/index.d.ts +11 -21
- package/dist/index.es.js +478 -475
- package/dist/index.umd.js +2 -2
- package/dist/styles/menu-bar.css +1 -1
- package/dist/styles/menu-bar.standalone.css +1 -1
- package/dist/types/user.d.ts +0 -7
- package/dist/types.d.ts +1 -15
- package/package.json +8 -6
- package/dist/__tests__/pinEvent.test.d.ts +0 -1
- package/src/MenuBar.tsx +0 -248
- package/src/__tests__/pinEvent.test.tsx +0 -68
- package/src/components/SessionInfo.tsx +0 -95
- package/src/contents/MenuContent.tsx +0 -79
- package/src/contents/MenuList.tsx +0 -400
- package/src/contents/OverlayShadow.tsx +0 -30
- package/src/elements/MenuSwitch.tsx +0 -134
- package/src/events/pinEvent.ts +0 -129
- package/src/hooks/useMenuPinState.ts +0 -58
- package/src/index.ts +0 -29
- package/src/lucide-icons.d.ts +0 -6
- package/src/styles/components.entry.css +0 -11
- package/src/styles/menu-bar.css +0 -224
- package/src/styles/standalone.entry.css +0 -12
- package/src/types/user.ts +0 -12
- package/src/types.ts +0 -24
package/README.md
CHANGED
|
@@ -4,10 +4,13 @@ A React menu bar component with authentication support and theming for modern we
|
|
|
4
4
|
|
|
5
5
|
## Key Features
|
|
6
6
|
|
|
7
|
-
- **Menu Navigation**:
|
|
7
|
+
- **Menu Navigation**: A drawer that slides in from the right, with top-level links and collapsible groups
|
|
8
|
+
- **Current Page Highlighting**: The entry the current route belongs to carries `aria-current="page"` and its group opens, with exact or prefix matching per entry
|
|
8
9
|
- **Dark/Light Theme**: Comprehensive theming support with scoped CSS
|
|
9
10
|
- **Session Management**: Built-in session information display with user profiles
|
|
10
|
-
- **
|
|
11
|
+
- **Pinning**: Keep the drawer open while navigating, with the pinned width exposed as a CSS variable and an event
|
|
12
|
+
- **Accessible**: Named switch with `aria-expanded`, an inert closed drawer, focus that returns to the switch, visible focus rings, reduced-motion and forced-colors support
|
|
13
|
+
- **Configurable Switch**: Position, size, layering and caption of the open/close switch set through CSS custom properties
|
|
11
14
|
- **TypeScript Support**: Full TypeScript definitions included
|
|
12
15
|
- **React Router Integration**: Built for React Router with generic link component support
|
|
13
16
|
- **CSS Scoped**: All styles are scoped to prevent conflicts with host applications
|
|
@@ -20,18 +23,26 @@ npm install @aiquants/menu-bar
|
|
|
20
23
|
|
|
21
24
|
## Exports
|
|
22
25
|
|
|
23
|
-
This package exports the following components and types:
|
|
26
|
+
This package exports the following components, functions and types:
|
|
24
27
|
|
|
25
28
|
```tsx
|
|
26
29
|
// Components
|
|
27
30
|
export { MenuBar } from '@aiquants/menu-bar';
|
|
28
31
|
export { SessionInfo, DefaultLink } from '@aiquants/menu-bar';
|
|
29
32
|
|
|
33
|
+
// Pin state (event and hook)
|
|
34
|
+
export { useMenuPinState, subscribeMenuPinChange, dispatchMenuPinChange, getInitialPinState, MENU_PIN_CHANGE_EVENT } from '@aiquants/menu-bar';
|
|
35
|
+
|
|
30
36
|
// Types
|
|
31
|
-
export type {
|
|
37
|
+
export type { IMenuBarProps } from '@aiquants/menu-bar';
|
|
38
|
+
export type { IMenu, ISubmenu, MenuBarPathMatch, MenuNavigationPayload } from '@aiquants/menu-bar';
|
|
32
39
|
export type { Theme } from '@aiquants/menu-bar';
|
|
33
40
|
export type { SessionInfoProps, LinkProps } from '@aiquants/menu-bar';
|
|
34
41
|
export type { UserProfile } from '@aiquants/menu-bar';
|
|
42
|
+
export type { MenuPinChangeDetail, MenuPinCustomEvent, SubscribeMenuPinOptions, UseMenuPinStateOptions } from '@aiquants/menu-bar';
|
|
43
|
+
|
|
44
|
+
// Collision-free aliases of the types above
|
|
45
|
+
export type { IMenuBarMenu, IMenuBarSubmenu, MenuBarTheme, MenuBarUserProfile, MenuBarLinkProps, MenuBarSessionInfoProps } from '@aiquants/menu-bar';
|
|
35
46
|
|
|
36
47
|
// CSS — non-Tailwind hosts load the self-contained standalone build once (Tailwind hosts: see "Styling (CSS)")
|
|
37
48
|
import '@aiquants/menu-bar/styles/menu-bar.standalone.css';
|
|
@@ -87,6 +98,8 @@ function App() {
|
|
|
87
98
|
}
|
|
88
99
|
```
|
|
89
100
|
|
|
101
|
+
`MenuBar` renders React Router links, so place it inside a router provider.
|
|
102
|
+
|
|
90
103
|
## Props
|
|
91
104
|
|
|
92
105
|
### MenuBar Props
|
|
@@ -98,9 +111,12 @@ function App() {
|
|
|
98
111
|
| `toggleTheme` | `() => void` | Yes | Function to toggle between light and dark themes |
|
|
99
112
|
| `onClickSignout` | `() => void` | Yes | Callback function for sign out action |
|
|
100
113
|
| `menuHeader` | `React.ReactNode` | No | Optional header content for the menu |
|
|
101
|
-
| `
|
|
114
|
+
| `isMenuHide` | `boolean` | No | Whether to hide the menu bar entirely (renders nothing) |
|
|
115
|
+
| `isMenuhide` | `boolean` | No | Legacy spelling of `isMenuHide`; read only when `isMenuHide` is not given |
|
|
102
116
|
| `onMenuNavigate` | `(payload: MenuNavigationPayload) => void` | No | Callback function triggered when a menu or submenu item is selected |
|
|
103
|
-
| `
|
|
117
|
+
| `isPinned` | `boolean` | No | Controlled pin state. When omitted, the menu bar keeps the pin state itself and stores it in `localStorage` |
|
|
118
|
+
| `pinnedWidth` | `number` | No | Width in pixels reported while pinned (default `240`) |
|
|
119
|
+
| `onPinChange` | `(pinned: boolean, widthPx: number) => void` | No | Called when the pin button toggles the pin state, and with `(false, 0)` whenever the switch, the backdrop or Escape closes the drawer (not when selecting an item closes it) |
|
|
104
120
|
| `pinnedSubmenuIds` | `number[]` | No | Pinned submenu IDs (controlled) |
|
|
105
121
|
| `initialPinnedSubmenuIds` | `number[]` | No | Initial pinned submenu IDs |
|
|
106
122
|
| `onSubmenuPinChange` | `(pinnedIds: number[]) => void` | No | Callback function triggered when submenu pin states change |
|
|
@@ -114,6 +130,7 @@ interface IMenu {
|
|
|
114
130
|
title: string;
|
|
115
131
|
href?: string;
|
|
116
132
|
submenu: ISubmenu[];
|
|
133
|
+
match?: MenuBarPathMatch; // ignored on a group without href
|
|
117
134
|
}
|
|
118
135
|
```
|
|
119
136
|
|
|
@@ -126,9 +143,18 @@ interface ISubmenu {
|
|
|
126
143
|
href: string;
|
|
127
144
|
mark?: string;
|
|
128
145
|
new?: boolean;
|
|
146
|
+
match?: MenuBarPathMatch;
|
|
129
147
|
}
|
|
130
148
|
```
|
|
131
149
|
|
|
150
|
+
### MenuBarPathMatch Type
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
type MenuBarPathMatch = 'exact' | 'prefix';
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
How an entry's `href` is matched against the current path. See "Current Page Highlighting" below.
|
|
157
|
+
|
|
132
158
|
### MenuNavigationPayload Type
|
|
133
159
|
|
|
134
160
|
```tsx
|
|
@@ -167,17 +193,17 @@ interface LinkProps {
|
|
|
167
193
|
|
|
168
194
|
The package ships **two CSS artifacts** — pick the one that matches your host.
|
|
169
195
|
|
|
170
|
-
- **Tailwind v4 host** — import the components-only build into the `components` layer, and let your own Tailwind build generate the JSX utility classes by scanning the package
|
|
196
|
+
- **Tailwind v4 host** — import the components-only build into the `components` layer, and let your own Tailwind build generate the JSX utility classes by scanning the package's built JavaScript (the published package ships `dist`, not `src`; the class names stay intact in the bundle):
|
|
171
197
|
|
|
172
198
|
```css
|
|
173
199
|
/* app tailwind.css */
|
|
174
200
|
@import "@aiquants/menu-bar/styles/menu-bar.css" layer(components);
|
|
175
|
-
@source "../node_modules/@aiquants/menu-bar/
|
|
176
|
-
/*
|
|
201
|
+
@source "../node_modules/@aiquants/menu-bar/dist/**/*.js";
|
|
202
|
+
/* the package as a workspace sibling: @source "../../../../packages/menu-bar/src/**/*.{ts,tsx}"; */
|
|
177
203
|
```
|
|
178
204
|
|
|
179
|
-
`menu-bar.css` (Artifact A) carries only the hand-written `.aiquants-*` component classes
|
|
180
|
-
|
|
205
|
+
`menu-bar.css` (Artifact A) carries only the hand-written `.aiquants-*` component classes and the package's own `:root` variables — no Tailwind theme variables, no Tailwind utilities, no preflight — so it drops into `layer(components)` safely.
|
|
206
|
+
Its `:root` rule declares menu-bar's runtime variable `--aiquants-menu-pinned-width` (which the component sets inline at runtime) and the defaults of the menu switch custom properties (`--aqmb-switch-*`, see below).
|
|
181
207
|
Do **not** also `@import` the standalone build in a Tailwind host: it re-emits the same utilities your build produces, and two concatenated builds cannot reproduce the single-build canonical order (base → variants), so base/variant cascade winners flip.
|
|
182
208
|
|
|
183
209
|
- **Non-Tailwind host** — load the single self-contained standalone build (via `<link>` or a plain `@import`); it bundles the component classes plus every JSX utility:
|
|
@@ -188,9 +214,47 @@ The package ships **two CSS artifacts** — pick the one that matches your host.
|
|
|
188
214
|
|
|
189
215
|
Dark mode keys off the `.dark` class — add `.dark` to `<html>` to enable the dark styles.
|
|
190
216
|
|
|
217
|
+
In both setups the component classes sit in the `components` layer and the utilities in the later `utilities` layer, so a utility wins over a component rule for the same property.
|
|
218
|
+
|
|
219
|
+
## Menu Switch Custom Properties
|
|
220
|
+
|
|
221
|
+
The open/close switch is a square box with four bars that turn into a cross while the drawer is open, over a "menu" / "close" caption. Its whole drawing lives in the package stylesheet, and it carries no utility classes, so every custom property below takes effect. The defaults are declared on `:root`; override them on `:root` (or any ancestor of the menu bar).
|
|
222
|
+
|
|
223
|
+
| Property | Default | Effect |
|
|
224
|
+
| --- | --- | --- |
|
|
225
|
+
| `--aqmb-switch-position` | `fixed` | `position` of the switch |
|
|
226
|
+
| `--aqmb-switch-top` | `6px` | `top` of the switch |
|
|
227
|
+
| `--aqmb-switch-right` | `8px` | `right` of the switch |
|
|
228
|
+
| `--aqmb-switch-left` | `auto` | `left` of the switch |
|
|
229
|
+
| `--aqmb-switch-bottom` | `auto` | `bottom` of the switch |
|
|
230
|
+
| `--aqmb-switch-z-index` | `2147483647` | `z-index` of the switch |
|
|
231
|
+
| `--aqmb-switch-size` | `27px` | Side S of the switch's square box; every length of the drawing derives from it |
|
|
232
|
+
| `--aqmb-switch-caption-display` | `block` | `display` of the caption; `none` hides it |
|
|
233
|
+
|
|
234
|
+
How the drawing follows S:
|
|
235
|
+
|
|
236
|
+
- The bars are S/9 thick, at 0, 2S/9, 4S/9 and 6S/9 of a stack 7S/9 high. While open, the outer bars become 32S/27 wide and 4S/27 thick and cross at ±45°; the inner bars fade out.
|
|
237
|
+
- The caption's top edge is S/2 + 0.5rem below the top of the stack.
|
|
238
|
+
- With the caption shown (the default) and S below 48px (at a 16px root font size), the column of bars and caption is taller than the box: it starts at the box's top and the caption hangs below the box.
|
|
239
|
+
- With `--aqmb-switch-caption-display: none`, the bars are centred in the box and nothing is drawn above or below it. The cross can still extend slightly past the box's right edge, as it does in the default drawing.
|
|
240
|
+
- The default `27px` gives 3px bars spaced 6px apart, a 32px × 4px cross, and a caption 21.5px below the top of the box.
|
|
241
|
+
- The switch draws in black, and in white on the dark theme and while the drawer is open. Its keyboard focus ring is a 2px outline in the same colour at a 2px offset, so it occupies 4px around the box.
|
|
242
|
+
|
|
243
|
+
Example — fit the switch into a 32px header bar (4px + 24px line box + 4px):
|
|
244
|
+
|
|
245
|
+
```css
|
|
246
|
+
:root {
|
|
247
|
+
--aqmb-switch-size: 24px;
|
|
248
|
+
--aqmb-switch-caption-display: none;
|
|
249
|
+
--aqmb-switch-top: 4px;
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The box sits on the bar's 24px line box, and the focus ring (0 to 32px from the top) stays inside the bar. Adjust `--aqmb-switch-right` to align it horizontally.
|
|
254
|
+
|
|
191
255
|
## Theming
|
|
192
256
|
|
|
193
|
-
The menu bar supports both light and dark themes. All styles are scoped with the `.aiquants-menu-bar` class to prevent conflicts with your application's styles.
|
|
257
|
+
The menu bar supports both light and dark themes. All styles are scoped with the `.aiquants-menu-bar` class to prevent conflicts with your application's styles. The root element carries the theme as a class: `.aiquants-menu-bar.light` or `.aiquants-menu-bar.dark`.
|
|
194
258
|
|
|
195
259
|
### Custom Styling
|
|
196
260
|
|
|
@@ -201,11 +265,11 @@ You can override the default styles by targeting the scoped classes:
|
|
|
201
265
|
/* Custom menu bar styles */
|
|
202
266
|
}
|
|
203
267
|
|
|
204
|
-
.aiquants-menu-bar.
|
|
268
|
+
.aiquants-menu-bar.dark {
|
|
205
269
|
/* Dark theme overrides */
|
|
206
270
|
}
|
|
207
271
|
|
|
208
|
-
.aiquants-menu-bar.
|
|
272
|
+
.aiquants-menu-bar.light {
|
|
209
273
|
/* Light theme overrides */
|
|
210
274
|
}
|
|
211
275
|
```
|
|
@@ -247,6 +311,60 @@ function App() {
|
|
|
247
311
|
}
|
|
248
312
|
```
|
|
249
313
|
|
|
314
|
+
## Current Page Highlighting
|
|
315
|
+
|
|
316
|
+
The menu finds the one entry the current route belongs to, marks its link with `aria-current="page"`, and opens its group. Each top-level item with an `href` and each submenu item can choose how its `href` is matched with `match`:
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
const menuList: IMenu[] = [
|
|
320
|
+
{ id: 1, title: 'Home', href: '/', submenu: [] },
|
|
321
|
+
{
|
|
322
|
+
id: 2,
|
|
323
|
+
title: 'Admin',
|
|
324
|
+
submenu: [
|
|
325
|
+
// Also the current entry on /admin/console/search, /admin/console/users/42, ...
|
|
326
|
+
{ subid: 1, title: 'Console', href: '/admin/console', match: 'prefix' },
|
|
327
|
+
{ subid: 2, title: 'Audit log', href: '/admin/audit' },
|
|
328
|
+
],
|
|
329
|
+
},
|
|
330
|
+
];
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
The rule:
|
|
334
|
+
|
|
335
|
+
- `'exact'` (the default) matches the path itself. `'prefix'` also matches every path below the `href` at a segment boundary: `/admin` matches `/admin/users`, not `/administration`. Any other value behaves as `'exact'`.
|
|
336
|
+
- The query, the hash and a trailing slash are ignored on both the `href` and the path.
|
|
337
|
+
- An `href` of `/` matches only the root, even with `'prefix'`. External URLs never match.
|
|
338
|
+
- When several entries match, the longest `href` wins, across groups too; an exact match therefore always beats a prefix match. On a tie, the first entry in menu order wins (a top-level item before its own submenu items).
|
|
339
|
+
|
|
340
|
+
The group of the matched entry opens once per route change, and only one group is open at a time (pinned groups always stay open). A route with no matching entry leaves the open group as it is, and reloading the menu on the same route does not reopen a group the user closed.
|
|
341
|
+
|
|
342
|
+
## Accessibility
|
|
343
|
+
|
|
344
|
+
- **Switch**: a native `<button>` named "Open menu" / "Close menu" (`aria-label`) with `aria-expanded`; the visible caption is `aria-hidden`. Keyboard focus shows a 2px ring (`:focus-visible`).
|
|
345
|
+
- **Closed drawer is inert**: while closed, nothing in the drawer takes focus (by Tab or by script) or appears in the accessibility tree, so the switch is the menu bar's only Tab stop. A pinned drawer stays open and interactive. This relies on the HTML `inert` attribute.
|
|
346
|
+
- **Decorative backdrop**: the dimming layer behind the open drawer is an `aria-hidden` `div` that never takes focus. A pointer press on it closes the drawer without moving the focus; keyboard and assistive-technology users close the drawer with Escape (per `closeOnEscape`) or the switch.
|
|
347
|
+
- **Focus return**: when the drawer closes — by the switch, the backdrop, Escape or selecting an item while not pinned — with the focus inside it, the focus moves to the switch. A focus outside the drawer stays where it is.
|
|
348
|
+
- **Current page**: the matched entry's link carries `aria-current="page"`.
|
|
349
|
+
- **Groups**: group summaries toggle with Enter or Space and show a focus outline on keyboard focus. Every button has an accessible name.
|
|
350
|
+
- **Reduced motion**: the drawer's slide, the backdrop's fade and the switch's transitions run only under `prefers-reduced-motion: no-preference`.
|
|
351
|
+
- **Forced colors**: the switch's bars are drawn in `ButtonText`, so they stay visible when forced colors replace backgrounds.
|
|
352
|
+
|
|
353
|
+
## DOM Hooks
|
|
354
|
+
|
|
355
|
+
Select the menu bar's elements in scripts and tests by these attributes (or by role and accessible name). Class names — `.aiquants-*` and utilities — are styling and may change.
|
|
356
|
+
|
|
357
|
+
| Attribute | Element |
|
|
358
|
+
| --- | --- |
|
|
359
|
+
| `data-aqmb-menu-switch` | The open/close switch button |
|
|
360
|
+
| `data-aqmb-menu-content` | The drawer's root element |
|
|
361
|
+
| `data-aqmb-menu-overlay` | The backdrop |
|
|
362
|
+
| `data-aqmb-menu-list` | The scroll container of the menu list |
|
|
363
|
+
| `data-aqmb-pin-button` | The drawer's pin button |
|
|
364
|
+
| `data-aqmb-theme-button` | The theme toggle button |
|
|
365
|
+
| `data-aqmb-signout-button` | The sign-out button |
|
|
366
|
+
| `data-aqmb-submenu-pin-button` | The pin button of each group |
|
|
367
|
+
|
|
250
368
|
## Advanced Usage
|
|
251
369
|
|
|
252
370
|
### With Menu Header
|
|
@@ -280,7 +398,7 @@ import React from 'react';
|
|
|
280
398
|
theme={theme}
|
|
281
399
|
toggleTheme={toggleTheme}
|
|
282
400
|
onClickSignout={handleSignout}
|
|
283
|
-
|
|
401
|
+
isMenuHide={shouldHideMenu}
|
|
284
402
|
/>
|
|
285
403
|
```
|
|
286
404
|
|
|
@@ -290,11 +408,11 @@ import React from 'react';
|
|
|
290
408
|
|
|
291
409
|
The main menu bar component that includes:
|
|
292
410
|
|
|
293
|
-
-
|
|
294
|
-
-
|
|
295
|
-
-
|
|
296
|
-
-
|
|
297
|
-
-
|
|
411
|
+
- Open/close switch with four bars that turn into a cross
|
|
412
|
+
- Header slot (`menuHeader`) and navigation menu
|
|
413
|
+
- Pin, theme toggle and sign-out buttons
|
|
414
|
+
- Backdrop that closes the drawer when pressed
|
|
415
|
+
- Focus management and current page highlighting
|
|
298
416
|
|
|
299
417
|
### SessionInfo
|
|
300
418
|
|
|
@@ -322,18 +440,21 @@ import { SessionInfo } from '@aiquants/menu-bar';
|
|
|
322
440
|
|
|
323
441
|
- **Hierarchical Menu Structure**: Support for main menu items with submenus
|
|
324
442
|
- **Dynamic Navigation**: Menu items can have direct links or submenu collections
|
|
443
|
+
- **Current Page**: Exact or prefix matching per entry, with `aria-current="page"`
|
|
325
444
|
- **Visual Indicators**: Support for special marks (lock icon, etc.) and "new" badges
|
|
326
445
|
- **External Links**: Option to open links in new tabs
|
|
327
446
|
|
|
328
447
|
## CSS Classes
|
|
329
448
|
|
|
330
|
-
All CSS classes
|
|
449
|
+
All hand-written CSS classes use the `.aiquants-` prefix, for example:
|
|
331
450
|
|
|
332
|
-
- `.aiquants-menu-bar` - Main container
|
|
333
|
-
- `.aiquants-menu-switch` -
|
|
334
|
-
- `.aiquants-menu-content` -
|
|
335
|
-
- `.aiquants-
|
|
336
|
-
- `.aiquants-
|
|
451
|
+
- `.aiquants-menu-bar` - Main container (with the theme class `light` or `dark`)
|
|
452
|
+
- `.aiquants-menu-switch` - Open/close switch (`.aiquants-menu-switch-bars`, `.aiquants-menu-switch-bar`, `.aiquants-menu-switch-caption`)
|
|
453
|
+
- `.aiquants-menu-content` - Drawer
|
|
454
|
+
- `.aiquants-menu-list-item` - Menu item and group summary
|
|
455
|
+
- `.aiquants-session-card` - Session information card
|
|
456
|
+
|
|
457
|
+
These classes are a styling contract, not DOM hooks — use the `data-aqmb-*` attributes above to select elements.
|
|
337
458
|
|
|
338
459
|
## Browser Support
|
|
339
460
|
|
|
@@ -346,18 +467,26 @@ All CSS classes are scoped with `.aiquants-` prefix:
|
|
|
346
467
|
|
|
347
468
|
```bash
|
|
348
469
|
# Install dependencies
|
|
349
|
-
|
|
470
|
+
pnpm install
|
|
350
471
|
|
|
351
472
|
# Build the package
|
|
352
|
-
|
|
473
|
+
pnpm run build
|
|
353
474
|
|
|
354
475
|
# Run type checking
|
|
355
|
-
|
|
476
|
+
pnpm run typecheck
|
|
477
|
+
|
|
478
|
+
# Run the unit tests (Vitest, jsdom)
|
|
479
|
+
pnpm run test
|
|
480
|
+
|
|
481
|
+
# Run the browser tests (Playwright, Chromium) against a local harness page
|
|
482
|
+
pnpm run test:e2e
|
|
356
483
|
|
|
357
484
|
# Clean build files
|
|
358
|
-
|
|
485
|
+
pnpm run clean
|
|
359
486
|
```
|
|
360
487
|
|
|
488
|
+
The browser tests start a Vite server for the harness on port `25178`; set `MENU_BAR_E2E_PORT` to use another port, and `MENU_BAR_E2E_REUSE_SERVER=1` to reuse a server that is already running. Install Chromium once with `pnpm exec playwright install chromium` if it is missing.
|
|
489
|
+
|
|
361
490
|
## License
|
|
362
491
|
|
|
363
492
|
MIT License - see [LICENSE](LICENSE) file for details.
|
package/dist/MenuBar.d.ts
CHANGED
|
@@ -1,47 +1,20 @@
|
|
|
1
|
-
import { IMenu, MenuNavigationPayload } from
|
|
2
|
-
import { Theme } from
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Props interface for the MenuBar component.
|
|
6
|
-
* MenuBar コンポーネントのプロップスインターフェース。
|
|
7
|
-
*/
|
|
1
|
+
import { IMenu, MenuNavigationPayload } from "./contents/MenuList";
|
|
2
|
+
import { Theme } from "./types";
|
|
8
3
|
export interface IMenuBarProps {
|
|
9
|
-
/** List of menu items. メニュー項目のリスト。 */
|
|
10
4
|
menuList: IMenu[];
|
|
11
|
-
/** Optional header content rendered at top of menu drawer. メニューヘッダー要素。 */
|
|
12
5
|
menuHeader?: React.ReactNode;
|
|
13
|
-
/** Callback handler for sign out button click. サインアウト処理ハンドラー。 */
|
|
14
6
|
onClickSignout(): void;
|
|
15
|
-
/** Whether to hide the menu bar (legacy camelCase). メニュー非表示フラグ(旧表記)。 */
|
|
16
7
|
isMenuhide?: boolean;
|
|
17
|
-
/** Whether to hide the menu bar. メニュー非表示フラグ。 */
|
|
18
8
|
isMenuHide?: boolean;
|
|
19
|
-
/** Current active color theme. 現在のテーマ。 */
|
|
20
9
|
theme: Theme;
|
|
21
|
-
/** Toggle handler for switching color theme. テーマ切り替えハンドラー。 */
|
|
22
10
|
toggleTheme: () => void;
|
|
23
|
-
/** Navigation callback when a menu item is selected. メニュー遷移ハンドラー。 */
|
|
24
11
|
onMenuNavigate?(payload: MenuNavigationPayload): void;
|
|
25
|
-
/** Controlled pinning state (optional). ピン留め状態制御プロップ。 */
|
|
26
12
|
isPinned?: boolean;
|
|
27
|
-
/** Custom width in pixels when menu is pinned (default: 240). ピン留め時の幅(px)。 */
|
|
28
13
|
pinnedWidth?: number;
|
|
29
|
-
/** Callback invoked when menu pin state changes. ピン留め状態変更時のコールバック。 */
|
|
30
14
|
onPinChange?(pinned: boolean, widthPx: number): void;
|
|
31
|
-
/** Controlled list of pinned submenu IDs. ピン留めサブメニューIDリスト(制御)。 */
|
|
32
15
|
pinnedSubmenuIds?: number[];
|
|
33
|
-
/** Initial list of pinned submenu IDs for uncontrolled mode. ピン留めサブメニューID初期リスト。 */
|
|
34
16
|
initialPinnedSubmenuIds?: number[];
|
|
35
|
-
/** Callback invoked when submenu pin state changes. サブメニューピン留め変更時コールバック。 */
|
|
36
17
|
onSubmenuPinChange?(pinnedIds: number[]): void;
|
|
37
|
-
/** Controls whether the Escape key closes the menu drawer. Escape キーによるメニュー閉鎖制御。 */
|
|
38
18
|
closeOnEscape?: boolean | "auto";
|
|
39
19
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Renders the primary collapsible and pinnable menu bar with drawer navigation.
|
|
42
|
-
* 折りたたみ・ピン留め対応のプライマリメニューバーコンポーネント。
|
|
43
|
-
*
|
|
44
|
-
* @param props - MenuBarProps configuration.
|
|
45
|
-
* @returns Rendered JSX element or null.
|
|
46
|
-
*/
|
|
47
20
|
export declare const MenuBar: (props: IMenuBarProps) => React.JSX.Element | null;
|
|
@@ -1,37 +1,12 @@
|
|
|
1
|
-
import { UserProfile } from
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Link component props interface.
|
|
5
|
-
* リンクコンポーネントのプロパティ。
|
|
6
|
-
*/
|
|
1
|
+
import { UserProfile } from "../types/user";
|
|
7
2
|
export interface LinkProps {
|
|
8
|
-
/** Link destination URL */
|
|
9
3
|
to: string;
|
|
10
|
-
/** CSS class name */
|
|
11
4
|
className?: string;
|
|
12
|
-
/** Link content */
|
|
13
5
|
children: React.ReactNode;
|
|
14
6
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Generic Link component that can be replaced with router-specific implementation.
|
|
17
|
-
* ルーター固有の実装で置き換え可能な汎用リンクコンポーネント。
|
|
18
|
-
*/
|
|
19
7
|
export declare const DefaultLink: ({ to, className, children }: LinkProps) => React.JSX.Element;
|
|
20
|
-
/**
|
|
21
|
-
* SessionInfo component props interface.
|
|
22
|
-
* ユーザーセッション情報を表示するコンポーネントのプロパティ。
|
|
23
|
-
*/
|
|
24
8
|
export interface SessionInfoProps {
|
|
25
|
-
/** User profile information from Google OAuth */
|
|
26
9
|
user: UserProfile | null;
|
|
27
|
-
/** Custom Link component (optional, defaults to DefaultLink) */
|
|
28
10
|
LinkComponent?: React.ComponentType<LinkProps>;
|
|
29
11
|
}
|
|
30
|
-
/**
|
|
31
|
-
* SessionInfo component for displaying user session information.
|
|
32
|
-
* Displays user profile photo, navigation links, and user name when authenticated.
|
|
33
|
-
*
|
|
34
|
-
* ユーザーセッション情報を表示するコンポーネント。
|
|
35
|
-
* 認証時にプロフィール写真、ナビゲーションリンク、ユーザー名を表示する。
|
|
36
|
-
*/
|
|
37
12
|
export declare const SessionInfo: (props: SessionInfoProps) => React.JSX.Element;
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import { Theme } from
|
|
2
|
-
import { IMenu, MenuNavigationPayload } from
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @interface IMenuContentProps
|
|
6
|
-
* @description Props for the MenuContent component.
|
|
7
|
-
* @description MenuContent コンポーネントの Props。
|
|
8
|
-
*/
|
|
1
|
+
import { Theme } from "../types";
|
|
2
|
+
import { IMenu, MenuNavigationPayload } from "./MenuList";
|
|
9
3
|
interface IMenuContentProps {
|
|
10
4
|
menuList: IMenu[];
|
|
11
5
|
menuHeader?: React.ReactNode;
|
|
@@ -18,12 +12,7 @@ interface IMenuContentProps {
|
|
|
18
12
|
onTogglePin(): void;
|
|
19
13
|
pinnedSubmenuIds: number[];
|
|
20
14
|
onToggleSubmenuPin(id: number): void;
|
|
15
|
+
ref: React.Ref<HTMLDivElement>;
|
|
21
16
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Renders the content of the menu bar.
|
|
24
|
-
* メニューバーのコンテンツを描画します。
|
|
25
|
-
* @param {IMenuContentProps} props - The properties for the MenuContent component.
|
|
26
|
-
* @returns {React.JSX.Element} The rendered menu content.
|
|
27
|
-
*/
|
|
28
17
|
export declare const MenuContent: (props: IMenuContentProps) => React.JSX.Element;
|
|
29
18
|
export {};
|
|
@@ -1,58 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
* @module MenuList
|
|
3
|
-
* @description This module provides the list of menu items.
|
|
4
|
-
* @description このモジュールは、メニュー項目のリストを提供します。
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* @interface ISubmenu
|
|
8
|
-
* @description Interface for a submenu item.
|
|
9
|
-
* @description サブメニュー項目のインターフェース。
|
|
10
|
-
* @property {number} subid - The unique ID of the submenu item. サブメニュー項目の一意の ID。
|
|
11
|
-
* @property {string} [title] - The title of the submenu item. サブメニュー項目のタイトル。
|
|
12
|
-
* @property {string} href - The URL for the submenu item. サブメニュー項目の URL。
|
|
13
|
-
* @property {string} [mark] - The type of mark to display next to the item. 項目の横に表示するマークの種類。
|
|
14
|
-
* @property {boolean} [new] - Whether the item is a new link. 新しいリンクかどうか。
|
|
15
|
-
*/
|
|
1
|
+
export type MenuBarPathMatch = "exact" | "prefix";
|
|
16
2
|
export interface ISubmenu {
|
|
17
3
|
subid: number;
|
|
18
4
|
title?: string;
|
|
19
5
|
href: string;
|
|
20
6
|
mark?: string;
|
|
21
7
|
new?: boolean;
|
|
8
|
+
match?: MenuBarPathMatch;
|
|
22
9
|
}
|
|
23
|
-
/**
|
|
24
|
-
* @interface IMenu
|
|
25
|
-
* @description Interface for a menu item.
|
|
26
|
-
* @description メニュー項目のインターフェース。
|
|
27
|
-
* @property {number} id - The unique ID of the menu item. メニュー項目の一意の ID。
|
|
28
|
-
* @property {string} title - The title of the menu item. メニュー項目のタイトル。
|
|
29
|
-
* @property {string} [href] - The URL for the menu item. メニュー項目の URL。
|
|
30
|
-
* @property {ISubmenu[]} submenu - The list of submenu items. サブメニュー項目のリスト。
|
|
31
|
-
*/
|
|
32
10
|
export interface IMenu {
|
|
33
11
|
id: number;
|
|
34
12
|
title: string;
|
|
35
13
|
href?: string;
|
|
36
14
|
submenu: ISubmenu[];
|
|
15
|
+
match?: MenuBarPathMatch;
|
|
37
16
|
}
|
|
38
|
-
/**
|
|
39
|
-
* @typedef MenuNavigationPayload
|
|
40
|
-
* @description Payload emitted when a menu or submenu item is selected.
|
|
41
|
-
* @description メニューまたはサブメニューが選択された際に発火するペイロード。
|
|
42
|
-
* @property {{ id: number; title: string; href?: string }} menu - Selected menu metadata.
|
|
43
|
-
* @property {{ subid: number; title?: string; href: string; mark?: string; new?: boolean }} [submenu] - Selected submenu metadata.
|
|
44
|
-
*/
|
|
45
17
|
export type MenuNavigationPayload = {
|
|
46
18
|
menu: Pick<IMenu, "id" | "title" | "href">;
|
|
47
19
|
submenu?: Pick<ISubmenu, "subid" | "title" | "href" | "mark" | "new">;
|
|
48
20
|
};
|
|
49
|
-
/**
|
|
50
|
-
* @interface IMenuListProps
|
|
51
|
-
* @description Props for the MenuList component.
|
|
52
|
-
* @description MenuList コンポーネントの Props。
|
|
53
|
-
* @property {IMenu[]} menuList - The list of menu items. メニュー項目のリスト。
|
|
54
|
-
* @property {(payload: MenuNavigationPayload) => void} [onMenuSelected] - Function to handle menu item selection. メニュー項目選択を処理する関数。
|
|
55
|
-
*/
|
|
56
21
|
interface IMenuListProps {
|
|
57
22
|
menuList: IMenu[];
|
|
58
23
|
onMenuSelected?(payload: MenuNavigationPayload): void;
|
|
@@ -1,25 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module OverlayShadow
|
|
3
|
-
* @description This module provides an overlay shadow component.
|
|
4
|
-
* @description このモジュールは、オーバーレイシャドウコンポーネントを提供します。
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* @interface IOverlayProps
|
|
8
|
-
* @description Props for the OverlayShadow component.
|
|
9
|
-
* @description OverlayShadow コンポーネントの Props。
|
|
10
|
-
* @property {boolean} isOverlay - Whether the overlay is visible. オーバーレイが表示されているかどうか。
|
|
11
|
-
* @property {() => void} [onOverlayClick] - Function to handle overlay click. オーバーレイのクリックを処理する関数。
|
|
12
|
-
*/
|
|
13
1
|
interface IOverlayProps {
|
|
14
2
|
isOverlay: boolean;
|
|
15
|
-
onOverlayClick
|
|
3
|
+
onOverlayClick(): void;
|
|
16
4
|
}
|
|
17
|
-
/**
|
|
18
|
-
* @function OverlayShadow
|
|
19
|
-
* @description A component that renders an overlay shadow.
|
|
20
|
-
* @description オーバーレイシャドウを描画するコンポーネント。
|
|
21
|
-
* @param {IOverlayProps} props - The props for the component.
|
|
22
|
-
* @returns {React.JSX.Element} The rendered overlay shadow.
|
|
23
|
-
*/
|
|
24
5
|
export declare const OverlayShadow: (props: IOverlayProps) => React.JSX.Element;
|
|
25
6
|
export {};
|
|
@@ -1,22 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
open?: boolean;
|
|
10
|
-
isOpen?: boolean;
|
|
11
|
-
onMenuOpen?: () => void;
|
|
12
|
-
onMenuClose?: () => void;
|
|
13
|
-
onToggle?: (nextOpen: boolean) => void;
|
|
14
|
-
theme?: Theme;
|
|
15
|
-
pinned?: boolean;
|
|
16
|
-
isPinned?: boolean;
|
|
17
|
-
className?: string;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* 4本線バーと✕印アニメーションの従前の仕様の MenuSwitch コンポーネント。
|
|
21
|
-
*/
|
|
22
|
-
export declare const MenuSwitch: (props: IMenuSwitchProps) => React.JSX.Element;
|
|
1
|
+
type MenuSwitchProps = {
|
|
2
|
+
isMenuOpen: boolean;
|
|
3
|
+
onMenuOpen(): void;
|
|
4
|
+
onMenuClose(): void;
|
|
5
|
+
ref: React.Ref<HTMLButtonElement>;
|
|
6
|
+
};
|
|
7
|
+
export declare const MenuSwitch: ({ isMenuOpen, onMenuOpen, onMenuClose, ref }: MenuSwitchProps) => React.JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -1,44 +1,9 @@
|
|
|
1
|
-
import { MenuPinChangeDetail } from
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Canonical Window CustomEvent name for menu pin state changes.
|
|
5
|
-
* メニューのピン留め状態変更を通知する正準 CustomEvent 名。
|
|
6
|
-
*/
|
|
1
|
+
import { MenuPinChangeDetail } from "../types";
|
|
7
2
|
export declare const MENU_PIN_CHANGE_EVENT = "aiquants-menu-pin-change";
|
|
8
|
-
/**
|
|
9
|
-
* Reads initial pin state safely from document CSS variable or localStorage.
|
|
10
|
-
* document の CSS 変数または localStorage から初期のピン留め状態を安全に読み取ります。
|
|
11
|
-
*
|
|
12
|
-
* @param defaultPinnedWidth - Default width in pixels when pinned (default: 240).
|
|
13
|
-
* @returns Current MenuPinChangeDetail object.
|
|
14
|
-
*/
|
|
15
3
|
export declare const getInitialPinState: (defaultPinnedWidth?: number) => MenuPinChangeDetail;
|
|
16
|
-
|
|
17
|
-
* Dispatches a CustomEvent on window with menu pin state details.
|
|
18
|
-
* window に対してメニューピン留め状態変更カスタムイベントを発行します。
|
|
19
|
-
*
|
|
20
|
-
* @param pinned - Whether the menu is pinned.
|
|
21
|
-
* @param width - The pinned width in pixels.
|
|
22
|
-
*/
|
|
23
|
-
export declare const dispatchMenuPinChange: (pinned: boolean, width: number) => void;
|
|
24
|
-
/**
|
|
25
|
-
* Options for subscribing to menu pin state changes.
|
|
26
|
-
* メニューピン留め状態購読時のオプション。
|
|
27
|
-
*/
|
|
4
|
+
export declare const dispatchMenuPinChange: (pinned: boolean, width: number, active?: boolean) => void;
|
|
28
5
|
export interface SubscribeMenuPinOptions {
|
|
29
|
-
/** Whether to immediately invoke the listener with the current state upon subscription (default: true). 購読開始時に即座に現在の状態を通知するかどうか。 */
|
|
30
6
|
immediate?: boolean;
|
|
31
|
-
/** Default width when pinned (default: 240). ピン留め時のデフォルト幅。 */
|
|
32
7
|
defaultPinnedWidth?: number;
|
|
33
8
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Subscribes a listener to menu pin state changes on window.
|
|
36
|
-
* Returns an unsubscription cleanup function.
|
|
37
|
-
* window 上のメニューピン留め状態変更イベントを購読します。
|
|
38
|
-
* クリーンアップ関数を返します。
|
|
39
|
-
*
|
|
40
|
-
* @param listener - Callback invoked on pin state changes.
|
|
41
|
-
* @param options - Subscription options (e.g. immediate notification).
|
|
42
|
-
* @returns Cleanup function to unsubscribe.
|
|
43
|
-
*/
|
|
44
9
|
export declare const subscribeMenuPinChange: (listener: (detail: MenuPinChangeDetail) => void, options?: SubscribeMenuPinOptions) => (() => void);
|
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
import { MenuPinChangeDetail } from
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Options for the useMenuPinState hook.
|
|
5
|
-
* useMenuPinState フックのオプション。
|
|
6
|
-
*/
|
|
1
|
+
import { MenuPinChangeDetail } from "../types";
|
|
7
2
|
export interface UseMenuPinStateOptions {
|
|
8
|
-
/** Initial pinned state for server-side rendering (default: false). SSR 時の初期ピン留め状態。 */
|
|
9
3
|
initialPinned?: boolean;
|
|
10
|
-
/** Default width when pinned in pixels (default: 240). デフォルトのピン留め幅。 */
|
|
11
4
|
defaultPinnedWidth?: number;
|
|
12
5
|
}
|
|
13
|
-
/**
|
|
14
|
-
* React hook for consuming components to seamlessly subscribe to menu pin state changes.
|
|
15
|
-
* Hydration-safe, re-render optimized via React 18 useSyncExternalStore.
|
|
16
|
-
* 利用側 React コンポーネントがメニューのピン留め状態および幅をシームレスに購読するためのカスタムフック。
|
|
17
|
-
*
|
|
18
|
-
* @param options - Optional initial pin state and width configuration.
|
|
19
|
-
* @returns Current MenuPinChangeDetail ({ pinned: boolean, width: number }).
|
|
20
|
-
*/
|
|
21
6
|
export declare const useMenuPinState: (options?: UseMenuPinStateOptions) => MenuPinChangeDetail;
|