@boost33/ui 0.4.1 → 0.5.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 +26 -1
- package/dist/components/AccountMenu/AccountMenu.d.ts +131 -0
- package/dist/components/AccountMenu/icons.d.ts +11 -0
- package/dist/components/AccountMenu/labels.d.ts +39 -0
- package/dist/components/AppShell/AppShell.d.ts +71 -18
- package/dist/components/AppShell/AppShell.js +33 -35
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/AppShell/AppShell.module.css.js +11 -11
- package/dist/components/AppSwitcher/AppSwitcher.d.ts +23 -1
- package/dist/components/AppSwitcher/AppSwitcher.js +49 -43
- package/dist/components/AppSwitcher/AppSwitcher.js.map +1 -1
- package/dist/components/AppSwitcher/AppSwitcher.module.css.js +36 -36
- package/dist/components/AppSwitcher/labels.js.map +1 -1
- package/dist/components/CommandPalette/CommandPalette.d.ts +127 -0
- package/dist/components/CommandPalette/icons.d.ts +8 -0
- package/dist/components/CommandPalette/labels.d.ts +94 -0
- package/dist/components/DotMatrixLoader/DotMatrixLoader.d.ts +21 -0
- package/dist/components/DotMatrixLoader/DotMatrixLoader.js +18 -0
- package/dist/components/DotMatrixLoader/DotMatrixLoader.js.map +1 -0
- package/dist/components/DotMatrixLoader/DotMatrixLoader.module.css.js +12 -0
- package/dist/components/DotMatrixLoader/DotMatrixLoader.module.css.js.map +1 -0
- package/dist/components/LaunchLoader/LaunchLoader.d.ts +54 -0
- package/dist/components/LaunchLoader/LaunchLoader.js +57 -0
- package/dist/components/LaunchLoader/LaunchLoader.js.map +1 -0
- package/dist/components/LaunchLoader/LaunchLoader.module.css.js +24 -0
- package/dist/components/LaunchLoader/LaunchLoader.module.css.js.map +1 -0
- package/dist/components/NotificationCenter/NotificationCenter.d.ts +162 -0
- package/dist/components/NotificationCenter/icons.d.ts +34 -0
- package/dist/components/NotificationCenter/labels.d.ts +112 -0
- package/dist/components/ProductMark/ProductMark.d.ts +54 -1
- package/dist/components/ProductMark/ProductMark.js +23 -19
- package/dist/components/ProductMark/ProductMark.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.d.ts +17 -3
- package/dist/components/Sidebar/Sidebar.js +5 -5
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/WalletChip/WalletChip.d.ts +150 -27
- package/dist/components/WalletChip/WalletChip.js +38 -35
- package/dist/components/WalletChip/WalletChip.js.map +1 -1
- package/dist/components/WalletChip/WalletChip.module.css.js +36 -36
- package/dist/components/WalletChip/labels.d.ts +65 -47
- package/dist/components/WalletChip/labels.js +15 -20
- package/dist/components/WalletChip/labels.js.map +1 -1
- package/dist/fill-template.d.ts +10 -0
- package/dist/fill-template.js +10 -0
- package/dist/fill-template.js.map +1 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +36 -39
- package/dist/index.js.map +1 -1
- package/dist/launch-gradient.d.ts +36 -0
- package/dist/launch-gradient.js +8 -0
- package/dist/launch-gradient.js.map +1 -0
- package/dist/nav-height-budget.d.ts +4 -0
- package/dist/nav-height-budget.js +18 -19
- package/dist/nav-height-budget.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/theme.css +40 -14
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
The app shell every Boost33 product renders inside. Fourteen components and the
|
|
4
4
|
design tokens they are drawn with, published to public npm. Eleven ship today;
|
|
5
5
|
AccountMenu, NotificationCenter and CommandPalette are in v1's scope and not
|
|
6
|
-
built yet.
|
|
6
|
+
built yet. Outside the shell, it also ships `LaunchLoader`, the screen a product
|
|
7
|
+
shows while another one opens, and the `DotMatrixLoader` it is drawn with.
|
|
7
8
|
|
|
8
9
|
Built because the product switcher and the wallet chip worked in the Wallet and
|
|
9
10
|
nowhere else, and because a sidebar fixed in one app fixed nothing in the other
|
|
@@ -43,3 +44,27 @@ Watch for real test output in the log, not the agent's summary of it.
|
|
|
43
44
|
|
|
44
45
|
The Wallet migrates first, Score second. **No 1.0 until both render from this
|
|
45
46
|
package** — until then it has one consumer, which is a fork with a version number.
|
|
47
|
+
|
|
48
|
+
## The launch screen
|
|
49
|
+
|
|
50
|
+
`LaunchLoader` is presentational. The page that renders it owns the timing and
|
|
51
|
+
passes in a `caption` and a `progress` (0–100, or `null` while there is no
|
|
52
|
+
honest number). The background is built in and the same for every product:
|
|
53
|
+
black, with one blurred brand-orange glow rising from below the middle of the
|
|
54
|
+
screen. There are no product colours to pass; `stops` is deprecated and ignored
|
|
55
|
+
since 0.5.1. The caption sits straight on the glow, and
|
|
56
|
+
`npm run launch-contrast-check` measures it.
|
|
57
|
+
|
|
58
|
+
The glow is drawn by the package stylesheet and takes no prop, so both sides of
|
|
59
|
+
a hop paint the identical screen. Inline the ground colour too, so the instant
|
|
60
|
+
before the stylesheet and the markup arrive is already black rather than white:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { LaunchLoader, launchBackdropCss } from '@boost33/ui'
|
|
64
|
+
|
|
65
|
+
<style dangerouslySetInnerHTML={{ __html: launchBackdropCss() }} />
|
|
66
|
+
<LaunchLoader caption={caption} progress={progress} />
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
An animated backdrop, when one exists, goes in the `backdrop` slot. The screen
|
|
70
|
+
must stay complete without it. See `CHANGELOG.md` (B33-1389, B33-1401) for the full contract.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { ElementType } from 'react';
|
|
2
|
+
import { AccountMenuIcon } from './icons';
|
|
3
|
+
import { AccountMenuLabels } from './labels';
|
|
4
|
+
/**
|
|
5
|
+
* The signed-in person, at the top of the panel. Supplied by the consumer -- the
|
|
6
|
+
* menu reads no session and fetches nothing (CLAUDE.md).
|
|
7
|
+
*/
|
|
8
|
+
export interface AccountMenuPerson {
|
|
9
|
+
/** Their name, e.g. "Sofia Marques". */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Their email, on the line beneath. */
|
|
12
|
+
email: string;
|
|
13
|
+
/**
|
|
14
|
+
* An avatar image. Omit it -- the drawing does -- and the circle draws bare, in
|
|
15
|
+
* `--color-border-strong`.
|
|
16
|
+
*
|
|
17
|
+
* The image is decorative when it renders: it takes an empty `alt`, because the
|
|
18
|
+
* person's name is on the line beside it and a name here would make a screen
|
|
19
|
+
* reader say who you are twice. Same reasoning as `ProductMark`'s default.
|
|
20
|
+
*/
|
|
21
|
+
avatarUrl?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The organisation strip under the person: which account you are acting in.
|
|
25
|
+
* Identity's data, supplied per render, never looked up here (ADR-0003).
|
|
26
|
+
*/
|
|
27
|
+
export interface AccountMenuOrg {
|
|
28
|
+
/** The organisation's name, e.g. "Acme Coaching". */
|
|
29
|
+
name: string;
|
|
30
|
+
/** The initials on the tile beside it, e.g. "AC". Decorative -- the name is next to it. */
|
|
31
|
+
initials: string;
|
|
32
|
+
/**
|
|
33
|
+
* The role pill at the right of the strip, e.g. "Admin". Omit it and no pill is
|
|
34
|
+
* drawn -- it is Identity's word for what this person is in this organisation,
|
|
35
|
+
* so a consumer with nothing to say there says nothing rather than guessing.
|
|
36
|
+
*/
|
|
37
|
+
role?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* One row in the panel.
|
|
41
|
+
*
|
|
42
|
+
* Whether it navigates is decided by `href` and nothing else, which is ADR-0011's
|
|
43
|
+
* rule applied rather than re-decided: a row with an `href` is a link and renders
|
|
44
|
+
* through `linkComponent`; a row without one is an action and stays a `<button>`.
|
|
45
|
+
* That keeps the component out of the business of knowing which of Profile,
|
|
46
|
+
* Settings, Switch account and Sign out are navigations in a given product --
|
|
47
|
+
* four apps may answer differently, and the consumer already knows.
|
|
48
|
+
*/
|
|
49
|
+
export interface AccountMenuItem {
|
|
50
|
+
/** Stable identity: the React key, and what a test addresses the row by. */
|
|
51
|
+
id: string;
|
|
52
|
+
/** The row's name, e.g. "Profile". */
|
|
53
|
+
label: string;
|
|
54
|
+
/**
|
|
55
|
+
* The line beneath the name. Omit it and the row is a single line, set in the
|
|
56
|
+
* quieter register the drawing gives its Sign out row -- see `.rowLabel` in the
|
|
57
|
+
* stylesheet for why the two travel together.
|
|
58
|
+
*/
|
|
59
|
+
description?: string;
|
|
60
|
+
/** Where the row goes. Omit it and the row is an action, rendered as a button. */
|
|
61
|
+
href?: string;
|
|
62
|
+
/** Which glyph the row carries. Omit it and the glyph lane stays, empty. */
|
|
63
|
+
icon?: AccountMenuIcon;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A run of rows drawn together. The panel's hairline is drawn above every group,
|
|
67
|
+
* which is what separates the first group from the identity blocks above it as
|
|
68
|
+
* well as each group from the last.
|
|
69
|
+
*
|
|
70
|
+
* Grouped rather than flat because the drawing puts a rule between its three
|
|
71
|
+
* navigations and its one way out, and grouped rather than named (`items` plus a
|
|
72
|
+
* `footer`) because "footer" would be this package inventing a meaning for the
|
|
73
|
+
* second group. A consumer that wants one group passes one.
|
|
74
|
+
*/
|
|
75
|
+
export interface AccountMenuGroup {
|
|
76
|
+
/** Stable identity: the React key. */
|
|
77
|
+
id: string;
|
|
78
|
+
/** The rows, in the order they are drawn. */
|
|
79
|
+
items: AccountMenuItem[];
|
|
80
|
+
}
|
|
81
|
+
export interface AccountMenuProps {
|
|
82
|
+
/** The signed-in person. */
|
|
83
|
+
person: AccountMenuPerson;
|
|
84
|
+
/** The organisation this person is acting in. */
|
|
85
|
+
org: AccountMenuOrg;
|
|
86
|
+
/** The row groups, in order. A hairline is drawn above each one. */
|
|
87
|
+
groups: AccountMenuGroup[];
|
|
88
|
+
/**
|
|
89
|
+
* Every user-facing string that is not row data -- which is the panel's
|
|
90
|
+
* accessible name, and today nothing else. Each field defaults to English, and
|
|
91
|
+
* a field left out, or supplied as `undefined` (what a missed next-intl key
|
|
92
|
+
* looks like), keeps its default.
|
|
93
|
+
*/
|
|
94
|
+
labels?: Partial<AccountMenuLabels>;
|
|
95
|
+
/**
|
|
96
|
+
* The element a row with an `href` renders through. Defaults to a plain `a`; a
|
|
97
|
+
* router-owning consumer passes its own so the rows route client-side with the
|
|
98
|
+
* locale prefix intact (ADR-0011, B33-847). Rows without an `href` are buttons
|
|
99
|
+
* and ignore it.
|
|
100
|
+
*/
|
|
101
|
+
linkComponent?: ElementType;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The account menu: who you are, which organisation you are in, and the way into
|
|
105
|
+
* Settings and out of the session (B33-493).
|
|
106
|
+
*
|
|
107
|
+
* **This is Stage A, and this component is the panel alone.** It takes no
|
|
108
|
+
* trigger, holds no open state and wires nothing. That is not a half-built
|
|
109
|
+
* version of trigger-plus-panel -- it is the part of this component that the two
|
|
110
|
+
* reference drawings agree about. Paper's 004 "AccountMenu — popover" and
|
|
111
|
+
* Identity's 103 · 11 Account menu are identical in every property of the panel,
|
|
112
|
+
* and they disagree about the anchor (004 draws a bell on a tile, an unread dot
|
|
113
|
+
* and a halo behind the avatar; 103 draws none of the three) and about where the
|
|
114
|
+
* panel sits relative to it (004: 8px below, 16px right inset, overhanging the
|
|
115
|
+
* avatar by 4px; 103: 10px below, 20px inset, flush). CLAUDE.md puts that choice
|
|
116
|
+
* with the person at the fidelity gate, and says plainly that an agent picking
|
|
117
|
+
* one has invented a referent. So the anchor is not here, `TopBar` is untouched,
|
|
118
|
+
* and the specimen positions the panel in its own stylesheet.
|
|
119
|
+
*
|
|
120
|
+
* A disclosure, like the switcher (ADR-0010): no widget role on the panel, no
|
|
121
|
+
* role on the rows, every row the real `<a>` or `<button>` it already is, and Tab
|
|
122
|
+
* the document's own order. Dismissal, focus restoration and the trigger's
|
|
123
|
+
* `aria-expanded` are the shared anchored-overlay contract's (ADR-0009) and
|
|
124
|
+
* arrive with the trigger in Stage B.
|
|
125
|
+
*
|
|
126
|
+
* Nothing here is authoritative. Every measurement in the stylesheet is read off
|
|
127
|
+
* a drawing that no human has approved, and the gate is where it becomes a
|
|
128
|
+
* referent -- at which point the approved baseline and the measurements written
|
|
129
|
+
* into the spec are what binds, not Paper.
|
|
130
|
+
*/
|
|
131
|
+
export declare function AccountMenu({ person, org, groups, labels, linkComponent }: AccountMenuProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Which glyph a row carries. A closed union, unlike `ProductMark`'s `slug`:
|
|
4
|
+
* these four are the package's own chrome rather than a registry Identity owns
|
|
5
|
+
* and can run ahead of, so there is no served value to degrade from. A row that
|
|
6
|
+
* wants no glyph omits the field and keeps the empty lane -- see `.lane` in
|
|
7
|
+
* AccountMenu.module.css for why the lane stays.
|
|
8
|
+
*/
|
|
9
|
+
export type AccountMenuIcon = 'person' | 'settings' | 'switch' | 'signOut';
|
|
10
|
+
/** The glyph for each name, keyed so the row renders one lookup and no switch. */
|
|
11
|
+
export declare const ACCOUNT_MENU_ICONS: Record<AccountMenuIcon, (className?: string) => ReactElement>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Every user-facing string the account menu renders that is not row data. */
|
|
2
|
+
export interface AccountMenuLabels {
|
|
3
|
+
/**
|
|
4
|
+
* The panel's accessible name. A disclosure panel carries no widget role, so
|
|
5
|
+
* an `aria-label` is the only thing that names it (ADR-0010). Default
|
|
6
|
+
* "Account".
|
|
7
|
+
*
|
|
8
|
+
* It names the control rather than the person in it: the person's name and
|
|
9
|
+
* email are inside the panel and a screen reader reaches them on the way
|
|
10
|
+
* through, so putting them in the name as well says them twice.
|
|
11
|
+
*/
|
|
12
|
+
panel: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The English defaults. A consumer that passes nothing renders English.
|
|
16
|
+
*
|
|
17
|
+
* Frozen because it is a public export reaching four products on a version bump:
|
|
18
|
+
* `const` protects the binding and not the contents, so without the freeze a
|
|
19
|
+
* consumer aliasing it -- `const mine = defaultAccountMenuLabels; mine.panel =
|
|
20
|
+
* 'Compte'` -- renames every account menu in the process (B33-877). The freeze
|
|
21
|
+
* carries no `: AccountMenuLabels` annotation on purpose; the annotation widens
|
|
22
|
+
* `Readonly<...>` back to mutable in the emitted declaration file and makes the
|
|
23
|
+
* freeze runtime-only (B33-916). `satisfies` checks the shape without widening.
|
|
24
|
+
*/
|
|
25
|
+
export declare const defaultAccountMenuLabels: Readonly<{
|
|
26
|
+
panel: string;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* Merge a consumer's partial `labels` onto the English defaults, treating an
|
|
30
|
+
* explicitly-`undefined` field as unsupplied -- the same resolution
|
|
31
|
+
* `resolveWalletChipLabels` and `resolveAppSwitcherLabels` perform.
|
|
32
|
+
*
|
|
33
|
+
* A next-intl `t('...')` returns `undefined` on a missed key, so
|
|
34
|
+
* `{ panel: undefined }` is what one missing translation looks like at the call
|
|
35
|
+
* site. A plain object spread copies that `undefined` across and leaves the
|
|
36
|
+
* panel with an empty `aria-label`, which is the one thing naming it; a per-field
|
|
37
|
+
* skip does not (B33-873).
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveAccountMenuLabels(labels?: Partial<AccountMenuLabels>): AccountMenuLabels;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ElementType, ReactNode } from 'react';
|
|
2
2
|
import { TopBarProps } from '../TopBar/TopBar';
|
|
3
|
-
import { PageHeaderProps } from '../PageHeader/PageHeader';
|
|
4
3
|
/** One nav row, described as data. The consumer never hands the shell markup. */
|
|
5
4
|
export interface AppShellNavItem {
|
|
6
5
|
/** The row's name. Truncates rather than wrapping; on the rail it collapses to the accessibility tree. */
|
|
@@ -25,24 +24,51 @@ export interface AppShellNavSection {
|
|
|
25
24
|
}
|
|
26
25
|
export interface AppShellProps {
|
|
27
26
|
/**
|
|
28
|
-
* The
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* The accessible name of the sidebar landmark. A display string in the
|
|
28
|
+
* consumer's own language -- usually the product's name -- that reaches
|
|
29
|
+
* `aria-label` on the `<aside>` and is never rendered as text.
|
|
30
|
+
*
|
|
31
|
+
* **It is an accessible name, not product-awareness.** This prop was called
|
|
32
|
+
* `product` and its comment claimed it was "the whole of the shell's
|
|
33
|
+
* product-awareness" (B33-1272). Nothing branches on it, nothing is looked up
|
|
34
|
+
* with it, and no artwork or copy changes because of it -- so the comment
|
|
35
|
+
* described a mechanism that did not exist and sent the next reader looking
|
|
36
|
+
* for the rest of it. The shell is product-aware through `nav`, `switcher`,
|
|
37
|
+
* `wallet` and `footer`, which are the product's own data.
|
|
38
|
+
*
|
|
39
|
+
* **Not the Identity slug.** `AppSwitcher.currentSlug` takes that
|
|
40
|
+
* (`"wallet"`, `"boostscore"`); this takes what a person would read
|
|
41
|
+
* ("Wallet"). The shell carries two names for one product and nothing in the
|
|
42
|
+
* types connects them, so check which kind of string each prop wants.
|
|
32
43
|
*/
|
|
33
|
-
|
|
44
|
+
label: string;
|
|
45
|
+
/**
|
|
46
|
+
* The accessible name of the navigation landmark inside the sidebar.
|
|
47
|
+
* **Defaults to `label`, which is today's behaviour and not the fix.**
|
|
48
|
+
*
|
|
49
|
+
* Pass a distinct string here. Without one the `<aside>` and the `<nav>`
|
|
50
|
+
* nested inside it carry the same accessible name, and a screen-reader user
|
|
51
|
+
* moving by landmark hears "Wallet complementary" then "Wallet navigation" --
|
|
52
|
+
* the name doing none of the distinguishing that is the one job a landmark
|
|
53
|
+
* label has (B33-1272). Something like "Wallet sections" or "Wallet pages",
|
|
54
|
+
* in your language, resolves it.
|
|
55
|
+
*
|
|
56
|
+
* **The package cannot default this to anything better, which is why the
|
|
57
|
+
* fallback is the default.** A sensible default would have to compose a word
|
|
58
|
+
* -- "navigation", "sections" -- and that word is user-facing text a screen
|
|
59
|
+
* reader reads aloud, so it would have to be in the consumer's language. Both
|
|
60
|
+
* consumers default to French. This package hardcodes no user-facing English
|
|
61
|
+
* (`tests/no-hardcoded-strings.test.ts`), and a composed English default would
|
|
62
|
+
* ship an untranslatable word into assistive output for every consumer that
|
|
63
|
+
* never noticed the prop. Repeating `label` is at least a name the consumer
|
|
64
|
+
* chose and wrote in their own language. So the duplicate name is only fully
|
|
65
|
+
* resolved by a consumer passing this.
|
|
66
|
+
*/
|
|
67
|
+
navLabel?: string;
|
|
34
68
|
/** The navigation, as data. No product hardcodes rows; the shell composes them. */
|
|
35
69
|
nav: AppShellNavSection[];
|
|
36
70
|
/** The top bar's content -- the breadcrumb and the shell actions on its right. */
|
|
37
71
|
topBar: TopBarProps;
|
|
38
|
-
/**
|
|
39
|
-
* The page header's content -- the page title, its optional subtitle and
|
|
40
|
-
* trailing controls. Omit it for a screen that has no title: the Wallet's own
|
|
41
|
-
* overview is one, which is why its 9 Sep baseline shows the top bar's
|
|
42
|
-
* hairline sitting directly on the body (B33-750). The header draws no border
|
|
43
|
-
* of its own, so dropping it leaves that hairline in place rather than a gap.
|
|
44
|
-
*/
|
|
45
|
-
pageHeader?: PageHeaderProps;
|
|
46
72
|
/** The Boost33 mark for the wordmark lockup. Passed in, the way Wordmark takes it; on the rail it is all that shows. */
|
|
47
73
|
mark?: ReactNode;
|
|
48
74
|
/**
|
|
@@ -67,14 +93,41 @@ export interface AppShellProps {
|
|
|
67
93
|
collapsed?: boolean;
|
|
68
94
|
/**
|
|
69
95
|
* The element every linked nav row renders through. Defaults to a plain `a`;
|
|
70
|
-
* a consumer whose router owns navigation passes its own
|
|
71
|
-
*
|
|
96
|
+
* a consumer whose router owns navigation passes its own here and the shell
|
|
97
|
+
* threads it to every row of `nav`, so the whole nav goes client-side with the
|
|
72
98
|
* locale prefix intact rather than full-page-reloading (B33-847). This is the
|
|
73
99
|
* shell's one nod to how a consumer routes -- it renders the element, never
|
|
74
100
|
* knowing what it is. See docs/adr/0011.
|
|
101
|
+
*
|
|
102
|
+
* **It reaches the rows of `nav` and nothing else.** The slots (`switcher`,
|
|
103
|
+
* `wallet`, `footer`, `topBar.actions`) are `ReactNode`s the consumer has
|
|
104
|
+
* already created, so the shell cannot put a prop on what is inside one. A
|
|
105
|
+
* consumer composing a link-bearing component into a slot -- `AppSwitcher`,
|
|
106
|
+
* `AccountMenu`, `NotificationCenter` -- passes `linkComponent` to that
|
|
107
|
+
* component as well, or its rows stay bare anchors and reload the page. The
|
|
108
|
+
* Wallet shipped exactly that defect (B33-1281) by reading this prop as the
|
|
109
|
+
* single place, which is the 2026-10-06 correction on docs/adr/0011.
|
|
75
110
|
*/
|
|
76
111
|
linkComponent?: ElementType;
|
|
77
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* The page body. Sits on white, below the top bar, and the shell renders it
|
|
114
|
+
* inside the page's `<main>` -- so a consumer adds no wrapper of its own.
|
|
115
|
+
*
|
|
116
|
+
* **The page's title goes here too.** A screen with a title renders
|
|
117
|
+
* `PageHeader` as the first thing in its own body; a screen with none renders
|
|
118
|
+
* nothing and the top bar's hairline sits straight on the content, which is
|
|
119
|
+
* what the Wallet's overview does and what its 9 Sep baseline shows. The
|
|
120
|
+
* shell used to take a `pageHeader` prop and draw the header itself, above
|
|
121
|
+
* this; B33-1267 removed it, because no consumer ever passed it and Score
|
|
122
|
+
* forbids it by test (the page below owns the sole `h1`).
|
|
123
|
+
*
|
|
124
|
+
* **Everything here scrolls.** This box is the shell's one scrolling element,
|
|
125
|
+
* so the sidebar and top bar stay pinned and the page header goes up with the
|
|
126
|
+
* body. The removed prop sat outside the scroll box and pinned the header
|
|
127
|
+
* instead -- the one real difference between the two spellings, and the
|
|
128
|
+
* reason the choice was worth collapsing to one rather than leaving to a
|
|
129
|
+
* consumer to discover on a long page.
|
|
130
|
+
*/
|
|
78
131
|
children: ReactNode;
|
|
79
132
|
}
|
|
80
|
-
export declare function AppShell({
|
|
133
|
+
export declare function AppShell({ label, navLabel, nav, topBar, mark, switcher, wallet, footer, collapsed, linkComponent, children, }: AppShellProps): import("react").JSX.Element;
|
|
@@ -1,59 +1,57 @@
|
|
|
1
1
|
import { jsxs as i, jsx as r } from "react/jsx-runtime";
|
|
2
2
|
import { Sidebar as b } from "../Sidebar/Sidebar.js";
|
|
3
|
-
import { NavSection as
|
|
4
|
-
import { NavItem as
|
|
5
|
-
import { TopBar as
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
import { NavSection as k } from "../NavSection/NavSection.js";
|
|
4
|
+
import { NavItem as x } from "../NavItem/NavItem.js";
|
|
5
|
+
import { TopBar as S } from "../TopBar/TopBar.js";
|
|
6
|
+
import { Wordmark as g } from "../Wordmark/Wordmark.js";
|
|
7
|
+
import a from "./AppShell.module.css.js";
|
|
8
|
+
function T({
|
|
9
|
+
label: e,
|
|
10
|
+
navLabel: t,
|
|
11
11
|
nav: d,
|
|
12
12
|
topBar: h,
|
|
13
|
-
pageHeader: m,
|
|
14
13
|
mark: v,
|
|
15
|
-
switcher:
|
|
16
|
-
wallet:
|
|
17
|
-
footer:
|
|
18
|
-
collapsed:
|
|
14
|
+
switcher: m,
|
|
15
|
+
wallet: o,
|
|
16
|
+
footer: c,
|
|
17
|
+
collapsed: l = !1,
|
|
19
18
|
linkComponent: f,
|
|
20
19
|
children: p
|
|
21
20
|
}) {
|
|
22
|
-
return /* @__PURE__ */ i("div", { className:
|
|
23
|
-
/* @__PURE__ */ i(b, {
|
|
24
|
-
/* @__PURE__ */ r("div", { className:
|
|
25
|
-
|
|
26
|
-
/* @__PURE__ */ r("nav", { "aria-label": e, className:
|
|
21
|
+
return /* @__PURE__ */ i("div", { className: a.shell, children: [
|
|
22
|
+
/* @__PURE__ */ i(b, { landmarkLabel: e, collapsed: l, children: [
|
|
23
|
+
/* @__PURE__ */ r("div", { className: a.wordmark, children: /* @__PURE__ */ r(g, { mark: v, collapsed: l }) }),
|
|
24
|
+
m != null && /* @__PURE__ */ r("div", { className: a.switcher, children: m }),
|
|
25
|
+
/* @__PURE__ */ r("nav", { "aria-label": t ?? e, className: a.nav, children: d.map((s, u) => (
|
|
27
26
|
// No room for a header at 56px, so on the rail every section renders
|
|
28
27
|
// headerless -- its rows collapse to their icons and line up.
|
|
29
|
-
/* @__PURE__ */ r(
|
|
30
|
-
|
|
28
|
+
/* @__PURE__ */ r(k, { label: l ? void 0 : s.label, children: s.items.map((n, N) => /* @__PURE__ */ r(
|
|
29
|
+
x,
|
|
31
30
|
{
|
|
32
|
-
icon:
|
|
33
|
-
active:
|
|
34
|
-
collapsed:
|
|
35
|
-
count:
|
|
36
|
-
countUrgent:
|
|
37
|
-
href:
|
|
31
|
+
icon: n.icon,
|
|
32
|
+
active: n.active,
|
|
33
|
+
collapsed: l,
|
|
34
|
+
count: n.count,
|
|
35
|
+
countUrgent: n.countUrgent,
|
|
36
|
+
href: n.href,
|
|
38
37
|
linkComponent: f,
|
|
39
|
-
children:
|
|
38
|
+
children: n.label
|
|
40
39
|
},
|
|
41
40
|
N
|
|
42
41
|
)) }, u)
|
|
43
42
|
)) }),
|
|
44
|
-
(
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
(o != null || c != null) && /* @__PURE__ */ i("div", { className: a.foot, children: [
|
|
44
|
+
o,
|
|
45
|
+
c
|
|
47
46
|
] })
|
|
48
47
|
] }),
|
|
49
|
-
/* @__PURE__ */ i("div", { className:
|
|
50
|
-
/* @__PURE__ */ r(
|
|
51
|
-
|
|
52
|
-
/* @__PURE__ */ r("div", { className: l.content, children: p })
|
|
48
|
+
/* @__PURE__ */ i("div", { className: a.main, children: [
|
|
49
|
+
/* @__PURE__ */ r(S, { ...h }),
|
|
50
|
+
/* @__PURE__ */ r("main", { className: a.content, children: p })
|
|
53
51
|
] })
|
|
54
52
|
] });
|
|
55
53
|
}
|
|
56
54
|
export {
|
|
57
|
-
|
|
55
|
+
T as AppShell
|
|
58
56
|
};
|
|
59
57
|
//# sourceMappingURL=AppShell.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppShell.js","sources":["../../../src/components/AppShell/AppShell.tsx"],"sourcesContent":["import type { ElementType, ReactNode } from 'react'\nimport { Sidebar } from '../Sidebar/Sidebar'\nimport { NavSection } from '../NavSection/NavSection'\nimport { NavItem } from '../NavItem/NavItem'\nimport { TopBar, type TopBarProps } from '../TopBar/TopBar'\nimport { PageHeader, type PageHeaderProps } from '../PageHeader/PageHeader'\nimport { Wordmark } from '../Wordmark/Wordmark'\nimport styles from './AppShell.module.css'\n\n/** One nav row, described as data. The consumer never hands the shell markup. */\nexport interface AppShellNavItem {\n /** The row's name. Truncates rather than wrapping; on the rail it collapses to the accessibility tree. */\n label: string\n /** The leading icon. Decorative -- the label carries the name. On the rail it is all that shows. */\n icon?: ReactNode\n /** Marks this row as the current page. Sets `aria-current=\"page\"`. */\n active?: boolean\n /** Trailing-slot count. Omit to leave the slot empty; it still reserves its width so counts line up. */\n count?: number\n /** Renders `count` in the primary color instead of muted. */\n countUrgent?: boolean\n /** Where the row goes. A nav row's destination is data; the shell renders it as a link. */\n href?: string\n}\n\n/** One nav section: an optional header over a run of rows. */\nexport interface AppShellNavSection {\n /** The section header. Omit for a headerless section (the nav's first section has none). On the rail every header drops. */\n label?: string\n /** The section's rows, as data. */\n items: AppShellNavItem[]\n}\n\nexport interface AppShellProps {\n /**\n * The product this shell renders for. The consumer names it once, here, and\n * the shell carries it down -- it labels the sidebar and its navigation, and\n * nothing above the nav (the Boost33 wordmark and brand) changes between\n * products. This one prop is the whole of the shell's product-awareness.\n */\n product: string\n /** The navigation, as data. No product hardcodes rows; the shell composes them. */\n nav: AppShellNavSection[]\n /** The top bar's content -- the breadcrumb and the shell actions on its right. */\n topBar: TopBarProps\n /**\n * The page header's content -- the page title, its optional subtitle and\n * trailing controls. Omit it for a screen that has no title: the Wallet's own\n * overview is one, which is why its 9 Sep baseline shows the top bar's\n * hairline sitting directly on the body (B33-750). The header draws no border\n * of its own, so dropping it leaves that hairline in place rather than a gap.\n */\n pageHeader?: PageHeaderProps\n /** The Boost33 mark for the wordmark lockup. Passed in, the way Wordmark takes it; on the rail it is all that shows. */\n mark?: ReactNode\n /**\n * The product switcher, sitting under the wordmark. A slot: the registry it\n * draws is served by Identity (ADR-0003), not owned here. Render its own\n * collapsed form for the rail.\n */\n switcher?: ReactNode\n /**\n * The wallet chip, pinned above the footer. A slot: the balance comes from\n * `@boost33/ledger-client`, never read here. Render its own collapsed form\n * for the rail.\n */\n wallet?: ReactNode\n /** The sidebar footer, below the wallet chip. A slot -- the product's own. */\n footer?: ReactNode\n /**\n * The sidebar collapsed to the 56px rail. A state of the shell: the sidebar\n * narrows and every piece it owns collapses off this one flag. The slots\n * (switcher, wallet, footer) render their own collapsed form.\n */\n collapsed?: boolean\n /**\n * The element every linked nav row renders through. Defaults to a plain `a`;\n * a consumer whose router owns navigation passes its own once here and the\n * shell threads it to every row, so the whole nav goes client-side with the\n * locale prefix intact rather than full-page-reloading (B33-847). This is the\n * shell's one nod to how a consumer routes -- it renders the element, never\n * knowing what it is. See docs/adr/0011.\n */\n linkComponent?: ElementType\n /** The page body. Sits on white, below the header. */\n children: ReactNode\n}\n\n// The shell every authenticated Boost33 screen renders inside. It composes the\n// pieces built in the earlier tickets -- sidebar, wordmark, switcher, nav,\n// wallet chip, top bar, page header -- into one thing a product imports instead\n// of building a sidebar. Product-aware only through `product`; everything below\n// that is the product's own data, and nothing above it changes.\nexport function AppShell({\n product,\n nav,\n topBar,\n pageHeader,\n mark,\n switcher,\n wallet,\n footer,\n collapsed = false,\n linkComponent,\n children,\n}: AppShellProps) {\n return (\n <div className={styles.shell}>\n <Sidebar label={product} collapsed={collapsed}>\n <div className={styles.wordmark}>\n <Wordmark mark={mark} collapsed={collapsed} />\n </div>\n {switcher != null && <div className={styles.switcher}>{switcher}</div>}\n <nav aria-label={product} className={styles.nav}>\n {nav.map((section, sectionIndex) => (\n // No room for a header at 56px, so on the rail every section renders\n // headerless -- its rows collapse to their icons and line up.\n <NavSection key={sectionIndex} label={collapsed ? undefined : section.label}>\n {section.items.map((item, itemIndex) => (\n <NavItem\n key={itemIndex}\n icon={item.icon}\n active={item.active}\n collapsed={collapsed}\n count={item.count}\n countUrgent={item.countUrgent}\n href={item.href}\n linkComponent={linkComponent}\n >\n {item.label}\n </NavItem>\n ))}\n </NavSection>\n ))}\n </nav>\n {(wallet != null || footer != null) && (\n <div className={styles.foot}>\n {wallet}\n {footer}\n </div>\n )}\n </Sidebar>\n <div className={styles.main}>\n <TopBar {...topBar} />\n {pageHeader != null && <PageHeader {...pageHeader} />}\n <div className={styles.content}>{children}</div>\n </div>\n </div>\n )\n}\n"],"names":["AppShell","product","nav","topBar","pageHeader","mark","switcher","wallet","footer","collapsed","linkComponent","children","jsxs","styles","Sidebar","jsx","Wordmark","section","sectionIndex","NavSection","item","itemIndex","NavItem","TopBar","PageHeader"],"mappings":";;;;;;;;AA6FO,SAASA,EAAS;AAAA,EACvB,SAAAC;AAAA,EACA,KAAAC;AAAA,EACA,QAAAC;AAAA,EACA,YAAAC;AAAA,EACA,MAAAC;AAAA,EACA,UAAAC;AAAA,EACA,QAAAC;AAAA,EACA,QAAAC;AAAA,EACA,WAAAC,IAAY;AAAA,EACZ,eAAAC;AAAA,EACA,UAAAC;AACF,GAAkB;AAChB,SACE,gBAAAC,EAAC,OAAA,EAAI,WAAWC,EAAO,OACrB,UAAA;AAAA,IAAA,gBAAAD,EAACE,GAAA,EAAQ,OAAOb,GAAS,WAAAQ,GACvB,UAAA;AAAA,MAAA,gBAAAM,EAAC,OAAA,EAAI,WAAWF,EAAO,UACrB,4BAACG,GAAA,EAAS,MAAAX,GAAY,WAAAI,GAAsB,EAAA,CAC9C;AAAA,MACCH,KAAY,QAAQ,gBAAAS,EAAC,SAAI,WAAWF,EAAO,UAAW,UAAAP,GAAS;AAAA,MAChE,gBAAAS,EAAC,OAAA,EAAI,cAAYd,GAAS,WAAWY,EAAO,KACzC,UAAAX,EAAI,IAAI,CAACe,GAASC;AAAA;AAAA;AAAA,QAGjB,gBAAAH,EAACI,GAAA,EAA8B,OAAOV,IAAY,SAAYQ,EAAQ,OACnE,UAAAA,EAAQ,MAAM,IAAI,CAACG,GAAMC,MACxB,gBAAAN;AAAA,UAACO;AAAA,UAAA;AAAA,YAEC,MAAMF,EAAK;AAAA,YACX,QAAQA,EAAK;AAAA,YACb,WAAAX;AAAA,YACA,OAAOW,EAAK;AAAA,YACZ,aAAaA,EAAK;AAAA,YAClB,MAAMA,EAAK;AAAA,YACX,eAAAV;AAAA,YAEC,UAAAU,EAAK;AAAA,UAAA;AAAA,UATDC;AAAA,QAAA,CAWR,KAdcH,CAejB;AAAA,OACD,GACH;AAAA,OACEX,KAAU,QAAQC,KAAU,2BAC3B,OAAA,EAAI,WAAWK,EAAO,MACpB,UAAA;AAAA,QAAAN;AAAA,QACAC;AAAA,MAAA,EAAA,CACH;AAAA,IAAA,GAEJ;AAAA,IACA,gBAAAI,EAAC,OAAA,EAAI,WAAWC,EAAO,MACrB,UAAA;AAAA,MAAA,gBAAAE,EAACQ,GAAA,EAAQ,GAAGpB,GAAQ;AAAA,MACnBC,KAAc,QAAQ,gBAAAW,EAACS,GAAA,EAAY,GAAGpB,EAAA,CAAY;AAAA,MACnD,gBAAAW,EAAC,OAAA,EAAI,WAAWF,EAAO,SAAU,UAAAF,EAAA,CAAS;AAAA,IAAA,EAAA,CAC5C;AAAA,EAAA,GACF;AAEJ;"}
|
|
1
|
+
{"version":3,"file":"AppShell.js","sources":["../../../src/components/AppShell/AppShell.tsx"],"sourcesContent":["import type { ElementType, ReactNode } from 'react'\nimport { Sidebar } from '../Sidebar/Sidebar'\nimport { NavSection } from '../NavSection/NavSection'\nimport { NavItem } from '../NavItem/NavItem'\nimport { TopBar, type TopBarProps } from '../TopBar/TopBar'\nimport { Wordmark } from '../Wordmark/Wordmark'\nimport styles from './AppShell.module.css'\n\n/** One nav row, described as data. The consumer never hands the shell markup. */\nexport interface AppShellNavItem {\n /** The row's name. Truncates rather than wrapping; on the rail it collapses to the accessibility tree. */\n label: string\n /** The leading icon. Decorative -- the label carries the name. On the rail it is all that shows. */\n icon?: ReactNode\n /** Marks this row as the current page. Sets `aria-current=\"page\"`. */\n active?: boolean\n /** Trailing-slot count. Omit to leave the slot empty; it still reserves its width so counts line up. */\n count?: number\n /** Renders `count` in the primary color instead of muted. */\n countUrgent?: boolean\n /** Where the row goes. A nav row's destination is data; the shell renders it as a link. */\n href?: string\n}\n\n/** One nav section: an optional header over a run of rows. */\nexport interface AppShellNavSection {\n /** The section header. Omit for a headerless section (the nav's first section has none). On the rail every header drops. */\n label?: string\n /** The section's rows, as data. */\n items: AppShellNavItem[]\n}\n\nexport interface AppShellProps {\n /**\n * The accessible name of the sidebar landmark. A display string in the\n * consumer's own language -- usually the product's name -- that reaches\n * `aria-label` on the `<aside>` and is never rendered as text.\n *\n * **It is an accessible name, not product-awareness.** This prop was called\n * `product` and its comment claimed it was \"the whole of the shell's\n * product-awareness\" (B33-1272). Nothing branches on it, nothing is looked up\n * with it, and no artwork or copy changes because of it -- so the comment\n * described a mechanism that did not exist and sent the next reader looking\n * for the rest of it. The shell is product-aware through `nav`, `switcher`,\n * `wallet` and `footer`, which are the product's own data.\n *\n * **Not the Identity slug.** `AppSwitcher.currentSlug` takes that\n * (`\"wallet\"`, `\"boostscore\"`); this takes what a person would read\n * (\"Wallet\"). The shell carries two names for one product and nothing in the\n * types connects them, so check which kind of string each prop wants.\n */\n label: string\n /**\n * The accessible name of the navigation landmark inside the sidebar.\n * **Defaults to `label`, which is today's behaviour and not the fix.**\n *\n * Pass a distinct string here. Without one the `<aside>` and the `<nav>`\n * nested inside it carry the same accessible name, and a screen-reader user\n * moving by landmark hears \"Wallet complementary\" then \"Wallet navigation\" --\n * the name doing none of the distinguishing that is the one job a landmark\n * label has (B33-1272). Something like \"Wallet sections\" or \"Wallet pages\",\n * in your language, resolves it.\n *\n * **The package cannot default this to anything better, which is why the\n * fallback is the default.** A sensible default would have to compose a word\n * -- \"navigation\", \"sections\" -- and that word is user-facing text a screen\n * reader reads aloud, so it would have to be in the consumer's language. Both\n * consumers default to French. This package hardcodes no user-facing English\n * (`tests/no-hardcoded-strings.test.ts`), and a composed English default would\n * ship an untranslatable word into assistive output for every consumer that\n * never noticed the prop. Repeating `label` is at least a name the consumer\n * chose and wrote in their own language. So the duplicate name is only fully\n * resolved by a consumer passing this.\n */\n navLabel?: string\n /** The navigation, as data. No product hardcodes rows; the shell composes them. */\n nav: AppShellNavSection[]\n /** The top bar's content -- the breadcrumb and the shell actions on its right. */\n topBar: TopBarProps\n /** The Boost33 mark for the wordmark lockup. Passed in, the way Wordmark takes it; on the rail it is all that shows. */\n mark?: ReactNode\n /**\n * The product switcher, sitting under the wordmark. A slot: the registry it\n * draws is served by Identity (ADR-0003), not owned here. Render its own\n * collapsed form for the rail.\n */\n switcher?: ReactNode\n /**\n * The wallet chip, pinned above the footer. A slot: the balance comes from\n * `@boost33/ledger-client`, never read here. Render its own collapsed form\n * for the rail.\n */\n wallet?: ReactNode\n /** The sidebar footer, below the wallet chip. A slot -- the product's own. */\n footer?: ReactNode\n /**\n * The sidebar collapsed to the 56px rail. A state of the shell: the sidebar\n * narrows and every piece it owns collapses off this one flag. The slots\n * (switcher, wallet, footer) render their own collapsed form.\n */\n collapsed?: boolean\n /**\n * The element every linked nav row renders through. Defaults to a plain `a`;\n * a consumer whose router owns navigation passes its own here and the shell\n * threads it to every row of `nav`, so the whole nav goes client-side with the\n * locale prefix intact rather than full-page-reloading (B33-847). This is the\n * shell's one nod to how a consumer routes -- it renders the element, never\n * knowing what it is. See docs/adr/0011.\n *\n * **It reaches the rows of `nav` and nothing else.** The slots (`switcher`,\n * `wallet`, `footer`, `topBar.actions`) are `ReactNode`s the consumer has\n * already created, so the shell cannot put a prop on what is inside one. A\n * consumer composing a link-bearing component into a slot -- `AppSwitcher`,\n * `AccountMenu`, `NotificationCenter` -- passes `linkComponent` to that\n * component as well, or its rows stay bare anchors and reload the page. The\n * Wallet shipped exactly that defect (B33-1281) by reading this prop as the\n * single place, which is the 2026-10-06 correction on docs/adr/0011.\n */\n linkComponent?: ElementType\n /**\n * The page body. Sits on white, below the top bar, and the shell renders it\n * inside the page's `<main>` -- so a consumer adds no wrapper of its own.\n *\n * **The page's title goes here too.** A screen with a title renders\n * `PageHeader` as the first thing in its own body; a screen with none renders\n * nothing and the top bar's hairline sits straight on the content, which is\n * what the Wallet's overview does and what its 9 Sep baseline shows. The\n * shell used to take a `pageHeader` prop and draw the header itself, above\n * this; B33-1267 removed it, because no consumer ever passed it and Score\n * forbids it by test (the page below owns the sole `h1`).\n *\n * **Everything here scrolls.** This box is the shell's one scrolling element,\n * so the sidebar and top bar stay pinned and the page header goes up with the\n * body. The removed prop sat outside the scroll box and pinned the header\n * instead -- the one real difference between the two spellings, and the\n * reason the choice was worth collapsing to one rather than leaving to a\n * consumer to discover on a long page.\n */\n children: ReactNode\n}\n\n// The shell every authenticated Boost33 screen renders inside. It composes the\n// pieces built in the earlier tickets -- sidebar, wordmark, switcher, nav,\n// wallet chip, top bar -- into one thing a product imports instead of building a\n// sidebar. Everything that differs between products is the product's own data --\n// `nav`, `switcher`, `wallet`, `footer` -- and nothing above the nav (the Boost33\n// wordmark and brand) changes at all. `label` and `navLabel` name the two\n// landmarks and decide nothing; B33-1272 corrected the claim that `label` (then\n// `product`) was the whole of the shell's product-awareness.\n//\n// `PageHeader` is deliberately not in that list. The shell drew it from a\n// `pageHeader` prop until B33-1267; a page's title is the page's, rendered at\n// the top of `children`, which is what both consumers already did.\nexport function AppShell({\n label,\n navLabel,\n nav,\n topBar,\n mark,\n switcher,\n wallet,\n footer,\n collapsed = false,\n linkComponent,\n children,\n}: AppShellProps) {\n return (\n <div className={styles.shell}>\n <Sidebar landmarkLabel={label} collapsed={collapsed}>\n <div className={styles.wordmark}>\n <Wordmark mark={mark} collapsed={collapsed} />\n </div>\n {switcher != null && <div className={styles.switcher}>{switcher}</div>}\n {/* Falls back to the sidebar's name rather than composing one. The\n composed string would be a word a screen reader reads aloud, so it\n would have to be in the consumer's language, and this package\n hardcodes no user-facing English. Falling back repeats a name the\n consumer wrote; it does not invent one. See `navLabel`. */}\n <nav aria-label={navLabel ?? label} className={styles.nav}>\n {nav.map((section, sectionIndex) => (\n // No room for a header at 56px, so on the rail every section renders\n // headerless -- its rows collapse to their icons and line up.\n <NavSection key={sectionIndex} label={collapsed ? undefined : section.label}>\n {section.items.map((item, itemIndex) => (\n <NavItem\n key={itemIndex}\n icon={item.icon}\n active={item.active}\n collapsed={collapsed}\n count={item.count}\n countUrgent={item.countUrgent}\n href={item.href}\n linkComponent={linkComponent}\n >\n {item.label}\n </NavItem>\n ))}\n </NavSection>\n ))}\n </nav>\n {(wallet != null || footer != null) && (\n <div className={styles.foot}>\n {wallet}\n {footer}\n </div>\n )}\n </Sidebar>\n {/* `.main` is the product's column -- the top bar over the body. The\n landmark is the body alone, one level down: jumping to main has to\n skip the chrome, and a <main> around the top bar would swallow the\n breadcrumb, the search box and the avatar. */}\n <div className={styles.main}>\n <TopBar {...topBar} />\n {/* The page's main landmark, and the shell's to supply. This was a plain\n div, so no route of any consumer had one unless that consumer\n remembered a wrapper of its own -- the Wallet did\n (`boostledger/src/app/(wallet)/layout.tsx`), and three products each\n having to remember it is three chances to ship without one. What the\n absence costs a screen-reader user is the first move they make on an\n unfamiliar screen: jump to main and start reading. Without one they\n land at the top of the document and walk the wordmark, the switcher,\n every nav row, the wallet chip, the footer links, the sidebar toggle,\n the search box and the avatar before the first word of the page -- on\n every navigation, because the shell is a layout and does not remount.\n\n The landmark is the body alone, which is the half that matters: a\n <main> drawn around `.main` would satisfy any \"has a landmark\" check\n and buy the reader nothing, because jumping to it would land them\n back on the breadcrumb.\n\n A consumer that already wraps `children` now has two, which is worse\n than none -- B33-1265 drops the Wallet's as it takes this version,\n and the CHANGELOG entry tells both consumers to.\n\n An element swap and nothing else: `main` and `div` are both\n `display: block`, the class and every declaration behind it are\n untouched, and all 27 gated screenshot states came back at the ratios\n they held before. */}\n <main className={styles.content}>{children}</main>\n </div>\n </div>\n )\n}\n"],"names":["AppShell","label","navLabel","nav","topBar","mark","switcher","wallet","footer","collapsed","linkComponent","children","jsxs","styles","Sidebar","jsx","Wordmark","section","sectionIndex","NavSection","item","itemIndex","NavItem","TopBar"],"mappings":";;;;;;;AAyJO,SAASA,EAAS;AAAA,EACvB,OAAAC;AAAA,EACA,UAAAC;AAAA,EACA,KAAAC;AAAA,EACA,QAAAC;AAAA,EACA,MAAAC;AAAA,EACA,UAAAC;AAAA,EACA,QAAAC;AAAA,EACA,QAAAC;AAAA,EACA,WAAAC,IAAY;AAAA,EACZ,eAAAC;AAAA,EACA,UAAAC;AACF,GAAkB;AAChB,SACE,gBAAAC,EAAC,OAAA,EAAI,WAAWC,EAAO,OACrB,UAAA;AAAA,IAAA,gBAAAD,EAACE,GAAA,EAAQ,eAAeb,GAAO,WAAAQ,GAC7B,UAAA;AAAA,MAAA,gBAAAM,EAAC,OAAA,EAAI,WAAWF,EAAO,UACrB,4BAACG,GAAA,EAAS,MAAAX,GAAY,WAAAI,GAAsB,EAAA,CAC9C;AAAA,MACCH,KAAY,QAAQ,gBAAAS,EAAC,SAAI,WAAWF,EAAO,UAAW,UAAAP,GAAS;AAAA,MAMhE,gBAAAS,EAAC,OAAA,EAAI,cAAYb,KAAYD,GAAO,WAAWY,EAAO,KACnD,UAAAV,EAAI,IAAI,CAACc,GAASC;AAAA;AAAA;AAAA,QAGjB,gBAAAH,EAACI,GAAA,EAA8B,OAAOV,IAAY,SAAYQ,EAAQ,OACnE,UAAAA,EAAQ,MAAM,IAAI,CAACG,GAAMC,MACxB,gBAAAN;AAAA,UAACO;AAAA,UAAA;AAAA,YAEC,MAAMF,EAAK;AAAA,YACX,QAAQA,EAAK;AAAA,YACb,WAAAX;AAAA,YACA,OAAOW,EAAK;AAAA,YACZ,aAAaA,EAAK;AAAA,YAClB,MAAMA,EAAK;AAAA,YACX,eAAAV;AAAA,YAEC,UAAAU,EAAK;AAAA,UAAA;AAAA,UATDC;AAAA,QAAA,CAWR,KAdcH,CAejB;AAAA,OACD,GACH;AAAA,OACEX,KAAU,QAAQC,KAAU,2BAC3B,OAAA,EAAI,WAAWK,EAAO,MACpB,UAAA;AAAA,QAAAN;AAAA,QACAC;AAAA,MAAA,EAAA,CACH;AAAA,IAAA,GAEJ;AAAA,IAKA,gBAAAI,EAAC,OAAA,EAAI,WAAWC,EAAO,MACrB,UAAA;AAAA,MAAA,gBAAAE,EAACQ,GAAA,EAAQ,GAAGnB,GAAQ;AAAA,MA0BpB,gBAAAW,EAAC,QAAA,EAAK,WAAWF,EAAO,SAAU,UAAAF,EAAA,CAAS;AAAA,IAAA,EAAA,CAC7C;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
const
|
|
2
|
-
shell:
|
|
3
|
-
wordmark:
|
|
1
|
+
const t = "_shell_10chf_10", c = "_wordmark_10chf_20", o = "_switcher_10chf_26", _ = "_foot_10chf_33", n = "_nav_10chf_45", s = "_main_10chf_54", h = "_content_10chf_75", e = {
|
|
2
|
+
shell: t,
|
|
3
|
+
wordmark: c,
|
|
4
4
|
switcher: o,
|
|
5
5
|
foot: _,
|
|
6
|
-
nav:
|
|
7
|
-
main:
|
|
8
|
-
content:
|
|
6
|
+
nav: n,
|
|
7
|
+
main: s,
|
|
8
|
+
content: h
|
|
9
9
|
};
|
|
10
10
|
export {
|
|
11
|
-
|
|
11
|
+
h as content,
|
|
12
12
|
e as default,
|
|
13
13
|
_ as foot,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
s as main,
|
|
15
|
+
n as nav,
|
|
16
|
+
t as shell,
|
|
17
17
|
o as switcher,
|
|
18
|
-
|
|
18
|
+
c as wordmark
|
|
19
19
|
};
|
|
20
20
|
//# sourceMappingURL=AppShell.module.css.js.map
|
|
@@ -66,7 +66,29 @@ export interface AppSwitcherAccount {
|
|
|
66
66
|
initials: string;
|
|
67
67
|
/** The line beneath the name, preformatted, e.g. "Admin · 2 workspaces". */
|
|
68
68
|
description: string;
|
|
69
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Chosen -- opens the workspace panel.
|
|
71
|
+
*
|
|
72
|
+
* **It is also what makes the row a control.** Supplied, the row is the
|
|
73
|
+
* `<button>` it has always been, with the chevron pointing at the panel it
|
|
74
|
+
* opens. Omitted, the row still draws -- the organisation and its workspace
|
|
75
|
+
* count are facts about the account whether or not anything opens -- but as a
|
|
76
|
+
* plain `<div>`: no chevron, no hover step, no tab stop (B33-1270).
|
|
77
|
+
*
|
|
78
|
+
* Both consumers ship it unarmed today, because the panel it would open is
|
|
79
|
+
* B33-493's and unbuilt: see the comment on `account` in
|
|
80
|
+
* `boostscore/components/app-shell/product-switcher.tsx` ("the Wallet's
|
|
81
|
+
* migration left the same row inert for the same reason"). Before this gate
|
|
82
|
+
* existed they got a button that swallowed a click and did nothing, and no way
|
|
83
|
+
* to reach it -- `.accountRow`'s class name is CSS-Modules-hashed.
|
|
84
|
+
*
|
|
85
|
+
* The whole row is not removed, and that is deliberate. ADR-0010's "Not
|
|
86
|
+
* entitled is not disabled" is the nearest settled reasoning: a row that
|
|
87
|
+
* carries information is not the component's to withhold, and `aria-disabled`
|
|
88
|
+
* does not belong on something nobody can act on anyway. So the information
|
|
89
|
+
* stays and the affordances go. Nor is the button `disabled` -- that is still
|
|
90
|
+
* a control a consumer cannot remove.
|
|
91
|
+
*/
|
|
70
92
|
onSelect?: () => void;
|
|
71
93
|
}
|
|
72
94
|
export interface AppSwitcherProps {
|