@lumiose/ui 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +53 -2
- package/dist/accordion/accordion.css +20 -0
- package/dist/accordion/accordion.script.ts +66 -0
- package/dist/accordion/accordion.tsx +288 -0
- package/dist/animation/animation.ts +34 -0
- package/dist/autocomplete/autocomplete.tsx +239 -0
- package/dist/avatar/avatar.css +54 -0
- package/dist/avatar/avatar.script.ts +96 -0
- package/dist/avatar/avatar.tsx +110 -0
- package/dist/button/button.script.ts +35 -0
- package/dist/button/button.tsx +40 -0
- package/dist/checkbox/checkbox.script.ts +225 -0
- package/dist/checkbox/checkbox.tsx +156 -0
- package/dist/checkbox/context.ts +17 -0
- package/dist/checkbox-group/checkbox-group.tsx +111 -0
- package/dist/collapsible/collapsible.css +12 -0
- package/dist/collapsible/collapsible.script.ts +7 -0
- package/dist/collapsible/collapsible.tsx +153 -0
- package/dist/collapsible/panel.ts +115 -0
- package/dist/combobox/combobox.css +37 -0
- package/dist/combobox/combobox.script.ts +974 -0
- package/dist/combobox/combobox.tsx +556 -0
- package/dist/combobox/context.ts +46 -0
- package/dist/context-menu/context-menu.css +16 -0
- package/dist/context-menu/context-menu.script.ts +172 -0
- package/dist/context-menu/context-menu.tsx +178 -0
- package/dist/dialog/dialog.css +35 -0
- package/dist/dialog/dialog.script.ts +269 -0
- package/dist/dialog/dialog.tsx +214 -0
- package/dist/drawer/drawer.css +38 -0
- package/dist/drawer/drawer.script.ts +207 -0
- package/dist/drawer/drawer.tsx +113 -0
- package/dist/field/context.ts +156 -0
- package/dist/field/field.css +16 -0
- package/dist/field/field.script.ts +422 -0
- package/dist/field/field.tsx +465 -0
- package/dist/fieldset/fieldset.tsx +41 -0
- package/dist/form/form.tsx +50 -0
- package/dist/form/native.ts +21 -0
- package/dist/form/reset.ts +53 -0
- package/dist/index.ts +261 -0
- package/dist/input/input.tsx +67 -0
- package/dist/layer/layer.ts +191 -0
- package/dist/locale/digits.ts +21 -0
- package/dist/menu/menu.css +22 -0
- package/dist/menu/menu.script.ts +591 -0
- package/dist/menu/menu.tsx +520 -0
- package/dist/menubar/menubar.css +16 -0
- package/dist/menubar/menubar.script.ts +195 -0
- package/dist/menubar/menubar.tsx +67 -0
- package/dist/meter/meter.tsx +134 -0
- package/dist/navigation-menu/navigation-menu.css +14 -0
- package/dist/navigation-menu/navigation-menu.script.ts +466 -0
- package/dist/navigation-menu/navigation-menu.tsx +266 -0
- package/dist/number-field/format.ts +70 -0
- package/dist/number-field/number-field.css +38 -0
- package/dist/number-field/number-field.script.ts +490 -0
- package/dist/number-field/number-field.tsx +403 -0
- package/dist/otp-field/otp-field.css +45 -0
- package/dist/otp-field/otp-field.script.ts +330 -0
- package/dist/otp-field/otp-field.tsx +320 -0
- package/dist/placement/place.ts +148 -0
- package/dist/placement/placement.css +155 -0
- package/dist/placement/placement.ts +60 -0
- package/dist/popover/hover.ts +178 -0
- package/dist/popover/popover.css +20 -0
- package/dist/popover/popover.script.ts +256 -0
- package/dist/popover/popover.tsx +317 -0
- package/dist/preview-card/preview-card.css +7 -0
- package/dist/preview-card/preview-card.script.ts +136 -0
- package/dist/preview-card/preview-card.tsx +128 -0
- package/dist/progress/progress.tsx +155 -0
- package/dist/progress/value.ts +23 -0
- package/dist/radio-group/radio-group.tsx +156 -0
- package/dist/scroll-area/scroll-area.css +72 -0
- package/dist/scroll-area/scroll-area.script.ts +281 -0
- package/dist/scroll-area/scroll-area.tsx +118 -0
- package/dist/select/select.css +25 -0
- package/dist/select/select.tsx +171 -0
- package/dist/separator/separator.css +12 -0
- package/dist/separator/separator.tsx +38 -0
- package/dist/slider/format.ts +33 -0
- package/dist/slider/slider.css +93 -0
- package/dist/slider/slider.script.ts +355 -0
- package/dist/slider/slider.tsx +374 -0
- package/dist/styles.css +30 -0
- package/dist/switch/switch.tsx +92 -0
- package/dist/tabs/tabs.css +54 -0
- package/dist/tabs/tabs.script.ts +225 -0
- package/dist/tabs/tabs.tsx +272 -0
- package/dist/themes/default.css +2111 -0
- package/dist/toast/command.ts +3 -0
- package/dist/toast/toast.css +68 -0
- package/dist/toast/toast.script.ts +516 -0
- package/dist/toast/toast.tsx +432 -0
- package/dist/toggle/context.ts +16 -0
- package/dist/toggle/toggle.tsx +99 -0
- package/dist/toggle-group/toggle-group.script.ts +166 -0
- package/dist/toggle-group/toggle-group.tsx +117 -0
- package/dist/toolbar/context.ts +18 -0
- package/dist/toolbar/toolbar.css +12 -0
- package/dist/toolbar/toolbar.script.ts +123 -0
- package/dist/toolbar/toolbar.tsx +180 -0
- package/dist/tooltip/tooltip.css +8 -0
- package/dist/tooltip/tooltip.script.ts +222 -0
- package/dist/tooltip/tooltip.tsx +149 -0
- package/docs/README.md +174 -0
- package/docs/accordion.md +208 -0
- package/docs/autocomplete.md +161 -0
- package/docs/avatar.md +198 -0
- package/docs/button.md +98 -0
- package/docs/checkbox-group.md +112 -0
- package/docs/checkbox.md +133 -0
- package/docs/collapsible.md +165 -0
- package/docs/combobox.md +369 -0
- package/docs/context-menu.md +197 -0
- package/docs/dialog.md +256 -0
- package/docs/drawer.md +204 -0
- package/docs/field.md +295 -0
- package/docs/fieldset.md +92 -0
- package/docs/form.md +131 -0
- package/docs/input.md +87 -0
- package/docs/installation.md +65 -0
- package/docs/llms-full.txt +6923 -0
- package/docs/llms.txt +53 -0
- package/docs/menu.md +365 -0
- package/docs/menubar.md +186 -0
- package/docs/meter.md +103 -0
- package/docs/navigation-menu.md +257 -0
- package/docs/number-field.md +181 -0
- package/docs/otp-field.md +160 -0
- package/docs/popover.md +271 -0
- package/docs/preview-card.md +130 -0
- package/docs/progress.md +115 -0
- package/docs/radio-group.md +127 -0
- package/docs/scroll-area.md +147 -0
- package/docs/select.md +236 -0
- package/docs/separator.md +116 -0
- package/docs/slider.md +184 -0
- package/docs/switch.md +98 -0
- package/docs/tabs.md +254 -0
- package/docs/theming.md +132 -0
- package/docs/toast.md +311 -0
- package/docs/toggle-group.md +119 -0
- package/docs/toggle.md +94 -0
- package/docs/toolbar.md +138 -0
- package/docs/tooltip.md +128 -0
- package/package.json +102 -3
package/docs/theming.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Theming
|
|
2
|
+
|
|
3
|
+
> `@lumiose/ui` is unstyled: its stylesheets hold only what the components need to work. The optional default theme gives them a look, light and dark, through `--lu-` custom properties you can change; or style the parts with your own CSS.
|
|
4
|
+
|
|
5
|
+
## Cascade layers
|
|
6
|
+
|
|
7
|
+
The components' stylesheets hold only what a component needs to work (display, positioning and
|
|
8
|
+
anchoring, the top layer, the CSS variables a component sets), in the `lu.core` cascade layer. The
|
|
9
|
+
default theme sits in `lu.theme`, above it:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import "@lumiose/ui/styles.css"; // what every component needs (or "@lumiose/ui/dialog.css", …)
|
|
13
|
+
import "@lumiose/ui/themes/default.css"; // optional: the default theme
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Both stylesheets declare the layers `lu.core, lu.theme`, in that order. CSS outside layers always
|
|
17
|
+
wins over layered CSS, so your own stylesheets override the package's without specificity games.
|
|
18
|
+
If your app orders its own layers, put `lu` first: `@layer lu, reset, components;`.
|
|
19
|
+
|
|
20
|
+
## The default theme
|
|
21
|
+
|
|
22
|
+
`@lumiose/ui/themes/default.css` is a ready look for demos, prototypes and getting started: neutral
|
|
23
|
+
surfaces, a blue primary, light and dark. It styles a few classes, which you put on the parts
|
|
24
|
+
through their `class` prop:
|
|
25
|
+
|
|
26
|
+
| Class | On | Variants |
|
|
27
|
+
| -------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
28
|
+
| `lu-button` | `Button`, any `<button>` part | `data-variant="primary"`, `data-variant="destructive"` |
|
|
29
|
+
| `lu-dialog` | `Dialog.Popup` | Styles the backdrop, the title, the description and a `<footer>` inside |
|
|
30
|
+
| `lu-popover` | `Popover.Popup` | Styles the title, the description and a `<footer>` inside |
|
|
31
|
+
| `lu-tooltip` | `Tooltip.Popup` | |
|
|
32
|
+
| `lu-tabs` | `Tabs.Root` | Styles the list, the tabs (the selected one underlined) and the panels |
|
|
33
|
+
| `lu-menu` | every `Menu.Popup` | Styles the items, their checkbox and radio marks, separators and labels |
|
|
34
|
+
| `lu-combobox` | `Combobox.Root`, `Autocomplete.Root` | Lays out the field (label, chips, input, buttons) and styles the popup |
|
|
35
|
+
| `lu-select` | `Select.Root` | Makes it a customizable select and styles the control and the picker |
|
|
36
|
+
| `lu-field` | `Field.Root` | Stacks the label, control, description and errors; errors in red |
|
|
37
|
+
| `lu-input` | `Input`, `Field.Control` | A text field with a focus ring and a red border when invalid |
|
|
38
|
+
| `lu-fieldset` | `Fieldset.Root`, `CheckboxGroup`, `RadioGroup` | The legend and the items in a column |
|
|
39
|
+
| `lu-checkbox` | `Checkbox.Root` | An 18px box in a 24px target, a ✓ or a dash |
|
|
40
|
+
| `lu-radio` | `Radio.Root` | An 18px circle in a 24px target, a thick ring when checked |
|
|
41
|
+
| `lu-switch` | `Switch.Root` | A track and a thumb on the side that tells on from off |
|
|
42
|
+
| `lu-collapsible` | `Collapsible.Root` | Styles the trigger (a chevron) and animates the panel's height |
|
|
43
|
+
| `lu-accordion` | `Accordion.Root` | Items between borders; styles the triggers and animates the panels |
|
|
44
|
+
| `lu-toast-viewport` | `Toast.Viewport` | A column at the window's bottom end |
|
|
45
|
+
| `lu-toast` | every `Toast.Root` | Styles the title, description and close button; `data-type="error"` |
|
|
46
|
+
| `lu-toggle` | `Toggle` | Pressed: a filled surface and a bar along the bottom |
|
|
47
|
+
| `lu-toggle-group` | `ToggleGroup` | The toggles in a row, or a column when vertical |
|
|
48
|
+
| `lu-progress` | `Progress.Root` | The label and value above a bar; a sliding segment when indeterminate |
|
|
49
|
+
| `lu-meter` | `Meter.Root` | The label and value above a bar |
|
|
50
|
+
| `lu-slider` | `Slider.Root` | The label and value above the track, the indicator and 24px thumbs |
|
|
51
|
+
| `lu-number-field` | `NumberField.Root` | The label above one bordered group: the input between its buttons |
|
|
52
|
+
| `lu-otp-field` | `OTPField.Root` | A box per character, the caret's box ringed while focused |
|
|
53
|
+
| `lu-preview-card` | `PreviewCard.Popup` | A surface like a popover's, with a fade |
|
|
54
|
+
| `lu-toolbar` | `Toolbar.Root` | A bordered row (or column), separators across it, links as tall as buttons |
|
|
55
|
+
| `lu-scroll-area` | `ScrollArea.Root` | A bordered box; 10px scrollbars shown on hover, scroll or focus |
|
|
56
|
+
| `lu-drawer` | `Drawer.Popup` | A surface against its edge (a handle on a bottom sheet), title, description, footer; slides |
|
|
57
|
+
| `lu-separator` | `Separator` | A 1px line in `--lu-border`, horizontal or vertical |
|
|
58
|
+
| `lu-avatar` | `Avatar.Root` | A 40px circle on a muted surface, the image covering it, the initials centred |
|
|
59
|
+
| `lu-menubar` | `Menubar` | A bordered bar of the triggers, the open one filled; `lu-menu` on each popup |
|
|
60
|
+
| `lu-navigation-menu` | `NavigationMenu.Root` | The list in a row, the current page underlined, contents as surfaces that slide in |
|
|
61
|
+
|
|
62
|
+
Its colours and radius are custom properties with shadcn/ui's names and a `--lu-` prefix, so a
|
|
63
|
+
shadcn palette carries over:
|
|
64
|
+
|
|
65
|
+
| Property | Used for |
|
|
66
|
+
| ------------------------------------------------- | ----------------------------------- |
|
|
67
|
+
| `--lu-background`, `--lu-foreground` | Page-level surfaces and text |
|
|
68
|
+
| `--lu-card`, `--lu-card-foreground` | Cards |
|
|
69
|
+
| `--lu-popover`, `--lu-popover-foreground` | Dialogs, popovers, menus |
|
|
70
|
+
| `--lu-primary`, `--lu-primary-foreground` | Primary actions |
|
|
71
|
+
| `--lu-secondary`, `--lu-secondary-foreground` | Secondary actions |
|
|
72
|
+
| `--lu-muted`, `--lu-muted-foreground` | Subdued surfaces and text |
|
|
73
|
+
| `--lu-accent`, `--lu-accent-foreground` | Hover and highlighted items |
|
|
74
|
+
| `--lu-destructive`, `--lu-destructive-foreground` | Destructive actions |
|
|
75
|
+
| `--lu-border`, `--lu-input`, `--lu-ring` | Borders, field borders, focus rings |
|
|
76
|
+
| `--lu-radius` | Corner radius |
|
|
77
|
+
|
|
78
|
+
The theme sets `color-scheme: light dark` on `:root` and every colour with `light-dark()`, so it
|
|
79
|
+
follows the system. Set `color-scheme: light` (or `dark`) on `:root` or on any element, in CSS,
|
|
80
|
+
inline or from a script, to force one, in development and in production builds alike (Lumiose's
|
|
81
|
+
CSS target keeps `light-dark()` as written; an older `build.cssTarget` would lower it to colours
|
|
82
|
+
that follow the system only). Redefine a property outside layers to change it:
|
|
83
|
+
|
|
84
|
+
```css
|
|
85
|
+
:root {
|
|
86
|
+
--lu-primary: #7c3aed;
|
|
87
|
+
--lu-radius: 0.375rem;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The theme meets WCAG 2.2 AA in light and dark: each `-foreground` colour reaches 4.5:1 on its
|
|
92
|
+
surface, and `--lu-input` (the borders of fields and controls) and `--lu-ring` (the focus ring)
|
|
93
|
+
reach 3:1 on the surfaces they sit on. `--lu-border` is for decoration, such as a dialog's edge:
|
|
94
|
+
it is too light to outline a control. Keep those ratios when you change the colours.
|
|
95
|
+
|
|
96
|
+
In forced colours (Windows' contrast themes), the browser drops shadows and gradients and paints
|
|
97
|
+
backgrounds in its own palette, which would erase the state a colour shows. The theme draws those
|
|
98
|
+
states again in system colours (`Highlight`, `HighlightText`, `ButtonText`, `Canvas`): a switch's
|
|
99
|
+
thumb and its on state, a pressed toggle, the selected tab's bar, progress and meter values, a
|
|
100
|
+
slider's range, the scroll thumb and the highlighted menu item. A pressed toggle's focus ring,
|
|
101
|
+
outside its `Highlight` fill, is `CanvasText`, the colour every contrast theme pairs with the page
|
|
102
|
+
(Firefox would draw it in the toggle's `HighlightText`). Do the same for the states your own CSS
|
|
103
|
+
draws with a colour, in a `@media (forced-colors: active)` block.
|
|
104
|
+
|
|
105
|
+
## Your own CSS
|
|
106
|
+
|
|
107
|
+
Style the parts through:
|
|
108
|
+
|
|
109
|
+
- **Class props.** Every part takes `class` (and `style`), like the element it renders.
|
|
110
|
+
- **State.** Components expose state the way the platform does, so selectors read it directly:
|
|
111
|
+
`dialog[open]`, `:modal`, `::backdrop`, `:popover-open`, `[aria-expanded]`, `[aria-checked]`,
|
|
112
|
+
`[aria-selected]`. Where the platform has no such state, they set Base UI's state attributes:
|
|
113
|
+
`data-highlighted` on the active option of a list, `data-side` and `data-align` on a placed
|
|
114
|
+
popup, `data-orientation`, `data-starting-style` and `data-ending-style` while a popup animates,
|
|
115
|
+
`data-swiping`, `data-indeterminate` and others. Each component's page lists its own.
|
|
116
|
+
- **Custom properties.** The values components measure or place, such as a panel's height, a
|
|
117
|
+
toast's index in its stack or a popup's offset, are custom properties with the `--lu-` prefix.
|
|
118
|
+
Base UI names them without a prefix, so its `--collapsible-panel-height` is
|
|
119
|
+
`--lu-collapsible-panel-height` here and its `--scroll-area-thumb-height` is
|
|
120
|
+
`--lu-scroll-area-thumb-height`.
|
|
121
|
+
|
|
122
|
+
Other `data-*` attributes on the rendered HTML, such as the markers scripts use to find a part or
|
|
123
|
+
the settings the server HTML passes to a script, are internal and may change between versions.
|
|
124
|
+
Style those parts through their class props.
|
|
125
|
+
|
|
126
|
+
Each component's page has an example stylesheet to start from.
|
|
127
|
+
|
|
128
|
+
## Related
|
|
129
|
+
|
|
130
|
+
- [UI components](README.md): the approach and the components.
|
|
131
|
+
- [Installation](installation.md): adding the package and importing its stylesheets.
|
|
132
|
+
- [Styles](https://itsjavi.com/lumiose/docs/concepts/styles/): scoped CSS and global stylesheets in Lumiose.
|
package/docs/toast.md
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# Toast
|
|
2
|
+
|
|
3
|
+
> Short notices in a region of their own, after Base UI's toast: server HTML renders them (a form action's result), and islands add, update and close them through a toast manager. `<lu-toast-viewport>`, a 2.6 kB light-DOM element, puts them in the top layer, times them (pausing on hover and focus), limits how many show, closes them on a swipe, Escape or their close button, focuses the region on F6, and makes screen readers announce them.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Toast } from "@lumiose/ui"; // or "@lumiose/ui/toast"
|
|
7
|
+
import "@lumiose/ui/toast.css"; // the viewport's box, the limit, swipes on touch screens
|
|
8
|
+
import "@lumiose/ui/themes/default.css"; // optional: the default theme
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Toast.Provider>
|
|
15
|
+
<Toast.Viewport>
|
|
16
|
+
<Toast.Root>
|
|
17
|
+
<Toast.Content>
|
|
18
|
+
<Toast.Title />
|
|
19
|
+
<Toast.Description />
|
|
20
|
+
</Toast.Content>
|
|
21
|
+
<Toast.Action />
|
|
22
|
+
<Toast.Close />
|
|
23
|
+
</Toast.Root>
|
|
24
|
+
</Toast.Viewport>
|
|
25
|
+
</Toast.Provider>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Part | Renders |
|
|
29
|
+
| ------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `Provider` | No element. Holds the toasts' defaults (`timeout`, `limit`) and a manager for `Toast.useToastManager()`. |
|
|
31
|
+
| `Viewport` | `<lu-toast-viewport role="region" aria-label="Notifications (F6)" tabindex="-1">`, the toasts' region. |
|
|
32
|
+
| `Root` | `<div role="status" aria-atomic="true">`, a toast: `role="alert"` for a high-priority one. |
|
|
33
|
+
| `Content` | `<div>`, around the text. |
|
|
34
|
+
| `Title` | `<h2>`: its children, or else the toast's `title`; nothing without either. |
|
|
35
|
+
| `Description` | `<p>`: its children, or else the toast's `description`; nothing without either. |
|
|
36
|
+
| `Action` | `<button type="button">` with the toast's `actionProps`; nothing without them or children. |
|
|
37
|
+
| `Close` | `<button type="button" command="--close-toast" commandfor="…">`: a custom invoker command that closes the toast. |
|
|
38
|
+
|
|
39
|
+
Each toast is a live region of its own, `role="status"` (announced when the reader is idle) or
|
|
40
|
+
`role="alert"` (announced at once), as in Zag; Base UI's are non-modal dialogs. The viewport is
|
|
41
|
+
a named region that F6 reaches.
|
|
42
|
+
|
|
43
|
+
## API
|
|
44
|
+
|
|
45
|
+
### Toast.createToastManager
|
|
46
|
+
|
|
47
|
+
`Toast.createToastManager()` returns a manager. Create one in a module and import it where toasts
|
|
48
|
+
are added and where they are shown: islands of a page share a module, so they share its manager.
|
|
49
|
+
|
|
50
|
+
| Method | Does |
|
|
51
|
+
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
52
|
+
| `toasts()` | The open toasts, newest first, read reactively: map them to `Toast.Root`s. |
|
|
53
|
+
| `add(options)` | Adds a toast and returns its id. With the `id` of an open toast, it updates that one in place. |
|
|
54
|
+
| `update(id, options)` | Changes an open toast in place: the same object, its fields read reactively, so its parts update without a new element. |
|
|
55
|
+
| `close(id?)` | Closes a toast, or every toast: it gets `transitionStatus: "ending"` and `data-ending-style`, then leaves after its exit animation. |
|
|
56
|
+
| `promise(promise, options)` | Shows a loading toast while `promise` runs (it stays), then turns it into a success or an error toast; returns `promise`. |
|
|
57
|
+
|
|
58
|
+
The options of `add` and `update`:
|
|
59
|
+
|
|
60
|
+
| Option | Type | Default | |
|
|
61
|
+
| ------------- | --------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| `id` | `string` | made up | An id of your own, to update or close the toast later. |
|
|
63
|
+
| `title` | `string` | | Shown by `Toast.Title`. |
|
|
64
|
+
| `description` | `string` | | Shown by `Toast.Description`. |
|
|
65
|
+
| `type` | `string` | | A type of your own (`"success"`, `"error"`, `"loading"`): the toast's `data-type`, for styling. |
|
|
66
|
+
| `timeout` | `number` | `5000` | Milliseconds before it closes on its own; `0` keeps it until closed. The provider's `timeout` sets the default. |
|
|
67
|
+
| `priority` | `"low" \| "high"` | `"low"` | `"high"` announces it at once (`role="alert"`), for errors that need attention. |
|
|
68
|
+
| `actionProps` | `JSX.IntrinsicElements["button"]` | | The props of its `Toast.Action`: `children` for the label, `onClick`, `class`, `disabled` and `aria-*`. Other keys are dropped, so options passed as they arrived (a notification payload) cannot set the button's `type`, `formaction` or `style`. Write any other prop on `Toast.Action` itself. |
|
|
69
|
+
| `onClose` | `() => void` | | Called when it starts closing. |
|
|
70
|
+
| `onRemove` | `() => void` | | Called once it is removed, after its exit animation. |
|
|
71
|
+
|
|
72
|
+
`promise`'s options are `loading`, `success` and `error`: options, a title, or (for `success` and
|
|
73
|
+
`error`) a function of the result. The loading toast has `type: "loading"` and `timeout: 0`; the
|
|
74
|
+
others `type: "success"` or `"error"` and the default timeout.
|
|
75
|
+
|
|
76
|
+
### Toast.Provider
|
|
77
|
+
|
|
78
|
+
| Prop | Type | Default | |
|
|
79
|
+
| -------------- | -------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
80
|
+
| `toastManager` | `ToastManager` | a new one | The manager its toasts come from, for `Toast.useToastManager()`. |
|
|
81
|
+
| `timeout` | `number` | `5000` | Milliseconds before a toast closes on its own; `0` keeps toasts open. |
|
|
82
|
+
| `limit` | `number` | `3` | How many toasts show at once; older ones wait, hidden and paused, until a newer one closes. The toast that holds focus always shows. |
|
|
83
|
+
|
|
84
|
+
`Toast.useToastManager()` returns the closest provider's manager.
|
|
85
|
+
|
|
86
|
+
### Toast.Root
|
|
87
|
+
|
|
88
|
+
| Prop | Type | Default | |
|
|
89
|
+
| ---------------- | ----------------------------------------- | ------------------- | ---------------------------------------------------------------- |
|
|
90
|
+
| `toast` | `ToastObject \| ToastOptions` | | The toast: one from a manager, or a plain object in server HTML. |
|
|
91
|
+
| `swipeDirection` | `("up" \| "down" \| "left" \| "right")[]` | `["down", "right"]` | The directions a swipe closes it in; `[]` turns swiping off. |
|
|
92
|
+
|
|
93
|
+
### The other parts
|
|
94
|
+
|
|
95
|
+
Each part takes the props of the element it renders (`class`, `style`, `aria-*`, `data-*`, event
|
|
96
|
+
handlers, `ref`) and passes them through. Your props override the defaults (`role`, `aria-label`,
|
|
97
|
+
`type`). A `Root`'s `id` names the toast's element, and its `Close` points at it (see
|
|
98
|
+
[Ids](README.md#ids)); `command` and `commandfor` stay the parts' own, since they connect them.
|
|
99
|
+
|
|
100
|
+
- **`Viewport`**: rename the region with `aria-label`; keep the hotkey in it.
|
|
101
|
+
- **`Root`**: the script fires a cancelable `close` event on it when a toast should close (a close
|
|
102
|
+
button, Escape, a swipe, its timer); a managed toast handles it through its manager.
|
|
103
|
+
- **`Close`**: name it, with text or `aria-label`.
|
|
104
|
+
|
|
105
|
+
## Example
|
|
106
|
+
|
|
107
|
+
In an island, a button and the toasts it adds, styled by the default theme:
|
|
108
|
+
|
|
109
|
+
```tsx check
|
|
110
|
+
import { Toast } from "@lumiose/ui";
|
|
111
|
+
|
|
112
|
+
export function SaveButton() {
|
|
113
|
+
const toasts = Toast.createToastManager();
|
|
114
|
+
return (
|
|
115
|
+
<Toast.Provider toastManager={toasts}>
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
class="lu-button"
|
|
119
|
+
onClick={() => toasts.add({ title: "Saved", description: "Your changes are live." })}
|
|
120
|
+
>
|
|
121
|
+
Save
|
|
122
|
+
</button>
|
|
123
|
+
<Toast.Viewport class="lu-toast-viewport">
|
|
124
|
+
{toasts.toasts().map((toast) => (
|
|
125
|
+
<Toast.Root class="lu-toast" toast={toast}>
|
|
126
|
+
<Toast.Title />
|
|
127
|
+
<Toast.Description />
|
|
128
|
+
<Toast.Close aria-label="Close">×</Toast.Close>
|
|
129
|
+
</Toast.Root>
|
|
130
|
+
))}
|
|
131
|
+
</Toast.Viewport>
|
|
132
|
+
</Toast.Provider>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
A promise toast, and an error that stays (`timeout: 0`) with an action:
|
|
138
|
+
|
|
139
|
+
```tsx check
|
|
140
|
+
import { Toast } from "@lumiose/ui";
|
|
141
|
+
|
|
142
|
+
export function UploadButton() {
|
|
143
|
+
const toasts = Toast.createToastManager();
|
|
144
|
+
const upload = () =>
|
|
145
|
+
toasts
|
|
146
|
+
.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
|
147
|
+
loading: "Uploading…",
|
|
148
|
+
success: "Uploaded",
|
|
149
|
+
error: "Upload failed",
|
|
150
|
+
})
|
|
151
|
+
.catch(() => undefined);
|
|
152
|
+
const fail = () =>
|
|
153
|
+
toasts.add({
|
|
154
|
+
title: "Payment failed",
|
|
155
|
+
description: "Check your card and try again.",
|
|
156
|
+
type: "error",
|
|
157
|
+
priority: "high",
|
|
158
|
+
timeout: 0,
|
|
159
|
+
actionProps: { children: "Retry", class: "lu-button", onClick: upload },
|
|
160
|
+
});
|
|
161
|
+
return (
|
|
162
|
+
<Toast.Provider toastManager={toasts}>
|
|
163
|
+
<button type="button" class="lu-button" onClick={upload}>
|
|
164
|
+
Upload
|
|
165
|
+
</button>
|
|
166
|
+
<button type="button" class="lu-button" data-variant="destructive" onClick={fail}>
|
|
167
|
+
Pay
|
|
168
|
+
</button>
|
|
169
|
+
<Toast.Viewport class="lu-toast-viewport">
|
|
170
|
+
{toasts.toasts().map((toast) => (
|
|
171
|
+
<Toast.Root class="lu-toast" toast={toast}>
|
|
172
|
+
<Toast.Title />
|
|
173
|
+
<Toast.Description />
|
|
174
|
+
<Toast.Action />
|
|
175
|
+
<Toast.Close aria-label="Close">×</Toast.Close>
|
|
176
|
+
</Toast.Root>
|
|
177
|
+
))}
|
|
178
|
+
</Toast.Viewport>
|
|
179
|
+
</Toast.Provider>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
A form action's result in server HTML, with no island: the page renders the toast, and the
|
|
185
|
+
script times and closes it.
|
|
186
|
+
|
|
187
|
+
```tsx check
|
|
188
|
+
import { Toast } from "@lumiose/ui";
|
|
189
|
+
|
|
190
|
+
export function SaveResult(props: { saved: boolean }) {
|
|
191
|
+
return (
|
|
192
|
+
<Toast.Viewport class="lu-toast-viewport">
|
|
193
|
+
{props.saved && (
|
|
194
|
+
<Toast.Root class="lu-toast" toast={{ title: "Project saved" }}>
|
|
195
|
+
<Toast.Title />
|
|
196
|
+
<Toast.Close aria-label="Close">×</Toast.Close>
|
|
197
|
+
</Toast.Root>
|
|
198
|
+
)}
|
|
199
|
+
</Toast.Viewport>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
To add toasts from several islands, create the manager in a module and import it in each: one
|
|
205
|
+
island renders the viewport from it, the others call `toasts.add()`.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
// src/toasts.ts
|
|
209
|
+
import { Toast } from "@lumiose/ui";
|
|
210
|
+
|
|
211
|
+
export const toasts = Toast.createToastManager();
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Styling
|
|
215
|
+
|
|
216
|
+
**With the default theme**, put `lu-toast-viewport` on `Viewport` and `lu-toast` on each `Root`: a
|
|
217
|
+
column at the window's bottom end, popover surfaces with the title, description and a 28px close
|
|
218
|
+
button, a slide in, a fade out, and a swipe that leaves in its direction. `data-type="error"`
|
|
219
|
+
adds a red edge.
|
|
220
|
+
|
|
221
|
+
**With your own CSS**, style the parts through `class` and the script's state:
|
|
222
|
+
|
|
223
|
+
- `[data-type]`, from the toast's `type`;
|
|
224
|
+
- `[data-starting-style]` on a new toast's first frame, and `[data-ending-style]` while it closes
|
|
225
|
+
(the script, or the manager, waits for its animations before removing it);
|
|
226
|
+
- `[data-swiping]`, `[data-swipe-direction]` (`up`, `down`, `left`, `right`) and the
|
|
227
|
+
`--lu-toast-swipe-movement-x` and `--lu-toast-swipe-movement-y` variables, in pixels, while a swipe
|
|
228
|
+
runs; `[data-swipe-direction]` stays while a swiped toast leaves;
|
|
229
|
+
- `--lu-toast-index` on each shown toast (0 for the first child), and `[data-limited]` on the ones
|
|
230
|
+
past the limit;
|
|
231
|
+
- `[data-expanded]` on the viewport while the pointer or keyboard focus is in it.
|
|
232
|
+
|
|
233
|
+
```css
|
|
234
|
+
.toast {
|
|
235
|
+
translate: var(--lu-toast-swipe-movement-x, 0) var(--lu-toast-swipe-movement-y, 0);
|
|
236
|
+
transition:
|
|
237
|
+
opacity 200ms,
|
|
238
|
+
translate 200ms;
|
|
239
|
+
}
|
|
240
|
+
.toast:is([data-starting-style], [data-ending-style]) {
|
|
241
|
+
opacity: 0;
|
|
242
|
+
}
|
|
243
|
+
.toast[data-swiping] {
|
|
244
|
+
transition: none;
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`@lumiose/ui/toast.css`, in the `lu.core` layer, makes the viewport a box fixed to the window's
|
|
249
|
+
bottom end, without a popover's default border, padding or centring; hides the toasts past the
|
|
250
|
+
limit whatever display your CSS gives them; sets `touch-action` on swipeable toasts, so a swipe
|
|
251
|
+
does not scroll the page along its axes, while pinch-zoom still works (where `touch-action` knows
|
|
252
|
+
it: not in Safari); and gives the page `scroll-padding-block-end` the height of the shown toasts
|
|
253
|
+
(`--lu-toast-stack-block-size`, which the script keeps on the root), so a control that takes focus
|
|
254
|
+
is scrolled clear of them (WCAG 2.4.11).
|
|
255
|
+
|
|
256
|
+
A modal dialog makes everything outside it inert, so while one is open the region moves into it,
|
|
257
|
+
and back when it closes or leaves the page.
|
|
258
|
+
|
|
259
|
+
## Keyboard and focus
|
|
260
|
+
|
|
261
|
+
| Key | Does |
|
|
262
|
+
| --------- | -------------------------------------------------------------------- |
|
|
263
|
+
| F6 | Focuses the toasts' region, when a toast shows. |
|
|
264
|
+
| Tab | Moves through the toasts' buttons. |
|
|
265
|
+
| Escape | In a toast: closes it. On the region: returns focus to where it was. |
|
|
266
|
+
| Shift+Tab | On the region: returns focus to where it was. |
|
|
267
|
+
|
|
268
|
+
- **When the focused toast closes**, focus moves to the next toast's first button, or back to
|
|
269
|
+
where it was before it entered the region (F6, Tab or a click).
|
|
270
|
+
- **Timers pause** while the pointer is over the region, while keyboard focus is in it, while the
|
|
271
|
+
window is in the background and while the page is hidden; they resume with the time that was
|
|
272
|
+
left. A press that focuses a toast pauses it only while the pointer stays, as in Base UI.
|
|
273
|
+
- **A swipe** closes the toast past 40px, unless it is pulled back 10px or more from its farthest
|
|
274
|
+
point before the release.
|
|
275
|
+
- **Swipes** have a single-pointer alternative, the close button, and a keyboard one, Escape
|
|
276
|
+
(WCAG 2.5.1 and 2.5.7). A swipe never starts on a button, link or field.
|
|
277
|
+
- **Time limits** (WCAG 2.2.1): a toast that times out must hold nothing the reader needs to act
|
|
278
|
+
on. Give a toast with an action, or with something to read twice, `timeout: 0`.
|
|
279
|
+
- **Over a modal dialog**: a modal dialog makes everything outside it inert, so while one is open
|
|
280
|
+
the region moves into it, and back when it closes. Toasts stay usable and announced.
|
|
281
|
+
- **Persistent**: a press in a toast does not close an open menu, popover or combobox.
|
|
282
|
+
|
|
283
|
+
## Without JavaScript, and the element's script
|
|
284
|
+
|
|
285
|
+
What is server-rendered and what needs the client:
|
|
286
|
+
|
|
287
|
+
| Server HTML | The element's script (2.6 kB, loaded once per page) |
|
|
288
|
+
| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
289
|
+
| The region and the toasts a page renders, with their text, roles and buttons. | The top layer (a manual popover), so toasts sit above dialogs and survive other popovers' light dismiss. |
|
|
290
|
+
| Toasts show at the window's bottom end (`toast.css`). | Timers, pausing, the limit, closing (button, Escape, swipe), F6, focus when a toast goes. |
|
|
291
|
+
| | The announcement mutation, and following modal dialogs. |
|
|
292
|
+
|
|
293
|
+
Without JavaScript a server-rendered toast stays on the page until the next one, and its close
|
|
294
|
+
button does nothing. The manager needs an island.
|
|
295
|
+
|
|
296
|
+
**Announcements.** Each toast is a live region, so screen readers read it without moving focus
|
|
297
|
+
(WCAG 4.1.3). Safari's VoiceOver misses the text of a region inserted with it, so 250ms after a
|
|
298
|
+
toast appears the script adds an invisible word joiner to it, a change VoiceOver reads, as Base UI
|
|
299
|
+
does. A toast in the page's HTML is announced the same way once the script runs.
|
|
300
|
+
|
|
301
|
+
## Browser support
|
|
302
|
+
|
|
303
|
+
- **Popover**: every current browser. Without it, toasts stay out of the top layer.
|
|
304
|
+
- **Invoker commands** (`Toast.Close`): Chrome 135, Firefox 144, Safari 26.2; elsewhere the
|
|
305
|
+
script handles the close button's click.
|
|
306
|
+
|
|
307
|
+
## Related
|
|
308
|
+
|
|
309
|
+
- [UI components](README.md): the approach, styling and the other components.
|
|
310
|
+
- [Dialog](dialog.md): a modal popup, which the toasts' region follows.
|
|
311
|
+
- [Data and actions](https://itsjavi.com/lumiose/docs/concepts/data-and-actions/): a form action's result, for a server-rendered toast.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Toggle Group
|
|
2
|
+
|
|
3
|
+
> A set of [Toggles](toggle.md) with one value, after Base UI's ToggleGroup: text alignment (one pressed at a time) or text style (several). `<lu-toggle-group role="group">` presses its toggles and gives the group one tab stop that the arrow keys, Home and End move, following the orientation and the direction, on server pages and in islands. `bind:value` keeps a signal in sync.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Toggle, ToggleGroup } from "@lumiose/ui"; // or "@lumiose/ui/toggle-group"
|
|
7
|
+
import "@lumiose/ui/themes/default.css"; // optional: the default theme
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Anatomy
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<ToggleGroup aria-label="Alignment">
|
|
14
|
+
<Toggle value="left">Left</Toggle>
|
|
15
|
+
<Toggle value="center">Center</Toggle>
|
|
16
|
+
</ToggleGroup>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Part | Renders |
|
|
20
|
+
| ------------- | ---------------------------------------------------------------------- |
|
|
21
|
+
| `ToggleGroup` | `<lu-toggle-group role="group" data-orientation>`; `multiple` if set |
|
|
22
|
+
| `Toggle` | `<button aria-pressed value>`; the group's tab stop has `tabindex="0"` |
|
|
23
|
+
|
|
24
|
+
## API
|
|
25
|
+
|
|
26
|
+
### ToggleGroup
|
|
27
|
+
|
|
28
|
+
| Prop | Type | Default | |
|
|
29
|
+
| --------------- | ------------------------------ | -------------- | -------------------------------------------------------------------------- |
|
|
30
|
+
| `value` | `string[]` | | Presses these toggles when it changes. Pair it with `onValueChange`. |
|
|
31
|
+
| `defaultValue` | `string[]` | `[]` | The toggles pressed at first. |
|
|
32
|
+
| `onValueChange` | `(value: string[]) => void` | | Called after the user presses or releases a toggle. |
|
|
33
|
+
| `bind:value` | `Signal<string[]>` | | Stands in for `value` and `onValueChange`. |
|
|
34
|
+
| `multiple` | `boolean` | `false` | Several toggles can be pressed; otherwise pressing one releases the other. |
|
|
35
|
+
| `orientation` | `"horizontal"` \| `"vertical"` | `"horizontal"` | Which arrow keys move focus. |
|
|
36
|
+
| `loopFocus` | `boolean` | `true` | The arrow keys go from the last toggle to the first, and back. |
|
|
37
|
+
| `disabled` | `boolean` | `false` | Disables every toggle. |
|
|
38
|
+
|
|
39
|
+
Every other prop passes through to `<lu-toggle-group>`: `aria-label`, `aria-labelledby`, `id`,
|
|
40
|
+
`class`. Name the group with one of the first two.
|
|
41
|
+
|
|
42
|
+
Each toggle's `value` is its identity in the group; its `onPressedChange` still fires. A
|
|
43
|
+
`pressedchange` event (`detail.pressed`) bubbles from a toggle before it changes, and
|
|
44
|
+
`event.preventDefault()` cancels the change.
|
|
45
|
+
|
|
46
|
+
**Differences from Base UI's ToggleGroup.** No `render`. Focus follows `tabindex` on the toggles
|
|
47
|
+
(roving), as Base UI does; disabled toggles are natively `disabled`, so focus skips them.
|
|
48
|
+
|
|
49
|
+
## Example
|
|
50
|
+
|
|
51
|
+
```tsx check
|
|
52
|
+
import { Toggle, ToggleGroup } from "@lumiose/ui";
|
|
53
|
+
import { signal } from "lumiose";
|
|
54
|
+
|
|
55
|
+
export function TextAlignment() {
|
|
56
|
+
const align = signal<string[]>(["left"]);
|
|
57
|
+
return (
|
|
58
|
+
<p>
|
|
59
|
+
<ToggleGroup aria-label="Text alignment" bind:value={align} class="lu-toggle-group">
|
|
60
|
+
<Toggle value="left" class="lu-toggle">
|
|
61
|
+
Left
|
|
62
|
+
</Toggle>
|
|
63
|
+
<Toggle value="center" class="lu-toggle">
|
|
64
|
+
Center
|
|
65
|
+
</Toggle>
|
|
66
|
+
<Toggle value="right" class="lu-toggle">
|
|
67
|
+
Right
|
|
68
|
+
</Toggle>
|
|
69
|
+
</ToggleGroup>{" "}
|
|
70
|
+
Aligned: {align()[0] ?? "none"}.
|
|
71
|
+
</p>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Styling
|
|
77
|
+
|
|
78
|
+
**With the default theme**, put `lu-toggle-group` on the group and `lu-toggle` on each toggle: the
|
|
79
|
+
toggles in a row with a small gap, in a column when `data-orientation="vertical"`.
|
|
80
|
+
|
|
81
|
+
**With your own CSS**, style the group by `[data-orientation]` and `[multiple]`, and the toggles by
|
|
82
|
+
`[aria-pressed="true"]`, `:focus-visible` and `:disabled`.
|
|
83
|
+
|
|
84
|
+
Toggle Group ships no stylesheet: it needs none to work.
|
|
85
|
+
|
|
86
|
+
## Keyboard and focus
|
|
87
|
+
|
|
88
|
+
| Key | Does |
|
|
89
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
90
|
+
| Tab | Enters the group on the last focused toggle (at first, the first pressed or the first enabled), and leaves it. |
|
|
91
|
+
| Arrow Right / Arrow Left | Horizontal: the next or previous toggle (swapped right to left), looping. |
|
|
92
|
+
| Arrow Down / Arrow Up | Vertical: the next or previous toggle, looping. |
|
|
93
|
+
| Home / End | The first or last enabled toggle. |
|
|
94
|
+
| Enter / Space | Presses or releases the focused toggle. |
|
|
95
|
+
|
|
96
|
+
Arrows move focus without pressing, and skip disabled toggles. With `loopFocus={false}` they stop at
|
|
97
|
+
the ends.
|
|
98
|
+
|
|
99
|
+
Inside a [Toolbar](toolbar.md), the group still presses its toggles, but the toolbar's arrow keys
|
|
100
|
+
and single tab stop take over: the toggles are toolbar items.
|
|
101
|
+
|
|
102
|
+
## Without JavaScript, and the element's script
|
|
103
|
+
|
|
104
|
+
The server HTML has each toggle's `aria-pressed` and the tab stop. `<lu-toggle-group>`'s script
|
|
105
|
+
(0.9 kB brotli) presses the toggles and moves focus, on server pages and in islands; when the tab
|
|
106
|
+
stop's toggle goes or is disabled, or a disabled group is enabled, the first pressed (else enabled)
|
|
107
|
+
toggle takes the tab stop. With no script
|
|
108
|
+
at all the toggles show their state but do not change. For values a form submits, use a
|
|
109
|
+
[Checkbox Group](checkbox-group.md) or a [Radio](radio-group.md) group.
|
|
110
|
+
|
|
111
|
+
## Browser support
|
|
112
|
+
|
|
113
|
+
Custom elements and `aria-pressed`: every supported browser.
|
|
114
|
+
|
|
115
|
+
## Related
|
|
116
|
+
|
|
117
|
+
- [Toggle](toggle.md): one toggle, on its own or in a group.
|
|
118
|
+
- [Radio](radio-group.md): one choice submitted with a form.
|
|
119
|
+
- [Tabs](tabs.md): toggles that switch panels.
|