@mortenbrudvik/waveui 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +425 -104
- package/dist/components/input/Calendar.cjs +2 -0
- package/dist/components/input/Calendar.cjs.map +1 -0
- package/dist/components/input/Calendar.mjs +352 -0
- package/dist/components/input/Calendar.mjs.map +1 -0
- package/dist/components/input/Calendar.views.cjs +2 -0
- package/dist/components/input/Calendar.views.cjs.map +1 -0
- package/dist/components/input/Calendar.views.mjs +194 -0
- package/dist/components/input/Calendar.views.mjs.map +1 -0
- package/dist/components/input/ColorArea.cjs +1 -1
- package/dist/components/input/ColorArea.cjs.map +1 -1
- package/dist/components/input/ColorArea.mjs +18 -18
- package/dist/components/input/ColorArea.mjs.map +1 -1
- package/dist/components/input/Combobox.cjs +1 -1
- package/dist/components/input/Combobox.cjs.map +1 -1
- package/dist/components/input/Combobox.mjs +232 -156
- package/dist/components/input/Combobox.mjs.map +1 -1
- package/dist/components/input/Combobox.text.cjs +2 -0
- package/dist/components/input/Combobox.text.cjs.map +1 -0
- package/dist/components/input/Combobox.text.mjs +24 -0
- package/dist/components/input/Combobox.text.mjs.map +1 -0
- package/dist/components/input/DatePicker.cjs +1 -1
- package/dist/components/input/DatePicker.cjs.map +1 -1
- package/dist/components/input/DatePicker.mjs +203 -288
- package/dist/components/input/DatePicker.mjs.map +1 -1
- package/dist/components/input/Dropdown.cjs +1 -1
- package/dist/components/input/Dropdown.cjs.map +1 -1
- package/dist/components/input/Dropdown.mjs +135 -109
- package/dist/components/input/Dropdown.mjs.map +1 -1
- package/dist/components/input/Listbox.cjs +2 -0
- package/dist/components/input/Listbox.cjs.map +1 -0
- package/dist/components/input/Listbox.mjs +134 -0
- package/dist/components/input/Listbox.mjs.map +1 -0
- package/dist/components/input/Option.cjs +1 -1
- package/dist/components/input/Option.cjs.map +1 -1
- package/dist/components/input/Option.mjs +158 -97
- package/dist/components/input/Option.mjs.map +1 -1
- package/dist/components/input/SwatchPicker.cjs +1 -1
- package/dist/components/input/SwatchPicker.cjs.map +1 -1
- package/dist/components/input/SwatchPicker.context.cjs +2 -0
- package/dist/components/input/SwatchPicker.context.cjs.map +1 -0
- package/dist/components/input/SwatchPicker.context.mjs +35 -0
- package/dist/components/input/SwatchPicker.context.mjs.map +1 -0
- package/dist/components/input/SwatchPicker.mjs +131 -103
- package/dist/components/input/SwatchPicker.mjs.map +1 -1
- package/dist/components/input/SwatchPicker.swatches.cjs +2 -0
- package/dist/components/input/SwatchPicker.swatches.cjs.map +1 -0
- package/dist/components/input/SwatchPicker.swatches.mjs +207 -0
- package/dist/components/input/SwatchPicker.swatches.mjs.map +1 -0
- package/dist/components/input/TagPicker.cjs +1 -1
- package/dist/components/input/TagPicker.cjs.map +1 -1
- package/dist/components/input/TagPicker.mjs +144 -139
- package/dist/components/input/TagPicker.mjs.map +1 -1
- package/dist/components/input/TimePicker.cjs +1 -1
- package/dist/components/input/TimePicker.cjs.map +1 -1
- package/dist/components/input/TimePicker.mjs +77 -77
- package/dist/components/input/TimePicker.mjs.map +1 -1
- package/dist/components/input/dateUtils.cjs +1 -1
- package/dist/components/input/dateUtils.cjs.map +1 -1
- package/dist/components/input/dateUtils.mjs +114 -96
- package/dist/components/input/dateUtils.mjs.map +1 -1
- package/dist/components/input/pickerLabels.cjs +2 -0
- package/dist/components/input/pickerLabels.cjs.map +1 -0
- package/dist/components/input/pickerLabels.mjs +16 -0
- package/dist/components/input/pickerLabels.mjs.map +1 -0
- package/dist/components/input/pickerValues.cjs +2 -0
- package/dist/components/input/pickerValues.cjs.map +1 -0
- package/dist/components/input/pickerValues.mjs +24 -0
- package/dist/components/input/pickerValues.mjs.map +1 -0
- package/dist/components/table/useGridNavigation.cjs +1 -1
- package/dist/components/table/useGridNavigation.cjs.map +1 -1
- package/dist/components/table/useGridNavigation.mjs +67 -70
- package/dist/components/table/useGridNavigation.mjs.map +1 -1
- package/dist/hooks/useActiveDescendant.cjs +2 -0
- package/dist/hooks/useActiveDescendant.cjs.map +1 -0
- package/dist/hooks/useActiveDescendant.mjs +56 -0
- package/dist/hooks/useActiveDescendant.mjs.map +1 -0
- package/dist/hooks/useListbox.cjs +1 -1
- package/dist/hooks/useListbox.cjs.map +1 -1
- package/dist/hooks/useListbox.mjs +250 -210
- package/dist/hooks/useListbox.mjs.map +1 -1
- package/dist/hooks/useRovingGrid.cjs +2 -0
- package/dist/hooks/useRovingGrid.cjs.map +1 -0
- package/dist/hooks/useRovingGrid.mjs +69 -0
- package/dist/hooks/useRovingGrid.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2147 -155
- package/dist/index.d.ts +2147 -155
- package/dist/index.mjs +107 -101
- package/dist/lib/classNames.cjs +1 -1
- package/dist/lib/classNames.cjs.map +1 -1
- package/dist/lib/classNames.mjs +84 -48
- package/dist/lib/classNames.mjs.map +1 -1
- package/dist/lib/date.cjs +2 -0
- package/dist/lib/date.cjs.map +1 -0
- package/dist/lib/date.mjs +44 -0
- package/dist/lib/date.mjs.map +1 -0
- package/dist/lib/focus.cjs +1 -1
- package/dist/lib/focus.cjs.map +1 -1
- package/dist/lib/focus.mjs +5 -2
- package/dist/lib/focus.mjs.map +1 -1
- package/dist/lib/layers.cjs.map +1 -1
- package/dist/lib/layers.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @mortenbrudvik/waveui
|
|
2
2
|
|
|
3
|
-
A React component library inspired by the Fluent UI 2 design language:
|
|
3
|
+
A React component library inspired by the Fluent UI 2 design language: 74 accessible, composable components built with TypeScript, Tailwind CSS 4 and CSS custom properties, with light, dark and high-contrast themes and right-to-left support.
|
|
4
4
|
|
|
5
5
|
> This is **not** Microsoft's official `@fluentui/react-components` package.
|
|
6
6
|
|
|
7
7
|
- [Requirements](#requirements) · [Installation](#installation) · [Quick start](#quick-start)
|
|
8
8
|
- [Styles](#styles) · [Theming](#theming) · [Global effects](#global-effects) · [Styling hooks](#styling-hooks)
|
|
9
9
|
- [React Server Components](#react-server-components) · [Components](#components) · [Usage notes](#usage-notes)
|
|
10
|
-
- [Keyboard support](#keyboard-support) · [Hooks and utilities](#hooks-and-utilities) · [Upgrading from 0.8](#upgrading-from-08) · [Upgrading from 0.7](#upgrading-from-07) · [Upgrading from 0.6](#upgrading-from-06) · [Upgrading from 0.5](#upgrading-from-05) · [Upgrading from 0.4](#upgrading-from-04) · [Development](#development)
|
|
10
|
+
- [Keyboard support](#keyboard-support) · [Hooks and utilities](#hooks-and-utilities) · [Upgrading from 0.9](#upgrading-from-09) · [Upgrading from 0.8](#upgrading-from-08) · [Upgrading from 0.7](#upgrading-from-07) · [Upgrading from 0.6](#upgrading-from-06) · [Upgrading from 0.5](#upgrading-from-05) · [Upgrading from 0.4](#upgrading-from-04) · [Development](#development)
|
|
11
11
|
|
|
12
12
|
## Requirements
|
|
13
13
|
|
|
@@ -366,9 +366,9 @@ export function checkedRows(tree: HTMLElement) {
|
|
|
366
366
|
|
|
367
367
|
## React Server Components
|
|
368
368
|
|
|
369
|
-
- Every component and hook module of the package starts with `"use client"`. The entry point, `cn`, the slot helpers, the class-name constants (`buttonClassNames`, …), `hsvToHex`, `hexToHsv`, `createToaster` and the types are server-safe, so a Server Component can import from `@mortenbrudvik/waveui`, read the constants, create a toaster handle and render Wave components with serializable props.
|
|
369
|
+
- Every component and hook module of the package starts with `"use client"`. The entry point, `cn`, the slot helpers, the class-name constants (`buttonClassNames`, …), `hsvToHex`, `hexToHsv`, `createToaster`, the date helpers (`addDays`, `addMonths`, `isSameDay`, `startOfWeek`, `getWeekNumber`) and the types are server-safe, so a Server Component can import from `@mortenbrudvik/waveui`, read the constants, create a toaster handle, compute dates and render Wave components with serializable props.
|
|
370
370
|
- A Server Component **cannot dot into a client component**: `Card.Header` throws "Cannot access Header on the server". Every sub-component is therefore also exported under a flat name. Use the flat names in Server Components and either form in client components.
|
|
371
|
-
- Every compound can be composed in a Server Component. Parts written there reach the client as lazy references, and the compounds recognise them, so the server HTML and the client behaviour are the same as in a client file: Accordion items, Carousel slides, the options of Combobox and
|
|
371
|
+
- Every compound can be composed in a Server Component. Parts written there reach the client as lazy references, and the compounds recognise them, so the server HTML and the client behaviour are the same as in a client file: Accordion items, Carousel slides, the options of Combobox, Dropdown and Listbox, DataGrid parts, Drawer and Menu triggers, the header of a `Menu.Group`, the halves of a `Menu.SplitGroup`, List, Nav, TabList and Tree items, the stars of a Rating or RatingDisplay, the parts of Toast and MessageBar, the ColorPicker parts, `SwatchPicker.Row` and the swatches (`ColorSwatch`, `ImageSwatch`, `EmptySwatch`, in rows of the grid layout or the implicit ones), `Tree.ItemPersonaLayout`, a `Label` or `InfoLabel` passed as the `label` of a Field, and a `Button` passed as a MessageBar, SearchBox or Tag dismiss slot.
|
|
372
372
|
- Event handlers and other functions cannot be passed from a Server Component; put interactive parts in a `'use client'` component.
|
|
373
373
|
- A `Popover`, `Dialog`, `Drawer`, popup `Menu`, `Combobox`, `Dropdown`, `DatePicker`, `TimePicker` or `TagPicker` rendered on the server with `defaultOpen` (or `open`) is closed in the server HTML and opens once hydrated, so no ARIA reference points at a popup that is not there. `onOpenChange` is not called for it.
|
|
374
374
|
|
|
@@ -451,6 +451,7 @@ export function SaveArea() {
|
|
|
451
451
|
| `Drawer` | `DrawerTrigger`, `DrawerTitle`, `DrawerClose` |
|
|
452
452
|
| `Dropdown` | `DropdownOption`, `DropdownOptionGroup` |
|
|
453
453
|
| `List` | `ListItem` |
|
|
454
|
+
| `Listbox` | `ListboxOption`, `ListboxOptionGroup` (also `Option`, `OptionGroup`) |
|
|
454
455
|
| `Menu` | `MenuTrigger`, `MenuPopover`, `MenuItem`, `MenuDivider`, `MenuItemCheckbox`, `MenuItemRadio`, `MenuItemSwitch`, `MenuItemLink`, `MenuGroup`, `MenuGroupHeader`, `MenuSplitGroup` |
|
|
455
456
|
| `MessageBar` | `MessageBarTitle`, `MessageBarActions` |
|
|
456
457
|
| `Nav` | `NavCategory`, `NavItem`, `NavSubItem` |
|
|
@@ -461,6 +462,7 @@ export function SaveArea() {
|
|
|
461
462
|
| `RatingDisplay` | `RatingDisplayItem` (also `RatingItem`) |
|
|
462
463
|
| `Skeleton` | `SkeletonGroup` |
|
|
463
464
|
| `Stepper` | `StepperStep` |
|
|
465
|
+
| `SwatchPicker` | `SwatchPickerRow` |
|
|
464
466
|
| `Table` | `TableHeader`, `TableHeaderCell`, `TableBody`, `TableRow`, `TableCell` |
|
|
465
467
|
| `TabList` | `TabListTab`, `TabListPanel`, `TabListPanels` |
|
|
466
468
|
| `Toast` | `ToastTitle`, `ToastBody`, `ToastFooter`, `ToastClose` |
|
|
@@ -469,7 +471,7 @@ export function SaveArea() {
|
|
|
469
471
|
|
|
470
472
|
## Components
|
|
471
473
|
|
|
472
|
-
|
|
474
|
+
74 components, plus the `Portal` and `Presence` utilities. Every component accepts `ref` as a prop (React 19), a `className`, and the native attributes of its element. Storybook (`npm run dev`) shows every component with its props, states and themes.
|
|
473
475
|
|
|
474
476
|
Your `className` is merged last with `cn()`, so it replaces a conflicting class **of the same variant**: `bg-error` replaces a resting `bg-primary`. A class behind a variant is replaced only by a class with the same variant. That matters for the gated hover and pressed classes (`not-disabled:not-aria-disabled:hover:…` / `…:active:…`) and the state classes (`data-[selected]:…`, `aria-disabled:…`): they stay next to yours and win while their state applies, because they are more specific. A bare `hover:bg-error` therefore does not override a Button's built-in hover color while the Button is enabled. It still paints on hover while the Button is disabled or `aria-disabled`, and on `appearance="transparent"`, which has no hover background. To override the gated and state classes, use the same prefix (`not-disabled:not-aria-disabled:hover:bg-error`, `data-[selected]:bg-error`) or the important modifier (`hover:bg-error!`); see [Buttons](#buttons).
|
|
475
477
|
|
|
@@ -500,16 +502,21 @@ Your `className` is merged last with `cn()`, so it replaces a conflicting class
|
|
|
500
502
|
| `SearchBox` | Search input with a clear button; `size`, `appearance`, `onValueChange`; input attributes go to the `<input>`. The root draws the field, so `className` and `style` style the field box, and `contentBefore`/`contentAfter` sit beside the text. |
|
|
501
503
|
| `Slider` | Styled native range input, filled up to the thumb; `size` (`small`, `medium`); `onValueChange(number)`. |
|
|
502
504
|
| `SpinButton` | Numeric input with step buttons; `size`, `appearance`; typed text is a draft committed on blur or Enter; `min`, `max`, `step`, `largeStep`, `precision`; `displayValue` shows formatted text; `allowEmpty` allows a `null` value; holding a step button repeats it (see [SpinButton](#spinbutton)). |
|
|
503
|
-
| `Combobox` | Editable combobox: typing filters the options; `freeform` makes the typed text the value; a chevron (`expandIcon`) and `clearable`. `Combobox.Option`, `Combobox.OptionGroup
|
|
504
|
-
| `Dropdown` | Select-only combobox (a button) with typeahead; `clearable`. `Dropdown.Option`, `Dropdown.OptionGroup`.
|
|
505
|
-
| `
|
|
506
|
-
| `
|
|
505
|
+
| `Combobox` | Editable combobox: typing filters the options (`filter` replaces the match, `query` controls the typed text); `multiselect` selects several values; `freeform` makes the typed text the value; a chevron (`expandIcon`) and `clearable`. `Combobox.Option`, `Combobox.OptionGroup` (see [Multi-select pickers](#multi-select-pickers) and [Filtering and async search](#filtering-and-async-search)). |
|
|
506
|
+
| `Dropdown` | Select-only combobox (a button) with typeahead; `multiselect` selects several values; `clearable`, `expandIcon` and `renderValue`. `Dropdown.Option`, `Dropdown.OptionGroup`. |
|
|
507
|
+
| `Listbox` | A list of options in the page that holds focus itself (`aria-activedescendant`), single-select or `multiselect`, with typeahead, groups and native form support; `Listbox.Option`, `Listbox.OptionGroup`. The listbox primitives under the pickers are public, for pickers of your own (see [Listbox and custom pickers](#listbox-and-custom-pickers)). |
|
|
508
|
+
| `TagPicker` | Multi-select combobox that shows the selection as removable tags (a list named "Selected"); `size` (`medium`, `large`, `extra-large`), `appearance`; the input is described by a summary of the selection ("Selected: Apple, Banana"), and additions and removals are announced; `filter` and `query` as in Combobox. |
|
|
509
|
+
| `DatePicker` | Date input with a calendar dialog (month and year views, week numbers, marked days, optional "Go to today" and Close buttons); `size`, `appearance`; locale-aware format and parse, `minDate`, `maxDate`, `disabledDates`, `onInvalidInput`. |
|
|
510
|
+
| `Calendar` | The DatePicker's calendar in the page (`role="group"`): month and year views, week numbers, marked days and custom day content, week, work-week or month ranges, "Go to today", native form support (see [Calendar](#calendar)). |
|
|
507
511
|
| `TimePicker` | Time combobox (`12h`/`24h`, `step`, `minTime`, `maxTime`); `size`, `appearance`; value is `HH:mm`; `open`/`defaultOpen`/`onOpenChange` control the list; rejected text is kept and flagged (`onInvalidInput`). |
|
|
508
512
|
| `ColorPicker` | Hex value (lowercase `#rrggbb`, or `#rrggbbaa` when not opaque) with a color area, a hue slider, a hex field, preset swatches and an optional opacity slider; with children, your own layout of `ColorArea`, `ColorSlider` and `AlphaSlider` (bound to the picker) and `ColorPicker.HexField`, `.Swatches`, `.Preview` (see [Color](#color)); `shape`. Picking a preset keeps the current opacity (a preset's alpha digits are ignored, with a development warning). |
|
|
509
513
|
| `ColorArea` | 2-D saturation and brightness field for an `HsvColor`, with two hidden range inputs (one Tab stop), pointer and touch dragging; mirrored in RTL. |
|
|
510
514
|
| `ColorSlider` | Hue (default), saturation or brightness slider (`channel`), horizontal or vertical. |
|
|
511
515
|
| `AlphaSlider` | Opacity slider over a checkerboard; `transparency` shows transparency. |
|
|
512
|
-
| `SwatchPicker` |
|
|
516
|
+
| `SwatchPicker` | Color swatches from `items` or children: a radio group (the default row layout) or a grid of `SwatchPicker.Row` rows (`layout="grid"`); `focusMode="tab"` makes every swatch a Tab stop; four sizes and `spacing` (see [Swatches](#swatches)). |
|
|
517
|
+
| `ColorSwatch` | A color swatch of a SwatchPicker (`value`, `color`), with an optional `icon` and `borderColor`; `disabled` crosses it out. A `Tooltip` around it can name it. |
|
|
518
|
+
| `ImageSwatch` | An image swatch of a SwatchPicker: a pattern or texture (`src`) drawn as a covering background. |
|
|
519
|
+
| `EmptySwatch` | An "add" button in a SwatchPicker (named "Add color" by default) that selects nothing: your `onClick` opens a color dialog, for example. |
|
|
513
520
|
| `Rating` | Star rating (`role="radiogroup"`); `RatingDisplay` is the read-only version (a fractional value draws a partly filled star; `showValue`, `count` and `compact` add the value and the number of ratings as text). |
|
|
514
521
|
|
|
515
522
|
### Data display
|
|
@@ -698,7 +705,7 @@ export function AccountSettings({ backedUp }: { backedUp: number }) {
|
|
|
698
705
|
}
|
|
699
706
|
```
|
|
700
707
|
|
|
701
|
-
- Library controls inside a `Field` are named by its label, described by its validation message and hint, and marked `aria-invalid`/`aria-required`. With `required`, native inputs (`Input`, `Select`, `Textarea`, `Slider`, `SpinButton`, `SearchBox`) also get the native `required` attribute, so the browser blocks an empty submit. The choice and picker controls (`Checkbox`, `Switch`, `RadioGroup`, `Rating`, `SwatchPicker`, `ColorPicker`, `ColorArea`, `ColorSlider`, `AlphaSlider`, `Combobox`, `Dropdown`, `TagPicker`, `DatePicker`, `TimePicker`) are required too, also without `name`: the browser blocks the submit until the control is checked, switched on or has a value, so `<Field required><Switch /></Field>` means "must be on". Like a native read-only input, a `readOnly` Combobox, TagPicker, DatePicker or TimePicker does not block the submit (it keeps `aria-required`). Add `noValidate` to a `<form>` that validates in its submit handler.
|
|
708
|
+
- Library controls inside a `Field` are named by its label, described by its validation message and hint, and marked `aria-invalid`/`aria-required`. With `required`, native inputs (`Input`, `Select`, `Textarea`, `Slider`, `SpinButton`, `SearchBox`) also get the native `required` attribute, so the browser blocks an empty submit. The choice and picker controls (`Checkbox`, `Switch`, `RadioGroup`, `Rating`, `SwatchPicker`, `ColorPicker`, `ColorArea`, `ColorSlider`, `AlphaSlider`, `Combobox`, `Dropdown`, `Listbox`, `TagPicker`, `DatePicker`, `Calendar`, `TimePicker`) are required too, also without `name`: the browser blocks the submit until the control is checked, switched on or has a value, so `<Field required><Switch /></Field>` means "must be on". Like a native read-only input, a `readOnly` Combobox, TagPicker, DatePicker or TimePicker does not block the submit (it keeps `aria-required`). Add `noValidate` to a `<form>` that validates in its submit handler.
|
|
702
709
|
- **Validation states.** `validationMessage` renders below the control in the style of `validationState`: `error` (the default when a message is given) marks the control `aria-invalid` and announces the message (`role="alert"`); `warning` announces it without making the control invalid; `success` and `none` show it without announcing it. Each state but `none` shows an icon before the message; `validationMessageIcon` replaces it, and `null` removes it. `validationState="error"` without a message marks the control invalid and shows nothing. The Field root carries `data-validation-state`.
|
|
703
710
|
- **`error`** is the shorthand for an error message: `error="Enter an email address"`, or `error={true}` to mark the control invalid without a message. It wins over `validationMessage` and `validationState` (a development warning fires when both are set).
|
|
704
711
|
- **The hint** stays visible below the message, after it; the control is described by the message, then the hint.
|
|
@@ -728,7 +735,7 @@ export function AccountSettings({ backedUp }: { backedUp: number }) {
|
|
|
728
735
|
|
|
729
736
|
- One control per Field. Put wrapper or layout components (a `Tooltip`, your own row) **inside a plain `<div>`** in the Field; the library control inside is then labelled correctly.
|
|
730
737
|
- `error` on `Input`, `Select` and `Textarea`: a string renders the message after the control in a `role="alert"` element linked with `aria-describedby`/`aria-errormessage`; `error={true}` only marks the control invalid. Inside a Field that shows its own error, the message is not repeated.
|
|
731
|
-
- Value controls take part in native forms when you pass `name`, or when they are required (their own `required`, or a required `Field`): `Checkbox`, `Switch`, `RadioGroup`, `Rating`, `SpinButton`, `SwatchPicker`, `ColorPicker`, `ColorArea`, `ColorSlider` and `AlphaSlider` (the hex of the color; inside a ColorPicker they are bound and the picker submits), `Combobox`, `Dropdown
|
|
738
|
+
- Value controls take part in native forms when you pass `name`, or when they are required (their own `required`, or a required `Field`): `Checkbox`, `Switch`, `RadioGroup`, `Rating`, `SpinButton`, `SwatchPicker`, `ColorPicker`, `ColorArea`, `ColorSlider` and `AlphaSlider` (the hex of the color; inside a ColorPicker they are bound and the picker submits), `Combobox`, `Dropdown` and `Listbox` (one input per value with `multiselect`), `TagPicker`, `DatePicker` and `Calendar` (ISO `yyyy-mm-dd`) and `TimePicker` (`HH:mm`) render a hidden input, honour `required` and `form`, and reset with their form. `SearchBox` submits through its own `<input name>` and resets with its form too. No name is generated for you.
|
|
732
739
|
|
|
733
740
|
### Sizes and appearances
|
|
734
741
|
|
|
@@ -835,15 +842,17 @@ export function Reviews() {
|
|
|
835
842
|
|
|
836
843
|
`onChange` is the native DOM change event (`Input`, `Textarea`, `Select`, `Slider`). State callbacks are named after the state and receive the value:
|
|
837
844
|
|
|
838
|
-
| State
|
|
839
|
-
|
|
|
840
|
-
| single value
|
|
841
|
-
|
|
|
842
|
-
|
|
|
843
|
-
| checked
|
|
844
|
-
|
|
|
845
|
+
| State | Props |
|
|
846
|
+
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
847
|
+
| single value | `value` / `defaultValue` / `onValueChange(value)` |
|
|
848
|
+
| several values (`multiselect` Dropdown, Combobox, Listbox) | `value` / `defaultValue` / `onValueChange(values)` |
|
|
849
|
+
| typed text (Combobox, TagPicker) | `query` / `defaultQuery` / `onQueryChange(query)` |
|
|
850
|
+
| checked | `checked` / `defaultChecked` / `onCheckedChange(checked)` |
|
|
851
|
+
| open | `open` / `defaultOpen` / `onOpenChange(open)` |
|
|
852
|
+
| checked values (Menu, Toolbar) | `checkedValues` / `defaultCheckedValues` / `onCheckedValuesChange(checkedValues, details)` |
|
|
853
|
+
| checked items (Tree) | `checkedItems` / `defaultCheckedItems` / `onCheckedItemsChange(checkedItems, details)` |
|
|
845
854
|
|
|
846
|
-
Extra data goes in a second, optional `details` argument: Dialog and Drawer call `onOpenChange(open, details)` with `details.reason` and `details.event` (see [Dialogs and triggers](#dialogs-and-triggers)), Menu and Toolbar call `onCheckedValuesChange(checkedValues, details)` with `details.name`, `details.checkedItems` (the group's new values) and `details.event` (see [Menus](#menus)),
|
|
855
|
+
Extra data goes in a second, optional `details` argument: Dialog and Drawer call `onOpenChange(open, details)` with `details.reason` and `details.event` (see [Dialogs and triggers](#dialogs-and-triggers)), Menu and Toolbar call `onCheckedValuesChange(checkedValues, details)` with `details.name`, `details.checkedItems` (the group's new values) and `details.event` (see [Menus](#menus)), Tree calls `onCheckedItemsChange(checkedItems, details)` with `details.value`, `details.checked` and `details.event` (see [Trees](#trees)), and Calendar calls `onValueChange(date, details)` with `details.range`, the selected days (see [Calendar](#calendar)). These fire only when the value changes. Event callbacks such as `onPageChange`, `onStepChange` and `Tree`'s `onItemSelect` fire on every activation, also when the current item is activated again. Array props that Wave only reads (`openItems`, `expandedItems`, `checkedItems`, `selectedItems`, `columns`, `options`, `items`, `steps`, …) accept readonly arrays such as `as const` values; the callbacks receive a new, mutable array. The 0.4 names still work as deprecated aliases that warn once in development; see the [CHANGELOG](CHANGELOG.md) for the full table.
|
|
847
856
|
|
|
848
857
|
### Composite controls
|
|
849
858
|
|
|
@@ -853,6 +862,213 @@ Checkbox, Switch, SearchBox, SpinButton, Combobox, Dropdown, TagPicker, DatePick
|
|
|
853
862
|
|
|
854
863
|
`ColorSlider`, `AlphaSlider` and `ColorPicker.HexField` route the same props to their `<input>` and take `controlRef`. `ColorArea` is a group around two inputs: the group keeps `aria-label`, `aria-labelledby` and `aria-describedby` (a Field's label names it), with `className`, `style`, `data-*` and `ref`; both inputs get `aria-invalid` and `aria-errormessage`; `autoFocus` and `controlRef` reach the saturation input; `tabIndex` reaches the input that holds the Tab stop.
|
|
855
864
|
|
|
865
|
+
`Listbox` has no wrapper: every prop, `ref` and handler go to its `<ul role="listbox">`, which has focus. `Calendar` keeps them on its `role="group"` root, which the Field's label names. A swatch (`ColorSwatch`, `ImageSwatch`, `EmptySwatch`) puts its `ref`, `className`, `style`, name and other props on its own `<button>` in every layout (in the grid layout, a `role="gridcell"` element wraps it), so a `Tooltip` around a swatch reaches the element that has focus.
|
|
866
|
+
|
|
867
|
+
### Multi-select pickers
|
|
868
|
+
|
|
869
|
+
```tsx
|
|
870
|
+
import * as React from 'react';
|
|
871
|
+
import { Combobox, Dropdown, Field } from '@mortenbrudvik/waveui';
|
|
872
|
+
|
|
873
|
+
export function PizzaForm() {
|
|
874
|
+
const [toppings, setToppings] = React.useState<string[]>(['cheese']);
|
|
875
|
+
return (
|
|
876
|
+
<form>
|
|
877
|
+
<Field label="Toppings">
|
|
878
|
+
<Dropdown
|
|
879
|
+
multiselect
|
|
880
|
+
value={toppings}
|
|
881
|
+
onValueChange={setToppings}
|
|
882
|
+
name="toppings"
|
|
883
|
+
clearable
|
|
884
|
+
>
|
|
885
|
+
<Dropdown.Option value="cheese">Cheese</Dropdown.Option>
|
|
886
|
+
<Dropdown.Option value="olives">Olives</Dropdown.Option>
|
|
887
|
+
<Dropdown.Option value="peppers">Peppers</Dropdown.Option>
|
|
888
|
+
</Dropdown>
|
|
889
|
+
</Field>
|
|
890
|
+
<Field label="Deliver to" required>
|
|
891
|
+
<Combobox multiselect defaultValue={['oslo']} name="cities">
|
|
892
|
+
<Combobox.Option value="oslo">Oslo</Combobox.Option>
|
|
893
|
+
<Combobox.Option value="bergen">Bergen</Combobox.Option>
|
|
894
|
+
<Combobox.Option value="trondheim">Trondheim</Combobox.Option>
|
|
895
|
+
</Combobox>
|
|
896
|
+
</Field>
|
|
897
|
+
</form>
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
- **Values.** `multiselect` makes `value`, `defaultValue` and `onValueChange` arrays, in selection order: checking an option appends its value, unchecking removes it. Write `multiselect` as a literal: `multiselect={flag}` with a `boolean` variable is a type error, since the two modes take different value types; render two elements instead. `React.ComponentProps<typeof Dropdown>` is still the single-select `DropdownProps`, and `DropdownProps<true>` (`ComboboxProps<true>`, `ListboxProps<true>`) is the multi-select shape.
|
|
903
|
+
- **Toggling.** The listbox is `aria-multiselectable`, and every option draws a checkbox square with the check inside (`checkIcon` replaces the check). Enter, Space (Dropdown) and a click toggle an option and keep the list open; Tab, Escape and Alt+ArrowUp close it without committing.
|
|
904
|
+
- **What shows.** The Dropdown button and the Combobox input show the selected labels in selection order, joined with `", "` (`labels.selection`; values without an option are left out), in place of the placeholder. Dropdown's `renderValue(values)` replaces that content.
|
|
905
|
+
- **Typing in a multi-select Combobox.** Focusing the input selects the labels, so typing, pasting or an IME composition replaces them with a query that filters the options; a toggle, a close or a blur clears the query and shows the labels again, selected while the input has focus. Backspace and Delete only edit the text: the options, the clear button and a form reset change the value. `freeform` is not available with `multiselect`.
|
|
906
|
+
- **Announcements.** Each toggle the user makes is announced through a polite live region ("Olives added, 2 selected", from `labels.added` and `labels.removed`); a controlled change, a clear or a form reset is not. A single-select picker adds no announcer regions to `document.body`.
|
|
907
|
+
- **Forms.** `clearable` clears every value. A form submits one entry per value (read them with `FormData.getAll(name)`), `required` needs one value, and a form reset restores `defaultValue`, compared by content, so restoring the same values reports nothing. The deprecated `onOptionSelect` fires on every toggle.
|
|
908
|
+
- **For both modes.** `onActiveOptionChange(value)` (Dropdown, Combobox, Listbox) reports the option `aria-activedescendant` points at, after it changes (opening, the keys, typeahead, the pointer, a filter change), and `null` when the list closes. `disabledOptionsFocusable` keeps disabled options in the keys (the arrow keys, Home/End, PageUp/PageDown and typeahead; in a Combobox the arrow keys), but they are never selected: Enter, Space and a click do nothing on them and the list stays open. Dropdown's `expandIcon` replaces the chevron as Combobox's does (`false` hides it), and `renderValue(value)` renders the button's content while a value is selected; keep it non-interactive, since its text is the combobox's value.
|
|
909
|
+
|
|
910
|
+
### Filtering and async search
|
|
911
|
+
|
|
912
|
+
Combobox and TagPicker take a `filter` of yours and a controllable query. A search on a server keeps every option the server returns (`filter={() => true}`) and searches as the query changes:
|
|
913
|
+
|
|
914
|
+
```tsx
|
|
915
|
+
import * as React from 'react';
|
|
916
|
+
import { Combobox } from '@mortenbrudvik/waveui';
|
|
917
|
+
|
|
918
|
+
export function CountrySearch({ search }: { search: (query: string) => Promise<string[]> }) {
|
|
919
|
+
const [value, setValue] = React.useState('');
|
|
920
|
+
const [query, setQuery] = React.useState('');
|
|
921
|
+
const [results, setResults] = React.useState<string[]>([]);
|
|
922
|
+
React.useEffect(() => {
|
|
923
|
+
let current = true;
|
|
924
|
+
const timer = setTimeout(() => {
|
|
925
|
+
void search(query).then((names) => {
|
|
926
|
+
if (current) setResults(names);
|
|
927
|
+
});
|
|
928
|
+
}, 300);
|
|
929
|
+
return () => {
|
|
930
|
+
current = false;
|
|
931
|
+
clearTimeout(timer);
|
|
932
|
+
};
|
|
933
|
+
}, [query, search]);
|
|
934
|
+
return (
|
|
935
|
+
<Combobox
|
|
936
|
+
aria-label="Country"
|
|
937
|
+
value={value}
|
|
938
|
+
onValueChange={setValue}
|
|
939
|
+
query={query}
|
|
940
|
+
onQueryChange={setQuery}
|
|
941
|
+
filter={() => true}
|
|
942
|
+
>
|
|
943
|
+
{results.map((name) => (
|
|
944
|
+
<Combobox.Option key={name} value={name}>
|
|
945
|
+
{name}
|
|
946
|
+
</Combobox.Option>
|
|
947
|
+
))}
|
|
948
|
+
{/* The selected option stays, hidden, while the results leave it out: its label shows. */}
|
|
949
|
+
{value !== '' && !results.includes(value) && (
|
|
950
|
+
<Combobox.Option key={value} value={value} hidden>
|
|
951
|
+
{value}
|
|
952
|
+
</Combobox.Option>
|
|
953
|
+
)}
|
|
954
|
+
</Combobox>
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
- **`filter(option, query)`** decides which options match the typed text. It is called whenever the text is not empty, with every option that is not hidden, as a `ListboxItem` (`value`, `label`, `textValue`). The default is a case-insensitive substring of `textValue`, else `label`. A filter may keep options that do not contain the text: one that always keeps an option of yours gives a "Create …" entry. A freeform Combobox filters by its text too.
|
|
960
|
+
- **The query** (`query`, `defaultQuery`, `onQueryChange(query)`) is the text typed since the last commit. Combobox resets it to `''` on a commit, a close, a blur, Escape, the clear button and a form reset; TagPicker when a tag is added, on Escape with the list closed and on a form reset (a blur or a close keeps it). Turning on `disabled` or `readOnly` while text is typed reports `''`; a picker that mounts locked keeps a controlled query until it unlocks. A freeform Combobox's text is its value: it never calls `onQueryChange`, and a `query` or `defaultQuery` prop warns in development.
|
|
961
|
+
- The typing model is unchanged: typing never clears a Combobox selection, and a blur selects nothing. The `CustomFilter` and `AsyncSearch` stories of Combobox show both props.
|
|
962
|
+
|
|
963
|
+
### Listbox and custom pickers
|
|
964
|
+
|
|
965
|
+
`Listbox` is the option list of the pickers placed in the page: a `<ul role="listbox">` that holds focus itself and points `aria-activedescendant` at the active option.
|
|
966
|
+
|
|
967
|
+
```tsx
|
|
968
|
+
import { Field, Listbox } from '@mortenbrudvik/waveui';
|
|
969
|
+
|
|
970
|
+
export function FruitList() {
|
|
971
|
+
return (
|
|
972
|
+
<Field label="Fruit" required>
|
|
973
|
+
<Listbox name="fruit" defaultValue="banana" className="max-h-48">
|
|
974
|
+
<Listbox.OptionGroup label="Common">
|
|
975
|
+
<Listbox.Option value="apple">Apple</Listbox.Option>
|
|
976
|
+
<Listbox.Option value="banana">Banana</Listbox.Option>
|
|
977
|
+
</Listbox.OptionGroup>
|
|
978
|
+
<Listbox.Option value="durian" disabled>
|
|
979
|
+
Durian
|
|
980
|
+
</Listbox.Option>
|
|
981
|
+
</Listbox>
|
|
982
|
+
</Field>
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
- **Listbox or List.** Use `Listbox` for a choice among options (the option model of Dropdown and Combobox: typeahead, groups, disabled options, forms). Use `List` with `selectable` when each item needs real focus, actions of its own or interactive content.
|
|
988
|
+
- **Focus and selection.** One Tab stop. The active option exists only while the list has focus: the selected option (with `multiselect`, the first selected one in list order), else the first; while no option can be active, the list draws its own focus ring. Space and Enter select, so the selection does not follow the active option; a single-select Listbox never deselects. A press on an option focuses the list and makes that option active without scrolling, so the click selects it even while the selected option is scrolled out of view. Tab, Escape and the Alt+arrow keys are left to the page: an enclosing Dialog closes on Escape.
|
|
989
|
+
- **Props.** `multiselect` (an array value, announced toggles; see [Multi-select pickers](#multi-select-pickers)), `onActiveOptionChange` (`null` when focus leaves the list), `disabled` (out of the Tab order, selects and submits nothing), `disabledOptionsFocusable`, `autoFocus`, `name`, `form` and `required`, and Field wiring. It has no built-in height: give it one through `className` and it scrolls.
|
|
990
|
+
- **Option groups.** `OptionGroup`'s `label` is optional and takes any text content (an icon next to the text is fine). Without one no heading renders, and `aria-label` or `aria-labelledby` names the group; these two always land on the `role="group"` list. A group without a name warns in development.
|
|
991
|
+
|
|
992
|
+
**Pickers of your own.** The pieces under Dropdown, Combobox and TagPicker are public:
|
|
993
|
+
|
|
994
|
+
- `useListbox(options)` holds the listbox: its keys, typeahead, filtering, the active option and the registration of the options. `mode` is `'select-only'` (a button combobox, as Dropdown), `'editable'` (an `<input>` combobox, as Combobox) or `'standalone'` (a list that holds focus, as Listbox). You own the open state (`open`, `onOpenChange(open, details)`) and the value (`selectedValues`, `onSelect(value, details)`, with `details.item` and `details.event`); spread `getComboboxProps()` on your combobox and attach `onKeyDown` and `onKeyUp`.
|
|
995
|
+
- `useListboxPopup(options)` dismisses the popup (Escape, a press outside, focus leaving: `DismissReason`) and positions it, and `ListboxSurface` renders the list: in a portaled surface while it is shown, `hidden` in place otherwise, so the options register from the first render. `listClassName` and `surfaceClassName` are merged last (`max-h-*` replaces the list's height). Two duties remain yours: set your combobox's `aria-expanded` from `expanded` after spreading `getComboboxProps()`, and while the listbox is open with nothing to show, close it on Escape yourself (outside an IME composition) without calling `listbox.onKeyDown`, so the Escape reaches an enclosing layer.
|
|
996
|
+
- `ListboxProvider` provides the listbox's context to its options when you render the list yourself instead of through `ListboxSurface`.
|
|
997
|
+
- Options are `Option` and `OptionGroup`, or components of your own on `useListboxOption`, marked with `markListboxElement` so that `collectOptionLabels(children)` knows their labels before they register (in the server HTML and the first render).
|
|
998
|
+
|
|
999
|
+
```tsx
|
|
1000
|
+
import * as React from 'react';
|
|
1001
|
+
import {
|
|
1002
|
+
ListboxSurface,
|
|
1003
|
+
Option,
|
|
1004
|
+
collectOptionLabels,
|
|
1005
|
+
useListbox,
|
|
1006
|
+
useListboxPopup,
|
|
1007
|
+
useMergedRefs,
|
|
1008
|
+
} from '@mortenbrudvik/waveui';
|
|
1009
|
+
|
|
1010
|
+
const SIZES = (
|
|
1011
|
+
<>
|
|
1012
|
+
<Option value="s">Small</Option>
|
|
1013
|
+
<Option value="m">Medium</Option>
|
|
1014
|
+
<Option value="l">Large</Option>
|
|
1015
|
+
</>
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
export function SizePicker() {
|
|
1019
|
+
const [open, setOpen] = React.useState(false);
|
|
1020
|
+
const [value, setValue] = React.useState('m');
|
|
1021
|
+
const rootRef = React.useRef<HTMLDivElement>(null);
|
|
1022
|
+
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
|
1023
|
+
const listbox = useListbox({
|
|
1024
|
+
open,
|
|
1025
|
+
onOpenChange: setOpen,
|
|
1026
|
+
mode: 'select-only',
|
|
1027
|
+
selectedValues: [value],
|
|
1028
|
+
// A single-select commit also closes the list, through onOpenChange.
|
|
1029
|
+
onSelect: setValue,
|
|
1030
|
+
});
|
|
1031
|
+
const expanded = open && listbox.items.length > 0;
|
|
1032
|
+
const { layerId, setReference, surfaceRef, floatingProps } = useListboxPopup({
|
|
1033
|
+
open,
|
|
1034
|
+
surfaceOpen: expanded,
|
|
1035
|
+
onDismiss: () => setOpen(false),
|
|
1036
|
+
rootRef,
|
|
1037
|
+
anchorRef: buttonRef,
|
|
1038
|
+
});
|
|
1039
|
+
const buttonMergedRef = useMergedRefs(buttonRef, setReference);
|
|
1040
|
+
return (
|
|
1041
|
+
<div ref={rootRef}>
|
|
1042
|
+
<button
|
|
1043
|
+
type="button"
|
|
1044
|
+
aria-label="Size"
|
|
1045
|
+
{...listbox.getComboboxProps()}
|
|
1046
|
+
aria-expanded={expanded}
|
|
1047
|
+
ref={buttonMergedRef}
|
|
1048
|
+
onClick={() => setOpen(!open)}
|
|
1049
|
+
onKeyDown={listbox.onKeyDown}
|
|
1050
|
+
onKeyUp={listbox.onKeyUp}
|
|
1051
|
+
>
|
|
1052
|
+
{listbox.getItem(value)?.label ?? collectOptionLabels(SIZES).get(value)}
|
|
1053
|
+
</button>
|
|
1054
|
+
<ListboxSurface
|
|
1055
|
+
listbox={listbox}
|
|
1056
|
+
layerId={layerId}
|
|
1057
|
+
surfaceRef={surfaceRef}
|
|
1058
|
+
floatingProps={floatingProps}
|
|
1059
|
+
open={open}
|
|
1060
|
+
expanded={expanded}
|
|
1061
|
+
aria-label="Sizes"
|
|
1062
|
+
>
|
|
1063
|
+
{SIZES}
|
|
1064
|
+
</ListboxSurface>
|
|
1065
|
+
</div>
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
The `CustomPicker` story in `stories/Listbox.stories.tsx` is an editable font picker built only on these exports, and `CommandPalette` uses `useActiveDescendant` alone, for a composite that is not a listbox. The guide's [Building a custom picker](docs/WAVE-UI-GUIDE.md#pattern-12-building-a-custom-picker) adds option components of your own, a list rendered with `ListboxProvider`, and [active-descendant widgets](docs/WAVE-UI-GUIDE.md#pattern-13-active-descendant-widgets).
|
|
1071
|
+
|
|
856
1072
|
### Color
|
|
857
1073
|
|
|
858
1074
|
```tsx
|
|
@@ -881,6 +1097,83 @@ export function AccentPicker() {
|
|
|
881
1097
|
- **Assistive technology.** The area is a group of two sliders, "Saturation" and "Brightness": desktop screen readers list one 2-D slider, and touch screen readers can swipe each. Each value text starts with its own channel and names the color ("Saturation 72%, Brightness 74%, dark blue"); a hue slider says "210°, blue". `labels.colorName(color)` replaces the English color names (return `''` to leave them out), and `labels.valueText(color, channel)` a whole value text, for languages whose word order differs.
|
|
882
1098
|
- `ColorSlider` takes `channel` (`'hue'`, `'saturation'` or `'value'`) and `orientation` (`'vertical'` draws the rail bottom to top); `AlphaSlider` shows opacity, or with `transparency` transparency (the value's `a` is still the opacity). The roots carry `data-orientation`, `data-channel` (ColorSlider) and, while disabled, `data-disabled`. `shape="square"` removes the corner radius. Inside a `Field`, a standalone slider is named by the Field's label, else by its `labels` ("Hue", "Saturation", "Brightness").
|
|
883
1099
|
|
|
1100
|
+
### Swatches
|
|
1101
|
+
|
|
1102
|
+
```tsx
|
|
1103
|
+
import {
|
|
1104
|
+
ColorSwatch,
|
|
1105
|
+
EmptySwatch,
|
|
1106
|
+
ImageSwatch,
|
|
1107
|
+
SwatchPicker,
|
|
1108
|
+
Tooltip,
|
|
1109
|
+
} from '@mortenbrudvik/waveui';
|
|
1110
|
+
|
|
1111
|
+
export function AccentSwatches({ onAddColor }: { onAddColor: () => void }) {
|
|
1112
|
+
return (
|
|
1113
|
+
<SwatchPicker layout="grid" aria-label="Accent" name="accent" defaultValue="red">
|
|
1114
|
+
<SwatchPicker.Row>
|
|
1115
|
+
<Tooltip content="Red" relationship="label">
|
|
1116
|
+
<ColorSwatch value="red" color="#d13438" />
|
|
1117
|
+
</Tooltip>
|
|
1118
|
+
<ColorSwatch value="blue" color="#0f6cbd" aria-label="Blue" />
|
|
1119
|
+
<ColorSwatch value="sand" color="#faf9f8" borderColor="#8a8886" aria-label="Sand" />
|
|
1120
|
+
</SwatchPicker.Row>
|
|
1121
|
+
<SwatchPicker.Row>
|
|
1122
|
+
<ImageSwatch value="dots" src="/patterns/dots.png" aria-label="Dots" />
|
|
1123
|
+
<ColorSwatch value="green" color="#107c10" aria-label="Green" disabled />
|
|
1124
|
+
<EmptySwatch onClick={onAddColor} />
|
|
1125
|
+
</SwatchPicker.Row>
|
|
1126
|
+
</SwatchPicker>
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
- **Swatches as children.** `ColorSwatch`, `ImageSwatch` and `EmptySwatch` read the picker's context (one outside a SwatchPicker throws in development), so a swatch can sit in a `Tooltip`: its `ref`, `className`, `style`, name and other props go to its own `<button>` in every layout, and a Tooltip with `relationship="label"` names it. `items` is optional and renders `ColorSwatch`es before the children. Swatch values are unique (a duplicate warns in development).
|
|
1132
|
+
- **Kinds.** A `ColorSwatch` (`value`, `color`) is named by `aria-label` or a Tooltip, else by its color value (with a development warning); `icon` is a decorative glyph shown while it is not selected, and `borderColor` sets its border for a color close to the page background (forced colors replace it). An `ImageSwatch` (`value`, `src`) draws a pattern or texture as a covering background. `disabled` is native (out of the keys and the Tab order, never selected): the swatch is dimmed and crossed by a diagonal slash, so the state does not rely on its color, and a disabled swatch that is selected keeps its selected look. An `EmptySwatch` is an "add" button with no value, named "Add color" by default (`aria-label`), with a plus `icon` (`false` hides it) and your `onClick`. Every swatch takes `size` and `shape`, which override the picker's.
|
|
1133
|
+
- **Row layout** (the default): a `role="radiogroup"` of `role="radio"` buttons whose arrow keys select and wrap, as in 0.9. An `EmptySwatch` there is a plain button with its own Tab stop, outside the arrow keys.
|
|
1134
|
+
- **Grid layout** (`layout="grid"`): a `role="grid"` of `SwatchPicker.Row` rows (flat `SwatchPickerRow`), each swatch button inside a `role="gridcell"`. `items` with the children that follow them up to a row, and every other run of children that are not rows, form implicit rows, so a Row must be a direct child of the picker or inside a Fragment (one inside a component of yours lands in an implicit row). Rows do not wrap, so size them yourself. The arrow keys move focus only, in two dimensions; Space, Enter and a click select. The selected swatch's cell is `aria-selected` and its button `aria-pressed`. An `EmptySwatch` is a cell in the arrow order. Changing `layout` on a mounted picker remounts the swatches.
|
|
1135
|
+
- **Tab mode** (`focusMode="tab"`, in either layout): every enabled swatch is its own Tab stop and the arrow keys do nothing, so the picker is a `role="group"` of toggle buttons (`aria-pressed`); rows have no role. Pressing the selected swatch keeps it selected. Fluent keeps the radio and grid roles there.
|
|
1136
|
+
- **Sizes.** `size` is `'extra-small'` (20px), `'small'` (24px), `'medium'` (32px, the default) or `'large'` (40px), and `spacing` is `'small'` (4px) or `'medium'` (8px, the default), between swatches and between rows; a 20px swatch meets WCAG 2.5.8 through its spacing. `shape` is `'circular'` by default.
|
|
1137
|
+
- **Required.** The row layout carries `aria-required`. A grid or a group cannot, so the grid layout and tab mode say "Required" in the picker's description (`labels.required`).
|
|
1138
|
+
- The root carries `data-layout`, `data-focus-mode`, `data-size`, `data-shape` and `data-spacing`. `ColorPicker.Swatches` takes the same props, so a ColorPicker's presets can use the grid layout or end in an `EmptySwatch`.
|
|
1139
|
+
|
|
1140
|
+
### Calendar
|
|
1141
|
+
|
|
1142
|
+
```tsx
|
|
1143
|
+
import * as React from 'react';
|
|
1144
|
+
import { Calendar, Field } from '@mortenbrudvik/waveui';
|
|
1145
|
+
|
|
1146
|
+
export function SprintWeek({ today }: { today: Date }) {
|
|
1147
|
+
const [days, setDays] = React.useState<Date[]>([]);
|
|
1148
|
+
return (
|
|
1149
|
+
<Field label="Sprint week" hint={`${days.length} days selected`} required>
|
|
1150
|
+
<Calendar
|
|
1151
|
+
name="sprint"
|
|
1152
|
+
locale="en-GB"
|
|
1153
|
+
today={today}
|
|
1154
|
+
firstDayOfWeek={1}
|
|
1155
|
+
showWeekNumbers
|
|
1156
|
+
firstWeekOfYear="first-four-day-week"
|
|
1157
|
+
selectionRange="work-week"
|
|
1158
|
+
showGoToToday
|
|
1159
|
+
markedDates={(date) => date.getDay() === 5}
|
|
1160
|
+
onValueChange={(_date, details) => setDays(details?.range ?? [])}
|
|
1161
|
+
/>
|
|
1162
|
+
</Field>
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
```
|
|
1166
|
+
|
|
1167
|
+
- **Value.** `value` and `defaultValue` take a `Date` or `null`; `onValueChange(date, details)` fires only when the day changes, with local midnight, and `details.range` lists the selected days (Fluent's `onSelectDate` fires on every pick). Picking the selected day again does nothing. A controlled value in another month shows that month. `minDate`, `maxDate`, `disabledDates`, `firstDayOfWeek` and `locale` work as in DatePicker.
|
|
1168
|
+
- **Month and year views** (`showMonthPicker`, on by default): the month text is a button, named by that text and described by `labels.chooseMonth`, that shows the 12 months of the year; their heading, the year, is a button to the year view (`labels.chooseYear`): a decade with the year before and after it. The header buttons move by a month, a year or a decade. A picked year shows its months and a picked month its days (the focused day keeps its day of the month, clamped), and Escape goes back a view to the period shown before. Every view change moves focus into the new grid. Months and years wholly outside `minDate`/`maxDate` stay focusable and `aria-disabled`. `showMonthPicker={false}` keeps a static month text.
|
|
1169
|
+
- **Week numbers.** `showWeekNumbers` adds a leading column of row headers ("Week 23", `labels.weekNumber`), which the keys skip. `firstWeekOfYear` is `'first-day'` (the default), `'first-full-week'` or `'first-four-day-week'`; ISO 8601 weeks are `'first-four-day-week'` with `firstDayOfWeek={1}`.
|
|
1170
|
+
- **Ranges** (Calendar only). `selectionRange` is `'day'` (the default), `'week'`, `'work-week'` or `'month'`, and `workWeekDays` the days of a work week (Monday to Friday by default). A pick selects the picked day's week, work week or month without its unavailable days: the value is the picked day, `details.range` the selected days, and every selected day is `aria-selected`, `aria-pressed` and `data-selected`.
|
|
1171
|
+
- **Marked days and custom content.** `markedDates(date)` draws a dot under the number, sets `data-marked` and adds "marked" to the day's name (`labels.markedDay`). `renderDay(date, defaultContent)` replaces the content of a day button (non-interactive content only), and `dayLabel(date, defaultLabel)` sets its name, since the content does not.
|
|
1172
|
+
- **Footer.** `showGoToToday` adds a button that shows today's month and focuses today without selecting it (disabled while today is outside `minDate`/`maxDate`). DatePicker's `showCloseButton` adds a Close button that closes the calendar as Escape does. Both are off by default.
|
|
1173
|
+
- **Server rendering and `today`.** `today` (the current date by default) is the day marked `aria-current="date"`. Without it, the server HTML marks no day and the browser marks today once hydrated. The month shown first is the value's, else `initialVisibleDate`'s, else today's, clamped into `minDate`/`maxDate`: on the server, pass `locale` and one of `today`, `initialVisibleDate` or a value, since without them the month comes from the server's clock (a development warning says so).
|
|
1174
|
+
- **Forms and Field.** `name`, `form` and `required` add a hidden input (ISO `yyyy-mm-dd`) that resets with its form to `defaultValue`; when the form reports a required Calendar empty, its tab stop takes focus. Inside a `Field` the `role="group"` root takes the label, the description and `aria-invalid`, and says "Required" in its description (`labels.required`), since a group cannot carry `aria-required`. The month text is a `<div>` that labels the grid (DatePicker's dialog keeps its `<h2>`), since a component cannot know the page's heading levels.
|
|
1175
|
+
- **DatePicker** shows the same calendar in its dialog, with the props above except `selectionRange` and `workWeekDays` (its input holds one date). The date helpers it uses are public: see [Hooks and utilities](#hooks-and-utilities).
|
|
1176
|
+
|
|
884
1177
|
### Menus
|
|
885
1178
|
|
|
886
1179
|
```tsx
|
|
@@ -1519,103 +1812,131 @@ Icon slots are decorative (`aria-hidden`). Content that renders nothing (`''`, `
|
|
|
1519
1812
|
|
|
1520
1813
|
Slots that replace a built-in glyph follow one rule per kind:
|
|
1521
1814
|
|
|
1522
|
-
- **Optional indicators** (`MenuButton.menuIcon`, the `expandIcon` of Combobox, TimePicker and `Tree.Item`): `null` or `undefined` keep the default glyph; `false` or any value that renders nothing hides it (for Combobox and TimePicker, the whole expand button).
|
|
1523
|
-
- **Required indicators** (`SplitButton.menuIcon`): the indicator always shows. A value that renders nothing keeps the default glyph, with a development warning.
|
|
1815
|
+
- **Optional indicators** (`MenuButton.menuIcon`, the `expandIcon` of Combobox, Dropdown, TimePicker and `Tree.Item`, `EmptySwatch.icon`): `null` or `undefined` keep the default glyph; `false` or any value that renders nothing hides it (for Combobox and TimePicker, the whole expand button; for Dropdown, the chevron and its room).
|
|
1816
|
+
- **Required indicators** (`SplitButton.menuIcon`, `Option.checkIcon`): the indicator always shows. A value that renders nothing keeps the default glyph, with a development warning. The check is the sign of an option's selection that does not rely on color, so Fluent's `checkIcon={null}`, which removes it, keeps it here.
|
|
1524
1817
|
- **Status icons** (`MessageBar.icon`, `Toast.icon`, Field `validationMessageIcon`): only `undefined` keeps the default icon; `null` or a value that renders nothing shows none.
|
|
1525
|
-
- A glyph slot inside a built-in button (`expandIcon`,
|
|
1818
|
+
- A glyph slot inside a built-in button (`expandIcon`, the `menuIcon` of SplitButton and MenuButton, and the `icon` of `ColorSwatch` and `EmptySwatch`) is decorative content. A `<button>` or `Button` passed there, or a slot object whose `as` is one, is not nested: its children become the glyph and its props are dropped, with a development warning.
|
|
1526
1819
|
|
|
1527
1820
|
### Built-in text
|
|
1528
1821
|
|
|
1529
1822
|
The names and hidden texts that components render themselves are English by default. These props translate them:
|
|
1530
1823
|
|
|
1531
|
-
| Component | Props
|
|
1532
|
-
| --------------------------- |
|
|
1533
|
-
| `Dialog.Content`, `Drawer` | `closeLabel` (the Close button)
|
|
1534
|
-
| `TeachingPopover` | `closeLabel` (the Close button), `labels` (`back`, `next`, `done`, `step(index, count)` with a zero-based index)
|
|
1535
|
-
| `MessageBar`, `Toast` | `statusLabel` (the hidden severity text; also an option of both `dispatchToast` forms); `Toast` also `dismissLabel` (also an option of the text form)
|
|
1536
|
-
| `Tag` | `dismissLabel`
|
|
1537
|
-
| `Carousel` | `labels` (`previous`, `next`, `picker`, `slide(index, total)` with a zero-based index, `carouselRoleDescription`, `slideRoleDescription`), `autoPlayLabels`
|
|
1538
|
-
| `Stepper` | `statusLabels` (`completed`, `error`; `''` omits the text)
|
|
1539
|
-
| `Pagination` | `getItemAriaLabel(type, page, selected)`
|
|
1540
|
-
| `ColorPicker` | `labels` (the 0.7 strings, plus `area` (the name of a bound area's group, "Saturation and brightness"), `saturation`, `brightness`, `roleDescription`, `hue`, `transparency`, `colorName(color)` and `valueText(color, channel)` for bound parts)
|
|
1541
|
-
| `ColorArea` | `labels` (`saturation`, `brightness`, `roleDescription` ("2D slider"), `colorName(color)`, `valueText(color, channel)`)
|
|
1542
|
-
| `ColorSlider` | `labels` (`hue`, `saturation`, `brightness` (the `value` channel), `colorName(color)`, `valueText(color, channel)`)
|
|
1543
|
-
| `AlphaSlider` | `labels` (`opacity`, `transparency`, `valueText(color, transparency)`)
|
|
1544
|
-
| `Combobox` | `labels` (`noMatches`, `clear` (the clear button), `expand` (the chevron button))
|
|
1545
|
-
| `Dropdown` | `labels` (`clear
|
|
1546
|
-
| `
|
|
1547
|
-
| `
|
|
1548
|
-
| `
|
|
1549
|
-
| `
|
|
1550
|
-
| `
|
|
1551
|
-
| `
|
|
1552
|
-
| `
|
|
1553
|
-
| `
|
|
1554
|
-
| `
|
|
1555
|
-
| `
|
|
1556
|
-
| `
|
|
1824
|
+
| Component | Props |
|
|
1825
|
+
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1826
|
+
| `Dialog.Content`, `Drawer` | `closeLabel` (the Close button) |
|
|
1827
|
+
| `TeachingPopover` | `closeLabel` (the Close button), `labels` (`back`, `next`, `done`, `step(index, count)` with a zero-based index) |
|
|
1828
|
+
| `MessageBar`, `Toast` | `statusLabel` (the hidden severity text; also an option of both `dispatchToast` forms); `Toast` also `dismissLabel` (also an option of the text form) |
|
|
1829
|
+
| `Tag` | `dismissLabel` |
|
|
1830
|
+
| `Carousel` | `labels` (`previous`, `next`, `picker`, `slide(index, total)` with a zero-based index, `carouselRoleDescription`, `slideRoleDescription`), `autoPlayLabels` |
|
|
1831
|
+
| `Stepper` | `statusLabels` (`completed`, `error`; `''` omits the text) |
|
|
1832
|
+
| `Pagination` | `getItemAriaLabel(type, page, selected)` |
|
|
1833
|
+
| `ColorPicker` | `labels` (the 0.7 strings, plus `area` (the name of a bound area's group, "Saturation and brightness"), `saturation`, `brightness`, `roleDescription`, `hue`, `transparency`, `colorName(color)` and `valueText(color, channel)` for bound parts) |
|
|
1834
|
+
| `ColorArea` | `labels` (`saturation`, `brightness`, `roleDescription` ("2D slider"), `colorName(color)`, `valueText(color, channel)`) |
|
|
1835
|
+
| `ColorSlider` | `labels` (`hue`, `saturation`, `brightness` (the `value` channel), `colorName(color)`, `valueText(color, channel)`) |
|
|
1836
|
+
| `AlphaSlider` | `labels` (`opacity`, `transparency`, `valueText(color, transparency)`) |
|
|
1837
|
+
| `Combobox` | `labels` (`noMatches`, `clear` (the clear button), `expand` (the chevron button), and with `multiselect` `selection(labels)` (the selected labels the input shows, joined with `", "`), `added(label, count)` and `removed(label, count)` (the toggle announcements, "Olives added, 2 selected", "Olives removed, 1 selected")) |
|
|
1838
|
+
| `Dropdown` | `labels` (`clear` (the clear button), and with `multiselect` `selection(labels)` (the button's text), `added(label, count)` and `removed(label, count)`, as in Combobox) |
|
|
1839
|
+
| `Listbox` | `labels` (`added(label, count)` and `removed(label, count)`, the `multiselect` toggle announcements, as in Combobox) |
|
|
1840
|
+
| `DatePicker` | `labels` (`clear`, `openCalendar`, `close` (the Close button, `showCloseButton`), the calendar's labels below (not `required`), and the error texts `invalidDate(pattern)`, `outOfRange(min, max)`, `unavailableDate`) |
|
|
1841
|
+
| `Calendar` | `labels` (the header buttons `previousMonth`, `nextMonth`, `previousYear`, `nextYear`, `previousDecade`, `nextDecade`; the heading button's hints `chooseMonth` ("Choose a month") and `chooseYear` ("Choose a year"); `goToToday`; the week-number column `week` ("Week") and `weekNumber(week)` ("Week 23"); `markedDay(dayLabel)` ("Monday, June 2, 2025, marked"); `required` ("Required", the hint of a required calendar)) |
|
|
1842
|
+
| `SwatchPicker` | `labels` (`required`: "Required", said in the description where the picker's role cannot carry `aria-required`, the grid layout and tab mode) |
|
|
1843
|
+
| `EmptySwatch` | `aria-label` ("Add color") |
|
|
1844
|
+
| `TimePicker` | `labels` (`clear`, `expand`, `list`, `noTimes`, `noMatches`, and the error texts `invalidTime(format)`, `outOfRange(min, max)`) |
|
|
1845
|
+
| `TagPicker` | `labels` (`remove(label)`, `selected`, `summary(labels)`, `added(label, count)`, `removed(label, count)`, `noMatches`) |
|
|
1846
|
+
| `Rating` | `labels` (`star(value, max)`; it also names the half values of a `step={0.5}` Rating, "2.5 stars") |
|
|
1847
|
+
| `RatingDisplay` | `labels` (`rating(value, max, formattedValue)`, `count(count, formattedCount)`); `locale` formats the value and the count (pass it when rendering on the server) |
|
|
1848
|
+
| `SpinButton` | `labels` (`increment`, `decrement`) |
|
|
1849
|
+
| `DataGrid` | `labels` (`selectAll`, `selectionHeader`); each row's fallback name "Select row": `DataGrid.Row` `selectionLabel` |
|
|
1850
|
+
| `AvatarGroup` | `overflowLabel`, `unnamedMemberLabel` |
|
|
1851
|
+
| `InfoButton` | `aria-label` (the button and its note; default "Information") |
|
|
1852
|
+
| `InfoLabel`, `SplitButton` | `infoButtonLabel` (InfoLabel's button is named "‹label› ‹infoButtonLabel›"), `menuButtonLabel` |
|
|
1853
|
+
| `Spinner`, `Skeleton.Group` | `label` |
|
|
1557
1854
|
|
|
1558
1855
|
Without a text prop, these defaults are replaced through `aria-label`: the PresenceBadge status names ("Available", …: `aria-label` on the badge), the MessageBar "Dismiss" and SearchBox "Clear search" button names (`dismiss={{ 'aria-label': 'Lukk' }}`, which keeps the default icon), and the names of the Breadcrumb, Pagination, Nav, Stepper, Carousel and Toaster regions ("Breadcrumb", "Pagination", "Navigation", "Progress", "Carousel", "Notifications"). The placeholders of Dropdown, DatePicker, TimePicker and SearchBox are `placeholder` props, and ColorPicker's default presets are replaced through `presets`.
|
|
1559
1856
|
|
|
1560
|
-
0.7 and 0.8 add no other built-in text: the submenu half of a `Menu.SplitGroup`, a context menu (`Menu.Popover`) and a `Toolbar.RadioGroup` are named by the `aria-label` you pass, a toast's container by its severity and title, and a tree row's actions toolbar by its item's label.
|
|
1857
|
+
0.7 and 0.8 add no other built-in text: the submenu half of a `Menu.SplitGroup`, a context menu (`Menu.Popover`) and a `Toolbar.RadioGroup` are named by the `aria-label` you pass, a toast's container by its severity and title, and a tree row's actions toolbar by its item's label. Nor does 0.10, beyond the rows above: a `Listbox`, a `Calendar` and a `ColorSwatch` or `ImageSwatch` are named by the `aria-label` you pass (or a `Field`'s label, or a `Tooltip` around the swatch), an `OptionGroup` by its label, and the month and year buttons by their text in `locale`.
|
|
1561
1858
|
|
|
1562
1859
|
## Keyboard support
|
|
1563
1860
|
|
|
1564
|
-
As implemented in 0.
|
|
1565
|
-
|
|
1566
|
-
| Component
|
|
1567
|
-
|
|
|
1568
|
-
| Toolbar
|
|
1569
|
-
| RadioGroup, SwatchPicker
|
|
1570
|
-
|
|
|
1571
|
-
|
|
|
1572
|
-
|
|
|
1573
|
-
|
|
|
1574
|
-
|
|
|
1575
|
-
|
|
|
1576
|
-
|
|
|
1577
|
-
|
|
|
1578
|
-
|
|
|
1579
|
-
|
|
|
1580
|
-
|
|
|
1581
|
-
|
|
|
1582
|
-
|
|
|
1583
|
-
|
|
|
1584
|
-
|
|
|
1585
|
-
|
|
|
1586
|
-
|
|
|
1587
|
-
|
|
|
1588
|
-
|
|
|
1589
|
-
|
|
|
1861
|
+
As implemented in 0.10. Buttons, links, checkboxes, switches and the trigger buttons of Accordion, Carousel, Nav, Breadcrumb and Pagination are native elements: Tab to reach them, Enter or Space to activate. A Link or `Button as="a"` without `href`, and a Button or Link rendered as a `div` or `span`, are buttons: Tab reaches them, Enter (key down) and Space (key up) activate them. A `disabledFocusable` control stays in the Tab order but Enter, Space and clicks do nothing.
|
|
1862
|
+
|
|
1863
|
+
| Component | Keys |
|
|
1864
|
+
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1865
|
+
| Toolbar | One Tab stop (the last focused control; focusing a nested composite, such as a radio group or tab list, or a text field, select, slider or spin button keeps it where it was). Left/Right (Up/Down when vertical) move and wrap, Home/End jump; mirrored in RTL; natively disabled controls and controls hidden with CSS are skipped, while `disabledFocusable` controls stay reachable (a Tooltip on them opens on focus). Text fields, selects, sliders, spin buttons and editable comboboxes keep their own arrow keys; a Dropdown keeps Up/Down, Home and End for its list, but Left/Right move past it. A nested composite keeps its own Tab stop and arrow keys; a `Toolbar.RadioGroup` is none: its radios are part of the arrow order, and Up/Down (Left/Right when vertical, mirrored in RTL) also move among them, wrapping inside the group. The arrows never press a toggle or check a radio: Space and Enter do. |
|
|
1866
|
+
| RadioGroup, SwatchPicker (row layout) | One Tab stop. All four arrows move and select (wrapping), Home/End jump; Left/Right mirrored in RTL. Keys pressed on a link inside a radio's label stay the link's. An `EmptySwatch` in the row layout is a Tab stop of its own, and the arrow keys pressed on it do nothing. |
|
|
1867
|
+
| SwatchPicker (`layout="grid"`) | One Tab stop: the last focused swatch, at first the selected one, else the first. The arrows move focus only: Left/Right through the swatches in reading order across the rows (mirrored in RTL), stopping at the grid's first and last swatch; Up/Down to the enabled swatch of the previous/next row nearest the same column, stopping at the first and last row; Home/End to the first/last swatch of the row, Ctrl+Home/Ctrl+End of the grid. Space, Enter and a click select. Disabled swatches are skipped; an `EmptySwatch` is a cell like the others. |
|
|
1868
|
+
| SwatchPicker (`focusMode="tab"`) | Every enabled swatch is a Tab stop, and the arrow keys do nothing; Space, Enter and a click select (the selected swatch stays selected). |
|
|
1869
|
+
| Listbox | One Tab stop: the list. The active option shows only while the list has focus (the selected option, else the first). Down/Up move without wrapping, Home/End, PageUp/PageDown by 10, typing moves to the matching option (a Space within 500 ms of a letter continues the search); Space and Enter select, or with `multiselect` toggle. Tab, Escape and Alt with an arrow are left to the page (an enclosing Dialog closes on Escape). With `disabledOptionsFocusable` the keys reach disabled options, but Space and Enter do nothing on them. |
|
|
1870
|
+
| Rating | Right/Up one step more (a star, or half a star with `step={0.5}`), Left/Down one fewer (never below `step`), Home/End `step`/`max`; mirrored in RTL. A pointer click on a half-star Rating chooses the half or the whole star by position; Space and Enter on a focused radio choose that radio's own value. |
|
|
1871
|
+
| TabList | One Tab stop (the selected tab). Left/Right (Up/Down when vertical) move and select, Home/End; with `selectTabOnFocus={false}` they only move focus, and Enter or Space selects the focused tab; disabled tabs skipped; mirrored in RTL. |
|
|
1872
|
+
| Tree | One Tab stop. Up/Down, Home/End; Right expands or moves to the first child, Left collapses or moves to the parent (mirrored in RTL); Enter activates; Space activates, or in a checkbox tree (`selectionMode="multiple"`) toggles the focused item's check; `*` expands siblings; type to jump (a Space within 500 ms of a letter continues the search). Row actions: Tab from the focused item enters them and Shift+Tab returns (`navigationMode="tree"`); with `navigationMode="treegrid"` the "next" arrow moves into them (on an expanded parent too, instead of to its first child), "previous" returns, and Up/Down move between items. |
|
|
1873
|
+
| List (selectable) | One Tab stop. Up/Down (wrapping), Home/End, Enter/Space toggle, typeahead above 7 items (a Space within 500 ms of a letter continues the search). With item actions (grid): Up/Down between rows, Left/Right into the actions, Enter/F2 into a text field, Escape back. |
|
|
1874
|
+
| DataGrid | One Tab stop. Arrows between cells (Left/Right to the previous/next cell of the row, mirrored in RTL; Up/Down and PageUp/PageDown keep the visual column across `colSpan`/`rowSpan` cells of grouped headers), Home/End row start/end, Ctrl+Home/Ctrl+End grid start/end, PageUp/PageDown 10 rows; Enter/F2 into a cell's widgets, Escape back; Space on a cell toggles its row's selection. |
|
|
1875
|
+
| Menu | Trigger: Enter/Space/Down open and focus the first item, Up opens on the last; a trigger with `aria-disabled="true"` (a `disabledFocusable` MenuButton or SplitButton) ignores them and its click. Menu: Up/Down (wrapping), Home/End, typeahead, Enter/Space activate (a Space within 500 ms of a letter continues the search); on a checkbox, radio or switch item Space changes it and keeps the menu open, Enter changes it and closes a popup menu; on `Menu.ItemLink` Enter is the browser's link activation. A submenu's item: Right (Left in RTL), Enter and Space open the submenu and focus its first item; in a submenu Left (Right in RTL) and Escape close only it and return focus to its item. `Menu.SplitGroup`: Right (Left in RTL) moves from the action to the submenu button, Left back. Escape closes and returns focus, Tab closes every level. Context menu (`openOnContext`): Shift+F10 or the ContextMenu key on the region opens it at the focused element; Escape and item activation return focus there, and Tab moves on from it. |
|
|
1876
|
+
| Combobox, TagPicker, TimePicker | Down/Up open and move, Alt+Down opens, typing makes the first match active (not in a `freeform` Combobox), Enter commits the active option (with no active option, Enter submits the form; TimePicker instead never submits while its text is edited: a complete time within the bounds is committed, erased text clears the value, other text is kept and flagged invalid, and the list closes; leaving the input with edited text settles it the same way and closes the list), Alt+Up and Tab close, Escape closes and then discards typed text. The chevron button (Combobox, TimePicker) is not a Tab stop; the clear button (`clearable`) is a Tab stop after the input. Tab from erased TimePicker text clears the value first, so focus moves past the clear button that goes with it. TagPicker: Backspace in the empty input focuses the last tag, Backspace/Delete removes it. Multi-select (`multiselect`, Combobox): Enter toggles the active option and keeps the list open; Tab, Escape and Alt+Up close without committing; focusing the input selects the shown labels, so typing replaces them with a query, and Backspace and Delete only edit the text. With `disabledOptionsFocusable` (Combobox) the arrow keys reach disabled options, but Enter does nothing on them. |
|
|
1877
|
+
| Dropdown | Down/Up/Home/End and typing open and move, PageUp/PageDown move by 10, Enter/Space commit (a Space within 500 ms of a letter continues the search), Alt+Up and Tab commit and close, Escape closes. The clear button (`clearable`) is a Tab stop after the combobox button. Multi-select (`multiselect`): Enter and Space toggle the active option and keep the list open; Tab, Escape and Alt+Up close without committing. With `disabledOptionsFocusable` the keys reach disabled options, but Enter and Space do nothing on them, and Tab or Alt+Up on one closes the list without selecting. |
|
|
1878
|
+
| SpinButton | Up/Down step, PageUp/PageDown large step, Home/End jump to a finite min/max (otherwise they move the caret; with Shift they select text); Enter commits typed text, Escape reverts it. With the pointer, holding a step button repeats the step; a short press steps once on release. |
|
|
1879
|
+
| SearchBox | Escape clears the text and keeps focus (the key is consumed, so an enclosing Dialog, Drawer or Popover stays open); in an empty field Escape reaches the enclosing overlay. |
|
|
1880
|
+
| DatePicker | Alt+Down opens the calendar. The clear button (`clearable`) is a Tab stop between the input and the calendar button; Tab from erased text clears the value first and moves on to the calendar button. Calendar dialog: the keys of Calendar below; Escape in the month or year view goes back a view, and in the day view closes the calendar and returns focus. Tab stays inside: the previous button, the heading button (`showMonthPicker`), the next button, the grid, then the footer buttons (`showGoToToday`, `showCloseButton`). |
|
|
1881
|
+
| Calendar | One Tab stop in the grid, after the header buttons: the focused day (the selected day of the shown month, else today, else the first day of the month in range). Day view: arrows by day/week (mirrored in RTL), PageUp/PageDown by month, Shift+PageUp/PageDown by year (the day clamped to the month), Home/End week start/end, Enter/Space select. Month view: arrows by a month and a row of four (mirrored in RTL; past December or January they show the next or previous year), Home/End to the row's ends, PageUp/PageDown by a year, Enter/Space show the month's days. Year view: the arrows, Home and End stay on the shown page of 12 years while the target year is on it (the muted years before and after the decade included), PageUp/PageDown move a decade, Enter/Space show the year's months. Escape in the month or year view goes back a view (it is consumed, also from the header and footer buttons); in the day view it is left to the page. |
|
|
1882
|
+
| Dialog, Drawer | Focus is trapped; Escape closes; focus returns to the first of these that can take focus: `finalFocusRef`, the element that had focus when the modal opened (a trigger's wrapper span resolves to the element inside it), the trigger, an element next to where that opener was. |
|
|
1883
|
+
| Popover, TeachingPopover | Escape closes. When focus was inside (or lost to `<body>`), Popover returns it to its trigger, TeachingPopover to where focus was before it opened; a close by hover moves no focus. In the Tab order the content follows its trigger (TeachingPopover with `target`: the target): Tab from the trigger enters it, Tab past its end continues after the trigger, Shift+Tab from the element after the trigger enters it at its last element. For a trigger outside the tab order it follows the tab stop before the trigger; with no tab stop before the trigger or target, it is reached at the end of the page. A Tab lap visits it once: Tab from the last element of the page leaves the page, and Shift+Tab from outside the page reaches the page's last element. Focus leaving it does not close it. A context popover (`openOnContext`): Shift+F10 or the ContextMenu key on the region opens it and moves focus into the content, and Escape returns focus to the element that had it; opened by a right click, it is reached by Tab from that element. |
|
|
1884
|
+
| Tooltip, InfoButton, InfoLabel | Open on keyboard focus (Tooltip after `openDelay`); Escape closes. InfoButton and InfoLabel: a click pins the note; focus stays on the button when the note opens, Tab from the button enters the note's first focusable element (a link) and Tab past its last element continues after the button; Shift+Tab returns; Escape closes it and returns focus to the button when it was in the note. `openOnHover={false}` opens it on a click (Enter, Space) only. |
|
|
1885
|
+
| Card (selectable) | With the default `selectionControl="card"`: Enter (key down) or Space (key up) selects. With `"checkbox"`: the built-in checkbox. |
|
|
1886
|
+
| Stepper | Each reachable step is a Tab stop; Enter/Space activate. |
|
|
1887
|
+
| Slider | Native range keys. |
|
|
1888
|
+
| Toaster | The app's `shortcuts.focus` key press focuses the newest toast; Tab and Shift+Tab move through the toasts' controls; Escape dismisses every toast and returns focus (a tooltip or menu of a toast closes first); Delete or Backspace dismisses the focused toast (the keys are ignored in editable fields), and focus moves to the next toast, or the previous one, else back to where it was before the toasts. |
|
|
1889
|
+
| ColorArea | One Tab stop. Left/Right change saturation (mirrored in RTL), Up/Down brightness; Shift with an arrow by 10; PageUp/PageDown brightness by 10; Home/End saturation to 0/100. |
|
|
1890
|
+
| ColorSlider, AlphaSlider | The arrows of their axis by 1 (mirrored in RTL when horizontal), Shift with an arrow by 10, PageUp/PageDown by 10, Home/End to the minimum/maximum. |
|
|
1590
1891
|
|
|
1591
1892
|
Accordion and Carousel have no arrow-key navigation: their triggers and controls are Tab stops.
|
|
1592
1893
|
|
|
1593
|
-
Typeahead (Menu, selectable List, Tree, Dropdown) accepts characters typed with AltGr, which Windows reports as Ctrl+Alt (Polish `ł`, Romanian `ș`); Ctrl+Alt with an arrow, Home, End or Space is left to the browser. An item that cannot take focus is passed over for the next match. Menu typeahead matches the item labels, not icons or shortcuts. Options and option groups with `hidden` are skipped like a native `<option hidden>`.
|
|
1894
|
+
Typeahead (Menu, selectable List, Tree, Dropdown, Listbox) accepts characters typed with AltGr, which Windows reports as Ctrl+Alt (Polish `ł`, Romanian `ș`); Ctrl+Alt with an arrow, Home, End or Space is left to the browser. An item that cannot take focus is passed over for the next match. Menu typeahead matches the item labels, not icons or shortcuts. Options and option groups with `hidden` are skipped like a native `<option hidden>`.
|
|
1594
1895
|
|
|
1595
1896
|
## Hooks and utilities
|
|
1596
1897
|
|
|
1597
|
-
| Export
|
|
1598
|
-
|
|
|
1599
|
-
| `useControllable(value, defaultValue, onChange)`
|
|
1600
|
-
| `useRovingTabIndex(options)`
|
|
1601
|
-
| `useId(prefix?)`
|
|
1602
|
-
| `useEventCallback(fn)`
|
|
1603
|
-
| `useMergedRefs(...refs)`
|
|
1604
|
-
| `useIsClient()`
|
|
1605
|
-
| `usePresence(visible, options?)`, `<Presence visible>`
|
|
1606
|
-
| `useFieldControl(props, options?)`
|
|
1607
|
-
| `useAnnounce()`, `announce(message, politeness?)`
|
|
1608
|
-
| `
|
|
1609
|
-
| `
|
|
1610
|
-
| `
|
|
1611
|
-
| `
|
|
1612
|
-
| `
|
|
1613
|
-
| `
|
|
1614
|
-
| `
|
|
1615
|
-
| `
|
|
1616
|
-
| `
|
|
1617
|
-
| `
|
|
1618
|
-
| `
|
|
1898
|
+
| Export | Purpose |
|
|
1899
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1900
|
+
| `useControllable(value, defaultValue, onChange)` | Controlled/uncontrolled state; returns `[value, setValue, isControlled]`; `setValue` accepts a value or an updater and skips no-op updates. |
|
|
1901
|
+
| `useRovingTabIndex(options)` | Roving tab stop and arrow-key navigation for composite widgets; items marked `data-disabled-focusable` stay reachable, a composite marked `data-roving-transparent` (a toolbar radio group) adds its items to the enclosing arrow order, and `tabStop: 'none'` leaves every item out of the Tab order (a composite reached by arrow keys only). |
|
|
1902
|
+
| `useId(prefix?)` | SSR-safe ids (`prefix-<react id>`; never parse the React part). |
|
|
1903
|
+
| `useEventCallback(fn)` | Stable callback that always calls the latest `fn`. |
|
|
1904
|
+
| `useMergedRefs(...refs)` | One stable callback ref for several refs. |
|
|
1905
|
+
| `useIsClient()` | `false` on the server and during hydration. |
|
|
1906
|
+
| `usePresence(visible, options?)`, `<Presence visible>` | Mount and unmount an element with CSS enter and exit motion (`data-presence` phases; see [Enter and exit motion](#enter-and-exit-motion)). |
|
|
1907
|
+
| `useFieldControl(props, options?)` | Merge a custom control's labelling props with the surrounding `Field` (its label, validation message, hint and state). |
|
|
1908
|
+
| `useAnnounce()`, `announce(message, politeness?)` | Screen-reader announcements through a shared live region. |
|
|
1909
|
+
| `useActiveDescendant(options)` | The `aria-activedescendant` model under the pickers, for composites that keep focus on one element: `items`, `getId`, `getElement` (the item's element for scrolling; by default the element with its id), `enabled`, `fallback`, `loop`, `activateFirstOnChange`, `onActiveValueChange`; returns `activeValue`, `activeDescendantId` and stable `setActiveValue`, `highlight` (pointer moves, not scrolled into view), `first`, `last`, `next`, `prev` and `move`. State-based: it reads no DOM during render, so an active fallback item's id is in the server HTML. |
|
|
1910
|
+
| `useListbox(options)`, `useListboxOption(props)` | The listbox under Dropdown, Combobox, TagPicker and Listbox: keys, typeahead, filtering, the active option and option registration, in `'select-only'`, `'editable'` or `'standalone'` mode, with `multiselect` and `disabledOptionsFocusable`; `onOpenChange(open, details)` and `onSelect(value, details)` (see [Listbox and custom pickers](#listbox-and-custom-pickers)). `useListboxOption` builds an option component of your own. |
|
|
1911
|
+
| `useListboxPopup(options)`, `ListboxSurface` | The popup of a custom picker: dismissal (`onDismiss(reason)`, a `DismissReason`: `'escape'`, `'outside-press'` or `'focus-outside'`) and positioning, and the surface that renders the list (`listClassName`, `surfaceClassName`). |
|
|
1912
|
+
| `ListboxProvider` | Provides a listbox's context to its options when you render the list yourself. |
|
|
1913
|
+
| `collectOptionLabels(children)`, `markListboxElement(component, kind)` | Read option labels from children before the options register (server HTML, first render); mark your own option and group components so they are read. |
|
|
1914
|
+
| `useWaveTheme()` | Current theme, direction, portal container and `inputDefaults`. |
|
|
1915
|
+
| `useToastController()` | `dispatchToast` (content or options) / `dismissToast` / `dismissAllToasts` inside `<Toaster>`. |
|
|
1916
|
+
| `createToaster()` | A toast controller for code outside components; bind it with `<Toaster toaster={handle}>` (see [Toasts](#toasts)). Server-safe. |
|
|
1917
|
+
| `useIsOverflowing(target)`, `useOverflowMenu()`, `useIsOverflowItemVisible(id)` | Overflow detection. |
|
|
1918
|
+
| `hsvToHex(color)`, `hexToHsv(hex)` | Convert between an `HsvColor` and a hex string (`#rrggbb`, or `#rrggbbaa` below full opacity); `hexToHsv` returns `null` for other text. Server-safe. |
|
|
1919
|
+
| `addDays(date, amount)`, `addMonths(date, amount)`, `isSameDay(a, b)`, `startOfWeek(date, firstDayOfWeek?)`, `getWeekNumber(date, options?)` | Date helpers on calendar days: every returned date is local midnight (Fluent's keep the time of day). `addMonths` clamps the day to the target month (January 31 plus a month is February 28 or 29); `getWeekNumber` takes `firstDayOfWeek` and `firstWeekOfYear` (`GetWeekNumberOptions`; ISO 8601 is `{ firstDayOfWeek: 1, firstWeekOfYear: 'first-four-day-week' }`). With the types `DayOfWeek` (`0`–`6`, Sunday first) and `FirstWeekOfYear`. Server-safe. |
|
|
1920
|
+
| `buttonClassNames`, `toastTitleClassNames`, … | The stable class names of every component and part (see [Styling hooks](#styling-hooks)). Server-safe. |
|
|
1921
|
+
| `cn(...classes)` | clsx + tailwind-merge that knows Wave's type ramp and shadows; the last class wins over a conflicting class of the same variant. |
|
|
1922
|
+
| `composeEventHandlers(theirs, ours)` | Run a consumer handler, then yours unless it called `preventDefault()`. |
|
|
1923
|
+
| `mergeRefs(...refs)` | Merge refs outside components. |
|
|
1924
|
+
| `resolveSlot`, `renderSlot` | The slot helpers the components use. |
|
|
1925
|
+
| `getThemeClassName(theme)` | The theme classes of a theme. |
|
|
1926
|
+
|
|
1927
|
+
## Upgrading from 0.9
|
|
1928
|
+
|
|
1929
|
+
0.10 removes nothing public; these changes can still affect your tests and types:
|
|
1930
|
+
|
|
1931
|
+
1. DatePicker's calendar heading is a button (the month picker, on by default): the calendar dialog gains a Tab stop between the previous-month and next-month buttons, so tests that count Tab presses or list the dialog's tabbable elements change. Queries by the heading and the names of the dialog and the grid are unchanged. Escape in the month or year view goes back a view and keeps the dialog open; only Escape in the day view closes it. `showMonthPicker={false}` keeps a static heading.
|
|
1932
|
+
2. The development error of an `Option` outside a listbox names every listbox: `… must be used within a listbox (Listbox, Combobox, Dropdown or a ListboxProvider)`.
|
|
1933
|
+
3. `OptionGroup`'s `aria-label` and `aria-labelledby` go to its `role="group"` list, which they name; 0.9 put them on the group's `role="presentation"` item.
|
|
1934
|
+
4. SwatchPicker renders `data-layout`, `data-focus-mode`, `data-size`, `data-shape` and `data-spacing` on its root, and the swatches of `items` carry `wave-color-swatch` next to `wave-swatch-picker__swatch`: assertions on the root's exact attributes or a swatch's complete class string change.
|
|
1935
|
+
5. The popups of Dropdown, Combobox and TagPicker carry `wave-listbox-surface` and `wave-listbox-surface__list` next to their own stable classes: assertions on a complete class string change.
|
|
1936
|
+
6. `SwatchPickerSize` gains `'extra-small'`: a `Record<SwatchPickerSize, …>` of yours needs the key.
|
|
1937
|
+
7. A multi-select Dropdown or Combobox needs a literal `multiselect`: `multiselect={flag}` with a `boolean` variable matches neither call signature of the component, so render two elements, one for each mode.
|
|
1938
|
+
|
|
1939
|
+
The CHANGELOG's [0.10.0 "Changed" section](CHANGELOG.md#changed) lists every behaviour, DOM and type change.
|
|
1619
1940
|
|
|
1620
1941
|
## Upgrading from 0.8
|
|
1621
1942
|
|
|
@@ -1630,7 +1951,7 @@ Typeahead (Menu, selectable List, Tree, Dropdown) accepts characters typed with
|
|
|
1630
1951
|
7. `Rating` and `RatingDisplay` `color` is a `RatingColor` (`'neutral' | 'brand' | 'marigold'`): it narrows the `color?: string` inherited from `React.HTMLAttributes`. Both gain the static member `Item` and render their `children` (0.7 ignored them).
|
|
1631
1952
|
8. `WaveContextValue` (the result of `useWaveTheme()`) gains `inputDefaults`: code that builds one (a test double) adds it. `InputProps`, `SelectProps` and `SliderProps` declare their own `size`, and `SpinButtonProps` gains `allowEmpty?: false` next to the new `SpinButtonAllowEmptyProps`.
|
|
1632
1953
|
|
|
1633
|
-
The CHANGELOG's [0.9.0 "Upgrading from 0.8"](CHANGELOG.md#upgrading-from-08) and [0.9.0 "Changed" section](CHANGELOG.md#changed) list every behaviour, DOM, visual and type change.
|
|
1954
|
+
The CHANGELOG's [0.9.0 "Upgrading from 0.8"](CHANGELOG.md#upgrading-from-08) and [0.9.0 "Changed" section](CHANGELOG.md#changed-1) list every behaviour, DOM, visual and type change.
|
|
1634
1955
|
|
|
1635
1956
|
## Upgrading from 0.7
|
|
1636
1957
|
|
|
@@ -1648,7 +1969,7 @@ The CHANGELOG's [0.9.0 "Upgrading from 0.8"](CHANGELOG.md#upgrading-from-08) and
|
|
|
1648
1969
|
10. `ToastController['dispatchToast']` is overloaded: a test double that implements it must accept content too.
|
|
1649
1970
|
11. A `dispatchToast` options argument that is not a plain object (a class instance, `Object.create(defaults)`) is now read as React content and throws when it renders: pass a plain object (spread it: `{ ...defaults, title }`).
|
|
1650
1971
|
|
|
1651
|
-
The CHANGELOG's [0.8.0 "Changed" section](CHANGELOG.md#changed-
|
|
1972
|
+
The CHANGELOG's [0.8.0 "Changed" section](CHANGELOG.md#changed-2) lists every behaviour, DOM and type change.
|
|
1652
1973
|
|
|
1653
1974
|
## Upgrading from 0.6
|
|
1654
1975
|
|
|
@@ -1664,7 +1985,7 @@ The CHANGELOG's [0.8.0 "Changed" section](CHANGELOG.md#changed-1) lists every be
|
|
|
1664
1985
|
8. Wave now requires tailwind-merge 3.7, so an axis shorthand in your `className` (`px-6`) replaces a component's own logical side class of the same variant (`pe-8` on `Dialog.Title`) instead of losing to it in the CSS: write `px-6 pe-8` to keep the side.
|
|
1665
1986
|
9. Wave requires Node.js 22.12 or later (`engines`; Node.js 20 reached its end of life on 2026-04-30). npm and pnpm warn on an older Node.js (npm stops with `engine-strict`), and Yarn 1 refuses to install.
|
|
1666
1987
|
|
|
1667
|
-
The CHANGELOG's [0.7.0 "Changed" section](CHANGELOG.md#changed-
|
|
1988
|
+
The CHANGELOG's [0.7.0 "Changed" section](CHANGELOG.md#changed-3) lists every behaviour, DOM and type change.
|
|
1668
1989
|
|
|
1669
1990
|
## Upgrading from 0.5
|
|
1670
1991
|
|
|
@@ -1679,11 +2000,11 @@ The CHANGELOG's [0.7.0 "Changed" section](CHANGELOG.md#changed-2) lists every be
|
|
|
1679
2000
|
7. RatingDisplay formats its accessible name with the runtime locale when `locale` is omitted: pass `locale` when rendering on the server.
|
|
1680
2001
|
8. Badge `color="important"` becomes a neutral color in 1.0: switch to `color="severe"` to keep the orange look.
|
|
1681
2002
|
|
|
1682
|
-
The CHANGELOG's [0.6.0 "Changed" section](CHANGELOG.md#changed-
|
|
2003
|
+
The CHANGELOG's [0.6.0 "Changed" section](CHANGELOG.md#changed-4) lists every behaviour, DOM and type change.
|
|
1683
2004
|
|
|
1684
2005
|
## Upgrading from 0.4
|
|
1685
2006
|
|
|
1686
|
-
npm went from 0.4.0 to 0.7.0: 0.5.0 and 0.6.0 were never published, and their changes ship in 0.7.0. From 0.4.0, take these steps, then those of [Upgrading from 0.5](#upgrading-from-05), [Upgrading from 0.6](#upgrading-from-06), [Upgrading from 0.7](#upgrading-from-07)
|
|
2007
|
+
npm went from 0.4.0 to 0.7.0: 0.5.0 and 0.6.0 were never published, and their changes ship in 0.7.0. From 0.4.0, take these steps, then those of [Upgrading from 0.5](#upgrading-from-05), [Upgrading from 0.6](#upgrading-from-06), [Upgrading from 0.7](#upgrading-from-07), [Upgrading from 0.8](#upgrading-from-08) and [Upgrading from 0.9](#upgrading-from-09).
|
|
1687
2008
|
|
|
1688
2009
|
1. The npm package name is unchanged (`@mortenbrudvik/waveui`, as in 0.4.0). Replace `waveui` imports copied from the 0.4 guide, and a git or local dependency named `waveui`, with `@mortenbrudvik/waveui`.
|
|
1689
2010
|
2. Styles: without Tailwind, keep `import '@mortenbrudvik/waveui/styles'` and make sure a `WaveProvider` wraps the app; with Tailwind 4, switch to `@import '@mortenbrudvik/waveui/tailwind';` after `@import 'tailwindcss';`. Import `preflight.css` if you relied on Wave's Preflight.
|