diff --git a/.changeset/large-frogs-smile.md b/.changeset/large-frogs-smile.md new file mode 100644 index 000000000..b068b2190 --- /dev/null +++ b/.changeset/large-frogs-smile.md @@ -0,0 +1,5 @@ +--- +'@ithaka/pharos': minor +--- + +Update PharosButton to use a11y naming convention and include wider ARIA support diff --git a/packages/pharos/src/components/button/pharos-button.ts b/packages/pharos/src/components/button/pharos-button.ts index dedef5bf6..6831db979 100644 --- a/packages/pharos/src/components/button/pharos-button.ts +++ b/packages/pharos/src/components/button/pharos-button.ts @@ -17,9 +17,6 @@ export type ButtonType = 'button' | 'submit' | 'reset'; export type ButtonVariant = 'primary' | 'secondary' | 'subtle' | 'overlay'; -// undefined means no state has been expressed at all and won't render; 'undefined' is an explicit state -export type PressedState = 'false' | 'true' | 'mixed' | 'undefined' | undefined; - const TYPES = ['button', 'submit', 'reset'] as ButtonType[]; const VARIANTS = ['primary', 'secondary', 'subtle', 'overlay'] as ButtonVariant[]; @@ -112,12 +109,41 @@ export class PharosButton extends ScopedRegistryMixin(FocusMixin(AnchorElement)) public large = false; /** + * @deprecated * Indicates the aria label to apply to the button. * @attr label */ @property({ type: String, reflect: true }) public label?: string; + /** + * Indicates the aria label to apply to the button. + * @attr a11y-label + */ + @property({ type: String, reflect: true, attribute: 'a11y-label' }) + public a11yLabel?: string; + + /** + * Indicates the aria description to apply to the button. + * @attr a11y-description + */ + @property({ type: String, reflect: true, attribute: 'a11y-description' }) + public a11yDescription?: string; + + /** + * Indicates the aria expanded state to apply to the button. + * @attr a11y-expanded + */ + @property({ type: String, reflect: true, attribute: 'a11y-expanded' }) + public a11yExpanded: AriaExpandedState = undefined; + + /** + * Indicates the aria expanded state to apply to the button. + * @attr a11y-haspopup + */ + @property({ type: String, reflect: true, attribute: 'a11y-haspopup' }) + public a11yHaspopup: AriaPopupState = undefined; + /** * Indicates the button's width should match its container. * @attr full-width @@ -140,11 +166,19 @@ export class PharosButton extends ScopedRegistryMixin(FocusMixin(AnchorElement)) public value?: string; /** + * @deprecated * Indicates this button is a toggle button and whether it is pressed or not. * @attr value */ @property({ type: String, reflect: true }) - public pressed: PressedState = undefined; + public pressed: AriaPressedState = undefined; + + /** + * Indicates this button is a toggle button and whether it is pressed or not. + * @attr value + */ + @property({ type: String, reflect: true, attribute: 'a11y-pressed' }) + public a11yPressed: AriaPressedState = undefined; @query('#button-element') private _button!: HTMLButtonElement | HTMLAnchorElement; @@ -178,6 +212,14 @@ export class PharosButton extends ScopedRegistryMixin(FocusMixin(AnchorElement)) `${this.variant} is not a valid variant. Valid variants are: ${VARIANTS.join(', ')}` ); } + + if (this.label) { + console.warn("The 'label' attribute is deprecated. Use 'a11y-label' instead."); + } + + if (this.pressed) { + console.warn("The 'pressed' attribute is deprecated. Use 'a11y-pressed' instead."); + } } override connectedCallback(): void { @@ -243,6 +285,10 @@ export class PharosButton extends ScopedRegistryMixin(FocusMixin(AnchorElement)) } protected override render(): TemplateResult { + // TODO: Remove in future release once sufficient time elapsed to update naming convention + const a11yLabel = this.a11yLabel ?? this.label; + const a11yPressed = this.a11yPressed ?? this.pressed; + return this.href ? html` ${this.buttonContent} @@ -269,8 +318,11 @@ export class PharosButton extends ScopedRegistryMixin(FocusMixin(AnchorElement)) ?autofocus=${this.autofocus} ?disabled=${this.disabled} type="${ifDefined(this.type)}" - aria-label=${ifDefined(this.label)} - aria-pressed=${ifDefined(this.pressed)} + aria-label=${ifDefined(a11yLabel)} + aria-description=${ifDefined(this.a11yDescription)} + aria-pressed=${ifDefined(a11yPressed)} + aria-expanded=${ifDefined(this.a11yExpanded)} + aria-haspopup=${ifDefined(this.a11yHaspopup)} > ${this.buttonContent} diff --git a/packages/pharos/src/components/sidenav/pharos-sidenav-button.ts b/packages/pharos/src/components/sidenav/pharos-sidenav-button.ts index 6dcab2e42..174826cc6 100644 --- a/packages/pharos/src/components/sidenav/pharos-sidenav-button.ts +++ b/packages/pharos/src/components/sidenav/pharos-sidenav-button.ts @@ -4,8 +4,8 @@ import { PharosButton } from '../button/pharos-button'; import type { PharosSidenav } from './pharos-sidenav'; import type { LinkTarget } from '../base/anchor-element'; -import type { ButtonType, IconName, ButtonVariant, PressedState } from '../button/pharos-button'; -export type { LinkTarget, ButtonType, IconName, ButtonVariant, PressedState }; +import type { ButtonType, IconName, ButtonVariant } from '../button/pharos-button'; +export type { LinkTarget, ButtonType, IconName, ButtonVariant }; /** * Pharos sidenav button component. diff --git a/packages/pharos/src/components/toast/pharos-toast-button.ts b/packages/pharos/src/components/toast/pharos-toast-button.ts index 7cdb2eab4..3908b43a5 100644 --- a/packages/pharos/src/components/toast/pharos-toast-button.ts +++ b/packages/pharos/src/components/toast/pharos-toast-button.ts @@ -3,9 +3,9 @@ import { toastButtonStyles } from './pharos-toast-button.css'; import { PharosButton } from '../button/pharos-button'; import type { LinkTarget } from '../base/anchor-element'; -import type { ButtonType, IconName, ButtonVariant, PressedState } from '../button/pharos-button'; +import type { ButtonType, IconName, ButtonVariant } from '../button/pharos-button'; -export type { LinkTarget, ButtonType, IconName, ButtonVariant, PressedState }; +export type { LinkTarget, ButtonType, IconName, ButtonVariant }; /** * Pharos toast button component. diff --git a/packages/pharos/src/components/toggle-button-group/pharos-toggle-button.ts b/packages/pharos/src/components/toggle-button-group/pharos-toggle-button.ts index 1952fc9ab..004cba931 100644 --- a/packages/pharos/src/components/toggle-button-group/pharos-toggle-button.ts +++ b/packages/pharos/src/components/toggle-button-group/pharos-toggle-button.ts @@ -3,14 +3,8 @@ import type { CSSResultArray, PropertyValues } from 'lit'; import { toggleButtonStyles } from './pharos-toggle-button.css'; import { PharosButton } from '../button/pharos-button'; -import type { - ButtonType, - LinkTarget, - IconName, - ButtonVariant, - PressedState, -} from '../button/pharos-button'; -export type { ButtonType, LinkTarget, IconName, ButtonVariant, PressedState }; +import type { ButtonType, LinkTarget, IconName, ButtonVariant } from '../button/pharos-button'; +export type { ButtonType, LinkTarget, IconName, ButtonVariant }; /** * Pharos toggle button component. diff --git a/packages/pharos/src/typings/a11y-attributes.d.ts b/packages/pharos/src/typings/a11y-attributes.d.ts new file mode 100644 index 000000000..0876b0ee3 --- /dev/null +++ b/packages/pharos/src/typings/a11y-attributes.d.ts @@ -0,0 +1,17 @@ +export {}; + +declare global { + type AriaHiddenState = 'false' | 'true' | 'undefined' | undefined; + type AriaPressedState = 'false' | 'true' | 'mixed' | 'undefined' | undefined; + type AriaExpandedState = 'false' | 'true' | 'undefined' | undefined; + type AriaDisabledState = 'false' | 'true' | undefined; + type AriaPopupState = + | 'false' + | 'true' + | 'menu' + | 'tree' + | 'grid' + | 'listbox' + | 'dialog' + | undefined; +}