diff --git a/package-lock.json b/package-lock.json index eeff97b10..2eb88b941 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11018,6 +11018,10 @@ "resolved": "packages/uui-toggle", "link": true }, + "node_modules/@umbraco-ui/uui-visually-hidden": { + "resolved": "packages/uui-visually-hidden", + "link": true + }, "node_modules/@web/browser-logs": { "version": "0.3.3", "dev": true, @@ -32928,6 +32932,13 @@ "@umbraco-ui/uui-base": "1.5.0-rc.0", "@umbraco-ui/uui-boolean-input": "1.5.0-rc.0" } + }, + "packages/uui-visually-hidden": { + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@umbraco-ui/uui-base": "1.5.0-rc.0" + } } } } diff --git a/packages/uui-visually-hidden/README.md b/packages/uui-visually-hidden/README.md new file mode 100644 index 000000000..5f420e4a3 --- /dev/null +++ b/packages/uui-visually-hidden/README.md @@ -0,0 +1,39 @@ +# uui-visually-hidden + +![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-visually-hidden?logoColor=%231B264F) + +Umbraco style visually-hidden component. + +The visually hidden utility makes content accessible to assistive devices without displaying it on the screen. + +According to [The A11Y Project](https://www.a11yproject.com/posts/how-to-hide-content/): + +> There are real world situations where visually hiding content may be appropriate, while the content should remain available to assistive technologies, such as screen readers. For instance, hiding a search field's label as a common magnifying glass icon is used in its stead. + +Since visually hidden content can receive focus when tabbing, the element will become visible when something inside receives focus. This behavior is intentional, as sighted keyboard user won’t be able to determine where the focus indicator is without it. + +## Installation + +### ES imports + +```zsh +npm i @umbraco-ui/uui-visually-hidden +``` + +Import the registration of `` via: + +```javascript +import '@umbraco-ui/uui-visually-hidden'; +``` + +When looking to leverage the `UUIVisuallyHiddenElement` base class as a type and/or for extension purposes, do so via: + +```javascript +import { UUIVisuallyHiddenElement } from '@umbraco-ui/uui-visually-hidden'; +``` + +## Usage + +```html + +``` diff --git a/packages/uui-visually-hidden/lib/index.ts b/packages/uui-visually-hidden/lib/index.ts new file mode 100644 index 000000000..68234c3ad --- /dev/null +++ b/packages/uui-visually-hidden/lib/index.ts @@ -0,0 +1 @@ +export * from './uui-visually-hidden.element'; diff --git a/packages/uui-visually-hidden/lib/uui-visually-hidden.element.ts b/packages/uui-visually-hidden/lib/uui-visually-hidden.element.ts new file mode 100644 index 000000000..8e9a36e61 --- /dev/null +++ b/packages/uui-visually-hidden/lib/uui-visually-hidden.element.ts @@ -0,0 +1,34 @@ +import { defineElement } from '@umbraco-ui/uui-base/lib/registration'; +import { css, html, LitElement } from 'lit'; + +/** + * @element uui-visually-hidden + */ +@defineElement('uui-visually-hidden') +export class UUIVisuallyHiddenElement extends LitElement { + static styles = [ + css` + :host(:not(:focus-within)) { + position: absolute !important; + width: 1px !important; + height: 1px !important; + clip: rect(0 0 0 0) !important; + clip-path: inset(50%) !important; + border: none !important; + overflow: hidden !important; + white-space: nowrap !important; + padding: 0 !important; + } + `, + ]; + + render() { + return html``; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'uui-visually-hidden': UUIVisuallyHiddenElement; + } +} diff --git a/packages/uui-visually-hidden/lib/uui-visually-hidden.story.ts b/packages/uui-visually-hidden/lib/uui-visually-hidden.story.ts new file mode 100644 index 000000000..670093b06 --- /dev/null +++ b/packages/uui-visually-hidden/lib/uui-visually-hidden.story.ts @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; + +import './uui-visually-hidden.element'; +import type { UUIVisuallyHiddenElement } from './uui-visually-hidden.element'; +import readme from '../README.md?raw'; + +const meta: Meta = { + id: 'uui-visually-hidden', + title: 'Visually Hidden', + component: 'uui-visually-hidden', + parameters: { + readme: { markdown: readme }, + docs: { + source: { + code: ``, + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Overview: Story = {}; + +export const SkipNavigation: Story = { + args: {}, + parameters: { + docs: { + source: { + code: ` + Skip to main content +`, + }, + }, + }, +}; diff --git a/packages/uui-visually-hidden/lib/uui-visually-hidden.test.ts b/packages/uui-visually-hidden/lib/uui-visually-hidden.test.ts new file mode 100644 index 000000000..cc3aad168 --- /dev/null +++ b/packages/uui-visually-hidden/lib/uui-visually-hidden.test.ts @@ -0,0 +1,20 @@ +import { html, fixture, expect } from '@open-wc/testing'; +import { UUIVisuallyHiddenElement } from './uui-visually-hidden.element'; + +describe('UUIVisuallyHiddenElement', () => { + let element: UUIVisuallyHiddenElement; + + beforeEach(async () => { + element = await fixture( + html` ` + ); + }); + + it('is defined with its own instance', () => { + expect(element).to.be.instanceOf(UUIVisuallyHiddenElement); + }); + + it('passes the a11y audit', async () => { + await expect(element).shadowDom.to.be.accessible(); + }); +}); diff --git a/packages/uui-visually-hidden/package.json b/packages/uui-visually-hidden/package.json new file mode 100644 index 000000000..70c496b19 --- /dev/null +++ b/packages/uui-visually-hidden/package.json @@ -0,0 +1,44 @@ +{ + "name": "@umbraco-ui/uui-visually-hidden", + "version": "0.0.0", + "license": "MIT", + "keywords": [ + "Umbraco", + "Custom elements", + "Web components", + "UI", + "Lit", + "Visually Hidden" + ], + "description": "Umbraco UI visually-hidden component", + "repository": { + "type": "git", + "url": "https://github.com/umbraco/Umbraco.UI.git", + "directory": "packages/uui-visually-hidden" + }, + "bugs": { + "url": "https://github.com/umbraco/Umbraco.UI/issues" + }, + "main": "./lib/index.js", + "module": "./lib/index.js", + "types": "./lib/index.d.ts", + "type": "module", + "customElements": "custom-elements.json", + "files": [ + "lib/**/*.d.ts", + "lib/**/*.js", + "custom-elements.json" + ], + "dependencies": { + "@umbraco-ui/uui-base": "1.5.0-rc.0" + }, + "scripts": { + "build": "npm run analyze && tsc --build --force && rollup -c rollup.config.js", + "clean": "tsc --build --clean && rimraf -g dist lib/*.js lib/**/*.js *.tgz lib/**/*.d.ts custom-elements.json", + "analyze": "web-component-analyzer **/*.element.ts --outFile custom-elements.json" + }, + "publishConfig": { + "access": "public" + }, + "homepage": "https://uui.umbraco.com/?path=/story/uui-visually-hidden" +} diff --git a/packages/uui-visually-hidden/rollup.config.js b/packages/uui-visually-hidden/rollup.config.js new file mode 100644 index 000000000..34524a90d --- /dev/null +++ b/packages/uui-visually-hidden/rollup.config.js @@ -0,0 +1,5 @@ +import { UUIProdConfig } from '../rollup-package.config.mjs'; + +export default UUIProdConfig({ + entryPoints: ['index'], +}); diff --git a/packages/uui-visually-hidden/tsconfig.json b/packages/uui-visually-hidden/tsconfig.json new file mode 100644 index 000000000..40d176776 --- /dev/null +++ b/packages/uui-visually-hidden/tsconfig.json @@ -0,0 +1,17 @@ +// Don't edit this file directly. It is generated by /scripts/generate-ts-config.js + +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./lib", + "rootDir": "./lib", + "composite": true + }, + "include": ["./**/*.ts"], + "exclude": ["./**/*.test.ts", "./**/*.story.ts"], + "references": [ + { + "path": "../uui-base" + } + ] +} diff --git a/packages/uui/lib/index.ts b/packages/uui/lib/index.ts index c4a779bda..b9c8043f8 100644 --- a/packages/uui/lib/index.ts +++ b/packages/uui/lib/index.ts @@ -78,3 +78,4 @@ export * from '@umbraco-ui/uui-toast-notification-container/lib'; export * from '@umbraco-ui/uui-toast-notification-layout/lib'; export * from '@umbraco-ui/uui-toast-notification/lib'; export * from '@umbraco-ui/uui-toggle/lib'; +export * from '@umbraco-ui/uui-visually-hidden/lib';