@12-apps/ui 6.50.0 → 6.51.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/components-catalog.md +2 -2
- package/dist/_internal/components/data-display/Badge/Badge.js +4 -4
- package/dist/_internal/components/data-display/Badge/Badge.styles.js +4 -4
- package/dist/_internal/components/data-display/Badge/BadgeContent.js +4 -4
- package/dist/_internal/components/data-display/Carousel/Carousel.chrome.js +4 -4
- package/dist/_internal/components/data-display/Carousel/Carousel.js +4 -4
- package/dist/_internal/components/data-display/Carousel/Carousel.parts.js +4 -4
- package/dist/_internal/components/data-display/Carousel/Carousel.styles.js +4 -4
- package/dist/_internal/components/data-display/Chart/Chart.js +4 -4
- package/dist/_internal/components/data-display/Chart/SpecChart.js +4 -4
- package/dist/_internal/components/data-display/Chart/chart-context.js +4 -4
- package/dist/_internal/components/data-display/Chart/chart-internals.js +4 -4
- package/dist/_internal/components/data-display/Chart/chart-renderers.js +4 -4
- package/dist/_internal/components/data-display/DataGrid/DataGrid.js +6 -6
- package/dist/_internal/components/data-display/DataGrid/DataGrid.model.js +5 -5
- package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +16 -16
- package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +17 -17
- package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +8 -8
- package/dist/_internal/components/data-display/DataViews/base-list-card-slots.js +7 -7
- package/dist/_internal/components/data-display/DataViews/base-list-card.js +9 -9
- package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +7 -7
- package/dist/_internal/components/data-display/DataViews/data-views-display-tabs.js +6 -6
- package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +9 -9
- package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +15 -15
- package/dist/_internal/components/data-display/DataViews/data-views-layout-context.js +5 -5
- package/dist/_internal/components/data-display/DataViews/data-views-scope-tabs.js +6 -6
- package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +9 -9
- package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +8 -8
- package/dist/_internal/components/data-display/DataViews/list-card-rails.js +6 -6
- package/dist/_internal/components/data-display/FleetMap/FleetMap.js +4 -4
- package/dist/_internal/components/data-display/Sheet/Sheet.body.js +4 -4
- package/dist/_internal/components/data-display/Sheet/Sheet.js +4 -4
- package/dist/_internal/components/data-display/Sheet/Sheet.styles.js +4 -4
- package/dist/_internal/components/data-display/Table/Table.js +5 -5
- package/dist/_internal/components/data-display/index.js +7 -7
- package/dist/_internal/components/form/Command/Command.js +4 -4
- package/dist/_internal/components/form/Command/Command.styles.js +4 -4
- package/dist/_internal/components/form/Label/Label.js +4 -4
- package/dist/_internal/components/form/Label/Label.styles.js +4 -4
- package/dist/_internal/components/form/Menubar/Menubar.item.js +4 -4
- package/dist/_internal/components/form/Menubar/Menubar.js +4 -4
- package/dist/_internal/components/form/Menubar/Menubar.styles.js +4 -4
- package/dist/_internal/components/form/Menubar/Menubar.toolbar.js +4 -4
- package/dist/_internal/components/form/index.js +4 -4
- package/dist/_internal/components/index.js +23 -23
- package/dist/_internal/components/layout/Box/Box.js +7 -7
- package/dist/_internal/components/layout/SettingCard/SettingCard.js +9 -9
- package/dist/_internal/components/layout/SettingCard/SettingCard.parts.js +8 -8
- package/dist/_internal/components/layout/SettingCard/SettingGrid.js +9 -9
- package/dist/_internal/components/layout/SettingCard/SettingGroup.js +10 -10
- package/dist/_internal/components/layout/SettingCard/SettingSwitch.js +9 -9
- package/dist/_internal/components/layout/SettingCard/SettingToggle.js +10 -10
- package/dist/_internal/components/layout/Stack/Stack.js +8 -8
- package/dist/_internal/components/layout/index.js +13 -13
- package/dist/_internal/components/navigation/AppHeader/AppHeader.details.js +4 -4
- package/dist/_internal/components/navigation/AppHeader/AppHeader.identity.js +4 -4
- package/dist/_internal/components/navigation/SectionNav/SectionNav.bar.js +9 -9
- package/dist/_internal/components/navigation/SectionNav/SectionNav.helpers.js +5 -1
- package/dist/_internal/components/navigation/SectionNav/SectionNav.js +11 -11
- package/dist/_internal/components/navigation/SectionNav/SectionNav.parts.js +12 -6
- package/dist/_internal/components/navigation/SectionNav/SectionNav.primary.js +7 -7
- package/dist/_internal/components/navigation/SectionNav/SectionNav.rail.js +7 -7
- package/dist/_internal/components/navigation/SectionNav/SectionNav.sheet.js +7 -7
- package/dist/_internal/components/navigation/SectionNav/__stories__/fixtures.js +16 -4
- package/dist/_internal/components/navigation/SectionNav/__stories__/fixtures.js.map +1 -1
- package/dist/_internal/components/navigation/index.js +11 -11
- package/dist/_internal/components/utility/WorkflowStep/WorkflowStep.helpers.js +4 -4
- package/dist/_internal/components/utility/WorkflowStep/WorkflowStep.js +4 -4
- package/dist/_internal/components/utility/WorkflowStep/WorkflowStep.parts.js +4 -4
- package/dist/_internal/components/utility/WorkflowStep/WorkflowStepRow.js +4 -4
- package/dist/_internal/components/utility/index.js +4 -4
- package/dist/_internal/icons/Icon.js +7 -7
- package/dist/_internal/provider/UiProvider.js +6 -6
- package/dist/_internal/provider/mui-bridge.js +5 -5
- package/dist/_internal/provider/use-ui-theme.js +6 -6
- package/dist/_internal/tokens/density-overrides.js +3 -3
- package/dist/_internal/tokens/density-overrides.navigation.js +1 -1
- package/dist/_internal/tokens/density-overrides.selection.js +1 -1
- package/dist/_internal/tokens/density.js +4 -4
- package/dist/charts.js +4 -4
- package/dist/{chunk-XK77FY42.js → chunk-25MSGLNU.js} +4 -3
- package/dist/chunk-25MSGLNU.js.map +1 -0
- package/dist/{chunk-7VC6YBD3.js → chunk-3U45DRNS.js} +2 -2
- package/dist/{chunk-ZQCZMSLO.js → chunk-4B5WSVS7.js} +2 -2
- package/dist/{chunk-F4FOBTLJ.js → chunk-4BI5PCOM.js} +2 -2
- package/dist/{chunk-HVZ4HF4M.js → chunk-5N2UJOCT.js} +2 -2
- package/dist/{chunk-DFCX3FNB.js → chunk-5ZBFADBL.js} +2 -2
- package/dist/{chunk-GLKQT2DS.js → chunk-A436VORP.js} +33 -4
- package/dist/chunk-A436VORP.js.map +1 -0
- package/dist/{chunk-LWBR5U6V.js → chunk-ABPTS3XK.js} +2 -2
- package/dist/{chunk-43EQITNF.js → chunk-BHPLHGZP.js} +4 -4
- package/dist/{chunk-YMW2VYGU.js → chunk-DLO5V4AV.js} +2 -2
- package/dist/{chunk-7FQF5BC2.js → chunk-ESW4Z5I7.js} +2 -2
- package/dist/{chunk-ZPD6MGAO.js → chunk-FDJLYYDA.js} +4 -4
- package/dist/{chunk-LZL6HDTZ.js → chunk-FM5RBGG7.js} +2 -2
- package/dist/{chunk-V2SAZOT2.js → chunk-FYTC4LOD.js} +2 -2
- package/dist/{chunk-4ELI2MM3.js → chunk-GAXHYRX6.js} +9 -6
- package/dist/chunk-GAXHYRX6.js.map +1 -0
- package/dist/{chunk-CT45KNCT.js → chunk-GPTICJVN.js} +3 -3
- package/dist/{chunk-URGH4673.js → chunk-H556NMKE.js} +4 -4
- package/dist/{chunk-VOF6QVCF.js → chunk-HF7V3RI5.js} +2 -2
- package/dist/{chunk-NKJMOWY3.js → chunk-HJUR6ZY6.js} +2 -2
- package/dist/{chunk-RX7J6VRI.js → chunk-HKEBTPKY.js} +119 -42
- package/dist/chunk-HKEBTPKY.js.map +1 -0
- package/dist/{chunk-ZC3NEUNQ.js → chunk-I2LF53MJ.js} +2 -2
- package/dist/{chunk-ZFQVT6HZ.js → chunk-II7Z5UB6.js} +2 -2
- package/dist/{chunk-K65NSHQI.js → chunk-L3CQTVFU.js} +4 -4
- package/dist/{chunk-EY4GPLMF.js → chunk-MT7XMJUJ.js} +5 -5
- package/dist/{chunk-TELVW2OI.js → chunk-MYCR7FMI.js} +2 -2
- package/dist/{chunk-56TGECKZ.js → chunk-NP6SHLQ3.js} +4 -3
- package/dist/chunk-NP6SHLQ3.js.map +1 -0
- package/dist/{chunk-WB6BTUXQ.js → chunk-OWILUXWM.js} +2 -2
- package/dist/{chunk-VHVI3D4R.js → chunk-P3GBXE73.js} +11 -2
- package/dist/chunk-P3GBXE73.js.map +1 -0
- package/dist/{chunk-WSBTMZJM.js → chunk-P7YUOH2E.js} +2 -2
- package/dist/{chunk-VOP2BXKL.js → chunk-QWTPK3FT.js} +2 -2
- package/dist/{chunk-XQ6Y5Z7R.js → chunk-RCJOTNIW.js} +2 -2
- package/dist/{chunk-32JMSHEA.js → chunk-S4ZUZ6DS.js} +10 -6
- package/dist/chunk-S4ZUZ6DS.js.map +1 -0
- package/dist/{chunk-SWDDXMV7.js → chunk-SJ4IAZ3V.js} +4 -4
- package/dist/{chunk-FHWBOOS3.js → chunk-TH7RBREI.js} +2 -2
- package/dist/{chunk-SA2ERYVV.js → chunk-VAO63IIP.js} +3 -3
- package/dist/{chunk-43Q6CVPB.js → chunk-VELGOFCL.js} +127 -78
- package/dist/chunk-VELGOFCL.js.map +1 -0
- package/dist/{chunk-RCJYLDXV.js → chunk-VJBTFWDK.js} +4 -4
- package/dist/chunk-WNKRFMSI.js +113 -0
- package/dist/chunk-WNKRFMSI.js.map +1 -0
- package/dist/{chunk-LM2YOONY.js → chunk-XYHNXO6Q.js} +2 -2
- package/dist/{chunk-FT7BNFGS.js → chunk-YK4ZG5Y4.js} +2 -2
- package/dist/data-display/Badge.js +4 -4
- package/dist/data-display/Carousel.js +4 -4
- package/dist/data-display/Chart.js +4 -4
- package/dist/data-display/DataGrid.js +6 -6
- package/dist/data-display/DataViews.js +20 -20
- package/dist/data-display/FleetMap.js +4 -4
- package/dist/data-display/Sheet.js +4 -4
- package/dist/data-display/Table.js +5 -5
- package/dist/form/Command.js +4 -4
- package/dist/form/Label.js +4 -4
- package/dist/form/Menubar.js +4 -4
- package/dist/icons.js +7 -7
- package/dist/layout/Box.js +7 -7
- package/dist/layout/SettingCard.js +13 -13
- package/dist/layout/Stack.js +8 -8
- package/dist/navigation/AppHeader.js +4 -4
- package/dist/navigation/SectionNav.js +11 -11
- package/dist/provider.js +7 -7
- package/dist/tokens.js +4 -4
- package/dist/types/components/navigation/SectionNav/SectionNav.bar.d.ts.map +1 -1
- package/dist/types/components/navigation/SectionNav/SectionNav.helpers.d.ts +12 -1
- package/dist/types/components/navigation/SectionNav/SectionNav.helpers.d.ts.map +1 -1
- package/dist/types/components/navigation/SectionNav/SectionNav.parts.d.ts +36 -2
- package/dist/types/components/navigation/SectionNav/SectionNav.parts.d.ts.map +1 -1
- package/dist/types/components/navigation/SectionNav/SectionNav.primary.d.ts +22 -4
- package/dist/types/components/navigation/SectionNav/SectionNav.primary.d.ts.map +1 -1
- package/dist/types/components/navigation/SectionNav/SectionNav.rail.d.ts.map +1 -1
- package/dist/types/components/navigation/SectionNav/SectionNav.sheet.d.ts.map +1 -1
- package/dist/types/components/navigation/SectionNav/SectionNav.types.d.ts +81 -5
- package/dist/types/components/navigation/SectionNav/SectionNav.types.d.ts.map +1 -1
- package/dist/types/components/navigation/SectionNav/__stories__/fixtures.d.ts +11 -1
- package/dist/types/components/navigation/SectionNav/__stories__/fixtures.d.ts.map +1 -1
- package/dist/types/components/navigation/SectionNav/index.d.ts +1 -1
- package/dist/types/components/navigation/SectionNav/index.d.ts.map +1 -1
- package/dist/types/tokens/density-overrides.d.ts.map +1 -1
- package/dist/types/tokens/density-overrides.navigation.d.ts.map +1 -1
- package/dist/types/tokens/density-overrides.selection.d.ts.map +1 -1
- package/dist/utility/WorkflowStep.js +4 -4
- package/package.json +1 -1
- package/src/components/navigation/SectionNav/SectionNav.bar.tsx +161 -88
- package/src/components/navigation/SectionNav/SectionNav.helpers.ts +16 -1
- package/src/components/navigation/SectionNav/SectionNav.md +40 -4
- package/src/components/navigation/SectionNav/SectionNav.parts.tsx +59 -1
- package/src/components/navigation/SectionNav/SectionNav.primary.tsx +107 -30
- package/src/components/navigation/SectionNav/SectionNav.rail.tsx +154 -41
- package/src/components/navigation/SectionNav/SectionNav.sheet.tsx +6 -2
- package/src/components/navigation/SectionNav/SectionNav.types.ts +82 -5
- package/src/components/navigation/SectionNav/__stories__/fixtures.tsx +21 -1
- package/src/components/navigation/SectionNav/index.ts +1 -0
- package/src/tokens/density-overrides.navigation.ts +15 -2
- package/src/tokens/density-overrides.selection.ts +31 -14
- package/src/tokens/density-overrides.ts +27 -11
- package/dist/chunk-32JMSHEA.js.map +0 -1
- package/dist/chunk-43Q6CVPB.js.map +0 -1
- package/dist/chunk-4ELI2MM3.js.map +0 -1
- package/dist/chunk-56TGECKZ.js.map +0 -1
- package/dist/chunk-GLKQT2DS.js.map +0 -1
- package/dist/chunk-RX7J6VRI.js.map +0 -1
- package/dist/chunk-RZ4SBSLP.js +0 -62
- package/dist/chunk-RZ4SBSLP.js.map +0 -1
- package/dist/chunk-VHVI3D4R.js.map +0 -1
- package/dist/chunk-XK77FY42.js.map +0 -1
- /package/dist/{chunk-7VC6YBD3.js.map → chunk-3U45DRNS.js.map} +0 -0
- /package/dist/{chunk-ZQCZMSLO.js.map → chunk-4B5WSVS7.js.map} +0 -0
- /package/dist/{chunk-F4FOBTLJ.js.map → chunk-4BI5PCOM.js.map} +0 -0
- /package/dist/{chunk-HVZ4HF4M.js.map → chunk-5N2UJOCT.js.map} +0 -0
- /package/dist/{chunk-DFCX3FNB.js.map → chunk-5ZBFADBL.js.map} +0 -0
- /package/dist/{chunk-LWBR5U6V.js.map → chunk-ABPTS3XK.js.map} +0 -0
- /package/dist/{chunk-43EQITNF.js.map → chunk-BHPLHGZP.js.map} +0 -0
- /package/dist/{chunk-YMW2VYGU.js.map → chunk-DLO5V4AV.js.map} +0 -0
- /package/dist/{chunk-7FQF5BC2.js.map → chunk-ESW4Z5I7.js.map} +0 -0
- /package/dist/{chunk-ZPD6MGAO.js.map → chunk-FDJLYYDA.js.map} +0 -0
- /package/dist/{chunk-LZL6HDTZ.js.map → chunk-FM5RBGG7.js.map} +0 -0
- /package/dist/{chunk-V2SAZOT2.js.map → chunk-FYTC4LOD.js.map} +0 -0
- /package/dist/{chunk-CT45KNCT.js.map → chunk-GPTICJVN.js.map} +0 -0
- /package/dist/{chunk-URGH4673.js.map → chunk-H556NMKE.js.map} +0 -0
- /package/dist/{chunk-VOF6QVCF.js.map → chunk-HF7V3RI5.js.map} +0 -0
- /package/dist/{chunk-NKJMOWY3.js.map → chunk-HJUR6ZY6.js.map} +0 -0
- /package/dist/{chunk-ZC3NEUNQ.js.map → chunk-I2LF53MJ.js.map} +0 -0
- /package/dist/{chunk-ZFQVT6HZ.js.map → chunk-II7Z5UB6.js.map} +0 -0
- /package/dist/{chunk-K65NSHQI.js.map → chunk-L3CQTVFU.js.map} +0 -0
- /package/dist/{chunk-EY4GPLMF.js.map → chunk-MT7XMJUJ.js.map} +0 -0
- /package/dist/{chunk-TELVW2OI.js.map → chunk-MYCR7FMI.js.map} +0 -0
- /package/dist/{chunk-WB6BTUXQ.js.map → chunk-OWILUXWM.js.map} +0 -0
- /package/dist/{chunk-WSBTMZJM.js.map → chunk-P7YUOH2E.js.map} +0 -0
- /package/dist/{chunk-VOP2BXKL.js.map → chunk-QWTPK3FT.js.map} +0 -0
- /package/dist/{chunk-XQ6Y5Z7R.js.map → chunk-RCJOTNIW.js.map} +0 -0
- /package/dist/{chunk-SWDDXMV7.js.map → chunk-SJ4IAZ3V.js.map} +0 -0
- /package/dist/{chunk-FHWBOOS3.js.map → chunk-TH7RBREI.js.map} +0 -0
- /package/dist/{chunk-SA2ERYVV.js.map → chunk-VAO63IIP.js.map} +0 -0
- /package/dist/{chunk-RCJYLDXV.js.map → chunk-VJBTFWDK.js.map} +0 -0
- /package/dist/{chunk-LM2YOONY.js.map → chunk-XYHNXO6Q.js.map} +0 -0
- /package/dist/{chunk-FT7BNFGS.js.map → chunk-YK4ZG5Y4.js.map} +0 -0
|
@@ -31,10 +31,13 @@ screen.
|
|
|
31
31
|
- **layout**: `'bar' | 'rail'`.
|
|
32
32
|
- **label**: the navigation landmark's accessible name.
|
|
33
33
|
- **destinations**: `SectionNavDestination[]`, each with `id`, `label`, `icon`,
|
|
34
|
-
`href`, `badge
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
`href` OR `onSelect`, `badge?`, `active?`, `disabled?`, `dimmed?`,
|
|
35
|
+
`loading?` and `dataTestId?`.
|
|
36
|
+
- **primary**: a `SectionNavMenu` (`label`, `icon`, `title`, `groups`) or a
|
|
37
|
+
`SectionNavAction` (`label`, `icon`, `onSelect`, `disabled?`, `loading?`). In
|
|
38
|
+
the bar it is the raised button. A menu opens its sheet, and the button's name
|
|
39
|
+
becomes `copy.close` while it is open. An action runs on tap and its label is
|
|
40
|
+
drawn under the button.
|
|
38
41
|
- **more**: a `SectionNavMenu`. In the bar it is the last slot, and its badge
|
|
39
42
|
is the sum of its entries' badges.
|
|
40
43
|
- **back**, **heading**: the rail only.
|
|
@@ -48,6 +51,36 @@ screen.
|
|
|
48
51
|
named packs; `SECTION_NAV_COPY` is the locale pack.
|
|
49
52
|
- **dataTestId**: the prefix for every test id (`section-nav`).
|
|
50
53
|
|
|
54
|
+
## A bar of verbs
|
|
55
|
+
|
|
56
|
+
The same bar pins a SCREEN's actions at its foot: give destinations an
|
|
57
|
+
`onSelect` instead of an `href`, and `primary` a `SectionNavAction`. A bar of
|
|
58
|
+
places and a bar of verbs are one component, so they cannot drift into two
|
|
59
|
+
looks one tap apart.
|
|
60
|
+
|
|
61
|
+
- `disabled` draws the slot dimmed and makes it a native disabled button, even
|
|
62
|
+
when it carries an `href`: a link cannot be disabled, and one that looks
|
|
63
|
+
dimmed but still navigates is a lie. A tap does nothing; `onSelect` is not
|
|
64
|
+
called.
|
|
65
|
+
- `dimmed` is for an act the screen cannot do now but should EXPLAIN when
|
|
66
|
+
tapped. It is drawn exactly like `disabled` (the `text.disabled` colour) and
|
|
67
|
+
reports `aria-disabled="true"`, but it is NOT natively disabled: it stays
|
|
68
|
+
focusable, its `onSelect` still fires, and the host answers with a message
|
|
69
|
+
(why not, and what would make it possible). It is never lit.
|
|
70
|
+
- `loading` replaces the icon with a spinner and reports `aria-busy` and
|
|
71
|
+
`aria-disabled="true"` until the write it started answers. It stays
|
|
72
|
+
focusable, so the keyboard keeps its place, and a tap is ignored. The same
|
|
73
|
+
holds for a loading action `primary`. Native `disabled` is used only for
|
|
74
|
+
`disabled`.
|
|
75
|
+
- `active` on an action slot marks a TOGGLE that is on: it lights the slot and
|
|
76
|
+
reports `aria-pressed="true"` (`false` when `active: false`). Only a link
|
|
77
|
+
reports `aria-current="page"`; an action is not a page.
|
|
78
|
+
- A menu entry, and a whole menu, can be `disabled` too. In the bar a disabled
|
|
79
|
+
menu's trigger is inert and opens no sheet; in the rail, where the menu is
|
|
80
|
+
listed rather than behind a trigger, every row of it is inert.
|
|
81
|
+
- `dataTestId` on a destination, entry, action or menu replaces the id the nav
|
|
82
|
+
would derive, for a host whose suites already drive their own.
|
|
83
|
+
|
|
51
84
|
A menu's **groups** carry `entries`. Each entry has an `href` (a link) or an
|
|
52
85
|
`onSelect` (an action). `layout: 'grid'` draws three tiles across in the sheet;
|
|
53
86
|
`list` (the default) draws one row per entry with its `description`. Choosing
|
|
@@ -81,6 +114,9 @@ the slot reports the sheet through `aria-expanded`.
|
|
|
81
114
|
|
|
82
115
|
The raised round button on its own, for a host that already draws its own bar:
|
|
83
116
|
drop it between two tabs in a flex row. Without `open` it is a plain action.
|
|
117
|
+
With `captioned` its label is drawn under it, hidden from assistive tech so
|
|
118
|
+
the button is named once. `disabled` is native; `loading` keeps it focusable
|
|
119
|
+
with `aria-busy` and `aria-disabled`, and drops the click.
|
|
84
120
|
With `open` it reports `aria-expanded`, takes `closeLabel` as its name, and its
|
|
85
121
|
icon turns 45° while open.
|
|
86
122
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Box from '@mui/material/Box/index.js';
|
|
2
|
+
import CircularProgress from '@mui/material/CircularProgress/index.js';
|
|
2
3
|
import type { SxProps, Theme } from '@mui/material/styles/index.js';
|
|
3
4
|
import type { ElementType, ReactNode } from 'react';
|
|
4
5
|
|
|
@@ -6,6 +7,7 @@ import { rem } from '../../../tokens/scales';
|
|
|
6
7
|
import { Badge } from '../../data-display/Badge';
|
|
7
8
|
|
|
8
9
|
import { shownCount } from './SectionNav.helpers';
|
|
10
|
+
import type { SectionNavDestination } from './SectionNav.types';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* An icon with its count on the shoulder.
|
|
@@ -59,21 +61,76 @@ export function focusRing(theme: Theme): SxProps<Theme> {
|
|
|
59
61
|
* `href`, a button for an `onSelect`.
|
|
60
62
|
*
|
|
61
63
|
* Returned as props for a `Box`, so the control stays ONE interactive
|
|
62
|
-
* element — never a button wrapped around a link.
|
|
64
|
+
* element — never a button wrapped around a link. Whatever it would lead to,
|
|
65
|
+
* a control that is not live is a BUTTON: a link cannot be disabled, and one
|
|
66
|
+
* that looks dimmed but still navigates is the lie this rules out.
|
|
67
|
+
*
|
|
68
|
+
* - `inert` — a native `disabled` button: no focus, no click.
|
|
69
|
+
* - `busy` — a write is in flight: `aria-disabled`, still focusable, and the
|
|
70
|
+
* click is dropped, so the keyboard keeps its place until the write answers.
|
|
71
|
+
* - `dimmed` — `aria-disabled`, still focusable, and the click STILL runs, so
|
|
72
|
+
* the host can explain why the act cannot be done now.
|
|
63
73
|
*/
|
|
64
74
|
export function controlProps({
|
|
65
75
|
href,
|
|
66
76
|
onClick,
|
|
67
77
|
linkComponent,
|
|
78
|
+
inert = false,
|
|
79
|
+
busy = false,
|
|
80
|
+
dimmed = false,
|
|
68
81
|
}: {
|
|
69
82
|
href: string | undefined;
|
|
70
83
|
onClick?: () => void;
|
|
71
84
|
linkComponent: ElementType | undefined;
|
|
85
|
+
inert?: boolean;
|
|
86
|
+
busy?: boolean;
|
|
87
|
+
dimmed?: boolean;
|
|
72
88
|
}): Record<string, unknown> {
|
|
89
|
+
if (inert) return { component: 'button', type: 'button', disabled: true };
|
|
90
|
+
if (busy) return { component: 'button', type: 'button', 'aria-disabled': 'true' };
|
|
91
|
+
if (dimmed) return { component: 'button', type: 'button', 'aria-disabled': 'true', onClick };
|
|
73
92
|
if (href !== undefined) return { component: linkComponent ?? 'a', href, onClick };
|
|
74
93
|
return { component: 'button', type: 'button', onClick };
|
|
75
94
|
}
|
|
76
95
|
|
|
96
|
+
/**
|
|
97
|
+
* A destination's control: `controlProps` from its own state, plus
|
|
98
|
+
* `aria-pressed` when it is an action slot that is a toggle. A link reports
|
|
99
|
+
* where the viewer is through `aria-current` instead — the row draws that.
|
|
100
|
+
*/
|
|
101
|
+
export function destinationControl(
|
|
102
|
+
destination: SectionNavDestination,
|
|
103
|
+
linkComponent: ElementType | undefined,
|
|
104
|
+
onClick: () => void,
|
|
105
|
+
): Record<string, unknown> {
|
|
106
|
+
return {
|
|
107
|
+
...controlProps({
|
|
108
|
+
href: destination.href,
|
|
109
|
+
linkComponent,
|
|
110
|
+
inert: destination.disabled === true,
|
|
111
|
+
busy: destination.loading === true,
|
|
112
|
+
dimmed: destination.dimmed === true,
|
|
113
|
+
onClick,
|
|
114
|
+
}),
|
|
115
|
+
'aria-pressed': destination.href === undefined ? destination.active : undefined,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The selector for a control drawn as not live: natively disabled, or
|
|
121
|
+
* `aria-disabled` (in flight, or dimmed) and still focusable.
|
|
122
|
+
*/
|
|
123
|
+
export const NOT_LIVE = '&:disabled, &[aria-disabled="true"]';
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The icon, or the spinner that stands in for it while the slot's write is in
|
|
127
|
+
* flight — the same box, so the label under it does not move.
|
|
128
|
+
*/
|
|
129
|
+
export function SlotIcon({ icon, loading }: { icon: ReactNode; loading: boolean }): React.JSX.Element {
|
|
130
|
+
if (!loading) return <>{icon}</>;
|
|
131
|
+
return <CircularProgress size="1em" color="inherit" thickness={5} data-testid="section-nav-spinner" />;
|
|
132
|
+
}
|
|
133
|
+
|
|
77
134
|
/** The base reset every clickable in the nav shares: no underline, no button chrome. */
|
|
78
135
|
export const CONTROL_RESET = {
|
|
79
136
|
border: 0,
|
|
@@ -83,6 +140,7 @@ export const CONTROL_RESET = {
|
|
|
83
140
|
textDecoration: 'none',
|
|
84
141
|
cursor: 'pointer',
|
|
85
142
|
margin: 0,
|
|
143
|
+
[NOT_LIVE]: { cursor: 'default' },
|
|
86
144
|
} as const;
|
|
87
145
|
|
|
88
146
|
export { Box };
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Typography from '@mui/material/Typography/index.js';
|
|
2
|
+
import { useTheme, type SxProps, type Theme } from '@mui/material/styles/index.js';
|
|
2
3
|
import type { ReactNode } from 'react';
|
|
3
4
|
|
|
4
5
|
import { rem } from '../../../tokens/scales';
|
|
5
6
|
|
|
6
|
-
import { Box, CONTROL_RESET, focusRing } from './SectionNav.parts';
|
|
7
|
+
import { Box, CONTROL_RESET, NOT_LIVE, SlotIcon, focusRing } from './SectionNav.parts';
|
|
7
8
|
|
|
8
9
|
export interface RaisedActionButtonProps {
|
|
9
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* The button's accessible name. Drawn under the button only when
|
|
12
|
+
* `captioned`; the caption is hidden from assistive tech, so the name is
|
|
13
|
+
* read once either way.
|
|
14
|
+
*/
|
|
10
15
|
label: string;
|
|
11
16
|
icon: ReactNode;
|
|
12
17
|
onClick: () => void;
|
|
@@ -18,6 +23,19 @@ export interface RaisedActionButtonProps {
|
|
|
18
23
|
open?: boolean;
|
|
19
24
|
/** The name it takes while `open` — the same control closes what it opened. REQUIRED with `open`. */
|
|
20
25
|
closeLabel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Draw `label` under the button. A bar of VERBS names the act it is for; a
|
|
28
|
+
* bar of places can leave its one action to the icon.
|
|
29
|
+
*/
|
|
30
|
+
captioned?: boolean;
|
|
31
|
+
/** Dimmed and not operable: a native `disabled` button. */
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* A write it started is in flight: a spinner replaces the icon, it reports
|
|
35
|
+
* `aria-busy` and `aria-disabled`, and a tap is ignored. It stays focusable,
|
|
36
|
+
* so the keyboard does not lose its place while the write answers.
|
|
37
|
+
*/
|
|
38
|
+
loading?: boolean;
|
|
21
39
|
dataTestId?: string;
|
|
22
40
|
}
|
|
23
41
|
|
|
@@ -26,8 +44,9 @@ export interface RaisedActionButtonProps {
|
|
|
26
44
|
* the row that is an ACTION rather than a place.
|
|
27
45
|
*
|
|
28
46
|
* Lifted half out of the bar so it reads as different in kind from the tabs
|
|
29
|
-
* beside it
|
|
30
|
-
*
|
|
47
|
+
* beside it. Uncaptioned it costs the row less width than a tab: that width is
|
|
48
|
+
* what lets a bar keep four labelled tabs AND this button at 320px. A bar of
|
|
49
|
+
* verbs captions it, naming the act under the button.
|
|
31
50
|
*
|
|
32
51
|
* `SectionNav` draws it for its `primary` menu. It is exported on its own for
|
|
33
52
|
* a bar the host already draws — drop it between two tabs, in a flex row.
|
|
@@ -38,44 +57,102 @@ export function RaisedActionButton({
|
|
|
38
57
|
onClick,
|
|
39
58
|
open,
|
|
40
59
|
closeLabel,
|
|
60
|
+
captioned = false,
|
|
61
|
+
disabled = false,
|
|
62
|
+
loading = false,
|
|
41
63
|
dataTestId = 'raised-action',
|
|
42
64
|
}: RaisedActionButtonProps): React.JSX.Element {
|
|
43
65
|
const theme = useTheme();
|
|
44
|
-
const size = rem(theme, 52);
|
|
45
66
|
const expanded = open === true;
|
|
46
67
|
return (
|
|
47
|
-
<Box
|
|
68
|
+
<Box
|
|
69
|
+
sx={{
|
|
70
|
+
flex: 'none',
|
|
71
|
+
display: 'flex',
|
|
72
|
+
flexDirection: 'column',
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
justifyContent: 'flex-start',
|
|
75
|
+
px: 0.5,
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
48
78
|
<Box
|
|
49
79
|
component="button"
|
|
50
80
|
type="button"
|
|
51
|
-
|
|
81
|
+
{...raisedState({ onClick, disabled, loading })}
|
|
52
82
|
aria-label={expanded && closeLabel ? closeLabel : label}
|
|
53
83
|
aria-expanded={open}
|
|
54
84
|
data-testid={dataTestId}
|
|
55
|
-
sx={
|
|
56
|
-
...CONTROL_RESET,
|
|
57
|
-
...focusRing(theme),
|
|
58
|
-
width: size,
|
|
59
|
-
height: size,
|
|
60
|
-
// Lifted half out of the bar: the one control that is not a place.
|
|
61
|
-
marginTop: rem(theme, -18),
|
|
62
|
-
borderRadius: '50%',
|
|
63
|
-
display: 'flex',
|
|
64
|
-
alignItems: 'center',
|
|
65
|
-
justifyContent: 'center',
|
|
66
|
-
bgcolor: 'primary.main',
|
|
67
|
-
color: 'primary.contrastText',
|
|
68
|
-
boxShadow: theme.shadows[6],
|
|
69
|
-
'& svg': {
|
|
70
|
-
fontSize: rem(theme, 26),
|
|
71
|
-
transition: theme.transitions.create('transform', { duration: theme.transitions.duration.shorter }),
|
|
72
|
-
transform: expanded ? 'rotate(45deg)' : 'none',
|
|
73
|
-
},
|
|
74
|
-
'@media (prefers-reduced-motion: reduce)': { '& svg': { transition: 'none' } },
|
|
75
|
-
}}
|
|
85
|
+
sx={raisedSx(theme, expanded)}
|
|
76
86
|
>
|
|
77
|
-
{icon}
|
|
87
|
+
<SlotIcon icon={icon} loading={loading} />
|
|
78
88
|
</Box>
|
|
89
|
+
{captioned ? <RaisedCaption label={label} inert={disabled || loading} /> : null}
|
|
79
90
|
</Box>
|
|
80
91
|
);
|
|
81
92
|
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The button's operability. Only `disabled` is native: an in-flight button
|
|
96
|
+
* keeps its focus, reports `aria-busy` and `aria-disabled`, and drops the click.
|
|
97
|
+
*/
|
|
98
|
+
function raisedState({
|
|
99
|
+
onClick,
|
|
100
|
+
disabled,
|
|
101
|
+
loading,
|
|
102
|
+
}: {
|
|
103
|
+
onClick: () => void;
|
|
104
|
+
disabled: boolean;
|
|
105
|
+
loading: boolean;
|
|
106
|
+
}): Record<string, unknown> {
|
|
107
|
+
const busy = loading && !disabled;
|
|
108
|
+
return {
|
|
109
|
+
onClick: busy ? undefined : onClick,
|
|
110
|
+
disabled,
|
|
111
|
+
'aria-disabled': busy ? 'true' : undefined,
|
|
112
|
+
'aria-busy': loading || undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The label under a captioned button. Hidden from assistive tech: the button
|
|
118
|
+
* already carries the same words as its name, and reading them twice is noise.
|
|
119
|
+
*/
|
|
120
|
+
function RaisedCaption({ label, inert }: { label: string; inert: boolean }): React.JSX.Element {
|
|
121
|
+
return (
|
|
122
|
+
<Typography
|
|
123
|
+
component="span"
|
|
124
|
+
variant="caption"
|
|
125
|
+
aria-hidden
|
|
126
|
+
sx={{ lineHeight: 1.3, mt: 0.25, whiteSpace: 'nowrap', color: inert ? 'text.disabled' : 'text.primary' }}
|
|
127
|
+
>
|
|
128
|
+
{label}
|
|
129
|
+
</Typography>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The round button: lifted half out of the bar, its icon turning while what it opened is open. */
|
|
134
|
+
function raisedSx(theme: Theme, expanded: boolean): SxProps<Theme> {
|
|
135
|
+
const size = rem(theme, 52);
|
|
136
|
+
return {
|
|
137
|
+
...CONTROL_RESET,
|
|
138
|
+
...focusRing(theme),
|
|
139
|
+
width: size,
|
|
140
|
+
height: size,
|
|
141
|
+
// Lifted half out of the bar: the one control that is not a place.
|
|
142
|
+
marginTop: rem(theme, -18),
|
|
143
|
+
borderRadius: '50%',
|
|
144
|
+
display: 'flex',
|
|
145
|
+
alignItems: 'center',
|
|
146
|
+
justifyContent: 'center',
|
|
147
|
+
bgcolor: 'primary.main',
|
|
148
|
+
color: 'primary.contrastText',
|
|
149
|
+
boxShadow: theme.shadows[6],
|
|
150
|
+
[NOT_LIVE]: { cursor: 'default', bgcolor: 'action.disabledBackground', color: 'text.disabled', boxShadow: 'none' },
|
|
151
|
+
'& svg': {
|
|
152
|
+
fontSize: rem(theme, 26),
|
|
153
|
+
transition: theme.transitions.create('transform', { duration: theme.transitions.duration.shorter }),
|
|
154
|
+
transform: expanded ? 'rotate(45deg)' : 'none',
|
|
155
|
+
},
|
|
156
|
+
'@media (prefers-reduced-motion: reduce)': { '& svg': { transition: 'none' } },
|
|
157
|
+
} as SxProps<Theme>;
|
|
158
|
+
}
|
|
@@ -5,8 +5,24 @@ import type { ElementType, ReactNode } from 'react';
|
|
|
5
5
|
import type { SectionNavCopy } from '../../../copy';
|
|
6
6
|
import { rem } from '../../../tokens/scales';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
8
|
+
import { destinationState, isMenu } from './SectionNav.helpers';
|
|
9
|
+
import {
|
|
10
|
+
Box,
|
|
11
|
+
CONTROL_RESET,
|
|
12
|
+
CountedIcon,
|
|
13
|
+
NOT_LIVE,
|
|
14
|
+
SlotIcon,
|
|
15
|
+
controlProps,
|
|
16
|
+
destinationControl,
|
|
17
|
+
focusRing,
|
|
18
|
+
} from './SectionNav.parts';
|
|
19
|
+
import type {
|
|
20
|
+
SectionNavAction,
|
|
21
|
+
SectionNavBack,
|
|
22
|
+
SectionNavDestination,
|
|
23
|
+
SectionNavMenu,
|
|
24
|
+
SectionNavProps,
|
|
25
|
+
} from './SectionNav.types';
|
|
10
26
|
|
|
11
27
|
/**
|
|
12
28
|
* One rail row: icon, label, and the count at the far end.
|
|
@@ -14,6 +30,10 @@ import type { SectionNavMenu, SectionNavProps } from './SectionNav.types';
|
|
|
14
30
|
* Destinations and menu entries draw the same row — in the rail there is room
|
|
15
31
|
* for everything, so the difference between "a place" and "an action" is the
|
|
16
32
|
* section it sits in, not its shape.
|
|
33
|
+
*
|
|
34
|
+
* `active` is the look; `current` is what `aria-current` reports, and is a
|
|
35
|
+
* link's claim only — an action row that is ON says so with `aria-pressed`,
|
|
36
|
+
* which arrives in `control`. Omitted, `current` follows `active`.
|
|
17
37
|
*/
|
|
18
38
|
function RailRow({
|
|
19
39
|
label,
|
|
@@ -21,24 +41,29 @@ function RailRow({
|
|
|
21
41
|
icon,
|
|
22
42
|
count,
|
|
23
43
|
active,
|
|
44
|
+
current = active,
|
|
24
45
|
copy,
|
|
25
46
|
testId,
|
|
26
47
|
control,
|
|
48
|
+
loading = false,
|
|
27
49
|
}: {
|
|
28
50
|
label: string;
|
|
29
51
|
description?: string;
|
|
30
52
|
icon: ReactNode;
|
|
31
53
|
count: number | undefined;
|
|
32
54
|
active: boolean;
|
|
55
|
+
current?: boolean;
|
|
33
56
|
copy: SectionNavCopy;
|
|
34
57
|
testId: string;
|
|
35
58
|
control: Record<string, unknown>;
|
|
59
|
+
loading?: boolean;
|
|
36
60
|
}): React.JSX.Element {
|
|
37
61
|
const theme = useTheme();
|
|
38
62
|
return (
|
|
39
63
|
<Box
|
|
40
64
|
{...control}
|
|
41
|
-
aria-current={
|
|
65
|
+
aria-current={current ? 'page' : undefined}
|
|
66
|
+
aria-busy={loading || undefined}
|
|
42
67
|
data-testid={testId}
|
|
43
68
|
sx={{
|
|
44
69
|
...CONTROL_RESET,
|
|
@@ -56,9 +81,15 @@ function RailRow({
|
|
|
56
81
|
bgcolor: active ? 'action.selected' : 'transparent',
|
|
57
82
|
'&:hover': { bgcolor: active ? 'action.selected' : 'action.hover' },
|
|
58
83
|
'& svg': { color: 'primary.main', fontSize: rem(theme, 20) },
|
|
84
|
+
[NOT_LIVE]: { color: 'text.disabled', '& svg': { color: 'text.disabled' } },
|
|
59
85
|
}}
|
|
60
86
|
>
|
|
61
|
-
<CountedIcon
|
|
87
|
+
<CountedIcon
|
|
88
|
+
icon={<SlotIcon icon={icon} loading={loading} />}
|
|
89
|
+
count={count}
|
|
90
|
+
label={copy.badge}
|
|
91
|
+
testId={`${testId}-badge`}
|
|
92
|
+
/>
|
|
62
93
|
<Box component="span" sx={{ display: 'flex', flexDirection: 'column', minWidth: 0 }}>
|
|
63
94
|
<Typography component="span" variant="body2" sx={{ fontWeight: active ? 700 : 500 }}>
|
|
64
95
|
{label}
|
|
@@ -82,7 +113,11 @@ function RailHeading({ children }: { children: ReactNode }): React.JSX.Element {
|
|
|
82
113
|
);
|
|
83
114
|
}
|
|
84
115
|
|
|
85
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* A menu's groups, listed under its title — nothing folded away on a wide
|
|
118
|
+
* screen. A `disabled` menu has no trigger here to refuse, so every row of it
|
|
119
|
+
* is inert instead.
|
|
120
|
+
*/
|
|
86
121
|
function RailMenu({
|
|
87
122
|
menu,
|
|
88
123
|
linkComponent,
|
|
@@ -113,8 +148,13 @@ function RailMenu({
|
|
|
113
148
|
count={entry.badge}
|
|
114
149
|
active={entry.active === true}
|
|
115
150
|
copy={copy}
|
|
116
|
-
testId={`${testId}-entry-${entry.id}`}
|
|
117
|
-
control={controlProps({
|
|
151
|
+
testId={entry.dataTestId ?? `${testId}-entry-${entry.id}`}
|
|
152
|
+
control={controlProps({
|
|
153
|
+
href: entry.href,
|
|
154
|
+
linkComponent,
|
|
155
|
+
inert: menu.disabled === true || entry.disabled === true,
|
|
156
|
+
onClick: () => entry.onSelect?.(),
|
|
157
|
+
})}
|
|
118
158
|
/>
|
|
119
159
|
))}
|
|
120
160
|
</Box>
|
|
@@ -123,6 +163,104 @@ function RailMenu({
|
|
|
123
163
|
);
|
|
124
164
|
}
|
|
125
165
|
|
|
166
|
+
/** The way back out of the section, at the top of the rail. */
|
|
167
|
+
function RailBack({
|
|
168
|
+
back,
|
|
169
|
+
linkComponent,
|
|
170
|
+
testId,
|
|
171
|
+
}: {
|
|
172
|
+
back: SectionNavBack;
|
|
173
|
+
linkComponent: ElementType | undefined;
|
|
174
|
+
testId: string;
|
|
175
|
+
}): React.JSX.Element {
|
|
176
|
+
const theme = useTheme();
|
|
177
|
+
return (
|
|
178
|
+
<Box
|
|
179
|
+
{...controlProps({ href: back.href, linkComponent })}
|
|
180
|
+
data-testid={testId}
|
|
181
|
+
sx={{
|
|
182
|
+
...CONTROL_RESET,
|
|
183
|
+
...focusRing(theme),
|
|
184
|
+
display: 'flex',
|
|
185
|
+
alignItems: 'center',
|
|
186
|
+
gap: 1,
|
|
187
|
+
px: 1.25,
|
|
188
|
+
py: 0.75,
|
|
189
|
+
mb: 0.5,
|
|
190
|
+
borderRadius: 1,
|
|
191
|
+
color: 'text.secondary',
|
|
192
|
+
'&:hover': { bgcolor: 'action.hover', color: 'text.primary' },
|
|
193
|
+
'& svg': { fontSize: rem(theme, 20) },
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
{back.icon}
|
|
197
|
+
<Typography component="span" variant="body2" sx={{ fontWeight: 600 }}>
|
|
198
|
+
{back.label}
|
|
199
|
+
</Typography>
|
|
200
|
+
</Box>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** A primary that ACTS, as one row: there is no sheet to list on a wide screen. */
|
|
205
|
+
function RailAction({
|
|
206
|
+
action,
|
|
207
|
+
linkComponent,
|
|
208
|
+
copy,
|
|
209
|
+
testId,
|
|
210
|
+
}: {
|
|
211
|
+
action: SectionNavAction;
|
|
212
|
+
linkComponent: ElementType | undefined;
|
|
213
|
+
copy: SectionNavCopy;
|
|
214
|
+
testId: string;
|
|
215
|
+
}): React.JSX.Element {
|
|
216
|
+
return (
|
|
217
|
+
<RailRow
|
|
218
|
+
label={action.label}
|
|
219
|
+
icon={action.icon}
|
|
220
|
+
count={undefined}
|
|
221
|
+
active={false}
|
|
222
|
+
copy={copy}
|
|
223
|
+
testId={action.dataTestId ?? testId}
|
|
224
|
+
loading={action.loading === true}
|
|
225
|
+
control={controlProps({
|
|
226
|
+
href: undefined,
|
|
227
|
+
linkComponent,
|
|
228
|
+
inert: action.disabled === true,
|
|
229
|
+
busy: action.loading === true,
|
|
230
|
+
onClick: action.onSelect,
|
|
231
|
+
})}
|
|
232
|
+
/>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** A destination as a row: a link, or an action (a toggle when it carries `active`). */
|
|
237
|
+
function RailDestination({
|
|
238
|
+
destination,
|
|
239
|
+
linkComponent,
|
|
240
|
+
copy,
|
|
241
|
+
testId,
|
|
242
|
+
}: {
|
|
243
|
+
destination: SectionNavDestination;
|
|
244
|
+
linkComponent: ElementType | undefined;
|
|
245
|
+
copy: SectionNavCopy;
|
|
246
|
+
testId: string;
|
|
247
|
+
}): React.JSX.Element {
|
|
248
|
+
const { current, lit } = destinationState(destination);
|
|
249
|
+
return (
|
|
250
|
+
<RailRow
|
|
251
|
+
label={destination.label}
|
|
252
|
+
icon={destination.icon}
|
|
253
|
+
count={destination.badge}
|
|
254
|
+
active={lit}
|
|
255
|
+
current={current}
|
|
256
|
+
copy={copy}
|
|
257
|
+
testId={testId}
|
|
258
|
+
loading={destination.loading === true}
|
|
259
|
+
control={destinationControl(destination, linkComponent, () => destination.onSelect?.())}
|
|
260
|
+
/>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
126
264
|
/**
|
|
127
265
|
* The wide-screen layout: a vertical rail — the way back, the destinations,
|
|
128
266
|
* then every menu's entries.
|
|
@@ -144,7 +282,6 @@ export function SectionNavRail({
|
|
|
144
282
|
Pick<SectionNavProps, 'primary' | 'more' | 'back' | 'heading'> & {
|
|
145
283
|
linkComponent: ElementType | undefined;
|
|
146
284
|
}): React.JSX.Element {
|
|
147
|
-
const theme = useTheme();
|
|
148
285
|
return (
|
|
149
286
|
<Box
|
|
150
287
|
component="nav"
|
|
@@ -161,45 +298,21 @@ export function SectionNavRail({
|
|
|
161
298
|
bgcolor: 'background.paper',
|
|
162
299
|
}}
|
|
163
300
|
>
|
|
164
|
-
{back ?
|
|
165
|
-
<Box
|
|
166
|
-
{...controlProps({ href: back.href, linkComponent })}
|
|
167
|
-
data-testid={`${dataTestId}-back`}
|
|
168
|
-
sx={{
|
|
169
|
-
...CONTROL_RESET,
|
|
170
|
-
...focusRing(theme),
|
|
171
|
-
display: 'flex',
|
|
172
|
-
alignItems: 'center',
|
|
173
|
-
gap: 1,
|
|
174
|
-
px: 1.25,
|
|
175
|
-
py: 0.75,
|
|
176
|
-
mb: 0.5,
|
|
177
|
-
borderRadius: 1,
|
|
178
|
-
color: 'text.secondary',
|
|
179
|
-
'&:hover': { bgcolor: 'action.hover', color: 'text.primary' },
|
|
180
|
-
'& svg': { fontSize: rem(theme, 20) },
|
|
181
|
-
}}
|
|
182
|
-
>
|
|
183
|
-
{back.icon}
|
|
184
|
-
<Typography component="span" variant="body2" sx={{ fontWeight: 600 }}>
|
|
185
|
-
{back.label}
|
|
186
|
-
</Typography>
|
|
187
|
-
</Box>
|
|
188
|
-
) : null}
|
|
301
|
+
{back ? <RailBack back={back} linkComponent={linkComponent} testId={`${dataTestId}-back`} /> : null}
|
|
189
302
|
{heading ? <RailHeading>{heading}</RailHeading> : null}
|
|
190
303
|
{destinations.map((destination) => (
|
|
191
|
-
<
|
|
304
|
+
<RailDestination
|
|
192
305
|
key={destination.id}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
count={destination.badge}
|
|
196
|
-
active={destination.active === true}
|
|
306
|
+
destination={destination}
|
|
307
|
+
linkComponent={linkComponent}
|
|
197
308
|
copy={copy}
|
|
198
|
-
testId={`${dataTestId}-dest-${destination.id}`}
|
|
199
|
-
control={controlProps({ href: destination.href, linkComponent })}
|
|
309
|
+
testId={destination.dataTestId ?? `${dataTestId}-dest-${destination.id}`}
|
|
200
310
|
/>
|
|
201
311
|
))}
|
|
202
|
-
{primary ? (
|
|
312
|
+
{primary && !isMenu(primary) ? (
|
|
313
|
+
<RailAction action={primary} linkComponent={linkComponent} copy={copy} testId={`${dataTestId}-primary`} />
|
|
314
|
+
) : null}
|
|
315
|
+
{isMenu(primary) ? (
|
|
203
316
|
<RailMenu menu={primary} linkComponent={linkComponent} copy={copy} testId={`${dataTestId}-primary`} />
|
|
204
317
|
) : null}
|
|
205
318
|
{more ? <RailMenu menu={more} linkComponent={linkComponent} copy={copy} testId={`${dataTestId}-more`} /> : null}
|
|
@@ -25,13 +25,14 @@ function GridEntry({ entry, linkComponent, copy, testId, onDone }: EntryProps):
|
|
|
25
25
|
{...controlProps({
|
|
26
26
|
href: entry.href,
|
|
27
27
|
linkComponent,
|
|
28
|
+
inert: entry.disabled === true,
|
|
28
29
|
onClick: () => {
|
|
29
30
|
entry.onSelect?.();
|
|
30
31
|
onDone();
|
|
31
32
|
},
|
|
32
33
|
})}
|
|
33
34
|
aria-current={entry.active ? 'page' : undefined}
|
|
34
|
-
data-testid={`${testId}-entry-${entry.id}`}
|
|
35
|
+
data-testid={entry.dataTestId ?? `${testId}-entry-${entry.id}`}
|
|
35
36
|
sx={{
|
|
36
37
|
...CONTROL_RESET,
|
|
37
38
|
...focusRing(theme),
|
|
@@ -48,6 +49,7 @@ function GridEntry({ entry, linkComponent, copy, testId, onDone }: EntryProps):
|
|
|
48
49
|
bgcolor: entry.active ? 'action.selected' : 'action.hover',
|
|
49
50
|
color: entry.active ? 'primary.main' : 'text.primary',
|
|
50
51
|
'& svg': { color: 'primary.main' },
|
|
52
|
+
'&:disabled': { color: 'text.disabled', '& svg': { color: 'text.disabled' } },
|
|
51
53
|
}}
|
|
52
54
|
>
|
|
53
55
|
<CountedIcon icon={entry.icon} count={entry.badge} label={copy.badge} testId={`${testId}-badge-${entry.id}`} />
|
|
@@ -66,13 +68,14 @@ function ListEntry({ entry, linkComponent, copy, testId, onDone }: EntryProps):
|
|
|
66
68
|
{...controlProps({
|
|
67
69
|
href: entry.href,
|
|
68
70
|
linkComponent,
|
|
71
|
+
inert: entry.disabled === true,
|
|
69
72
|
onClick: () => {
|
|
70
73
|
entry.onSelect?.();
|
|
71
74
|
onDone();
|
|
72
75
|
},
|
|
73
76
|
})}
|
|
74
77
|
aria-current={entry.active ? 'page' : undefined}
|
|
75
|
-
data-testid={`${testId}-entry-${entry.id}`}
|
|
78
|
+
data-testid={entry.dataTestId ?? `${testId}-entry-${entry.id}`}
|
|
76
79
|
sx={{
|
|
77
80
|
...CONTROL_RESET,
|
|
78
81
|
...focusRing(theme),
|
|
@@ -87,6 +90,7 @@ function ListEntry({ entry, linkComponent, copy, testId, onDone }: EntryProps):
|
|
|
87
90
|
textAlign: 'start',
|
|
88
91
|
bgcolor: entry.active ? 'action.selected' : 'action.hover',
|
|
89
92
|
'& svg': { color: 'primary.main' },
|
|
93
|
+
'&:disabled': { color: 'text.disabled', '& svg': { color: 'text.disabled' } },
|
|
90
94
|
}}
|
|
91
95
|
>
|
|
92
96
|
<CountedIcon icon={entry.icon} count={entry.badge} label={copy.badge} testId={`${testId}-badge-${entry.id}`} />
|