diff --git a/.changeset/lucky-scissors-nail.md b/.changeset/lucky-scissors-nail.md new file mode 100644 index 0000000..ce9bc31 --- /dev/null +++ b/.changeset/lucky-scissors-nail.md @@ -0,0 +1,14 @@ +--- +"@solid-aria/i18n": minor +"@solid-aria/primitives": minor +"@solid-aria/utils": minor +"@solid-aria/progress": patch +"@solid-aria/select": patch +"@solid-aria/separator": patch +--- + +- `@solid-aria/i18n`: added `createNumberFormatter` primitive. +- `@solid-aria/primitives`: added `progress` and `separator` packages. +- `@solid-aria/utils`: added number utilities. +- `@solid-aria/progress`: new package. +- `@solid-aria/separator`: new package. diff --git a/README.md b/README.md index 64ee883..0e9bedc 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ A library of high-quality primitives that help you build accessible user interfa - [selection](./packages/selection/) - Primitives for managing selection in collections. - [tree](./packages/tree/) - Primitives for managing tree collections. +### Content + +- [separator](./packages/separator/) - Provides the accessibility implementation for a separator. + ### Interactions - [focus](./packages/focus/) - Primitives for dealing with focus rings and focus management. @@ -49,6 +53,10 @@ A library of high-quality primitives that help you build accessible user interfa - [menu](./packages/menu/) - Provides the behavior and accessibility implementation for a menu component. - [overlays](./packages/overlays/) - Provides the behavior and accessibility implementation for overlay components such as dialogs, popovers, and menus. +### Status + +- [progress](./packages/progress/) - Provides the accessibility implementation for a progress bar component. + ### Utilities - [i18n](./packages/i18n/) - Primitives for dealing with locale and layout direction. @@ -75,11 +83,11 @@ A library of high-quality primitives that help you build accessible user interfa - [ ] NumberField - [x] Overlays - [ ] Pagination -- [ ] Progress +- [x] Progress - [x] Radio - [ ] SearchField - [x] Select -- [ ] Separator +- [x] Separator - [ ] Slider - [ ] SpinButton - [x] Switch @@ -91,6 +99,7 @@ A library of high-quality primitives that help you build accessible user interfa - [x] Toggle - [ ] Tooltip - [x] Visually-hidden +- [ ] Virtualizer ## Acknowledgment diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 2bc0577..405334f 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -39,6 +39,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@internationalized/number": "^3.1.1", "@solid-aria/types": "^0.1.2", "@solid-aria/utils": "^0.1.2", "@solid-primitives/utils": "^2.1.0" diff --git a/packages/i18n/src/createNumberFormatter.ts b/packages/i18n/src/createNumberFormatter.ts new file mode 100644 index 0000000..33884cc --- /dev/null +++ b/packages/i18n/src/createNumberFormatter.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2022 Solid Aria Working Group. + * MIT License + * + * Portions of this file are based on code from react-spectrum. + * Copyright 2020 Adobe. All rights reserved. + * + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { NumberFormatOptions, NumberFormatter } from "@internationalized/number"; +import { Accessor, createMemo } from "solid-js"; + +import { useLocale } from "./context"; + +/** + * Provides localized number formatting for the current locale. Automatically updates when the locale changes, + * and handles caching of the number formatter for performance. + * @param options - Formatting options. + */ +export function createNumberFormatter( + options: Accessor +): Accessor { + const locale = useLocale(); + + const formatter = createMemo(() => new NumberFormatter(locale().locale, options())); + + return formatter; +} diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts index c9d43c7..322cbc8 100644 --- a/packages/i18n/src/index.ts +++ b/packages/i18n/src/index.ts @@ -18,4 +18,5 @@ export * from "./context"; export * from "./createCollator"; export * from "./createDefaultLocale"; +export * from "./createNumberFormatter"; export * from "./utils"; diff --git a/packages/primitives/package.json b/packages/primitives/package.json index f71d379..801a50d 100644 --- a/packages/primitives/package.json +++ b/packages/primitives/package.json @@ -62,9 +62,11 @@ "@solid-aria/listbox": "^0.1.3", "@solid-aria/menu": "^0.1.3", "@solid-aria/overlays": "^0.1.2", + "@solid-aria/progress": "^0.0.0", "@solid-aria/radio": "^0.1.2", "@solid-aria/select": "^0.0.1", "@solid-aria/selection": "^0.1.3", + "@solid-aria/separator": "^0.0.0", "@solid-aria/switch": "^0.1.2", "@solid-aria/toggle": "^0.1.2", "@solid-aria/tree": "^0.1.2", diff --git a/packages/primitives/src/index.tsx b/packages/primitives/src/index.tsx index 14658ed..6afead3 100644 --- a/packages/primitives/src/index.tsx +++ b/packages/primitives/src/index.tsx @@ -29,9 +29,11 @@ export * from "@solid-aria/list"; export * from "@solid-aria/listbox"; export * from "@solid-aria/menu"; export * from "@solid-aria/overlays"; +export * from "@solid-aria/progress"; export * from "@solid-aria/radio"; export * from "@solid-aria/select"; export * from "@solid-aria/selection"; +export * from "@solid-aria/separator"; export * from "@solid-aria/switch"; export * from "@solid-aria/toggle"; export * from "@solid-aria/tree"; diff --git a/packages/progress/CHANGELOG.md b/packages/progress/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/packages/progress/LICENSE.md b/packages/progress/LICENSE.md new file mode 100644 index 0000000..4a0310d --- /dev/null +++ b/packages/progress/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Solid Aria Working Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/progress/README.md b/packages/progress/README.md new file mode 100644 index 0000000..1a16ed8 --- /dev/null +++ b/packages/progress/README.md @@ -0,0 +1,137 @@ +

