@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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Separator
|
|
2
|
+
|
|
3
|
+
> A native `<hr>` between groups of content, after Base UI's Separator: announced as a separator, horizontal by default, or vertical between the items of a row with `aria-orientation="vertical"`. No script.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Separator } from "@lumiose/ui"; // or "@lumiose/ui/separator"
|
|
7
|
+
import "@lumiose/ui/separator.css"; // a vertical separator's margins and height in a row
|
|
8
|
+
import "@lumiose/ui/themes/default.css"; // optional: the default theme
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Separator />
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| Part | Renders |
|
|
18
|
+
| ----------- | ------------------------------------------------------------------------------------ |
|
|
19
|
+
| `Separator` | `<hr data-orientation="…">`, with `aria-orientation="vertical"` when it is vertical. |
|
|
20
|
+
|
|
21
|
+
An `<hr>` is a separator for assistive technology with no role to add. It is flow content: it
|
|
22
|
+
cannot go inside a `<p>`, a `<span>` or a `<button>`, where the browser would move it out of the
|
|
23
|
+
element.
|
|
24
|
+
|
|
25
|
+
## API
|
|
26
|
+
|
|
27
|
+
### Separator
|
|
28
|
+
|
|
29
|
+
| Prop | Type | Default | |
|
|
30
|
+
| ------------- | ---------------------------- | -------------- | ------------------------------------------------------------------------- |
|
|
31
|
+
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Which way it runs: `"vertical"` between the items of a row (a toolbar's). |
|
|
32
|
+
|
|
33
|
+
Every other native prop passes through: `class`, `style`, `id`, `aria-*`, `data-*`.
|
|
34
|
+
|
|
35
|
+
**Differences from Base UI's Separator.** It is always an `<hr>`, not a `<div role="separator">`:
|
|
36
|
+
no `render`. A horizontal one leaves out `aria-orientation="horizontal"`, an `<hr>`'s default.
|
|
37
|
+
|
|
38
|
+
## Example
|
|
39
|
+
|
|
40
|
+
Links in a row, split into groups, styled by the default theme:
|
|
41
|
+
|
|
42
|
+
```tsx check
|
|
43
|
+
import { Separator } from "@lumiose/ui";
|
|
44
|
+
|
|
45
|
+
export function FooterLinks() {
|
|
46
|
+
return (
|
|
47
|
+
<nav aria-label="Footer" style={{ display: "flex", gap: "0.75rem", "align-items": "center" }}>
|
|
48
|
+
<a href="#docs">Docs</a>
|
|
49
|
+
<a href="#blog">Blog</a>
|
|
50
|
+
<Separator orientation="vertical" class="lu-separator" />
|
|
51
|
+
<a href="#privacy">Privacy</a>
|
|
52
|
+
<a href="#terms">Terms</a>
|
|
53
|
+
</nav>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Between sections of a page or a card, a horizontal one:
|
|
59
|
+
|
|
60
|
+
```tsx check
|
|
61
|
+
import { Separator } from "@lumiose/ui";
|
|
62
|
+
|
|
63
|
+
export function AccountCard() {
|
|
64
|
+
return (
|
|
65
|
+
<section aria-label="Account">
|
|
66
|
+
<p>Signed in as ada@example.com</p>
|
|
67
|
+
<Separator class="lu-separator" />
|
|
68
|
+
<p>Plan: Free</p>
|
|
69
|
+
</section>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Styling
|
|
75
|
+
|
|
76
|
+
**With the default theme**, put `lu-separator` on it: a 1px line in `--lu-border`, with a margin
|
|
77
|
+
above and below when horizontal, and as tall as its row when vertical.
|
|
78
|
+
|
|
79
|
+
**With your own CSS**, style it through `class` and `[data-orientation]`. Reset the borders an
|
|
80
|
+
`<hr>` has by default, and draw the line with a background:
|
|
81
|
+
|
|
82
|
+
```css
|
|
83
|
+
.separator {
|
|
84
|
+
border: 0;
|
|
85
|
+
background: #e4e4e7;
|
|
86
|
+
}
|
|
87
|
+
.separator[data-orientation="horizontal"] {
|
|
88
|
+
block-size: 1px;
|
|
89
|
+
margin: 1rem 0;
|
|
90
|
+
}
|
|
91
|
+
.separator[data-orientation="vertical"] {
|
|
92
|
+
inline-size: 1px;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`@lumiose/ui/separator.css`, in the `lu.core` layer, gives a vertical separator no margins (an
|
|
97
|
+
`<hr>`'s automatic ones would push the row's items apart) and the full height of its row, even one
|
|
98
|
+
that centres its items (`align-self: stretch`).
|
|
99
|
+
|
|
100
|
+
## Keyboard and focus
|
|
101
|
+
|
|
102
|
+
A separator takes no focus and has no keys: it only structures the content around it.
|
|
103
|
+
|
|
104
|
+
## Without JavaScript, and the client script
|
|
105
|
+
|
|
106
|
+
A native `<hr>` with attributes: it needs no script, on the server or in an island.
|
|
107
|
+
|
|
108
|
+
## Browser support
|
|
109
|
+
|
|
110
|
+
Native `<hr>` and `aria-orientation`: every browser.
|
|
111
|
+
|
|
112
|
+
## Related
|
|
113
|
+
|
|
114
|
+
- [Menu](menu.md): `Menu.Separator` between a menu's items.
|
|
115
|
+
- [Select](select.md): separators between a select's options.
|
|
116
|
+
- [UI components](README.md): the approach, styling and the other components.
|
package/docs/slider.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Slider
|
|
2
|
+
|
|
3
|
+
> A value picked within a range by dragging or with the keys, or a range with two thumbs, after Base UI's Slider. Each thumb is a native `<input type="range">`, so it drags, takes the arrow keys, Home and End, follows the text direction and submits with its form, in server HTML and without JavaScript. `<lu-slider>` adds Page Up and Page Down, keeps a range's thumbs in order, moves the nearest thumb on a press on the track and keeps the value text current. `bind:value` keeps a signal in sync.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Slider } from "@lumiose/ui"; // or "@lumiose/ui/slider"
|
|
7
|
+
import "@lumiose/ui/slider.css"; // or "@lumiose/ui/styles.css" for every component
|
|
8
|
+
import "@lumiose/ui/themes/default.css"; // optional: the default theme
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Slider.Root>
|
|
15
|
+
<Slider.Label />
|
|
16
|
+
<Slider.Value />
|
|
17
|
+
<Slider.Control>
|
|
18
|
+
<Slider.Track>
|
|
19
|
+
<Slider.Indicator />
|
|
20
|
+
</Slider.Track>
|
|
21
|
+
<Slider.Thumb />
|
|
22
|
+
</Slider.Control>
|
|
23
|
+
</Slider.Root>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Part | Renders |
|
|
27
|
+
| ----------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
28
|
+
| `Root` | `<lu-slider data-orientation data-thumb-alignment>` with `--lu-slider-start` and `--lu-slider-end` |
|
|
29
|
+
| `Label` | `<label for>` the first thumb; thumbs without an `aria-label` are labelled by it |
|
|
30
|
+
| `Value` | `<output for>` the thumbs, with the formatted values (`20 – 80`) |
|
|
31
|
+
| `Control` | `<div>`: the track and the thumbs stacked in one place; presses on it move a thumb. A range's is a `role="group"` the Label names |
|
|
32
|
+
| `Track` | `<div>`: the rail |
|
|
33
|
+
| `Indicator` | `<div>`: the selected part, from `--lu-slider-start` to `--lu-slider-end` |
|
|
34
|
+
| `Thumb` | `<input type="range" min max step value name>`, one per value, in order |
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
### Slider.Root
|
|
39
|
+
|
|
40
|
+
| Prop | Type | Default | |
|
|
41
|
+
| ------------------------ | -------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `value` | `number \| number[]` | | Moves the thumbs when it changes (an array: one value per thumb). Pair it with `onValueChange`. |
|
|
43
|
+
| `defaultValue` | `number \| number[]` | `min` | The value at first; a form reset goes back to it. |
|
|
44
|
+
| `onValueChange` | `(value) => void` | | Called with the new value as a thumb moves. |
|
|
45
|
+
| `onValueCommitted` | `(value) => void` | | Called when a move ends: a release, a key press. |
|
|
46
|
+
| `bind:value` | `Signal<number \| number[]>` | | Stands in for `value` and `onValueChange`. |
|
|
47
|
+
| `min` | `number` | `0` | The smallest value. |
|
|
48
|
+
| `max` | `number` | `100` | The largest value. |
|
|
49
|
+
| `step` | `number` | `1` | The step of the arrow keys; values snap to it. |
|
|
50
|
+
| `largeStep` | `number` | `10` | The step of Page Up, Page Down and Shift with an arrow key. |
|
|
51
|
+
| `minStepsBetweenValues` | `number` | `0` | With several thumbs: how many steps apart they stay. |
|
|
52
|
+
| `thumbCollisionBehavior` | `"push"` \| `"swap"` \| `"none"` | `"push"` | With several thumbs: a thumb moved into another pushes it along, swaps places with it (a drag), or stops at it. |
|
|
53
|
+
| `thumbAlignment` | `"edge"` \| `"center"` | `"edge"` | The thumbs stop inside the control at its ends, or with their centre on its ends (overhanging it). |
|
|
54
|
+
| `orientation` | `"horizontal"` \| `"vertical"` | `"horizontal"` | Vertical sliders run bottom to top. |
|
|
55
|
+
| `disabled` | `boolean` | `false` | Disables every thumb. |
|
|
56
|
+
| `name` | `string` | | The thumbs submit their values under it, one entry each. |
|
|
57
|
+
| `form` | `string` | | The id of a form the thumbs belong to, when they are outside it. |
|
|
58
|
+
| `format` | `Intl.NumberFormatOptions` | plain numbers | How the values are written; with it, each thumb's `aria-valuetext` is its formatted value. Options or a `locale` the browser refuses fall back to its defaults. |
|
|
59
|
+
| `locale` | `string` | the browser's | The locale of the formatted values (the server's in server HTML). |
|
|
60
|
+
|
|
61
|
+
The value's type follows `value`, `defaultValue` or `bind:value`: a number for one thumb, a
|
|
62
|
+
`number[]` for a range. Every other prop passes through to `<lu-slider>`: `id`, `class`, `style`.
|
|
63
|
+
|
|
64
|
+
### The other parts
|
|
65
|
+
|
|
66
|
+
`Label` takes `<label>` props, its `id` among them, which the `Control` and the thumbs point at. `Value` takes `<output>` props, and its `children` may be a function
|
|
67
|
+
`(formattedValues, values) => JSX.Element` (then it has no `for`, and you render its text). `Control`,
|
|
68
|
+
`Track` and `Indicator` take `<div>` props. `Thumb` takes `<input>` props except the range's own
|
|
69
|
+
(`aria-label`, `aria-valuetext`, `disabled`, `class`). A thumb keeps its own `id`, which the
|
|
70
|
+
`Label` and the `Value` before it point at (in a Field, the first thumb's is the field's control;
|
|
71
|
+
see [Ids](README.md#ids)). Give each thumb of a range an
|
|
72
|
+
`aria-label` with the label's text in it ("Minimum price" under "Price"), so speech users can say
|
|
73
|
+
what they see (WCAG 2.5.3); the Label names the range's group (`Control`), so screen readers
|
|
74
|
+
announce "Price, group" before the thumbs. Inside a Field, the first thumb takes the field's id,
|
|
75
|
+
name, description and error.
|
|
76
|
+
|
|
77
|
+
- **`thumbCollisionBehavior="swap"`.** A thumb dragged past another (with the pointer, or a press
|
|
78
|
+
on the track) trades places with it: the values stay in order, the other thumb takes the
|
|
79
|
+
dragged value and focus, and the drag goes on with it. A native drag keeps its own thumb, so
|
|
80
|
+
that thumb stays put and hands its value on. The keys stop at the other thumb, as with
|
|
81
|
+
`"none"`, and `minStepsBetweenValues` applies to keys only.
|
|
82
|
+
- **Blocked moves.** A move that changes no value, such as a thumb pushed against its neighbour
|
|
83
|
+
with `"none"`, fires no `input` or `change`, so `onValueChange` and `onValueCommitted` do not
|
|
84
|
+
run.
|
|
85
|
+
- **`thumbAlignment` and `--lu-slider-thumb-size`.** A native thumb stays inside its input. With
|
|
86
|
+
the thumbs' size in `--lu-slider-thumb-size` (the default theme sets it), slider.css runs the
|
|
87
|
+
track between the thumbs' centres at the ends, so the Indicator ends at a thumb's centre
|
|
88
|
+
whatever the value (`"edge"`); with `"center"` the inputs outgrow the control by half a thumb
|
|
89
|
+
at each end, so the thumbs' centres reach its ends and the track spans it. Without the
|
|
90
|
+
variable, the Indicator can end up to half a thumb from its centre.
|
|
91
|
+
|
|
92
|
+
**Differences from Base UI's Slider.** Thumbs are the range inputs themselves, so there is no hidden
|
|
93
|
+
input, no `inputRef` and no `getAriaLabel` (use `aria-label` on each Thumb). `thumbAlignment`
|
|
94
|
+
defaults to `"edge"` (Base UI's to `"center"`), and has no `"edge-client-only"`: the server HTML
|
|
95
|
+
is already right. `format` is `Intl.NumberFormatOptions` and `locale` a string, both written to
|
|
96
|
+
the element for its script.
|
|
97
|
+
|
|
98
|
+
## Example
|
|
99
|
+
|
|
100
|
+
A price range, in an island:
|
|
101
|
+
|
|
102
|
+
```tsx check
|
|
103
|
+
import { Slider } from "@lumiose/ui";
|
|
104
|
+
import { signal } from "lumiose";
|
|
105
|
+
|
|
106
|
+
export function PriceRange() {
|
|
107
|
+
const price = signal([20, 80]);
|
|
108
|
+
return (
|
|
109
|
+
<Slider.Root
|
|
110
|
+
name="price"
|
|
111
|
+
bind:value={price}
|
|
112
|
+
minStepsBetweenValues={5}
|
|
113
|
+
format={{ style: "currency", currency: "EUR", maximumFractionDigits: 0 }}
|
|
114
|
+
class="lu-slider"
|
|
115
|
+
>
|
|
116
|
+
<Slider.Label>Price</Slider.Label>
|
|
117
|
+
<Slider.Value />
|
|
118
|
+
<Slider.Control>
|
|
119
|
+
<Slider.Track>
|
|
120
|
+
<Slider.Indicator />
|
|
121
|
+
</Slider.Track>
|
|
122
|
+
<Slider.Thumb aria-label="Minimum price" />
|
|
123
|
+
<Slider.Thumb aria-label="Maximum price" />
|
|
124
|
+
</Slider.Control>
|
|
125
|
+
</Slider.Root>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Styling
|
|
131
|
+
|
|
132
|
+
**With the default theme**, put `lu-slider` on `Root`: the label and the value on a row above a 24px
|
|
133
|
+
tall control, an 8px track edged in `--lu-input` (3:1), the selected part in the primary colour,
|
|
134
|
+
and 24px round thumbs (`--lu-slider-thumb-size`) with a focus ring around the thumb. Vertical
|
|
135
|
+
sliders are 10rem tall. A center-aligned control is inset by half a thumb, so its thumbs overhang
|
|
136
|
+
it inside the slider's box.
|
|
137
|
+
|
|
138
|
+
**With your own CSS**, style the parts through `class`, the thumbs' pseudo-elements
|
|
139
|
+
(`::-webkit-slider-thumb`, `::-moz-range-thumb`), `[data-orientation="vertical"]`, `:disabled`,
|
|
140
|
+
`:focus-visible`, and the Indicator through `--lu-slider-start` and `--lu-slider-end`. Over a Track,
|
|
141
|
+
the thumbs' own tracks are transparent (`appearance: none`). Set `--lu-slider-thumb-size` to your
|
|
142
|
+
thumbs' size on the Root, so the track lines up with their centres.
|
|
143
|
+
|
|
144
|
+
`@lumiose/ui/slider.css` (0.4 kB brotli) stacks the track and thumbs, spans the thumbs over the
|
|
145
|
+
control and the Indicator over the selected part, lines the track up with the thumbs' centres
|
|
146
|
+
(`--lu-slider-thumb-size`, `data-thumb-alignment`), writes vertical thumbs bottom to top and lets
|
|
147
|
+
only the thumbs of a range take the pointer.
|
|
148
|
+
|
|
149
|
+
## Keyboard and focus
|
|
150
|
+
|
|
151
|
+
| Key | Does |
|
|
152
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------- |
|
|
153
|
+
| Tab | Moves between the thumbs, then on: one tab stop per thumb. |
|
|
154
|
+
| Arrow Right / Arrow Up | One step up (Arrow Left goes up right to left). |
|
|
155
|
+
| Arrow Left / Arrow Down | One step down (Arrow Right goes down right to left). |
|
|
156
|
+
| Page Up / Page Down | `largeStep` up or down. |
|
|
157
|
+
| Shift + an arrow | `largeStep` in that direction. |
|
|
158
|
+
| Home / End | The minimum or the maximum; in a range, at most the neighbouring thumb, which they never push. |
|
|
159
|
+
|
|
160
|
+
Every drag has these keys as its alternative (WCAG 2.5.7). A press on a range's track moves the
|
|
161
|
+
nearest thumb there and focuses it; a press on a single slider's track moves its thumb.
|
|
162
|
+
|
|
163
|
+
## Without JavaScript, and the element's script
|
|
164
|
+
|
|
165
|
+
The thumbs are native range inputs: dragging, the arrows, Home, End, right to left, vertical,
|
|
166
|
+
submission, `disabled` and reset work with no script, and the server HTML has the Indicator's
|
|
167
|
+
custom properties and the value text. `<lu-slider>`'s script (2.0 kB brotli) adds Page Up and Page
|
|
168
|
+
Down, runs the arrow keys the same in every browser (WebKit's native range does not mirror them
|
|
169
|
+
right to left), stops Home and End at a range's neighbouring thumb, keeps a range's thumbs in order and apart (or swaps them), moves the nearest thumb
|
|
170
|
+
on a press on its track, and keeps the Indicator, `aria-valuetext` and the Value text current (also
|
|
171
|
+
after a form reset).
|
|
172
|
+
Without it, a range's thumbs can cross, and the Value text and the Indicator stay where the server
|
|
173
|
+
put them.
|
|
174
|
+
|
|
175
|
+
## Browser support
|
|
176
|
+
|
|
177
|
+
Native range inputs: every browser. Vertical sliders use `writing-mode: vertical-lr` on the thumbs
|
|
178
|
+
(Chrome 124, Safari 17.4, Firefox 120 and later).
|
|
179
|
+
|
|
180
|
+
## Related
|
|
181
|
+
|
|
182
|
+
- [Field](field.md): a label, description and error for a slider.
|
|
183
|
+
- [Meter](meter.md): show a value in a range, without picking it.
|
|
184
|
+
- [Input](input.md): `type="number"` for an exact value.
|
package/docs/switch.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Switch
|
|
2
|
+
|
|
3
|
+
> On or off, for a setting: a native `<input type="checkbox" role="switch">`, after Base UI's Switch. Space and a label click toggle it, the form submits its value when on, `required` and reset work, in server HTML and without JavaScript, and assistive technology announces a switch. `bind:checked` keeps a signal in sync.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Switch } from "@lumiose/ui"; // or "@lumiose/ui/switch"
|
|
7
|
+
import "@lumiose/ui/themes/default.css"; // optional: the default theme
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Anatomy
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<label>
|
|
14
|
+
<Switch.Root /> Label
|
|
15
|
+
</label>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
| Part | Renders |
|
|
19
|
+
| ------ | ---------------------------------------------------------------------- |
|
|
20
|
+
| `Root` | `<input type="checkbox" role="switch">`; `aria-readonly` if read-only. |
|
|
21
|
+
|
|
22
|
+
## API
|
|
23
|
+
|
|
24
|
+
### Switch.Root
|
|
25
|
+
|
|
26
|
+
| Prop | Type | Default | |
|
|
27
|
+
| ----------------- | ---------------------------- | ------- | ----------------------------------------------------------------------------------- |
|
|
28
|
+
| `checked` | `boolean` | | Turns it on or off when it changes. Pair it with `onCheckedChange`. |
|
|
29
|
+
| `defaultChecked` | `boolean` | `false` | Whether it starts on; a form reset goes back to it. |
|
|
30
|
+
| `onCheckedChange` | `(checked: boolean) => void` | | Called after the user turns it on or off. |
|
|
31
|
+
| `bind:checked` | `Signal<boolean>` | | Stands in for `checked` and `onCheckedChange`. |
|
|
32
|
+
| `value` | `string` | `"on"` | What it submits when on. |
|
|
33
|
+
| `readOnly` | `boolean` | `false` | It cannot be toggled, but stays focusable and is submitted. |
|
|
34
|
+
| `uncheckedValue` | `string` | | What it submits when off (by the script); by default an off switch submits nothing. |
|
|
35
|
+
|
|
36
|
+
Every other native prop passes through: `name`, `required`, `disabled`, `form`, `id`, `class`,
|
|
37
|
+
`aria-*`. Inside a Field it takes the field's wiring.
|
|
38
|
+
|
|
39
|
+
**Differences from Base UI's Switch.** The switch is the native input, so there is no
|
|
40
|
+
`Switch.Thumb`: draw the track and thumb with `appearance: none` and `::before`. No `nativeButton` or
|
|
41
|
+
`inputRef`; `uncheckedValue` needs the client script (without it an off switch submits nothing). `<input switch>` exists only in
|
|
42
|
+
Safari, so `role="switch"` stays on a checkbox everywhere.
|
|
43
|
+
|
|
44
|
+
## Example
|
|
45
|
+
|
|
46
|
+
In an island, bound to a signal:
|
|
47
|
+
|
|
48
|
+
```tsx check
|
|
49
|
+
import { Switch } from "@lumiose/ui";
|
|
50
|
+
import { signal } from "lumiose";
|
|
51
|
+
|
|
52
|
+
export function AirplaneMode() {
|
|
53
|
+
const on = signal(false);
|
|
54
|
+
return (
|
|
55
|
+
<p>
|
|
56
|
+
<label>
|
|
57
|
+
<Switch.Root name="airplane" bind:checked={on} class="lu-switch" /> Airplane mode
|
|
58
|
+
</label>{" "}
|
|
59
|
+
{on() ? "Radios off." : ""}
|
|
60
|
+
</p>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Styling
|
|
66
|
+
|
|
67
|
+
**With the default theme**, put `lu-switch` on `Root`: a 40 by 24px track (`--lu-input` when off,
|
|
68
|
+
the primary colour when on) and a white thumb on the side that tells on from off, a focus ring.
|
|
69
|
+
|
|
70
|
+
**With your own CSS**, style the input and its state: `:checked`, `:disabled`, `:focus-visible`,
|
|
71
|
+
`[aria-readonly="true"]`, `[aria-invalid="true"]`.
|
|
72
|
+
|
|
73
|
+
Switch ships no stylesheet: it needs none to work.
|
|
74
|
+
|
|
75
|
+
## Keyboard and focus
|
|
76
|
+
|
|
77
|
+
| Key | Does |
|
|
78
|
+
| ----- | ---------------------------------------- |
|
|
79
|
+
| Space | Turns it on or off (not when read-only). |
|
|
80
|
+
| Tab | Moves on: one tab stop. |
|
|
81
|
+
|
|
82
|
+
Enter does not toggle it. A click on its label does.
|
|
83
|
+
|
|
84
|
+
## Without JavaScript, and the client script
|
|
85
|
+
|
|
86
|
+
A native checkbox with the switch role: toggling, submission, `required`, reset and `disabled`
|
|
87
|
+
work with no script. The shared checkbox script (1.0 kB brotli,
|
|
88
|
+
[Checkbox](checkbox.md#without-javascript-and-the-client-script)) adds read-only and
|
|
89
|
+
`uncheckedValue`.
|
|
90
|
+
|
|
91
|
+
## Browser support
|
|
92
|
+
|
|
93
|
+
`role="switch"` on a checkbox: every supported browser and screen reader.
|
|
94
|
+
|
|
95
|
+
## Related
|
|
96
|
+
|
|
97
|
+
- [Checkbox](checkbox.md): a choice to confirm, submitted with a form.
|
|
98
|
+
- [Field](field.md): a description and errors for a switch.
|
package/docs/tabs.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Tabs
|
|
2
|
+
|
|
3
|
+
> A tab list and the panel of the selected tab, after the WAI-ARIA tabs pattern: the server HTML holds the whole state (roles, `aria-selected`, the roving tab stop, the hidden panels), and `<lu-tabs>`, a 1.2 kB light-DOM element, adds the arrow keys and the selection. Manual or automatic activation, horizontal or vertical, right-to-left aware, with an optional indicator that slides to the selected tab; `bind:value` (or `value` and `onValueChange`) keeps a signal in sync.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Tabs } from "@lumiose/ui"; // or "@lumiose/ui/tabs"
|
|
7
|
+
import "@lumiose/ui/tabs.css"; // makes <lu-tabs> a block and places the indicator
|
|
8
|
+
import "@lumiose/ui/themes/default.css"; // optional: the default theme
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Tabs.Root>
|
|
15
|
+
<Tabs.List>
|
|
16
|
+
<Tabs.Tab />
|
|
17
|
+
<Tabs.Indicator />
|
|
18
|
+
</Tabs.List>
|
|
19
|
+
<Tabs.Panel />
|
|
20
|
+
</Tabs.Root>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
| Part | Renders |
|
|
24
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
25
|
+
| `Root` | `<lu-tabs>`, the element that handles the keys and the selection. |
|
|
26
|
+
| `List` | `<div role="tablist">`, with `aria-orientation="vertical"` when vertical. Name it with `aria-label` or `aria-labelledby`. |
|
|
27
|
+
| `Tab` | `<button type="button" role="tab" value="…" aria-selected="…" aria-controls="…" tabindex="…">`. |
|
|
28
|
+
| `Indicator` | `<span aria-hidden="true" data-indicator>`, over the selected tab's box and behind its text. Optional. |
|
|
29
|
+
| `Panel` | `<div role="tabpanel" tabindex="0" aria-labelledby="…">`, `hidden` unless its tab is selected. |
|
|
30
|
+
|
|
31
|
+
A `Tab` and a `Panel` with the same `value` belong together. Every panel stays in the page, hidden
|
|
32
|
+
when its tab is not selected, so the browser can switch them before any script loads. Panels go
|
|
33
|
+
inside their `Tabs.Root`: `<lu-tabs>` shows and hides only its own panels, whatever ids its tabs
|
|
34
|
+
name.
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
### Tabs.Root
|
|
39
|
+
|
|
40
|
+
| Prop | Type | Default | |
|
|
41
|
+
| --------------- | ---------------------------- | --------------------- | ------------------------------------------------ |
|
|
42
|
+
| `value` | `string` | | Selects the tab with this value when it changes. |
|
|
43
|
+
| `defaultValue` | `string` | the first enabled tab | The tab selected at first, without `value`. |
|
|
44
|
+
| `onValueChange` | `(value: string) => void` | | Called after the user selects another tab. |
|
|
45
|
+
| `bind:value` | `Signal<string>` | | Stands in for `value` and `onValueChange`. |
|
|
46
|
+
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Which arrow keys move between the tabs. |
|
|
47
|
+
|
|
48
|
+
`Root` also takes the props of an element (`class`, `style`, `data-*`, events) and passes them to
|
|
49
|
+
`<lu-tabs>`.
|
|
50
|
+
|
|
51
|
+
### Tabs.List
|
|
52
|
+
|
|
53
|
+
| Prop | Type | Default | |
|
|
54
|
+
| ----------------- | --------- | ------- | ------------------------------------------------------------------------------------------ |
|
|
55
|
+
| `activateOnFocus` | `boolean` | `false` | Selects a tab as soon as it receives focus (automatic activation), as Zag does by default. |
|
|
56
|
+
| `loopFocus` | `boolean` | `true` | The arrow keys go from the last tab to the first and back. |
|
|
57
|
+
|
|
58
|
+
The element reads them from the list's attributes: `data-activate-on-focus` and
|
|
59
|
+
`data-loop-focus="false"`.
|
|
60
|
+
|
|
61
|
+
### Tabs.Tab and Tabs.Panel
|
|
62
|
+
|
|
63
|
+
| Prop | On | Type | |
|
|
64
|
+
| ---------- | ----- | --------- | ------------------------------------------------------------- |
|
|
65
|
+
| `value` | both | `string` | Pairs a tab with its panel. Required. |
|
|
66
|
+
| `disabled` | `Tab` | `boolean` | The tab takes focus but cannot be selected (`aria-disabled`). |
|
|
67
|
+
|
|
68
|
+
Each takes the props of the element it renders. A `Tab`'s `id` names it, and its `Panel` points at
|
|
69
|
+
it; a `Panel` keeps its own `id`, which its tab points at (see [Ids](README.md#ids)). `role`,
|
|
70
|
+
`value`, `aria-selected`, `aria-controls`, `aria-labelledby`, `tabindex` on tabs and `hidden` stay
|
|
71
|
+
the parts' own.
|
|
72
|
+
|
|
73
|
+
- **Disabled tabs.** A selected tab that becomes disabled gives its selection to the first enabled
|
|
74
|
+
tab. With every tab disabled, none is selected and the first tab is the tab stop. There is one
|
|
75
|
+
tab stop: in the server HTML, the tab `value` or `defaultValue` names (a disabled one keeps it
|
|
76
|
+
until the script selects another), else the first enabled tab; once the script runs, the
|
|
77
|
+
selected tab.
|
|
78
|
+
- **The element.** `<lu-tabs>`' `value` property reads and sets the selection, and it fires
|
|
79
|
+
`valuechange` (not bubbling) when the user changes it, so `bind:value` works on the element too.
|
|
80
|
+
|
|
81
|
+
### Tabs.Indicator
|
|
82
|
+
|
|
83
|
+
Takes the props of a `<span>`, without children. Put it inside `Tabs.List`, after the tabs. It is
|
|
84
|
+
`hidden` while no tab can be selected.
|
|
85
|
+
|
|
86
|
+
- **Placed before any script.** CSS anchor positioning puts it over the selected tab from the
|
|
87
|
+
server HTML on (the selected tab carries the anchor `Root` names in `--lu-tabs-anchor`), so it
|
|
88
|
+
is in place at the first paint and nothing shifts when the script loads (doc-9 §8).
|
|
89
|
+
- **Then measured.** Once `<lu-tabs>`' script runs, it writes the selected tab's box, from the
|
|
90
|
+
list's padding edge, on the Root as `--lu-tabs-indicator-left`, `--lu-tabs-indicator-top`,
|
|
91
|
+
`--lu-tabs-indicator-width` and `--lu-tabs-indicator-height`, and keeps them current as the
|
|
92
|
+
selection changes (a click, the keys, an island), as the list or a tab resizes, and as a `dir`
|
|
93
|
+
attribute changes in the page. The indicator reads them over its anchor, so a `transition` can
|
|
94
|
+
slide it from tab to tab, and they place it in browsers without anchor positioning.
|
|
95
|
+
- **Behind the text.** The list isolates its stacking (`isolation: isolate`) and the indicator
|
|
96
|
+
sits at `z-index: -1`, so a highlight never covers a tab's text.
|
|
97
|
+
|
|
98
|
+
**Differences from Base UI's Tabs.Indicator.** The variables are on the Root, with these names,
|
|
99
|
+
instead of Base UI's `--active-tab-*` on the indicator, and there is no `renderBeforeHydration`:
|
|
100
|
+
anchor positioning places it from the server HTML. No `data-activation-direction`.
|
|
101
|
+
|
|
102
|
+
## Example
|
|
103
|
+
|
|
104
|
+
```tsx check
|
|
105
|
+
import { Tabs } from "@lumiose/ui";
|
|
106
|
+
|
|
107
|
+
export function AccountSettings() {
|
|
108
|
+
return (
|
|
109
|
+
<Tabs.Root class="lu-tabs" defaultValue="account">
|
|
110
|
+
<Tabs.List aria-label="Account settings">
|
|
111
|
+
<Tabs.Tab value="account">Account</Tabs.Tab>
|
|
112
|
+
<Tabs.Tab value="password">Password</Tabs.Tab>
|
|
113
|
+
<Tabs.Tab value="billing" disabled>
|
|
114
|
+
Billing
|
|
115
|
+
</Tabs.Tab>
|
|
116
|
+
</Tabs.List>
|
|
117
|
+
<Tabs.Panel value="account">Your name and email.</Tabs.Panel>
|
|
118
|
+
<Tabs.Panel value="password">Change your password.</Tabs.Panel>
|
|
119
|
+
<Tabs.Panel value="billing">Invoices and payment methods.</Tabs.Panel>
|
|
120
|
+
</Tabs.Root>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
In an island, bound to a signal: `bind:value={tab}` is short for `value={tab()}` plus
|
|
126
|
+
`onValueChange={(next) => tab.set(next)}`.
|
|
127
|
+
|
|
128
|
+
```tsx check
|
|
129
|
+
import { Tabs } from "@lumiose/ui";
|
|
130
|
+
import { signal } from "lumiose";
|
|
131
|
+
|
|
132
|
+
export function Project() {
|
|
133
|
+
const tab = signal("overview");
|
|
134
|
+
return (
|
|
135
|
+
<>
|
|
136
|
+
<Tabs.Root class="lu-tabs" bind:value={tab}>
|
|
137
|
+
<Tabs.List aria-label="Project" activateOnFocus>
|
|
138
|
+
<Tabs.Tab value="overview">Overview</Tabs.Tab>
|
|
139
|
+
<Tabs.Tab value="settings">Settings</Tabs.Tab>
|
|
140
|
+
</Tabs.List>
|
|
141
|
+
<Tabs.Panel value="overview">The project at a glance.</Tabs.Panel>
|
|
142
|
+
<Tabs.Panel value="settings">Name and members.</Tabs.Panel>
|
|
143
|
+
</Tabs.Root>
|
|
144
|
+
<button type="button" onClick={() => tab.set("settings")}>
|
|
145
|
+
Open the settings
|
|
146
|
+
</button>
|
|
147
|
+
</>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
With an indicator that slides to the selected tab:
|
|
153
|
+
|
|
154
|
+
```tsx check
|
|
155
|
+
import { Tabs } from "@lumiose/ui";
|
|
156
|
+
|
|
157
|
+
export function Release() {
|
|
158
|
+
return (
|
|
159
|
+
<Tabs.Root class="lu-tabs" defaultValue="notes">
|
|
160
|
+
<Tabs.List aria-label="Release">
|
|
161
|
+
<Tabs.Tab value="notes">Notes</Tabs.Tab>
|
|
162
|
+
<Tabs.Tab value="changelog">Full changelog</Tabs.Tab>
|
|
163
|
+
<Tabs.Indicator />
|
|
164
|
+
</Tabs.List>
|
|
165
|
+
<Tabs.Panel value="notes">The highlights.</Tabs.Panel>
|
|
166
|
+
<Tabs.Panel value="changelog">Every change.</Tabs.Panel>
|
|
167
|
+
</Tabs.Root>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Styling
|
|
173
|
+
|
|
174
|
+
**With the default theme**, put `lu-tabs` on `Root`: tabs in a row (or a column when vertical)
|
|
175
|
+
over a rule, the selected one underlined (beside it when vertical) in the foreground colour,
|
|
176
|
+
disabled ones faded, and focus rings on tabs and panels. The list wraps on narrow screens. With a
|
|
177
|
+
`Tabs.Indicator`, the indicator draws that bar and slides to the next tab in 0.2s (at once where
|
|
178
|
+
motion is reduced).
|
|
179
|
+
|
|
180
|
+
**With your own CSS**, style the parts through `class` and their state: `[role="tab"]`,
|
|
181
|
+
`[aria-selected="true"]`, `[aria-disabled="true"]`, `[role="tablist"][aria-orientation="vertical"]`
|
|
182
|
+
and `[role="tabpanel"]`. Mark the selected tab with more than a colour (WCAG 1.4.1), such as a bar
|
|
183
|
+
or a weight, at 3:1 against its background (WCAG 1.4.11).
|
|
184
|
+
|
|
185
|
+
```css
|
|
186
|
+
.tabs [role="tab"] {
|
|
187
|
+
padding: 0.5rem 1rem;
|
|
188
|
+
border: 0;
|
|
189
|
+
background: none;
|
|
190
|
+
color: #52525b;
|
|
191
|
+
}
|
|
192
|
+
.tabs [role="tab"][aria-selected="true"] {
|
|
193
|
+
color: #09090b;
|
|
194
|
+
box-shadow: inset 0 -2px currentColor;
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
A highlight behind the selected tab, with `Tabs.Indicator`:
|
|
199
|
+
|
|
200
|
+
```css
|
|
201
|
+
.tabs [data-indicator] {
|
|
202
|
+
border-radius: 6px;
|
|
203
|
+
background: #f4f4f5;
|
|
204
|
+
transition:
|
|
205
|
+
left 0.2s,
|
|
206
|
+
width 0.2s;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`@lumiose/ui/tabs.css` (0.3 kB brotli), in the `lu.core` layer, makes `<lu-tabs>` a block and
|
|
211
|
+
places the indicator: over the selected tab, behind the tabs' text, from `--lu-tabs-anchor` and the
|
|
212
|
+
script's `--lu-tabs-indicator-*` variables.
|
|
213
|
+
|
|
214
|
+
## Keyboard and focus
|
|
215
|
+
|
|
216
|
+
The [WAI-ARIA tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/):
|
|
217
|
+
|
|
218
|
+
| Key | Does |
|
|
219
|
+
| --------------------- | -------------------------------------------------------------------------------------------------- |
|
|
220
|
+
| Tab | Into the list, onto the selected tab; out of it, onto the panel. |
|
|
221
|
+
| ArrowRight, ArrowLeft | The next or previous tab (horizontal). Swapped in right-to-left. Loops unless `loopFocus={false}`. |
|
|
222
|
+
| ArrowDown, ArrowUp | The next or previous tab (vertical). |
|
|
223
|
+
| Home, End | The first or last tab. |
|
|
224
|
+
| Enter, Space | Selects the focused tab (manual activation). |
|
|
225
|
+
|
|
226
|
+
- **Manual activation** (the default, as in Base UI): the arrows move focus and Enter, Space or a
|
|
227
|
+
click selects. With `activateOnFocus`, moving focus selects too. Either way only the panels
|
|
228
|
+
change: nothing navigates or submits.
|
|
229
|
+
- **Only the main button** selects: a right-click does not. Disabled tabs take focus but are
|
|
230
|
+
never selected.
|
|
231
|
+
- The arrow keys do nothing while an input method is composing, or with Alt, Ctrl or Meta held.
|
|
232
|
+
|
|
233
|
+
## Without JavaScript, and the element's script
|
|
234
|
+
|
|
235
|
+
The server HTML is complete: the selected tab, its panel and the tab stop are right before any
|
|
236
|
+
script loads, and the panels of the other tabs are `hidden`. Selecting another tab needs
|
|
237
|
+
`<lu-tabs>`' script, a 1.2 kB (brotli) module that a page rendering `Tabs` loads once, without
|
|
238
|
+
the Lumiose runtime; islands bundle it. A `Tabs.Indicator` is placed by CSS alone until then; the
|
|
239
|
+
script makes it follow a new selection. `value`, `onValueChange` and `bind:value` need the
|
|
240
|
+
component to run in an island.
|
|
241
|
+
|
|
242
|
+
## Browser support
|
|
243
|
+
|
|
244
|
+
- **Custom elements** (`<lu-tabs>`) and everything the element uses: every current browser.
|
|
245
|
+
- The default theme's vertical tabs follow the direction with logical properties
|
|
246
|
+
(`border-inline-end`): every current browser.
|
|
247
|
+
- **CSS anchor positioning** places `Tabs.Indicator` before the script runs: Chrome and Edge 125,
|
|
248
|
+
Firefox 147, Safari 26. Elsewhere it appears once the script has measured the selected tab.
|
|
249
|
+
|
|
250
|
+
## Related
|
|
251
|
+
|
|
252
|
+
- [UI components](README.md): the approach, styling, the default theme and the other components.
|
|
253
|
+
- [Web components](https://itsjavi.com/lumiose/docs/guides/web-components/): custom elements, their properties and
|
|
254
|
+
`bind:` on them.
|