@pnx-mixtape/mxds 0.1.0-rc.4 → 0.1.0-rc.6
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/.claude/skills/create-mxds-component/SKILL.md +77 -0
- package/DESIGN.md +595 -0
- package/README.md +2 -3
- package/dist/build/accordion.entry.js +2 -1
- package/dist/build/autocomplete.entry.js +1 -1
- package/dist/build/back-to-top.css +1 -0
- package/dist/build/back-to-top.entry.js +1 -0
- package/dist/build/base.css +1 -1
- package/dist/build/button.css +1 -1
- package/dist/build/card.css +1 -1
- package/dist/build/chunks/Accordion-Bqe8vTNR.js +44 -0
- package/dist/build/chunks/AccordionGroup-BYtoAFxg.js +47 -0
- package/dist/build/chunks/AccordionMobile-D517oXYZ.js +26 -0
- package/dist/build/chunks/Autocomplete-kRo8RNbC.js +135 -0
- package/dist/build/chunks/BackToTop-4M-Wl7z3.js +64 -0
- package/dist/build/chunks/ClosableAlert-CZ114JsL.js +55 -0
- package/dist/build/chunks/CookieConsent-wd8gQ2Oe.js +18 -0
- package/dist/build/chunks/Dialog-DoYcGqhW.js +48 -0
- package/dist/build/chunks/DropMenu-CSTZq0Zt.js +68 -0
- package/dist/build/chunks/FeedbackForm-BsL7fv8s.js +64 -0
- package/dist/build/chunks/Gallery-DqJPujX5.js +165 -0
- package/dist/build/chunks/GlobalToggle-7H3F1Wvc.js +82 -0
- package/dist/build/chunks/InPageNavigation-D7m3IN89.js +86 -0
- package/dist/build/chunks/Navigation-CRModT40.js +142 -0
- package/dist/build/chunks/Popover-CiTAftNU.js +30 -0
- package/dist/build/chunks/Sticky-C1H8MNqK.js +51 -0
- package/dist/build/chunks/Tabs-BJD3kABL.js +160 -0
- package/dist/build/chunks/Translate-yAgySwet.js +46 -0
- package/dist/build/chunks/cookie-compliance-CG6x-TcV.js +119 -0
- package/dist/build/chunks/enums-BrHxPZZ_.js +36 -0
- package/dist/build/chunks/polyfills-D-qh1a_2.js +33 -0
- package/dist/build/chunks/utilities-B9mNRs7V.js +255 -0
- package/dist/build/content-block.css +1 -1
- package/dist/build/cookie-consent.entry.js +1 -1
- package/dist/build/dialog.entry.js +1 -1
- package/dist/build/drop-menu.entry.js +1 -1
- package/dist/build/elements.js +147 -1
- package/dist/build/feedback-form.css +1 -0
- package/dist/build/feedback-form.entry.js +1 -0
- package/dist/build/filters.entry.js +145 -1
- package/dist/build/form.css +1 -1
- package/dist/build/gallery.entry.js +1 -1
- package/dist/build/global-alert.entry.js +1 -1
- package/dist/build/header.css +1 -1
- package/dist/build/header.entry.js +1 -1
- package/dist/build/icon.css +1 -1
- package/dist/build/in-page-navigation.entry.js +1 -1
- package/dist/build/navigation.css +1 -1
- package/dist/build/navigation.entry.js +1 -1
- package/dist/build/pagination.css +1 -1
- package/dist/build/popover.entry.js +1 -1
- package/dist/build/react.js +2291 -1
- package/dist/build/side-navigation.css +1 -1
- package/dist/build/sidebar.css +1 -1
- package/dist/build/sticky.entry.js +1 -1
- package/dist/build/tabs.entry.js +1 -1
- package/dist/build/translate.css +1 -0
- package/dist/build/translate.entry.js +1 -0
- package/dist/build/utility-list.entry.js +53 -1
- package/dist/types/Component/BackToTop/BackToTop.d.ts +20 -0
- package/dist/types/Component/BackToTop/BackToTop.d.ts.map +1 -0
- package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts +27 -0
- package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts.map +1 -0
- package/dist/types/Component/BackToTop/back-to-top.entry.d.ts +2 -0
- package/dist/types/Component/BackToTop/back-to-top.entry.d.ts.map +1 -0
- package/dist/types/Component/DropMenu/DropMenu.d.ts +2 -1
- package/dist/types/Component/DropMenu/DropMenu.d.ts.map +1 -1
- package/dist/types/Component/Navigation/Elements/Navigation.d.ts +2 -3
- package/dist/types/Component/Navigation/Elements/Navigation.d.ts.map +1 -1
- package/dist/types/Component/SideNavigation/Components/SideNavigationTree.d.ts +3 -3
- package/dist/types/Component/SideNavigation/Components/SideNavigationTree.d.ts.map +1 -1
- package/dist/types/Component/SideNavigation/SideNavigation.d.ts +3 -1
- package/dist/types/Component/SideNavigation/SideNavigation.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Components/TabDropMenu.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Elements/Tabs.d.ts.map +1 -1
- package/dist/types/Component/Translate/Elements/Translate.d.ts +23 -0
- package/dist/types/Component/Translate/Elements/Translate.d.ts.map +1 -0
- package/dist/types/Component/Translate/Translate.d.ts +37 -0
- package/dist/types/Component/Translate/Translate.d.ts.map +1 -0
- package/dist/types/Component/Translate/languages.d.ts +11 -0
- package/dist/types/Component/Translate/languages.d.ts.map +1 -0
- package/dist/types/Component/Translate/translate.entry.d.ts +2 -0
- package/dist/types/Component/Translate/translate.entry.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts +37 -0
- package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts +36 -0
- package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts +2 -0
- package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts.map +1 -0
- package/dist/types/Utility/Elements/google-translate-widget.d.ts +91 -0
- package/dist/types/Utility/Elements/google-translate-widget.d.ts.map +1 -0
- package/dist/types/Utility/utilities.d.ts +2 -0
- package/dist/types/Utility/utilities.d.ts.map +1 -1
- package/dist/types/elements.d.ts +3 -0
- package/dist/types/elements.d.ts.map +1 -1
- package/dist/types/enums.d.ts +3 -1
- package/dist/types/enums.d.ts.map +1 -1
- package/dist/types/react.d.ts +3 -0
- package/dist/types/react.d.ts.map +1 -1
- package/dist/types/tokens.d.ts +1 -0
- package/dist/types/tokens.d.ts.map +1 -1
- package/docs/API.md +1215 -0
- package/docs/CSSGuidelines.md +893 -0
- package/docs/CodeQuality.md +940 -0
- package/docs/ComponentArchitecture.md +714 -0
- package/docs/Contributing.md +626 -0
- package/docs/MemoryManagement.md +598 -0
- package/docs/Migrations.md +6 -0
- package/docs/README.md +100 -0
- package/docs/Security.md +444 -0
- package/docs/Testing.md +739 -0
- package/docs/Troubleshooting.md +674 -0
- package/package.json +9 -6
- package/src/Atom/Button/Button.tsx +1 -1
- package/src/Atom/Button/__snapshots__/Button.stories.tsx.snap +2 -2
- package/src/Atom/Button/_buttons.css +1 -0
- package/src/Atom/Icon/README.md +1 -1
- package/src/Atom/Icon/_icon.css +1 -0
- package/src/Atom/Icon/images/thumb.svg +1 -0
- package/src/Atom/Link/_links.css +1 -0
- package/src/Atom/Media/__snapshots__/Media.stories.ts.snap +4 -9
- package/src/Atom/Media/media.twig +4 -5
- package/src/Component/BackToTop/BackToTop.stories.ts +127 -0
- package/src/Component/BackToTop/BackToTop.stories.tsx +53 -0
- package/src/Component/BackToTop/BackToTop.tsx +120 -0
- package/src/Component/BackToTop/Elements/BackToTop.ts +119 -0
- package/src/Component/BackToTop/README.md +164 -0
- package/src/Component/BackToTop/__snapshots__/BackToTop.stories.ts.snap +24 -0
- package/src/Component/BackToTop/__snapshots__/BackToTop.stories.tsx.snap +3 -0
- package/src/Component/BackToTop/back-to-top.css +70 -0
- package/src/Component/BackToTop/back-to-top.entry.js +1 -0
- package/src/Component/BackToTop/back-to-top.twig +26 -0
- package/src/Component/Card/Components/CardContent.tsx +1 -1
- package/src/Component/Card/Components/CardMedia.tsx +1 -1
- package/src/Component/Card/__snapshots__/Card.stories.tsx.snap +3 -3
- package/src/Component/Card/card.css +1 -1
- package/src/Component/Carousel/Carousel.stories.ts +66 -36
- package/src/Component/Carousel/__snapshots__/Carousel.stories.tsx.snap +5 -5
- package/src/Component/ContentBlock/Components/ContentBlockMedia.tsx +1 -1
- package/src/Component/ContentBlock/content-block.css +1 -1
- package/src/Component/DropMenu/DropMenu.stories.ts +21 -2
- package/src/Component/DropMenu/DropMenu.tsx +14 -4
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.ts.snap +3 -3
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.tsx.snap +2 -2
- package/src/Component/DropMenu/drop-menu.twig +7 -1
- package/src/Component/HeroBanner/__snapshots__/HeroBanner.stories.tsx.snap +1 -1
- package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.ts.snap +48 -13
- package/src/Component/InPageNavigation/in-page-navigation.twig +4 -6
- package/src/Component/ListItem/Components/ListItemMedia.tsx +1 -1
- package/src/Component/Navigation/Elements/Navigation.ts +25 -29
- package/src/Component/Navigation/README.md +11 -10
- package/src/Component/Navigation/__snapshots__/Navigation.stories.ts.snap +8 -8
- package/src/Component/Navigation/_navigation-dropdown.css +9 -11
- package/src/Component/Navigation/_navigation-mega.css +5 -11
- package/src/Component/Navigation/_navigation.css +5 -3
- package/src/Component/Navigation/menu.twig +1 -1
- package/src/Component/Navigation/navigation.twig +0 -1
- package/src/Component/Pagination/Pagination.stories.ts +57 -27
- package/src/Component/Pagination/pagination.css +16 -7
- package/src/Component/Popover/__snapshots__/Popover.stories.tsx.snap +3 -3
- package/src/Component/SideNavigation/Components/SideNavigationTree.tsx +68 -24
- package/src/Component/SideNavigation/README.md +78 -32
- package/src/Component/SideNavigation/SideNavigation.stories.ts +41 -2
- package/src/Component/SideNavigation/SideNavigation.stories.tsx +31 -0
- package/src/Component/SideNavigation/SideNavigation.tsx +13 -3
- package/src/Component/SideNavigation/__snapshots__/SideNavigation.stories.ts.snap +87 -3
- package/src/Component/SideNavigation/__snapshots__/SideNavigation.stories.tsx.snap +2 -0
- package/src/Component/SideNavigation/side-navigation.css +3 -3
- package/src/Component/SideNavigation/side-navigation.twig +10 -3
- package/src/Component/Tabs/Components/TabDropMenu.tsx +7 -1
- package/src/Component/Tabs/Elements/Tabs.ts +13 -3
- package/src/Component/Tabs/Tabs.stories.ts +33 -12
- package/src/Component/Tabs/Tabs.stories.tsx +28 -14
- package/src/Component/Tabs/__snapshots__/Tabs.stories.ts.snap +3 -3
- package/src/Component/Tabs/__snapshots__/Tabs.stories.tsx.snap +2 -2
- package/src/Component/Translate/Elements/Translate.ts +76 -0
- package/src/Component/Translate/README.md +285 -0
- package/src/Component/Translate/Translate.stories.ts +87 -0
- package/src/Component/Translate/Translate.stories.tsx +77 -0
- package/src/Component/Translate/Translate.tsx +213 -0
- package/src/Component/Translate/__snapshots__/Translate.stories.ts.snap +18 -0
- package/src/Component/Translate/__snapshots__/Translate.stories.tsx.snap +3 -0
- package/src/Component/Translate/languages.ts +39 -0
- package/src/Component/Translate/translate.css +62 -0
- package/src/Component/Translate/translate.entry.js +1 -0
- package/src/Component/Translate/translate.twig +44 -0
- package/src/Form/Autocomplete/Autocomplete.stories.tsx +2 -1
- package/src/Form/FeedbackForm/Elements/FeedbackForm.ts +122 -0
- package/src/Form/FeedbackForm/FeedbackForm.stories.ts +99 -0
- package/src/Form/FeedbackForm/FeedbackForm.stories.tsx +73 -0
- package/src/Form/FeedbackForm/FeedbackForm.tsx +196 -0
- package/src/Form/FeedbackForm/README.md +233 -0
- package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.ts.snap +125 -0
- package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.tsx.snap +5 -0
- package/src/Form/FeedbackForm/feedback-form.css +74 -0
- package/src/Form/FeedbackForm/feedback-form.entry.js +1 -0
- package/src/Form/FeedbackForm/feedback-form.twig +80 -0
- package/src/Form/form.css +17 -19
- package/src/Introduction.mdx +1 -1
- package/src/Layout/Footer/__snapshots__/Footer.stories.ts.snap +3 -3
- package/src/Layout/Header/Components/GlobalToggle.tsx +1 -1
- package/src/Layout/Header/Header.stories.ts +53 -48
- package/src/Layout/Header/Header.stories.tsx +35 -22
- package/src/Layout/Header/Header.tsx +2 -2
- package/src/Layout/Header/__snapshots__/Header.stories.ts.snap +18 -14
- package/src/Layout/Header/__snapshots__/Header.stories.tsx.snap +2 -2
- package/src/Layout/Header/_toggles.css +7 -7
- package/src/Layout/Page/__snapshots__/Page.stories.tsx.snap +1 -1
- package/src/Layout/Sidebar/sidebar.css +10 -3
- package/src/Utility/Elements/google-translate-widget.ts +293 -0
- package/src/Utility/global.d.ts +4 -0
- package/src/Utility/utilities.ts +2 -0
- package/src/_custom-media.css +2 -0
- package/src/elements.ts +3 -0
- package/src/enums.ts +2 -0
- package/src/react.ts +3 -0
- package/src/tokens.js +1 -0
- package/dist/build/chunks/Accordion-GN0sME4W.js +0 -1
- package/dist/build/chunks/AccordionGroup-Bzqp5__q.js +0 -1
- package/dist/build/chunks/AccordionMobile-DG5jSe8Q.js +0 -1
- package/dist/build/chunks/Autocomplete-B4RtkAyh.js +0 -1
- package/dist/build/chunks/ClosableAlert-DEhj8Nxm.js +0 -1
- package/dist/build/chunks/CookieConsent-BkCMRVST.js +0 -1
- package/dist/build/chunks/Dialog-Haahn-Yn.js +0 -1
- package/dist/build/chunks/DropMenu-1baQc8Yc.js +0 -1
- package/dist/build/chunks/Gallery-D8NGXy2Q.js +0 -1
- package/dist/build/chunks/GlobalToggle-B52fdBQu.js +0 -1
- package/dist/build/chunks/InPageNavigation-Dyn2TR-N.js +0 -1
- package/dist/build/chunks/Navigation-hOrfLq7h.js +0 -1
- package/dist/build/chunks/Popover-Cxg-s5Jn.js +0 -1
- package/dist/build/chunks/Sticky-BGCuuEJK.js +0 -1
- package/dist/build/chunks/Tabs-DYy1YTdU.js +0 -15
- package/dist/build/chunks/cookie-compliance-Bbm-0vpj.js +0 -1
- package/dist/build/chunks/enums-DLV_1NAf.js +0 -1
- package/dist/build/chunks/polyfills-C7pRt54M.js +0 -1
- package/dist/build/chunks/utilities-_Bq3TxAQ.js +0 -1
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
&:is(.mx-nav__has-subnav, :has(> ul)) {
|
|
21
21
|
position: relative;
|
|
22
22
|
|
|
23
|
+
/* Each sub-menu anchors to its own parent item. */
|
|
24
|
+
anchor-scope: --mx-nav-item;
|
|
25
|
+
anchor-name: --mx-nav-item;
|
|
26
|
+
|
|
23
27
|
& > a {
|
|
24
28
|
--toggle-gap: var(--spacing-l);
|
|
25
29
|
}
|
|
@@ -90,7 +94,11 @@
|
|
|
90
94
|
margin: 0;
|
|
91
95
|
|
|
92
96
|
@media (--global-nav-up) {
|
|
93
|
-
|
|
97
|
+
/* Fixed so fallbacks flip against the viewport, not the parent item. */
|
|
98
|
+
position: fixed;
|
|
99
|
+
position-anchor: --mx-nav-item;
|
|
100
|
+
position-area: block-end span-inline-end;
|
|
101
|
+
position-try-fallbacks: flip-inline;
|
|
94
102
|
inline-size: var(--dropdown-width, 270px);
|
|
95
103
|
border-radius: var(--border-radius);
|
|
96
104
|
background-color: var(
|
|
@@ -113,16 +121,6 @@
|
|
|
113
121
|
}
|
|
114
122
|
}
|
|
115
123
|
|
|
116
|
-
& .mx-nav--reverse .nav__level-2 {
|
|
117
|
-
@media (--global-nav-up) {
|
|
118
|
-
inset-inline: auto 0;
|
|
119
|
-
|
|
120
|
-
& .mx-nav__toggle::before {
|
|
121
|
-
transform: rotate(90deg);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
124
|
& .mx-nav__separator {
|
|
127
125
|
border-block-start: var(--line-width, 1px) solid var(--line-colour, var(--colour-border));
|
|
128
126
|
}
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
inline-size: 100%;
|
|
18
18
|
|
|
19
19
|
@media (--global-nav-up) {
|
|
20
|
-
position:
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
position: fixed;
|
|
21
|
+
position-anchor: --mx-nav-item;
|
|
22
|
+
position-area: span-block-end inline-end;
|
|
23
|
+
position-try-fallbacks: flip-inline;
|
|
23
24
|
inline-size: var(--dropdown-width, 270px);
|
|
24
|
-
min-block-size:
|
|
25
|
+
min-block-size: anchor-size(block);
|
|
25
26
|
padding-inline-start: 0;
|
|
26
27
|
border-block-start: 0;
|
|
27
28
|
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
|
@@ -37,12 +38,5 @@
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
-
& .mx-nav--reverse .mx-nav__level-2 ul {
|
|
42
|
-
@media (--global-nav-up) {
|
|
43
|
-
inset-inline-start: calc(var(--dropdown-width, 270px) * -1);
|
|
44
|
-
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
41
|
}
|
|
48
42
|
}
|
|
@@ -45,11 +45,13 @@
|
|
|
45
45
|
inset-block-start: var(--toggle-top, 12px);
|
|
46
46
|
inset-inline-end: 0;
|
|
47
47
|
cursor: pointer;
|
|
48
|
-
}
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
& > .mx-nav__toggle::before {
|
|
49
|
+
&[aria-expanded="true"]::before {
|
|
52
50
|
transform: rotate(180deg);
|
|
53
51
|
}
|
|
54
52
|
}
|
|
53
|
+
|
|
54
|
+
mx-nav:not(:defined) .mx-nav__toggle {
|
|
55
|
+
display: none;
|
|
56
|
+
}
|
|
55
57
|
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
{{ item.link.title }}
|
|
24
24
|
{% endif %}
|
|
25
25
|
{% if item.items is not empty %}
|
|
26
|
-
<button class="mx-nav__toggle mx-icon mx-icon--chevron-down mx-icon--only">Sub-navigation</button>
|
|
26
|
+
<button class="mx-nav__toggle mx-icon mx-icon--chevron-down mx-icon--only" type="button">Sub-navigation</button>
|
|
27
27
|
{{ nav.menutree(item.items, level + 1, setInert, baseClass) }}
|
|
28
28
|
{% endif %}
|
|
29
29
|
</li>
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
{% if type == "mega" %}
|
|
15
15
|
{# Horizontal menu with 3 levels deep, no hover flyout. #}
|
|
16
16
|
{% set needsJs = true %}
|
|
17
|
-
{% set componentAttributes = componentAttributes.addAttribute("edgeCheck") %}
|
|
18
17
|
{% set modifiers = ['dropdown', 'mega-nav'] %}
|
|
19
18
|
{% endif %}
|
|
20
19
|
{% if type == "footer" %}
|
|
@@ -136,35 +136,65 @@ export const Pagination: Story = {
|
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
138
|
* Steps a virtual screen reader through the page and asserts on what it announces.
|
|
139
|
+
*
|
|
140
|
+
* Pagination's narrow/wide layouts are driven by a CSS container query (not
|
|
141
|
+
* `matchMedia`) that swaps the numbered link list for a "Page X of Y" label -
|
|
142
|
+
* see `pagination.css`. Chromatic exercises both `chromatic.modes` (mobile and
|
|
143
|
+
* desktop), so this branches on the container's own rendered state (which of
|
|
144
|
+
* the two is actually visible) rather than hardcoding one shape. Whichever
|
|
145
|
+
* mode a local run happens to render at, only one branch runs meaningfully for
|
|
146
|
+
* that run - the other is exercised by Chromatic's other mode in CI. See
|
|
147
|
+
* "Testing Mobile / matchMedia-Driven Behavior in Play Functions" in
|
|
148
|
+
* docs/Testing.md.
|
|
139
149
|
*/
|
|
140
150
|
export const ScreenReaderAnnouncements: Story = {
|
|
141
|
-
play: async () => {
|
|
151
|
+
play: async ({ canvasElement }) => {
|
|
152
|
+
const label = canvasElement.querySelector(".mx-pagination__label")
|
|
153
|
+
const isNarrow = label !== null && getComputedStyle(label).display !== "none"
|
|
142
154
|
const log = await runScreenReaderWalkthrough()
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
155
|
+
|
|
156
|
+
if (isNarrow) {
|
|
157
|
+
await expect(log).toEqual([
|
|
158
|
+
"document",
|
|
159
|
+
"navigation, Pagination",
|
|
160
|
+
"link, Previous",
|
|
161
|
+
"Previous",
|
|
162
|
+
"end of link, Previous",
|
|
163
|
+
"Page 2 of 10",
|
|
164
|
+
"list",
|
|
165
|
+
"link, Next",
|
|
166
|
+
"Next",
|
|
167
|
+
"end of link, Next",
|
|
168
|
+
"end of navigation, Pagination",
|
|
169
|
+
"end of document",
|
|
170
|
+
])
|
|
171
|
+
} else {
|
|
172
|
+
await expect(log).toEqual([
|
|
173
|
+
"document",
|
|
174
|
+
"navigation, Pagination",
|
|
175
|
+
"link, Previous",
|
|
176
|
+
"Previous",
|
|
177
|
+
"end of link, Previous",
|
|
178
|
+
"list",
|
|
179
|
+
"listitem, level 1, position 1, set size 4",
|
|
180
|
+
"link, 1",
|
|
181
|
+
"end of listitem, level 1, position 1, set size 4",
|
|
182
|
+
"listitem, level 1, position 2, set size 4",
|
|
183
|
+
"link, 2, current page",
|
|
184
|
+
"end of listitem, level 1, position 2, set size 4",
|
|
185
|
+
"listitem, level 1, position 3, set size 4",
|
|
186
|
+
"link, 3",
|
|
187
|
+
"end of listitem, level 1, position 3, set size 4",
|
|
188
|
+
"listitem, level 1, position 4, set size 4",
|
|
189
|
+
"link, 10",
|
|
190
|
+
"end of listitem, level 1, position 4, set size 4",
|
|
191
|
+
"end of list",
|
|
192
|
+
"link, Next",
|
|
193
|
+
"Next",
|
|
194
|
+
"end of link, Next",
|
|
195
|
+
"end of navigation, Pagination",
|
|
196
|
+
"end of document",
|
|
197
|
+
])
|
|
198
|
+
}
|
|
169
199
|
},
|
|
170
200
|
}
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
* Pagination
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
@import "../../Utility/_mixins.css";
|
|
6
|
-
|
|
7
5
|
@layer design-system.components {
|
|
8
6
|
.mx-pagination {
|
|
9
7
|
margin-inline: 0;
|
|
@@ -102,11 +100,22 @@
|
|
|
102
100
|
& :is(.mx-pagination__previous, .mx-pagination__next) {
|
|
103
101
|
text-transform: capitalize;
|
|
104
102
|
|
|
105
|
-
&
|
|
106
|
-
@
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
& :is(a, button) {
|
|
104
|
+
@container pagination (max-width: 32ch) {
|
|
105
|
+
inline-size: var(--button-min-height, var(--spacing-xl));
|
|
106
|
+
block-size: var(--button-min-height, var(--spacing-xl));
|
|
107
|
+
flex-wrap: wrap;
|
|
108
|
+
position: relative;
|
|
109
|
+
padding: 0;
|
|
110
|
+
overflow: hidden;
|
|
111
|
+
|
|
112
|
+
& .mx-icon {
|
|
113
|
+
inline-size: var(--button-min-height, var(--spacing-xl));
|
|
114
|
+
block-size: var(--button-min-height, var(--spacing-xl));
|
|
115
|
+
place-content: center;
|
|
116
|
+
display: flex;
|
|
117
|
+
order: -1;
|
|
118
|
+
}
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
121
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`Content Only 1`] = `"<button class="mx-button mx-icon--end" type="button" id="story-popover-minimal-trigger" popovertarget="story-popover-minimal" style="anchor-name: --story-popover-minimal;">Popover trigger
|
|
3
|
+
exports[`Content Only 1`] = `"<button class="mx-button mx-icon--end" type="button" id="story-popover-minimal-trigger" popovertarget="story-popover-minimal" style="anchor-name: --story-popover-minimal;"><span>Popover trigger</span><span class="mx-icon mx-icon--question"></span></button><div class="mx-popover" id="story-popover-minimal" aria-describedby="story-popover-minimal-trigger" data-placement="bottom" popover="" style="position-anchor: --story-popover-minimal;"><div class="mx-rich-text mx-vertical-flow"><p>A simple hint with no heading or action link.</p></div></div>"`;
|
|
4
4
|
|
|
5
|
-
exports[`Default 1`] = `"<button class="mx-button mx-icon--end" type="button" id="story-popover-trigger" popovertarget="story-popover" style="anchor-name: --story-popover;">Popover trigger
|
|
5
|
+
exports[`Default 1`] = `"<button class="mx-button mx-icon--end" type="button" id="story-popover-trigger" popovertarget="story-popover" style="anchor-name: --story-popover;"><span>Popover trigger</span><span class="mx-icon mx-icon--question"></span></button><div class="mx-popover" id="story-popover" aria-describedby="story-popover-trigger" data-placement="bottom" popover="" style="position-anchor: --story-popover;"><div class="mx-rich-text mx-vertical-flow"><h2>Popover heading</h2><p>Short supplementary content. Use Popover for non-interactive hints; for menus use DropMenu, for modal content use Dialog.</p><a href="#" class="mx-link mx-link--more">Learn more</a></div></div>"`;
|
|
6
6
|
|
|
7
|
-
exports[`Placement 1`] = `"<button class="mx-button mx-icon--end" type="button" id="story-popover-placement-trigger" popovertarget="story-popover-placement" style="anchor-name: --story-popover-placement;">Popover trigger
|
|
7
|
+
exports[`Placement 1`] = `"<button class="mx-button mx-icon--end" type="button" id="story-popover-placement-trigger" popovertarget="story-popover-placement" style="anchor-name: --story-popover-placement;"><span>Popover trigger</span><span class="mx-icon mx-icon--question"></span></button><div class="mx-popover" id="story-popover-placement" aria-describedby="story-popover-placement-trigger" data-placement="top" popover="" style="position-anchor: --story-popover-placement;"><div class="mx-rich-text mx-vertical-flow"><h2>Popover heading</h2><p>Short supplementary content. Use Popover for non-interactive hints; for menus use DropMenu, for modal content use Dialog.</p><a href="#" class="mx-link mx-link--more">Learn more</a></div></div>"`;
|
|
@@ -1,37 +1,81 @@
|
|
|
1
|
-
import { JSX } from "react"
|
|
1
|
+
import { JSX, ComponentPropsWithoutRef, useContext, useId } from "react"
|
|
2
|
+
import { useDisclosureState } from "react-stately"
|
|
2
3
|
import { MenuTree } from "@pnx-mixtape/ids-shape"
|
|
3
4
|
import Link from "../../../Atom/Link/Link"
|
|
5
|
+
import { SideNavigationContext } from "../SideNavigation"
|
|
4
6
|
|
|
5
|
-
type SideNavigationTreeProps = {
|
|
7
|
+
type SideNavigationTreeProps = ComponentPropsWithoutRef<"ul"> & {
|
|
6
8
|
items: MenuTree[]
|
|
7
9
|
level?: number
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
type SideNavigationItemProps = {
|
|
13
|
+
item: MenuTree
|
|
14
|
+
level: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// True when this item, or any descendant, is the current page.
|
|
18
|
+
const hasCurrent = ({ link, items }: MenuTree): boolean =>
|
|
19
|
+
Boolean(link.current) || Boolean(items?.some(hasCurrent))
|
|
20
|
+
|
|
21
|
+
const SideNavigationItem = ({ item, level }: SideNavigationItemProps): JSX.Element => {
|
|
22
|
+
const collapsible = useContext(SideNavigationContext)
|
|
23
|
+
const { isExpanded, toggle } = useDisclosureState({ defaultExpanded: hasCurrent(item) })
|
|
24
|
+
const listId = useId()
|
|
25
|
+
const labelId = useId()
|
|
26
|
+
const hasItems = item.items && item.items.length > 0
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<li className={hasItems ? "mx-nav__has-subnav" : undefined}>
|
|
30
|
+
{item.link.href ? (
|
|
31
|
+
<Link
|
|
32
|
+
to={item.link.href}
|
|
33
|
+
id={hasItems && collapsible ? labelId : undefined}
|
|
34
|
+
aria-current={item.link.current ? "page" : undefined}
|
|
35
|
+
>
|
|
36
|
+
{item.link.title}
|
|
37
|
+
</Link>
|
|
38
|
+
) : (
|
|
39
|
+
item.link.title
|
|
40
|
+
)}
|
|
41
|
+
{hasItems && (
|
|
42
|
+
<>
|
|
43
|
+
<button
|
|
44
|
+
className="mx-nav__toggle mx-icon mx-icon--chevron-down mx-icon--only"
|
|
45
|
+
type="button"
|
|
46
|
+
{...(collapsible && {
|
|
47
|
+
"aria-expanded": isExpanded,
|
|
48
|
+
"aria-controls": listId,
|
|
49
|
+
onClick: toggle,
|
|
50
|
+
})}
|
|
51
|
+
>
|
|
52
|
+
Sub-navigation
|
|
53
|
+
</button>
|
|
54
|
+
<SideNavigationTree
|
|
55
|
+
items={item.items}
|
|
56
|
+
level={level + 1}
|
|
57
|
+
{...(collapsible && {
|
|
58
|
+
id: listId,
|
|
59
|
+
"aria-labelledby": item.link.href ? labelId : undefined,
|
|
60
|
+
inert: !isExpanded,
|
|
61
|
+
})}
|
|
62
|
+
/>
|
|
63
|
+
</>
|
|
64
|
+
)}
|
|
65
|
+
</li>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const SideNavigationTree = ({
|
|
70
|
+
items,
|
|
71
|
+
level = 0,
|
|
72
|
+
...props
|
|
73
|
+
}: SideNavigationTreeProps): JSX.Element => (
|
|
74
|
+
<ul className={`mx-nav__level-${level + 1}`} {...props}>
|
|
12
75
|
{items.map((item, i) => (
|
|
13
76
|
// Menu items have no stable id; index is the only available key.
|
|
14
77
|
// oxlint-disable-next-line react/no-array-index-key
|
|
15
|
-
<
|
|
16
|
-
{item.link.href ? (
|
|
17
|
-
<Link to={item.link.href} aria-current={item.link.current ? "page" : undefined}>
|
|
18
|
-
{item.link.title}
|
|
19
|
-
</Link>
|
|
20
|
-
) : (
|
|
21
|
-
item.link.title
|
|
22
|
-
)}
|
|
23
|
-
{item.items && item.items.length > 0 && (
|
|
24
|
-
<>
|
|
25
|
-
<button
|
|
26
|
-
className="mx-nav__toggle mx-icon mx-icon--chevron-down mx-icon--only"
|
|
27
|
-
type="button"
|
|
28
|
-
>
|
|
29
|
-
Sub-navigation
|
|
30
|
-
</button>
|
|
31
|
-
<SideNavigationTree items={item.items} level={level + 1} />
|
|
32
|
-
</>
|
|
33
|
-
)}
|
|
34
|
-
</li>
|
|
78
|
+
<SideNavigationItem key={i} item={item} level={level} />
|
|
35
79
|
))}
|
|
36
80
|
</ul>
|
|
37
81
|
)
|
|
@@ -2,18 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
A vertical, multi-level navigation rendered as a `<nav>` landmark with a parent page link at the top and a nested list of sibling/child pages below. Designed to live in the aside of content layouts to expose a section's local page tree. Extends the base `.mx-nav` styles from the Navigation component.
|
|
4
4
|
|
|
5
|
+
By default every level is rendered open. Set `collapsible` to wrap the nav in `<mx-nav openActive>`, which turns each sub-list into a click-to-expand disclosure and opens the branch containing the current page.
|
|
6
|
+
|
|
5
7
|
## CSS Import
|
|
6
8
|
|
|
7
9
|
```css
|
|
8
10
|
@import "@pnx-mixtape/mxds/src/Component/SideNavigation/side-navigation.css";
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
Depends on the Navigation component's base styles (for `.mx-nav
|
|
13
|
+
Depends on the Navigation component's base styles (for `.mx-nav`, sub-list level classes and, when collapsible, the `.mx-nav--collapsible` toggle/transition styles):
|
|
12
14
|
|
|
13
15
|
```css
|
|
14
16
|
@import "@pnx-mixtape/mxds/src/Component/Navigation/navigation.css";
|
|
15
17
|
```
|
|
16
18
|
|
|
19
|
+
## JS Import (collapsible only)
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import "@pnx-mixtape/mxds/src/Component/Navigation/Elements/Navigation"
|
|
23
|
+
```
|
|
24
|
+
|
|
17
25
|
---
|
|
18
26
|
|
|
19
27
|
## Twig
|
|
@@ -24,6 +32,7 @@ Depends on the Navigation component's base styles (for `.mx-nav` and sub-list le
|
|
|
24
32
|
title: 'Section navigation',
|
|
25
33
|
parent: parent_link,
|
|
26
34
|
items: menu_tree,
|
|
35
|
+
collapsible: true,
|
|
27
36
|
} %}
|
|
28
37
|
```
|
|
29
38
|
|
|
@@ -31,13 +40,14 @@ Depends on the Navigation component's base styles (for `.mx-nav` and sub-list le
|
|
|
31
40
|
|
|
32
41
|
**side-navigation.twig props** (`SideNavigation` type from `ids-shape`):
|
|
33
42
|
|
|
34
|
-
| Prop
|
|
35
|
-
|
|
|
36
|
-
| `id`
|
|
37
|
-
| `title`
|
|
38
|
-
| `parent`
|
|
39
|
-
| `items`
|
|
40
|
-
| `
|
|
43
|
+
| Prop | Type | Description |
|
|
44
|
+
| ------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| `id` | `string` | Written to `.mx-side-nav__parent` and used as the `<nav>`'s `aria-labelledby` fallback when `title` is empty |
|
|
46
|
+
| `title` | `string` | `aria-label` for the `<nav>` landmark. Provide a meaningful value so assistive tech can distinguish this nav from the site's primary nav |
|
|
47
|
+
| `parent` | `Link` | Pre-rendered Link atom representing the current section's parent page, shown above the list |
|
|
48
|
+
| `items` | `MenuTree[]` | Recursive menu tree — see shape below |
|
|
49
|
+
| `collapsible` | `boolean` | Optional. Wraps the `<nav>` in `<mx-nav openActive>` and adds `mx-nav--collapsible` so sub-lists collapse behind their toggle buttons. Not part of the `ids-shape` `SideNavigation` type |
|
|
50
|
+
| `attributes` | `Attribute` | Optional Drupal-style attributes object; the template merges `mx-nav mx-side-nav` onto its classes |
|
|
41
51
|
|
|
42
52
|
**`MenuTree` shape** (from `@pnx-mixtape/ids-shape`):
|
|
43
53
|
|
|
@@ -98,13 +108,43 @@ Rules for the markup:
|
|
|
98
108
|
- The top list is `mx-nav__level-1`, children are `mx-nav__level-2`, grandchildren are `mx-nav__level-3`. Each level adds more inline-start indent.
|
|
99
109
|
- Any `<li>` that contains a nested `<ul>` must carry `mx-nav__has-subnav`.
|
|
100
110
|
- The current page's `<a>` carries `aria-current="page"` — CSS uses this to bold the label and apply the active background.
|
|
101
|
-
- Sub-lists are **not** collapsed or inert
|
|
111
|
+
- Sub-lists are **not** collapsed or inert by default — every level is rendered open.
|
|
112
|
+
|
|
113
|
+
### Collapsible
|
|
114
|
+
|
|
115
|
+
```html
|
|
116
|
+
<mx-nav openActive>
|
|
117
|
+
<nav class="mx-nav mx-side-nav mx-nav--collapsible" aria-label="Section navigation">
|
|
118
|
+
<div class="mx-side-nav__parent" id="section-nav">
|
|
119
|
+
<a href="/about">About</a>
|
|
120
|
+
</div>
|
|
121
|
+
<ul class="mx-nav__level-1">
|
|
122
|
+
<li class="mx-nav__has-subnav">
|
|
123
|
+
<a href="/about/what-we-do">What we do</a>
|
|
124
|
+
<button class="mx-nav__toggle mx-icon mx-icon--chevron-down mx-icon--only" type="button">
|
|
125
|
+
Sub-navigation
|
|
126
|
+
</button>
|
|
127
|
+
<ul class="mx-nav__level-2">
|
|
128
|
+
<li>
|
|
129
|
+
<a href="/about/what-we-do/events" aria-current="page">Events</a>
|
|
130
|
+
</li>
|
|
131
|
+
</ul>
|
|
132
|
+
</li>
|
|
133
|
+
</ul>
|
|
134
|
+
</nav>
|
|
135
|
+
</mx-nav>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- Sub-lists are rendered open in the markup (no `inert`), so the full tree is available without JS.
|
|
139
|
+
- On connect, `<mx-nav>` wires each `.mx-nav__toggle` to its sibling `<ul>` (`aria-controls`, `aria-expanded`, `inert`), labels each sub-list by its parent link, then (because of `openActive`) re-opens any sub-list whose parent link or descendants carry `aria-current="page"` or `.is-active`.
|
|
140
|
+
- Sub-lists not on the current page's trail start collapsed.
|
|
141
|
+
- Toggles are hidden until `<mx-nav>` is defined (`mx-nav:not(:defined) .mx-nav__toggle`).
|
|
102
142
|
|
|
103
143
|
---
|
|
104
144
|
|
|
105
145
|
## React
|
|
106
146
|
|
|
107
|
-
Uses a composition pattern — always use `SideNavigationTree` inside `SideNavigation`.
|
|
147
|
+
Uses a composition pattern — always use `SideNavigationTree` inside `SideNavigation`. `SideNavigationTree` reads `collapsible` from `SideNavigation` via context, so it is only set once.
|
|
108
148
|
|
|
109
149
|
```tsx
|
|
110
150
|
import { SideNavigation, SideNavigationTree, Link } from "@pnx-mixtape/mxds/react"
|
|
@@ -126,6 +166,7 @@ const items: MenuTree[] = [
|
|
|
126
166
|
id="section-nav"
|
|
127
167
|
title="Section navigation"
|
|
128
168
|
parent={<Link to="/about">About</Link>}
|
|
169
|
+
collapsible
|
|
129
170
|
>
|
|
130
171
|
<SideNavigationTree items={items} />
|
|
131
172
|
</SideNavigation>
|
|
@@ -133,18 +174,20 @@ const items: MenuTree[] = [
|
|
|
133
174
|
|
|
134
175
|
**`SideNavigation` Props:**
|
|
135
176
|
|
|
136
|
-
| Prop
|
|
137
|
-
|
|
|
138
|
-
| `parent`
|
|
139
|
-
| `title`
|
|
140
|
-
| `id`
|
|
141
|
-
| `
|
|
177
|
+
| Prop | Type | Required | Description |
|
|
178
|
+
| ------------- | ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
179
|
+
| `parent` | `ReactNode` | No | Link/element shown above the list inside `.mx-side-nav__parent` |
|
|
180
|
+
| `title` | `string` | No | `aria-label` for the `<nav>` landmark. When omitted, falls back to `aria-labelledby` pointing at the parent wrapper |
|
|
181
|
+
| `id` | `string` | No | `id` on `.mx-side-nav__parent`, also used as the `aria-labelledby` target when `title` is not set. Auto-generated if omitted |
|
|
182
|
+
| `collapsible` | `boolean` | No | Adds `mx-nav--collapsible` and makes every nested `SideNavigationTree` sub-list a disclosure (`aria-expanded`/`aria-controls` on the toggle, `inert` when closed). Branches containing the current item start open. Default `false` |
|
|
183
|
+
| `className` | `string` | No | Extra class on the `<nav>` |
|
|
142
184
|
|
|
143
185
|
**`SideNavigationTree` Props:**
|
|
144
186
|
|
|
145
|
-
| Prop
|
|
146
|
-
|
|
|
147
|
-
| `items`
|
|
187
|
+
| Prop | Type | Required | Description |
|
|
188
|
+
| ---------- | --------------- | -------- | ----------------------------------------------------------------------------- |
|
|
189
|
+
| `items` | `MenuTree[]` | Yes | Recursive menu tree — rendered as nested `<ul class="mx-nav__level-N">` lists |
|
|
190
|
+
| `...props` | `ul` attributes | No | Spread onto the `<ul>` |
|
|
148
191
|
|
|
149
192
|
**`MenuTree` shape** (from `@pnx-mixtape/ids-shape`):
|
|
150
193
|
|
|
@@ -159,17 +202,18 @@ const items: MenuTree[] = [
|
|
|
159
202
|
|
|
160
203
|
## Modifiers
|
|
161
204
|
|
|
162
|
-
SideNavigation uses the base `.mx-nav` class for list reset and the component-specific classes below.
|
|
205
|
+
SideNavigation uses the base `.mx-nav` class for list reset and the component-specific classes below. The only variant is `mx-nav--collapsible`.
|
|
163
206
|
|
|
164
|
-
| Class | Applied by | Visual intent
|
|
165
|
-
| --------------------------------------------------------- | -------------------------------------- |
|
|
166
|
-
| `mx-nav` | Always (shared with Navigation) | Base list reset — no bullets, vertical layout
|
|
167
|
-
| `mx-side-nav` | Always | Side-nav specific styling: per-item dividers, indent ramp across levels, current-page background
|
|
168
|
-
| `mx-side-nav__parent` | Always | Parent-page wrapper above the list — bold label, thicker bottom border
|
|
169
|
-
| `mx-
|
|
170
|
-
| `mx-
|
|
171
|
-
| `mx-
|
|
172
|
-
| `
|
|
207
|
+
| Class | Applied by | Visual intent |
|
|
208
|
+
| --------------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
209
|
+
| `mx-nav` | Always (shared with Navigation) | Base list reset — no bullets, vertical layout |
|
|
210
|
+
| `mx-side-nav` | Always | Side-nav specific styling: per-item dividers, indent ramp across levels, current-page background |
|
|
211
|
+
| `mx-side-nav__parent` | Always | Parent-page wrapper above the list — bold label, thicker bottom border |
|
|
212
|
+
| `mx-nav--collapsible` | Twig `collapsible: true` | Shows the sub-list toggles and applies the collapsible nav's `inert`/transition styles |
|
|
213
|
+
| `mx-nav__level-1` / `mx-nav__level-2` / `mx-nav__level-3` | Structural | Sub-list nesting level — each level increases `--indent` |
|
|
214
|
+
| `mx-nav__has-subnav` | Structural | Applied to any `<li>` that contains a child `<ul>` |
|
|
215
|
+
| `mx-nav__toggle` | Structural (inherited from Navigation) | Hidden (`display: none`) unless the nav is `mx-nav--collapsible`, where it opens and closes its sibling sub-list |
|
|
216
|
+
| `is-active` | Authoring | Alternative to `aria-current="page"` for marking the current item — styled identically |
|
|
173
217
|
|
|
174
218
|
---
|
|
175
219
|
|
|
@@ -180,8 +224,10 @@ SideNavigation uses the base `.mx-nav` class for list reset and the component-sp
|
|
|
180
224
|
- Uses semantic nested `<ul>` / `<li>` lists, so assistive tech announces list depth and item count at each level.
|
|
181
225
|
- The current page is marked with `aria-current="page"` on the `<a>`. Style-only bolding is a visual reinforcement — the ARIA attribute is what assistive tech announces. Avoid relying on the background/weight alone.
|
|
182
226
|
- Active-trail ancestors (parent items on the path to the current page) are not given `aria-current` — only the exact current page. Apply the `is-active` class if you want to visually mark ancestors with the same active styling.
|
|
183
|
-
- Keyboard navigation: every item is a native `<a>`, so tab order follows source order
|
|
184
|
-
- The `.mx-nav__toggle` button is
|
|
227
|
+
- Keyboard navigation: every item is a native `<a>`, so tab order follows source order. In the default variant sub-lists are always visible, so there is no expand/collapse interaction to manage.
|
|
228
|
+
- The `.mx-nav__toggle` button is hidden via CSS in the default variant.
|
|
229
|
+
- Collapsible (Twig and React): each toggle is a native `<button>` with `aria-expanded` and `aria-controls`; collapsed sub-lists are `inert`, so they drop out of the tab order and the accessibility tree. In Twig, `<mx-nav>` also adds the shared `Keyboard` arrow-key handling between links; React has no equivalent. `Escape` does not close sub-lists in either — that is only wired up for `flyout` navs.
|
|
230
|
+
- Collapsible: every toggle's accessible name is "Sub-navigation". Screen-reader users get the context from the preceding link, but the name alone doesn't say which section it opens.
|
|
185
231
|
|
|
186
232
|
---
|
|
187
233
|
|
|
@@ -205,7 +251,7 @@ SideNavigation uses the base `.mx-nav` class for list reset and the component-sp
|
|
|
205
251
|
}
|
|
206
252
|
```
|
|
207
253
|
|
|
208
|
-
Level 2 and level 3 sub-lists override `--indent` to step the text further in — at narrow widths with `--spacing-s` / `--spacing-m`, and at the `--
|
|
254
|
+
Level 2 and level 3 sub-lists override `--indent` to step the text further in — at narrow widths with `--spacing-s` / `--spacing-m`, and at the `--sidebar-up` breakpoint with `--spacing-m` / `--spacing-l`.
|
|
209
255
|
|
|
210
256
|
---
|
|
211
257
|
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { Meta, StoryObj } from "@storybook/html-vite"
|
|
2
|
+
import { expect } from "storybook/test"
|
|
2
3
|
import Component from "./side-navigation.twig"
|
|
3
4
|
import Link from "../../Atom/Link/link.twig"
|
|
4
5
|
import "../Navigation/navigation.css"
|
|
5
6
|
import "./side-navigation.css"
|
|
7
|
+
import "../Navigation/Elements/Navigation"
|
|
6
8
|
import { SideNavigation as NavigationType } from "@pnx-mixtape/ids-shape"
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
type SideNavigationType = NavigationType & {
|
|
11
|
+
collapsible?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const meta: Meta<SideNavigationType> = {
|
|
9
15
|
tags: ["autodocs", "ids-mvp"],
|
|
10
16
|
component: Component,
|
|
11
17
|
args: {
|
|
@@ -128,10 +134,43 @@ const meta: Meta<NavigationType> = {
|
|
|
128
134
|
required: true,
|
|
129
135
|
},
|
|
130
136
|
},
|
|
137
|
+
collapsible: {
|
|
138
|
+
type: "boolean",
|
|
139
|
+
},
|
|
131
140
|
},
|
|
132
141
|
}
|
|
133
142
|
|
|
134
143
|
export default meta
|
|
135
|
-
type Story = StoryObj<
|
|
144
|
+
type Story = StoryObj<SideNavigationType>
|
|
136
145
|
|
|
137
146
|
export const SideNavigation: Story = {}
|
|
147
|
+
|
|
148
|
+
export const Collapsible: Story = {
|
|
149
|
+
args: {
|
|
150
|
+
collapsible: true,
|
|
151
|
+
},
|
|
152
|
+
play: async ({ canvas, userEvent, step }) => {
|
|
153
|
+
const item = canvas.getByRole("link", { name: "What we do" }).closest("li")
|
|
154
|
+
const trigger = item.querySelector(":scope > .mx-nav__toggle")
|
|
155
|
+
const list = item.querySelector(":scope > ul")
|
|
156
|
+
|
|
157
|
+
await step("Opens the active trail", async () => {
|
|
158
|
+
await expect(trigger).toHaveAttribute("aria-expanded", "true")
|
|
159
|
+
await expect(trigger).toHaveAttribute("aria-controls", list.id)
|
|
160
|
+
await expect(list).not.toHaveAttribute("inert")
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
await step("Closes on click", async () => {
|
|
164
|
+
await userEvent.click(trigger)
|
|
165
|
+
await expect(trigger).toHaveAttribute("aria-expanded", "false")
|
|
166
|
+
await expect(list).toHaveAttribute("inert")
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
await step("Reopens on click", async () => {
|
|
170
|
+
await userEvent.click(trigger)
|
|
171
|
+
await expect(trigger).toHaveAttribute("aria-expanded", "true")
|
|
172
|
+
await expect(list).not.toHaveAttribute("inert")
|
|
173
|
+
;(trigger as HTMLElement).blur()
|
|
174
|
+
})
|
|
175
|
+
},
|
|
176
|
+
}
|