+ Solid Aria - Progress +

+ +# @solid-aria/progress + +[![pnpm](https://img.shields.io/badge/maintained%20with-pnpm-cc00ff.svg?style=for-the-badge&logo=pnpm)](https://pnpm.io/) +[![turborepo](https://img.shields.io/badge/built%20with-turborepo-cc00ff.svg?style=for-the-badge&logo=turborepo)](https://turborepo.org/) +[![size](https://img.shields.io/bundlephobia/minzip/@solid-aria/progress?style=for-the-badge&label=size)](https://bundlephobia.com/package/@solid-aria/progress) +[![version](https://img.shields.io/npm/v/@solid-aria/progress?style=for-the-badge)](https://www.npmjs.com/package/@solid-aria/progress) +[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-0.json)](https://github.com/solidjs-community/solid-aria#contribution-process) + +Progress bars show either determinate or indeterminate progress of an operation over time. + +- [`createProgressBar`](#createprogressbar) - Provides the accessibility implementation for a progress bar component. + +## Installation + +```bash +npm install @solid-aria/progress +# or +yarn add @solid-aria/progress +# or +pnpm add @solid-aria/progress +``` + +## `createProgressBar` + +### Features + +The `` HTML element can be used to build a progress bar, however it is very difficult to style cross browser. `createProgressBar` helps achieve accessible progress bars and spinners that can be styled as needed. + +- Exposed to assistive technology as a progress bar via ARIA +- Labeling support for accessibility +- Internationalized number formatting as a percentage or value +- Determinate and indeterminate progress support + +### How to use it + +```tsx +import { AriaProgressBarProps, createProgressBar } from "@solid-aria/progress"; +import { createMemo, Show } from "solid-js"; + +function ProgressBar(props: AriaProgressBarProps) { + const { progressBarProps, labelProps, percentage } = createProgressBar(props); + + const barWidth = createMemo(() => `${Math.round(percentage() * 100)}%`); + + return ( +
+
+ + {props.label} + {progressBarProps["aria-valuetext"]} + +
+
+
+
+
+ ); +} + +function App() { + return ; +} +``` + +### Indeterminate state + +Progress bars can represent an indeterminate operation. They may also be used to represent progress visually as a circle rather than as a line. The following example shows an indeterminate progress bar visualized as a circular spinner using SVG. + +```tsx +import { createProgressBar } from "@solid-aria/progress"; + +function Spinner() { + const { progressBarProps } = createProgressBar({ + isIndeterminate: true, + "aria-label": "Loading..." + }); + + const center = 16; + const strokeWidth = 4; + const r = 16 - strokeWidth; + const c = 2 * r * Math.PI; + const offset = c - (1 / 4) * c; + + return ( + + + + + + + ); +} + +function App() { + return ; +} +``` + +### Internationalization + +#### Value formatting + +`createProgressBar` will handle localized formatting of the value label for accessibility automatically. This is returned in the `aria-valuetext` prop in `progressBarProps`. You can use this to create a visible label if needed and ensure that it is formatted correctly. The number formatting can also be controlled using the `formatOptions` prop. + +#### RTL + +In right-to-left languages, the progress bar should be mirrored. Ensure that your CSS accounts for this. + +## Changelog + +All notable changes are described in the [CHANGELOG.md](./CHANGELOG.md) file. diff --git a/packages/progress/dev/index.html b/packages/progress/dev/index.html new file mode 100644 index 0000000..c3eddaf --- /dev/null +++ b/packages/progress/dev/index.html @@ -0,0 +1,14 @@ + + + + + + + Solid App + + + +
+ + + diff --git a/packages/progress/dev/index.tsx b/packages/progress/dev/index.tsx new file mode 100644 index 0000000..b80e4e2 --- /dev/null +++ b/packages/progress/dev/index.tsx @@ -0,0 +1,7 @@ +import { render } from "solid-js/web"; + +function App() { + return
Hello Solid Aria!
; +} + +render(() => , document.getElementById("root") as HTMLDivElement); diff --git a/packages/progress/dev/vite.config.ts b/packages/progress/dev/vite.config.ts new file mode 100644 index 0000000..6b5d194 --- /dev/null +++ b/packages/progress/dev/vite.config.ts @@ -0,0 +1,3 @@ +import { viteConfig } from "../../../configs/vite.config"; + +export default viteConfig; diff --git a/packages/progress/jest.config.cjs b/packages/progress/jest.config.cjs new file mode 100644 index 0000000..992a8b0 --- /dev/null +++ b/packages/progress/jest.config.cjs @@ -0,0 +1,5 @@ +const baseJest = require("../../configs/jest.config.cjs"); + +module.exports = { + ...baseJest +}; diff --git a/packages/progress/package.json b/packages/progress/package.json new file mode 100644 index 0000000..4fc3412 --- /dev/null +++ b/packages/progress/package.json @@ -0,0 +1,66 @@ +{ + "name": "@solid-aria/progress", + "version": "0.0.0", + "private": false, + "description": "Primitives for building accessible progress bar component.", + "keywords": [ + "solid", + "aria", + "headless", + "design", + "system", + "components" + ], + "homepage": "https://github.com/solidjs-community/solid-aria/tree/main/packages/progress#readme", + "bugs": { + "url": "https://github.com/solidjs-community/solid-aria/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/solidjs-community/solid-aria.git" + }, + "license": "MIT", + "author": "Fabien Marie-Louise ", + "contributors": [], + "sideEffects": false, + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist", + "dev": "vite serve dev --host", + "test": "jest --passWithNoTests", + "test:watch": "jest --watch --passWithNoTests", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@solid-aria/i18n": "^0.1.2", + "@solid-aria/label": "^0.1.3", + "@solid-aria/types": "^0.1.2", + "@solid-aria/utils": "^0.1.2", + "@solid-primitives/props": "^2.1.7", + "@solid-primitives/utils": "^2.1.0" + }, + "devDependencies": { + "solid-js": "^1.4.4" + }, + "peerDependencies": { + "solid-js": "^1.4.4" + }, + "publishConfig": { + "access": "public" + }, + "primitive": { + "name": "progress", + "stage": 0, + "list": [ + "" + ], + "category": "" + } +} diff --git a/packages/progress/src/createProgressBar.ts b/packages/progress/src/createProgressBar.ts new file mode 100644 index 0000000..c7dd8db --- /dev/null +++ b/packages/progress/src/createProgressBar.ts @@ -0,0 +1,153 @@ +/* + * Copyright 2022 Solid Aria Working Group. + * MIT License + * + * Portions of this file are based on code from react-spectrum. + * Copyright 2020 Adobe. All rights reserved. + * + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { createNumberFormatter } from "@solid-aria/i18n"; +import { AriaLabelProps, createLabel } from "@solid-aria/label"; +import { AriaLabelingProps, DOMProps } from "@solid-aria/types"; +import { clamp, filterDOMProps } from "@solid-aria/utils"; +import { combineProps } from "@solid-primitives/props"; +import { Accessor, createMemo, JSX, mergeProps } from "solid-js"; + +export interface AriaProgressCircleProps extends DOMProps, AriaLabelingProps { + /** + * The current value (controlled). + * @default 0 + */ + value?: number; + + /** + * The smallest value allowed for the input. + * @default 0 + */ + minValue?: number; + + /** + * The largest value allowed for the input. + * @default 100 + */ + maxValue?: number; + + /** + * Whether presentation is indeterminate when progress isn't known. + */ + isIndeterminate?: boolean; +} + +export interface AriaProgressBarProps extends AriaProgressCircleProps { + /** + * The content to display as the label. + */ + label?: JSX.Element; + + /** + * The display format of the value label. + * @default {style: 'percent'} + */ + formatOptions?: Intl.NumberFormatOptions; + + /** + * The content to display as the value's label (e.g. 1 of 4). + */ + valueLabel?: JSX.Element; +} + +interface ProgressBarAria { + /** + * Props for the progress bar container element. + */ + progressBarProps: JSX.HTMLAttributes; + + /** + * Props for the progress bar's visual label element (if any). + */ + labelProps: JSX.HTMLAttributes; + + /** + * The value of the progress bar as a percentage (from 0 to 1). + */ + percentage: Accessor; +} + +const DEFAULT_VALUE = 0; +const DEFAULT_MIN_VALUE = 0; +const DEFAULT_MAX_VALUE = 100; + +/** + * Provides the accessibility implementation for a progress bar component. + * Progress bars show either determinate or indeterminate progress of an operation + * over time. + */ +export function createProgressBar(props: AriaProgressBarProps = {}): ProgressBarAria { + const defaultProps: AriaProgressBarProps = { + value: DEFAULT_VALUE, + minValue: DEFAULT_MIN_VALUE, + maxValue: DEFAULT_MAX_VALUE, + formatOptions: { + style: "percent" + } + }; + + // eslint-disable-next-line solid/reactivity + props = mergeProps(defaultProps, props); + + const domProps = mergeProps(createMemo(() => filterDOMProps(props, { labelable: true }))); + + const createLabelProps = mergeProps(props, { + // select is not an HTML input element so it + // shouldn't be labeled by a