diff --git a/package-lock.json b/package-lock.json index 8a7e9273e89..29b8d6676b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24367,6 +24367,7 @@ "@vitest/coverage-v8": "^3.2.4", "@wc-toolkit/storybook-helpers": "^9.0.1", "del": "^8.0.1", + "dom-accessibility-api": "^0.7.1", "globby": "^14.1.0", "happy-dom": "^18.0.1", "lit": "^3.3.1", @@ -24437,6 +24438,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "packages/craftcms-cp/node_modules/dom-accessibility-api": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.7.1.tgz", + "integrity": "sha512-vdnCeZD+3wZ+8h8xXL/ZtBlvvoobOFyPzSiIfO6sGOZDqjFx4aLMAjZhl4rawj5xYz3UwP6Tgvyh0iH4IOCVnQ==", + "dev": true, + "license": "MIT" + }, "packages/craftcms-cp/node_modules/glob": { "version": "11.0.3", "dev": true, diff --git a/packages/craftcms-cp/package.json b/packages/craftcms-cp/package.json index 529190bda51..6884c0fb04c 100644 --- a/packages/craftcms-cp/package.json +++ b/packages/craftcms-cp/package.json @@ -71,6 +71,7 @@ "@vitest/coverage-v8": "^3.2.4", "@wc-toolkit/storybook-helpers": "^9.0.1", "del": "^8.0.1", + "dom-accessibility-api": "^0.7.1", "globby": "^14.1.0", "happy-dom": "^18.0.1", "lit": "^3.3.1", diff --git a/packages/craftcms-cp/src/components/button/button.styles.ts b/packages/craftcms-cp/src/components/button/button.styles.ts index 4208c6cbbf0..d9c218371da 100644 --- a/packages/craftcms-cp/src/components/button/button.styles.ts +++ b/packages/craftcms-cp/src/components/button/button.styles.ts @@ -150,6 +150,25 @@ export default css` craft-button-reset, craft-button-submit { /* Temporarily make it very obvious when these are used */ - outline: 10px solid red; + outline: 10px solid var(--c-button-danger-border); + } + + .a11y-error { + position: relative; + outline: 2px solid var(--c-color-danger-border-normal) !important; + background-color: rgba(255, 0, 0, 0.1) !important; + + &:after { + content: '!'; + position: absolute; + display: inline-flex; + font-size: calc(11rem / 16); + padding: 0.125em 0.5em 0.25em; + inset-block-start: -2px; + inset-inline-start: 0; + background: var(--c-color-danger-bg-emphasis); + color: white; + transform: translateX(-100%); + } } `; diff --git a/packages/craftcms-cp/src/components/button/button.ts b/packages/craftcms-cp/src/components/button/button.ts index 64712d69107..19ff9128cc0 100644 --- a/packages/craftcms-cp/src/components/button/button.ts +++ b/packages/craftcms-cp/src/components/button/button.ts @@ -1,8 +1,11 @@ import {LionButtonSubmit} from '@lion/ui/button.js'; import {html, nothing} from 'lit'; -import {property} from 'lit/decorators.js'; +import {property, state} from 'lit/decorators.js'; import styles from './button.styles.js'; import '../spinner/spinner.js'; +import '../icon/icon.js'; +import {computeAccessibleName} from 'dom-accessibility-api'; +import {classMap} from 'lit/directives/class-map.js'; /** * @summary Interactive element that triggers an action or event. @@ -25,6 +28,27 @@ export default class CraftButton extends LionButtonSubmit { return [...super.styles, styles]; } + override async firstUpdated(changedProperties: Map) { + super.firstUpdated(changedProperties); + + await this.updateComplete; + + const childComponents = this.querySelectorAll('craft-icon, craft-spinner'); + await Promise.all( + Array.from(childComponents).map((child: any) => child.updateComplete) + ); + + if (!this.accessibleName) { + this.accessibleName = computeAccessibleName(this); + } + + this._hasAccessibilityError = + !this.accessibleName || this.accessibleName.trim() === ''; + } + + /** The computed accessible name */ + @property() accessibleName: string; + /** Visual appearance of the button */ @property({reflect: true}) appearance: 'accent' | 'plain' = 'accent'; @@ -39,9 +63,18 @@ export default class CraftButton extends LionButtonSubmit { /** Show a spinner instead of the label */ @property({reflect: true, type: Boolean}) loading: boolean = false; + @state() + private _hasAccessibilityError: boolean = false; + override render() { return html` -
+
diff --git a/packages/craftcms-cp/src/components/nav-item/nav-item.ts b/packages/craftcms-cp/src/components/nav-item/nav-item.ts index ee6e37a3794..5e00584230a 100644 --- a/packages/craftcms-cp/src/components/nav-item/nav-item.ts +++ b/packages/craftcms-cp/src/components/nav-item/nav-item.ts @@ -2,6 +2,7 @@ import {html, LitElement, css, nothing} from 'lit'; import {styleMap} from 'lit/directives/style-map.js'; import {property, state} from 'lit/decorators.js'; import styles from './nav-item.styles'; +import {t} from '@craftcms/cp/utilities/translate.ts.mjs'; /** * @@ -130,7 +131,7 @@ export default class CraftNavItem extends LitElement { ${this.indicator ? html`` : nothing} - +
diff --git a/resources/js/layout/AppLayout.vue b/resources/js/layout/AppLayout.vue index adb0bac8529..cf26577596f 100644 --- a/resources/js/layout/AppLayout.vue +++ b/resources/js/layout/AppLayout.vue @@ -1,5 +1,6 @@