From d42d3d3208422ad754cde964f8593d6e25e943d3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 09:14:36 +0200 Subject: [PATCH] :wheelchair: a11y(bal-nav): Connect Tabs and Overlays (#1451) * Create PR for #1416 * fix: implement a11y for nav and tabs --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Gery Hirschfeld --- .changeset/dirty-queens-brush.md | 5 ++++ .changeset/nervous-jobs-roll.md | 5 ++++ packages/core/src/components.d.ts | 24 +++++++++++++++++++ .../bal-nav-menu-bar/bal-nav-menu-bar.sass | 6 ++--- .../bal-nav-menu-flyout.tsx | 10 +++++--- .../core/src/components/bal-nav/bal-nav.sass | 7 ++++++ .../core/src/components/bal-nav/bal-nav.tsx | 9 +++++-- .../bal-nav/models/bal-nav-link-item.tsx | 2 +- .../bal-nav/models/bal-nav-menu-link-item.tsx | 15 ++++++++++-- .../bal-nav/models/bal-nav-meta-link-item.tsx | 15 ++++++++++-- .../bal-tabs/bal-tab-item/bal-tab-item.tsx | 24 +++++++++++++++---- .../src/components/bal-tabs/bal-tab.type.ts | 2 ++ .../bal-tabs/bal-tabs.interfaces.ts | 3 +++ .../bal-tabs/components/tab-button.tsx | 10 ++++++-- .../bal-tabs/components/tab-nav.tsx | 1 + 15 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 .changeset/dirty-queens-brush.md create mode 100644 .changeset/nervous-jobs-roll.md diff --git a/.changeset/dirty-queens-brush.md b/.changeset/dirty-queens-brush.md new file mode 100644 index 0000000000..4eaab2cf8b --- /dev/null +++ b/.changeset/dirty-queens-brush.md @@ -0,0 +1,5 @@ +--- +'@baloise/ds-core': patch +--- + +**nav**: add aria control to nav tabs and connect them to the flyout diff --git a/.changeset/nervous-jobs-roll.md b/.changeset/nervous-jobs-roll.md new file mode 100644 index 0000000000..d5ac0cea69 --- /dev/null +++ b/.changeset/nervous-jobs-roll.md @@ -0,0 +1,5 @@ +--- +'@baloise/ds-core': minor +--- + +**tabs**: tabs can be created without a integrated panel diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index b1863925b7..f6ce6b8d36 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -2037,6 +2037,10 @@ export namespace Components { "position": BalProps.BalNavMenuBarPosition; } interface BalNavMenuFlyout { + /** + * This is used to connect the flyout to the aria controls + */ + "navId": string; } interface BalNavMetaBar { /** @@ -3100,6 +3104,10 @@ export namespace Components { * Tells if this route is active and overrides the bal-tabs value property. */ "active": boolean; + /** + * A11y attributes for the native tab element. + */ + "aria"?: BalProps.BalTabItemAria; /** * If `true` a small red bubble is added to the tab. */ @@ -3128,6 +3136,10 @@ export namespace Components { * Label for the tab. */ "label": string; + /** + * If `true` the tab does not have a panel + */ + "noPanel": boolean; /** * Tell's if the linking is done by a router. */ @@ -7138,6 +7150,10 @@ declare namespace LocalJSX { "position"?: BalProps.BalNavMenuBarPosition; } interface BalNavMenuFlyout { + /** + * This is used to connect the flyout to the aria controls + */ + "navId"?: string; } interface BalNavMetaBar { /** @@ -8143,6 +8159,10 @@ declare namespace LocalJSX { * Tells if this route is active and overrides the bal-tabs value property. */ "active"?: boolean; + /** + * A11y attributes for the native tab element. + */ + "aria"?: BalProps.BalTabItemAria; /** * If `true` a small red bubble is added to the tab. */ @@ -8167,6 +8187,10 @@ declare namespace LocalJSX { * Label for the tab. */ "label"?: string; + /** + * If `true` the tab does not have a panel + */ + "noPanel"?: boolean; /** * Emitted when the link element has clicked */ diff --git a/packages/core/src/components/bal-nav/bal-nav-menu-bar/bal-nav-menu-bar.sass b/packages/core/src/components/bal-nav/bal-nav-menu-bar/bal-nav-menu-bar.sass index 113a665166..e51a9a0eb6 100644 --- a/packages/core/src/components/bal-nav/bal-nav-menu-bar/bal-nav-menu-bar.sass +++ b/packages/core/src/components/bal-nav/bal-nav-menu-bar/bal-nav-menu-bar.sass @@ -96,11 +96,11 @@ align-self: flex-start z-index: 1 height: auto - padding-top: .8em + margin-top: .8em +desktop - padding-top: 1rem + margin-top: 1rem +widescreen - padding-top: 1.5rem + margin-top: 1.5rem // Space helpers for the content after the bar // -------------------------------------------------- diff --git a/packages/core/src/components/bal-nav/bal-nav-menu-flyout/bal-nav-menu-flyout.tsx b/packages/core/src/components/bal-nav/bal-nav-menu-flyout/bal-nav-menu-flyout.tsx index c6475281a1..8946596b6f 100644 --- a/packages/core/src/components/bal-nav/bal-nav-menu-flyout/bal-nav-menu-flyout.tsx +++ b/packages/core/src/components/bal-nav/bal-nav-menu-flyout/bal-nav-menu-flyout.tsx @@ -1,4 +1,4 @@ -import { Component, h, ComponentInterface, Host, Element, State } from '@stencil/core' +import { Component, h, ComponentInterface, Host, Element, State, Prop } from '@stencil/core' import { BEM } from '../../../utils/bem' import { LogInstance, Loggable, Logger } from '../../../utils/log' import { BalResizeObserver, ListenToResize } from '../../../utils/resize' @@ -9,7 +9,6 @@ import { BalScrollHandler } from '../../../utils/scroll' styleUrl: 'bal-nav-menu-flyout.sass', }) export class NavMenuFlyout implements ComponentInterface, Loggable, BalResizeObserver { - private navMenuFlyoutId = `bal-nav-menu-flyout-${NavMenuFlyOutIds++}` private bodyScrollHandler = new BalScrollHandler() @Element() el!: HTMLElement @@ -28,6 +27,11 @@ export class NavMenuFlyout implements ComponentInterface, Loggable, BalResizeObs * ------------------------------------------------------ */ + /** + * This is used to connect the flyout to the aria controls + * */ + @Prop() navId = `bal-nav-x${NavMenuFlyOutIds++}` + /** * LIFECYCLE * ------------------------------------------------------ @@ -75,7 +79,7 @@ export class NavMenuFlyout implements ComponentInterface, Loggable, BalResizeObs return ( this.onMetaBarTabChange(ev)} > - {this.linkItems.map(item => item.render())} + {this.linkItems.map(item => + item.render({ + flyoutId: `${this.navId}-menu-flyout`, + }), + )} ) : ( @@ -450,13 +454,14 @@ export class Nav .find(item => item.value === this.activeMetaLinkValue) ?.mainLinkItems.map(item => item.render({ + flyoutId: `${this.navId}-menu-flyout`, onClick: () => this.onMenuBarTabChange(item.value), }), )} {this.isFlyoutActive ? ( - + void }) { + render(_context?: { onClick: () => void; flyoutId: string }) { return ( void }) { + override render(context?: { onClick: () => void; flyoutId: string }) { const hasChildren = this.sectionLinkItems.length > 0 || this.serviceLinkItems.length > 0 if (!hasChildren && this.isLink) { - return + return ( + + ) } return ( { context?.onClick() if (this.onClick) { diff --git a/packages/core/src/components/bal-nav/models/bal-nav-meta-link-item.tsx b/packages/core/src/components/bal-nav/models/bal-nav-meta-link-item.tsx index b7c36a83d9..5afe646bf3 100644 --- a/packages/core/src/components/bal-nav/models/bal-nav-meta-link-item.tsx +++ b/packages/core/src/components/bal-nav/models/bal-nav-meta-link-item.tsx @@ -77,15 +77,26 @@ export class NavMetaLinkItem extends NavLinkItem implements BalProps.BalNavMetaL ) } - override render() { + override render(context?: { flyoutId: string }) { if (this.isLink) { - return + return ( + + ) } return ( { if (this.onClick) { this.onClick(ev.detail) diff --git a/packages/core/src/components/bal-tabs/bal-tab-item/bal-tab-item.tsx b/packages/core/src/components/bal-tabs/bal-tab-item/bal-tab-item.tsx index c414199ab1..394c611163 100644 --- a/packages/core/src/components/bal-tabs/bal-tab-item/bal-tab-item.tsx +++ b/packages/core/src/components/bal-tabs/bal-tab-item/bal-tab-item.tsx @@ -64,6 +64,16 @@ export class TabItem { */ @Prop({ reflect: true }) icon?: string = undefined + /** + * If `true` the tab does not have a panel + */ + @Prop() noPanel = false + + /** + * A11y attributes for the native tab element. + */ + @Prop() aria?: BalProps.BalTabItemAria = undefined + /** * Emitted when the link element has clicked */ @@ -105,21 +115,27 @@ export class TabItem { prevent: this.prevent, navigate: this.balNavigate, trackingData: this.inheritAttributes, + noPanel: this.noPanel, + aria: this.aria, } } render() { + const hasPanel = !this.noPanel + const noPanelOrInactive = !this.isActive || this.noPanel + return ( diff --git a/packages/core/src/components/bal-tabs/bal-tab.type.ts b/packages/core/src/components/bal-tabs/bal-tab.type.ts index d884a59b0c..a4f679acd3 100644 --- a/packages/core/src/components/bal-tabs/bal-tab.type.ts +++ b/packages/core/src/components/bal-tabs/bal-tab.type.ts @@ -19,6 +19,8 @@ export interface BalTabOption { navigate?: EventEmitter trackingData?: Attributes hidden?: boolean // deprecated use invisible instead + noPanel?: boolean + aria?: BalProps.BalTabItemAria } export interface TabLineProps { diff --git a/packages/core/src/components/bal-tabs/bal-tabs.interfaces.ts b/packages/core/src/components/bal-tabs/bal-tabs.interfaces.ts index c5ba4d6731..c7d5193e7e 100644 --- a/packages/core/src/components/bal-tabs/bal-tabs.interfaces.ts +++ b/packages/core/src/components/bal-tabs/bal-tabs.interfaces.ts @@ -9,6 +9,9 @@ namespace BalProps { export type BalTabsVertical = boolean | 'mobile' | 'tablet' export type BalTabsFloat = 'left' | 'right' export type BalTabsColSize = 'one-quarter' | 'one-third' | 'half' | 'two-thirds' | 'three-quarters' | 'full' + export type BalTabItemAria = { + controls?: string + } } namespace BalEvents { diff --git a/packages/core/src/components/bal-tabs/components/tab-button.tsx b/packages/core/src/components/bal-tabs/components/tab-button.tsx index 5278aa5433..7148d4225d 100644 --- a/packages/core/src/components/bal-tabs/components/tab-button.tsx +++ b/packages/core/src/components/bal-tabs/components/tab-button.tsx @@ -14,6 +14,7 @@ export interface TabButtonProps { isVertical: boolean accordion: boolean isAccordionOpen: boolean + isLinkList: boolean inverted: boolean expanded: boolean spaceless: boolean @@ -32,6 +33,7 @@ export const TabButton: FunctionalComponent = ({ isVertical, accordion, isAccordionOpen, + isLinkList, inverted, expanded, spaceless, @@ -68,8 +70,8 @@ export const TabButton: FunctionalComponent = ({ ? { 'type': 'button', 'role': 'tab', - 'tabindex': item.active ? '0' : '-1', - 'aria-controls': item.tabPanelID, + 'aria-controls': item.aria?.controls || item.tabPanelID || undefined, + 'aria-expanded': item.active ? 'true' : 'false', 'aria-disabled': `${item.disabled}`, 'aria-label': item.label, } @@ -78,6 +80,10 @@ export const TabButton: FunctionalComponent = ({ target: item.target, } + if (!isLinkList) { + attrs['tabindex'] = item.active ? '0' : '-1' + } + return ( = ({ const Button: FunctionalComponent<{ item: BalTabOption; index: number }> = ({ item, index }) => (