Skip to content

Commit

Permalink
♿ a11y(tabs, carousel): keyboard interaction (#1432)
Browse files Browse the repository at this point in the history
* Create PR for #1425

* fix(tabs): only show line when value exists

* fix(tabs): only show line when value exists

* fix(tabs): improve keyboard navigation according to a11y criterias

* chore: fix typo

* Create PR for #1417

* fix(carousel): implement role list and listitem to improve screenreaders

* fix(carousel): add missing aria controls

* fix(carousel): improve keyboard inputs and a11y criterias

* fix(carousel): improve keyboard inputs and a11y criterias

* chore: add missing unit test

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Gery Hirschfeld <[email protected]>
  • Loading branch information
github-actions[bot] and hirsch88 committed Sep 5, 2024
1 parent ac9a631 commit d8c2bc4
Show file tree
Hide file tree
Showing 26 changed files with 422 additions and 97 deletions.
5 changes: 5 additions & 0 deletions .changeset/nine-snails-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baloise/ds-core': patch
---

**tabs**: only show line when value exists
5 changes: 5 additions & 0 deletions .changeset/olive-olives-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baloise/ds-core': patch
---

**carousel**: improve keyboard inputs and a11y criterias
5 changes: 5 additions & 0 deletions .changeset/slow-lies-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baloise/ds-core': patch
---

**tabs**: improve keyboard navigation according to a11y criterias
5 changes: 5 additions & 0 deletions .changeset/small-scissors-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baloise/ds-core': patch
---

**carousel**: implement role list and listitem to improve screenreaders
1 change: 0 additions & 1 deletion docs/.storybook/blocks/css-utils/CssElevation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export const CssElevationShadow = () => (
if (item.property === 'box-shadow') {
return <div className={`bg-green ${item.class} p-small`}></div>
}
console.log(item)
return <div className={`font-weight-bold text-large ${item.class} px-small`}>Aa</div>
}}
/>
Expand Down
4 changes: 0 additions & 4 deletions e2e/cypress/e2e/a11y/bal-tabs.a11y.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ describe('bal-tabs', () => {
it('tabs basic', () => {
cy.getByTestId('basic').testA11y()
})

it('tabs vertical', () => {
cy.getByTestId('vertical').testA11y()
})
})
})
})
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"loader/"
],
"scripts": {
"bubu": "vitest --ui"
"test:ui": "vitest --ui"
},
"dependencies": {
"@baloise/ds-styles": "16.3.0",
Expand Down
33 changes: 25 additions & 8 deletions packages/core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { BalConfigState } from "./utils/config";
import { AccordionState, BalAriaForm as BalAriaForm1, BalConfigState as BalConfigState1 } from "./interfaces";
import { BalCarouselItemData } from "./components/bal-carousel/bal-carousel.type";
import { BalCarouselItemData, BalSlide } from "./components/bal-carousel/bal-carousel.type";
import { BalCheckboxOption } from "./components/bal-checkbox/bal-checkbox.type";
import { BalAriaForm } from "./utils/form";
import { BalOption } from "./utils/dropdown";
Expand All @@ -18,7 +18,7 @@ import { BalStepOption } from "./components/bal-steps/bal-step.type";
import { BalTabOption } from "./components/bal-tabs/bal-tab.type";
export { BalConfigState } from "./utils/config";
export { AccordionState, BalAriaForm as BalAriaForm1, BalConfigState as BalConfigState1 } from "./interfaces";
export { BalCarouselItemData } from "./components/bal-carousel/bal-carousel.type";
export { BalCarouselItemData, BalSlide } from "./components/bal-carousel/bal-carousel.type";
export { BalCheckboxOption } from "./components/bal-checkbox/bal-checkbox.type";
export { BalAriaForm } from "./utils/form";
export { BalOption } from "./utils/dropdown";
Expand Down Expand Up @@ -378,6 +378,10 @@ export namespace Components {
* If `true` the carousel uses the full height
*/
"fullHeight": boolean;
/**
* Defines the role of the carousel.
*/
"htmlRole": 'tablist' | 'list' | '';
/**
* Defines special looks.
*/
Expand All @@ -390,11 +394,11 @@ export namespace Components {
* Defines how many slides are visible in the container for the user. `auto` will use the size of the actual item content
*/
"itemsPerView": 'auto' | 1 | 2 | 3 | 4;
"next": (steps?: number) => Promise<void>;
"next": (steps?: number) => Promise<BalSlide | undefined>;
/**
* PUBLIC METHODS ------------------------------------------------------
*/
"previous": (steps?: number) => Promise<void>;
"previous": (steps?: number) => Promise<BalSlide | undefined>;
/**
* If `true` vertical scrolling on mobile is enabled.
*/
Expand Down Expand Up @@ -426,6 +430,10 @@ export namespace Components {
* Specifies the URL of the page the link goes to
*/
"href"?: string;
/**
* Defines the role of the carousel.
*/
"htmlRole": 'tab' | 'listitem' | '';
/**
* Label of the slide which will be used for pagination tabs
*/
Expand All @@ -438,6 +446,7 @@ export namespace Components {
* Specifies the relationship of the target object to the link object. The value is a space-separated list of [link types](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types).
*/
"rel"?: string;
"setFocus": () => Promise<void>;
/**
* Src path to the image
*/
Expand Down Expand Up @@ -3148,7 +3157,7 @@ export namespace Components {
*/
"border": boolean;
/**
* If `true` the tabs or steps can be clicked.
* If `true` the tabs or tabs can be clicked.
*/
"clickable": boolean;
"closeAccordion": () => Promise<void>;
Expand Down Expand Up @@ -3190,7 +3199,7 @@ export namespace Components {
*/
"optionalTabSelection": boolean;
/**
* Steps can be passed as a property or through HTML markup.
* Tabs can be passed as a property or through HTML markup.
*/
"options": BalTabOption[];
/**
Expand Down Expand Up @@ -5402,6 +5411,10 @@ declare namespace LocalJSX {
* If `true` the carousel uses the full height
*/
"fullHeight"?: boolean;
/**
* Defines the role of the carousel.
*/
"htmlRole"?: 'tablist' | 'list' | '';
/**
* Defines special looks.
*/
Expand Down Expand Up @@ -5448,6 +5461,10 @@ declare namespace LocalJSX {
* Specifies the URL of the page the link goes to
*/
"href"?: string;
/**
* Defines the role of the carousel.
*/
"htmlRole"?: 'tab' | 'listitem' | '';
/**
* Label of the slide which will be used for pagination tabs
*/
Expand Down Expand Up @@ -8179,7 +8196,7 @@ declare namespace LocalJSX {
*/
"border"?: boolean;
/**
* If `true` the tabs or steps can be clicked.
* If `true` the tabs or tabs can be clicked.
*/
"clickable"?: boolean;
/**
Expand Down Expand Up @@ -8227,7 +8244,7 @@ declare namespace LocalJSX {
*/
"optionalTabSelection"?: boolean;
/**
* Steps can be passed as a property or through HTML markup.
* Tabs can be passed as a property or through HTML markup.
*/
"options"?: BalTabOption[];
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { Component, ComponentInterface, h, Host, Method, Element, Prop, Event, E
import { BEM } from '../../../utils/bem'
import { BalCarouselItemData } from '../bal-carousel.type'
import { Attributes } from '../../../interfaces'
import { waitAfterFramePaint } from '../../../utils/helpers'
import { inheritAttributes } from '../../../utils/attributes'

@Component({
tag: 'bal-carousel-item',
})
export class CarouselItem implements ComponentInterface {
private imageInheritAttributes: Attributes = {}
private buttonEl: HTMLButtonElement | HTMLLinkElement

@Element() el!: HTMLElement

Expand All @@ -22,6 +24,11 @@ export class CarouselItem implements ComponentInterface {
*/
@Prop({ reflect: true }) label = ''

/**
* Defines the role of the carousel.
*/
@Prop() htmlRole: 'tab' | 'listitem' | '' = 'listitem'

/**
* The type of button.
*/
Expand Down Expand Up @@ -93,6 +100,14 @@ export class CarouselItem implements ComponentInterface {
}
}

@Method()
async setFocus(): Promise<void> {
await waitAfterFramePaint()
if (this.buttonEl) {
this.buttonEl.focus()
}
}

private onClick = (ev: MouseEvent) => {
if (this.href !== undefined) {
this.balNavigate.emit(ev)
Expand All @@ -115,7 +130,7 @@ export class CarouselItem implements ComponentInterface {

if (!isProduct) {
return (
<Host class={{ ...itemEl.class() }}>
<Host role={this.htmlRole} class={{ ...itemEl.class() }}>
{this.src !== undefined ? (
<img draggable={false} onDragStart={() => false} src={this.src} {...this.imageInheritAttributes} />
) : (
Expand Down Expand Up @@ -144,21 +159,23 @@ export class CarouselItem implements ComponentInterface {
}

return (
<Host class={{ ...itemEl.class() }}>
<Host role={this.htmlRole} class={{ ...itemEl.class() }}>
<TagType
{...attrs}
class={{ ...button.class(), ...button.modifier(`color-${this.color}`).class() }}
part="native"
onFocus={this.onFocus}
onBlur={this.onBlur}
onClick={this.onClick}
ref={el => (this.buttonEl = el)}
>
{this.src !== undefined ? (
<img
class={{ ...image.class() }}
loading="lazy"
draggable={false}
onDragStart={() => false}
aria-hidden="true"
src={this.src}
{...this.imageInheritAttributes}
/>
Expand Down
Loading

0 comments on commit d8c2bc4

Please sign in to comment.