@lumiose/ui 0.0.0-stage → 0.1.1
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,112 @@
|
|
|
1
|
+
# Checkbox Group
|
|
2
|
+
|
|
3
|
+
> A group of checkboxes with one value, the list of the checked ones, after Base UI's CheckboxGroup: a native `<fieldset>` whose checkboxes share a name, so the form submits each checked value and a disabled group disables them all, without JavaScript. A parent checkbox checks them all and shows checked, mixed or unchecked as they are; `bind:value` keeps a signal in sync.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { CheckboxGroup, Checkbox, Fieldset } from "@lumiose/ui"; // or "@lumiose/ui/checkbox-group"
|
|
7
|
+
import "@lumiose/ui/themes/default.css"; // optional: the default theme
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Anatomy
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<CheckboxGroup>
|
|
14
|
+
<Fieldset.Legend />
|
|
15
|
+
<Checkbox.Root parent />
|
|
16
|
+
<Checkbox.Root />
|
|
17
|
+
</CheckboxGroup>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Part | Renders |
|
|
21
|
+
| --------------- | ----------------------------------------------------------------------------------- |
|
|
22
|
+
| `CheckboxGroup` | `<fieldset>` around the checkboxes; a `Fieldset.Legend` first inside names it. |
|
|
23
|
+
| `Checkbox.Root` | A [Checkbox](checkbox.md) of the group, checked when its `value` is in the group's. |
|
|
24
|
+
|
|
25
|
+
## API
|
|
26
|
+
|
|
27
|
+
### CheckboxGroup
|
|
28
|
+
|
|
29
|
+
| Prop | Type | Default | |
|
|
30
|
+
| --------------- | --------------------------- | ------- | -------------------------------------------------------------------------------------- |
|
|
31
|
+
| `value` | `string[]` | | Checks the checkboxes with these values when it changes. Pair it with `onValueChange`. |
|
|
32
|
+
| `defaultValue` | `string[]` | `[]` | The values checked at first. |
|
|
33
|
+
| `onValueChange` | `(value: string[]) => void` | | Called with the checked values after the user checks or unchecks one. |
|
|
34
|
+
| `bind:value` | `Signal<string[]>` | | Stands in for `value` and `onValueChange`. |
|
|
35
|
+
| `allValues` | `string[]` | | Every checkbox's value: the parent checkbox's state in server HTML. |
|
|
36
|
+
| `name` | `string` | | The name the checkboxes submit under, unless they have their own. |
|
|
37
|
+
| `disabled` | `boolean` | `false` | Disables every checkbox (a disabled fieldset). |
|
|
38
|
+
|
|
39
|
+
Every other `<fieldset>` prop passes through.
|
|
40
|
+
|
|
41
|
+
**Differences from Base UI's CheckboxGroup.** It renders a `<fieldset>`, so a legend names it and
|
|
42
|
+
`disabled` is native. `name` is added: Base UI gives each checkbox its own. A parent checkbox
|
|
43
|
+
needs only `parent` here, not a controlled group, since the script runs it from the DOM. There is
|
|
44
|
+
no `required` (one box checked at least), as in Base UI: HTML has no such constraint for a group,
|
|
45
|
+
so it would work only with a script and an unlocalized message of ours. Check it in the form
|
|
46
|
+
action instead and return the message with `fail()`: it renders with the page, without JavaScript
|
|
47
|
+
too.
|
|
48
|
+
|
|
49
|
+
## Example
|
|
50
|
+
|
|
51
|
+
Toppings in a server-rendered form, with a "select all" box:
|
|
52
|
+
|
|
53
|
+
```tsx check
|
|
54
|
+
import { Checkbox, CheckboxGroup, Fieldset } from "@lumiose/ui";
|
|
55
|
+
|
|
56
|
+
const TOPPINGS = ["Cheese", "Olives", "Peppers"];
|
|
57
|
+
|
|
58
|
+
export function ToppingsGroup() {
|
|
59
|
+
return (
|
|
60
|
+
<CheckboxGroup
|
|
61
|
+
name="toppings"
|
|
62
|
+
defaultValue={["Cheese"]}
|
|
63
|
+
allValues={TOPPINGS}
|
|
64
|
+
class="lu-fieldset"
|
|
65
|
+
>
|
|
66
|
+
<Fieldset.Legend>Toppings</Fieldset.Legend>
|
|
67
|
+
<label>
|
|
68
|
+
<Checkbox.Root parent class="lu-checkbox" /> All toppings
|
|
69
|
+
</label>
|
|
70
|
+
{TOPPINGS.map((topping) => (
|
|
71
|
+
<label>
|
|
72
|
+
<Checkbox.Root value={topping} class="lu-checkbox" /> {topping}
|
|
73
|
+
</label>
|
|
74
|
+
))}
|
|
75
|
+
</CheckboxGroup>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Styling
|
|
81
|
+
|
|
82
|
+
**With the default theme**, put `lu-fieldset` on the group and `lu-checkbox` on each checkbox
|
|
83
|
+
([Checkbox](checkbox.md#styling)). Style a parent checkbox with `[data-parent]` if it should look
|
|
84
|
+
different.
|
|
85
|
+
|
|
86
|
+
CheckboxGroup ships no stylesheet: it needs none to work.
|
|
87
|
+
|
|
88
|
+
## Keyboard and focus
|
|
89
|
+
|
|
90
|
+
Each checkbox is its own tab stop; Space toggles the focused one. The parent is checked, mixed or
|
|
91
|
+
unchecked as all, some or none of the group's checkboxes are, disabled ones counted (as the server
|
|
92
|
+
counts them, from `allValues`). Toggling it goes, as Base UI's, from a partial selection to all,
|
|
93
|
+
then to none, then back to the selection the user last made with the checkboxes; disabled
|
|
94
|
+
checkboxes keep their state through it. Its `aria-controls` names the checkboxes, whose ids are
|
|
95
|
+
the group's and their values (`<id>-<value>`) unless you give them your own.
|
|
96
|
+
|
|
97
|
+
## Without JavaScript, and the client script
|
|
98
|
+
|
|
99
|
+
The checkboxes are native: they toggle, submit under the group's name and reset with no script.
|
|
100
|
+
The parent checkbox shows its server state and is never submitted, but checks nothing until the
|
|
101
|
+
shared checkbox script (1.0 kB brotli, [Checkbox](checkbox.md#without-javascript-and-the-client-script))
|
|
102
|
+
runs.
|
|
103
|
+
|
|
104
|
+
## Browser support
|
|
105
|
+
|
|
106
|
+
Every supported browser.
|
|
107
|
+
|
|
108
|
+
## Related
|
|
109
|
+
|
|
110
|
+
- [Checkbox](checkbox.md): the checkboxes, `indeterminate` and `readOnly`.
|
|
111
|
+
- [Fieldset](fieldset.md): the legend.
|
|
112
|
+
- [Radio Group](radio-group.md): one value out of several.
|
package/docs/checkbox.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Checkbox
|
|
2
|
+
|
|
3
|
+
> A native `<input type="checkbox">`, after Base UI's Checkbox: Space and a label click toggle it, the form submits its value when checked, reset and `required` work, in server HTML and without JavaScript. `indeterminate`, `readOnly`, the parent checkbox of a [Checkbox Group](checkbox-group.md) and `bind:checked` come on top, with a 1.0 kB script shared with Radio Group and Switch.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Checkbox } from "@lumiose/ui"; // or "@lumiose/ui/checkbox"
|
|
7
|
+
import "@lumiose/ui/themes/default.css"; // optional: the default theme
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Anatomy
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<label>
|
|
14
|
+
<Checkbox.Root /> Label
|
|
15
|
+
</label>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
| Part | Renders |
|
|
19
|
+
| ------ | ----------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| `Root` | `<input type="checkbox">`; `data-indeterminate` in server HTML while indeterminate, `aria-readonly` if read-only. |
|
|
21
|
+
|
|
22
|
+
Name it with a wrapping `<label>`, a `<label for>`, a `Field.Label`, or `aria-label`.
|
|
23
|
+
|
|
24
|
+
## API
|
|
25
|
+
|
|
26
|
+
### Checkbox.Root
|
|
27
|
+
|
|
28
|
+
| Prop | Type | Default | |
|
|
29
|
+
| ----------------- | ---------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `checked` | `boolean` | | Checks or unchecks it when it changes. Pair it with `onCheckedChange`. |
|
|
31
|
+
| `defaultChecked` | `boolean` | `false` | Whether it starts checked; a form reset goes back to it. |
|
|
32
|
+
| `onCheckedChange` | `(checked: boolean) => void` | | Called after the user checks or unchecks it. |
|
|
33
|
+
| `bind:checked` | `Signal<boolean>` | | Stands in for `checked` and `onCheckedChange`. |
|
|
34
|
+
| `indeterminate` | `boolean` | `false` | Neither checked nor unchecked (mixed for assistive technology) until the user toggles it, and again after a form reset. |
|
|
35
|
+
| `value` | `string` | `"on"` | What it submits when checked, and its identity in a Checkbox Group. |
|
|
36
|
+
| `parent` | `boolean` | `false` | The "select all" checkbox of a Checkbox Group: it checks them all and follows them. Never submitted. |
|
|
37
|
+
| `readOnly` | `boolean` | `false` | It cannot be toggled, but stays focusable and is submitted. |
|
|
38
|
+
| `uncheckedValue` | `string` | | What it submits when unchecked (by the script); by default an unchecked box submits nothing. |
|
|
39
|
+
|
|
40
|
+
Every other native prop passes through: `name`, `required`, `disabled`, `form`, `id`, `class`,
|
|
41
|
+
`aria-*`. Inside a Field it takes the field's `id`, `name`, `aria-describedby` and
|
|
42
|
+
`aria-invalid`; inside a Checkbox Group, its `checked` state and `name`.
|
|
43
|
+
|
|
44
|
+
**Differences from Base UI's Checkbox.** The box is the native input, so there is no
|
|
45
|
+
`Checkbox.Indicator`: draw the box and its mark with `appearance: none` and `::before` (as the
|
|
46
|
+
default theme does). There is no `nativeButton` or `inputRef`. `uncheckedValue` needs the client
|
|
47
|
+
script: without it an unchecked box submits nothing, as a native one does.
|
|
48
|
+
|
|
49
|
+
## Example
|
|
50
|
+
|
|
51
|
+
In an island, bound to a signal:
|
|
52
|
+
|
|
53
|
+
```tsx check
|
|
54
|
+
import { Checkbox } from "@lumiose/ui";
|
|
55
|
+
import { signal } from "lumiose";
|
|
56
|
+
|
|
57
|
+
export function NewsletterOptIn() {
|
|
58
|
+
const subscribed = signal(false);
|
|
59
|
+
return (
|
|
60
|
+
<p>
|
|
61
|
+
<label>
|
|
62
|
+
<Checkbox.Root name="newsletter" bind:checked={subscribed} class="lu-checkbox" /> Send me
|
|
63
|
+
the newsletter
|
|
64
|
+
</label>{" "}
|
|
65
|
+
{subscribed() ? "Thanks!" : ""}
|
|
66
|
+
</p>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Styling
|
|
72
|
+
|
|
73
|
+
**With the default theme**, put `lu-checkbox` on `Root`: an 18px box drawn by `::before` inside a
|
|
74
|
+
24px target, a ✓ when checked and a dash when indeterminate (a symbol, not only a colour), the
|
|
75
|
+
primary colour for both, a focus ring, a red border with `aria-invalid="true"`.
|
|
76
|
+
|
|
77
|
+
**With your own CSS**, style the input and its state: `:checked`, `:indeterminate` (and
|
|
78
|
+
`[data-indeterminate]` before the script runs), `:disabled`, `:focus-visible`,
|
|
79
|
+
`[aria-readonly="true"]`, `[aria-invalid="true"]`.
|
|
80
|
+
|
|
81
|
+
```css
|
|
82
|
+
.checkbox {
|
|
83
|
+
appearance: none;
|
|
84
|
+
inline-size: 1.25rem;
|
|
85
|
+
block-size: 1.25rem;
|
|
86
|
+
border: 1px solid #85858e;
|
|
87
|
+
border-radius: 0.25rem;
|
|
88
|
+
}
|
|
89
|
+
.checkbox:checked {
|
|
90
|
+
background: #2563eb;
|
|
91
|
+
}
|
|
92
|
+
.checkbox:checked::before {
|
|
93
|
+
content: "✓" / "";
|
|
94
|
+
color: #fff;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Checkbox ships no stylesheet: it needs none to work.
|
|
99
|
+
|
|
100
|
+
## Keyboard and focus
|
|
101
|
+
|
|
102
|
+
| Key | Does |
|
|
103
|
+
| ----- | ------------------------------------------------------ |
|
|
104
|
+
| Space | Toggles it (not when read-only). |
|
|
105
|
+
| Tab | Moves to the next control: a checkbox is one tab stop. |
|
|
106
|
+
|
|
107
|
+
Enter does not toggle it. A click on its label toggles it too.
|
|
108
|
+
|
|
109
|
+
## Without JavaScript, and the client script
|
|
110
|
+
|
|
111
|
+
A native checkbox: toggling, submission, `required`, reset and `disabled` (its own or a fieldset's)
|
|
112
|
+
work with no script.
|
|
113
|
+
|
|
114
|
+
A page that renders a Checkbox, Checkbox Group, Radio Group or Switch loads one 1.0 kB (brotli)
|
|
115
|
+
script once, without the Lumiose runtime; islands bundle it. It turns server HTML's
|
|
116
|
+
`data-indeterminate` into the `indeterminate` property (which has no attribute), makes read-only
|
|
117
|
+
inputs ignore clicks, Space and the label, runs the parent checkbox of a group and a [Radio
|
|
118
|
+
Group](radio-group.md)'s arrow keys, and adds an
|
|
119
|
+
unchecked box's `uncheckedValue` (`data-unchecked-value`) to its form's data (the `formdata`
|
|
120
|
+
event, so it comes after the form's other values; a disabled box submits nothing). Without it, an
|
|
121
|
+
indeterminate box shows unchecked (the theme still draws it mixed), a read-only one toggles, and
|
|
122
|
+
an unchecked one submits nothing: treat a missing value as unchecked on the server too.
|
|
123
|
+
|
|
124
|
+
## Browser support
|
|
125
|
+
|
|
126
|
+
Native checkboxes, `:indeterminate` and `appearance: none` with `::before`: every supported
|
|
127
|
+
browser.
|
|
128
|
+
|
|
129
|
+
## Related
|
|
130
|
+
|
|
131
|
+
- [Checkbox Group](checkbox-group.md): several checkboxes with one value and a parent checkbox.
|
|
132
|
+
- [Switch](switch.md): on and off for a setting that applies at once.
|
|
133
|
+
- [Field](field.md): a label, a description and errors for a checkbox.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Collapsible
|
|
2
|
+
|
|
3
|
+
> A panel that its trigger shows and hides: a native `<details>` whose `<summary>` is the trigger, so it works in server HTML with no JavaScript, keeps the browser's keyboard handling, and opens when find-in-page or a link lands inside it. A 0.7 kB script adds Base UI's animation hooks. `bind:open` (or `open` and `onOpenChange`) keeps a signal in sync in islands.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Collapsible } from "@lumiose/ui"; // or "@lumiose/ui/collapsible"
|
|
7
|
+
import "@lumiose/ui/collapsible.css"; // a disabled trigger ignores the pointer
|
|
8
|
+
import "@lumiose/ui/themes/default.css"; // optional: the default theme
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Collapsible.Root>
|
|
15
|
+
<Collapsible.Trigger />
|
|
16
|
+
<Collapsible.Panel />
|
|
17
|
+
</Collapsible.Root>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Part | Renders |
|
|
21
|
+
| --------- | ------------------------------------------------------------------------------------------- |
|
|
22
|
+
| `Root` | `<details>`, `open` while the panel shows. |
|
|
23
|
+
| `Trigger` | `<summary aria-controls="…">`, the first child of `Root`: it opens and closes the panel. |
|
|
24
|
+
| `Panel` | `<div>`, the rest of the `<details>`: the browser hides it while the `<details>` is closed. |
|
|
25
|
+
|
|
26
|
+
The browser exposes the `<summary>` as a button with its expanded state, so the trigger has no
|
|
27
|
+
`aria-expanded` of its own. HTML allows one heading inside a `<summary>`, for a trigger that
|
|
28
|
+
titles a section (Accordion renders its headings that way).
|
|
29
|
+
|
|
30
|
+
## API
|
|
31
|
+
|
|
32
|
+
### Collapsible.Root
|
|
33
|
+
|
|
34
|
+
| Prop | Type | Default | |
|
|
35
|
+
| -------------- | ------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| `open` | `boolean` | | Opens or closes the panel when it changes. Server HTML starts as it says. |
|
|
37
|
+
| `defaultOpen` | `boolean` | `false` | Whether the panel starts open, when there is no `open`. |
|
|
38
|
+
| `onOpenChange` | `(open: boolean) => void` | | Called after the panel opens or closes, whatever did it: the trigger, find-in-page, a link, another item of its `name`; not when it renders open. |
|
|
39
|
+
| `bind:open` | `Signal<boolean>` | | Stands in for `open` and `onOpenChange`. |
|
|
40
|
+
| `disabled` | `boolean` | `false` | The trigger takes no focus and does not toggle the panel. |
|
|
41
|
+
|
|
42
|
+
Without `open`, `onOpenChange` or `bind:open` the element keeps its own state. `Root` takes the
|
|
43
|
+
props of a `<details>` too: `name` makes collapsibles with the same name an exclusive group, where
|
|
44
|
+
opening one closes the others.
|
|
45
|
+
|
|
46
|
+
### The other parts
|
|
47
|
+
|
|
48
|
+
Each part takes the props of the element it renders (`class`, `style`, `aria-*`, `data-*`, event
|
|
49
|
+
handlers, `ref`) and passes them through. The `Trigger`'s `id` names it, and the `Panel` points at
|
|
50
|
+
it; the `Panel` keeps its own `id`, which the trigger points at (see [Ids](README.md#ids)), and
|
|
51
|
+
`aria-controls` stays the trigger's own.
|
|
52
|
+
|
|
53
|
+
- **`Trigger`**: a disabled trigger gets `aria-disabled="true"` and `tabindex="-1"`.
|
|
54
|
+
- **`Panel`**: the client script sets `--lu-collapsible-panel-height` and `--lu-collapsible-panel-width`
|
|
55
|
+
on it while it opens and closes, and `data-starting-style` and `data-ending-style`
|
|
56
|
+
([Styling](#styling)).
|
|
57
|
+
|
|
58
|
+
## Example
|
|
59
|
+
|
|
60
|
+
A section in a server-rendered page, with no island, styled by the default theme:
|
|
61
|
+
|
|
62
|
+
```tsx check
|
|
63
|
+
import { Collapsible } from "@lumiose/ui";
|
|
64
|
+
|
|
65
|
+
export function RecoveryKeys() {
|
|
66
|
+
return (
|
|
67
|
+
<Collapsible.Root class="lu-collapsible">
|
|
68
|
+
<Collapsible.Trigger>Recovery keys</Collapsible.Trigger>
|
|
69
|
+
<Collapsible.Panel>
|
|
70
|
+
<p>Store these keys somewhere safe: each one lets you back in once.</p>
|
|
71
|
+
</Collapsible.Panel>
|
|
72
|
+
</Collapsible.Root>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
In an island, bound to a signal: `bind:open={open}` is short for `open={open()}` plus
|
|
78
|
+
`onOpenChange={(next) => open.set(next)}`.
|
|
79
|
+
|
|
80
|
+
```tsx check
|
|
81
|
+
import { Collapsible } from "@lumiose/ui";
|
|
82
|
+
import { signal } from "lumiose";
|
|
83
|
+
|
|
84
|
+
export function AdvancedOptions() {
|
|
85
|
+
const open = signal(false);
|
|
86
|
+
return (
|
|
87
|
+
<>
|
|
88
|
+
<Collapsible.Root class="lu-collapsible" bind:open={open}>
|
|
89
|
+
<Collapsible.Trigger>Advanced options</Collapsible.Trigger>
|
|
90
|
+
<Collapsible.Panel>Proxy, timeouts and retries.</Collapsible.Panel>
|
|
91
|
+
</Collapsible.Root>
|
|
92
|
+
<p>{open() ? "Showing the advanced options" : "Advanced options hidden"}</p>
|
|
93
|
+
</>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Styling
|
|
99
|
+
|
|
100
|
+
**With the default theme**, put `lu-collapsible` on `Root`: the trigger becomes a row with a
|
|
101
|
+
chevron that turns while the panel is open, and the panel's height animates.
|
|
102
|
+
|
|
103
|
+
**With your own CSS**, style the parts through `class` and `[open]` on the root. The client script
|
|
104
|
+
gives the panel what Base UI's does, for an open and close animation: `--lu-collapsible-panel-height`
|
|
105
|
+
(the content's height in pixels while it moves, `auto` once open), `data-starting-style` for the
|
|
106
|
+
frame an opening starts from and `data-ending-style` while it closes. The browser hides the panel
|
|
107
|
+
as soon as the `<details>` closes, so keep it visible while it closes with a transition on
|
|
108
|
+
`::details-content`:
|
|
109
|
+
|
|
110
|
+
```css
|
|
111
|
+
.collapsible > .panel {
|
|
112
|
+
overflow: hidden;
|
|
113
|
+
block-size: var(--lu-collapsible-panel-height, auto);
|
|
114
|
+
transition: block-size 200ms ease-out;
|
|
115
|
+
}
|
|
116
|
+
.collapsible > .panel:is([data-starting-style], [data-ending-style]) {
|
|
117
|
+
block-size: 0;
|
|
118
|
+
}
|
|
119
|
+
.collapsible::details-content {
|
|
120
|
+
transition: content-visibility 200ms allow-discrete;
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
A panel rendered open does not animate. `@lumiose/ui/collapsible.css` holds one rule, in the
|
|
125
|
+
`lu.core` layer: a disabled trigger (`summary[aria-disabled="true"]`) ignores the pointer.
|
|
126
|
+
|
|
127
|
+
## Keyboard and focus
|
|
128
|
+
|
|
129
|
+
| Key | Does |
|
|
130
|
+
| ------------ | ------------------------------------------------------------------- |
|
|
131
|
+
| Enter, Space | On the trigger: opens or closes the panel. |
|
|
132
|
+
| Tab | Moves to the trigger, then into the open panel's focusable content. |
|
|
133
|
+
|
|
134
|
+
The trigger is a `<summary>`, so the browser handles its keys and its focus. A disabled trigger is
|
|
135
|
+
out of the tab order.
|
|
136
|
+
|
|
137
|
+
## Without JavaScript, and the client script
|
|
138
|
+
|
|
139
|
+
Opening and closing, the exclusive group of a `name`, and find-in-page are HTML features: a
|
|
140
|
+
Collapsible in a server-rendered page works before any script loads, and with none. A disabled
|
|
141
|
+
trigger ignores the pointer through CSS. `open`, `onOpenChange` and `bind:open` need the component
|
|
142
|
+
to run in an island.
|
|
143
|
+
|
|
144
|
+
A page that renders a `Collapsible` loads a 0.7 kB (brotli) module once, without the Lumiose
|
|
145
|
+
runtime; islands bundle it. It covers every Collapsible and [Accordion](accordion.md) panel on the
|
|
146
|
+
page:
|
|
147
|
+
|
|
148
|
+
- the animation hooks above, set on every opening and closing, whatever caused it (the trigger,
|
|
149
|
+
find-in-page, a link, the `open` prop, another item of the group);
|
|
150
|
+
- a disabled trigger does not toggle, also from the keyboard.
|
|
151
|
+
|
|
152
|
+
## Browser support
|
|
153
|
+
|
|
154
|
+
- **`<details>` and `<summary>`**: every browser. **`name`** (exclusive groups): Chrome 120,
|
|
155
|
+
Firefox 130, Safari 17.2.
|
|
156
|
+
- **Find-in-page and links into a closed panel** open it: Chrome 97, Firefox 139. Safari has not
|
|
157
|
+
shipped it yet (WebKit bug 304174): there, find-in-page skips a closed panel.
|
|
158
|
+
- **`::details-content`**, which the closing transition needs: Chrome 131, Firefox 143, Safari
|
|
159
|
+
18.4. Elsewhere the panel disappears at once when it closes.
|
|
160
|
+
|
|
161
|
+
## Related
|
|
162
|
+
|
|
163
|
+
- [Accordion](accordion.md): a stack of collapsible items, with arrow keys between them.
|
|
164
|
+
- [UI components](README.md): the approach, styling and the other components.
|
|
165
|
+
- [Theming](theming.md): the default theme's classes and custom properties.
|