remix 3.0.0-beta.4 → 3.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/assets/types/hmr.d.ts +2 -0
- package/dist/cli-entry.js +1 -1
- package/dist/data-table/cli.d.ts +2 -0
- package/dist/data-table/cli.d.ts.map +1 -0
- package/dist/{ui/scroll-lock.js → data-table/cli.js} +1 -1
- package/dist/node-hmr/runtime.d.ts +2 -0
- package/dist/node-hmr/runtime.d.ts.map +1 -0
- package/dist/node-hmr/runtime.js +2 -0
- package/dist/node-hmr/types.d.ts +2 -0
- package/dist/node-hmr.d.ts +2 -0
- package/dist/node-hmr.d.ts.map +1 -0
- package/dist/{ui/glyph.js → node-hmr.js} +1 -1
- package/dist/ui/accordion/primitives.d.ts +2 -0
- package/dist/ui/accordion/primitives.d.ts.map +1 -0
- package/dist/ui/accordion/primitives.js +2 -0
- package/dist/ui/button.d.ts +1 -0
- package/dist/ui/button.d.ts.map +1 -1
- package/dist/ui/button.js +1 -0
- package/dist/ui/checkbox.d.ts +3 -0
- package/dist/ui/checkbox.d.ts.map +1 -0
- package/dist/ui/checkbox.js +3 -0
- package/dist/ui/combobox/primitives.d.ts +2 -0
- package/dist/ui/combobox/primitives.d.ts.map +1 -0
- package/dist/ui/combobox/primitives.js +2 -0
- package/dist/ui/dev/refresh.d.ts +2 -0
- package/dist/ui/dev/refresh.d.ts.map +1 -0
- package/dist/ui/dev/refresh.js +2 -0
- package/dist/ui/input.d.ts +3 -0
- package/dist/ui/input.d.ts.map +1 -0
- package/dist/ui/input.js +3 -0
- package/dist/ui/menu/primitives.d.ts +2 -0
- package/dist/ui/menu/primitives.d.ts.map +1 -0
- package/dist/ui/menu/primitives.js +2 -0
- package/dist/ui/radio.d.ts +3 -0
- package/dist/ui/radio.d.ts.map +1 -0
- package/dist/ui/radio.js +3 -0
- package/dist/ui/select/primitives.d.ts +2 -0
- package/dist/ui/select/primitives.d.ts.map +1 -0
- package/dist/ui/select/primitives.js +2 -0
- package/dist/ui/tabs/primitives.d.ts +2 -0
- package/dist/ui/tabs/primitives.d.ts.map +1 -0
- package/dist/ui/tabs/primitives.js +2 -0
- package/dist/ui/tabs.d.ts +2 -0
- package/dist/ui/tabs.d.ts.map +1 -0
- package/{src/ui/theme.ts → dist/ui/tabs.js} +1 -1
- package/dist/ui/toggle/primitives.d.ts +2 -0
- package/dist/ui/toggle/primitives.d.ts.map +1 -0
- package/dist/ui/toggle/primitives.js +2 -0
- package/dist/ui/toggle.d.ts +3 -0
- package/dist/ui/toggle.d.ts.map +1 -0
- package/dist/ui/toggle.js +3 -0
- package/dist/ui-hmr/assets.d.ts +2 -0
- package/dist/ui-hmr/assets.d.ts.map +1 -0
- package/dist/ui-hmr/assets.js +2 -0
- package/dist/ui-hmr/node.d.ts +3 -0
- package/dist/ui-hmr/node.d.ts.map +1 -0
- package/dist/ui-hmr/node.js +3 -0
- package/dist/ui-hmr/runtime/browser.d.ts +2 -0
- package/dist/ui-hmr/runtime/browser.d.ts.map +1 -0
- package/dist/ui-hmr/runtime/browser.js +2 -0
- package/dist/ui-hmr/runtime/server.d.ts +2 -0
- package/dist/ui-hmr/runtime/server.d.ts.map +1 -0
- package/dist/ui-hmr/runtime/server.js +2 -0
- package/dist/ui-hmr.d.ts +2 -0
- package/dist/ui-hmr.d.ts.map +1 -0
- package/{src/ui/glyph.ts → dist/ui-hmr.js} +1 -1
- package/package.json +122 -142
- package/src/assets/README.md +322 -56
- package/src/assets/types/hmr.d.ts +2 -0
- package/src/cli/README.md +105 -1
- package/src/cookie/README.md +4 -4
- package/src/data-table/README.md +202 -68
- package/src/data-table/cli.ts +2 -0
- package/src/data-table-mysql/README.md +46 -17
- package/src/data-table-postgres/README.md +39 -13
- package/src/data-table-sqlite/README.md +38 -20
- package/src/fetch-proxy/README.md +25 -0
- package/src/form-data-parser/README.md +4 -4
- package/src/mime/README.md +8 -1
- package/src/node-fetch-server/README.md +39 -13
- package/src/node-hmr/README.md +307 -0
- package/src/node-hmr/runtime.ts +2 -0
- package/src/node-hmr/types.d.ts +2 -0
- package/{dist/ui/theme.js → src/node-hmr.ts} +1 -1
- package/src/route-pattern/README.md +141 -13
- package/src/session/README.md +1 -1
- package/src/session-middleware/README.md +9 -7
- package/src/test/README.md +161 -115
- package/src/ui/README.md +116 -157
- package/src/ui/accordion/README.md +50 -14
- package/src/ui/accordion/primitives/README.md +202 -0
- package/src/ui/accordion/primitives.ts +2 -0
- package/src/ui/anchor/README.md +37 -2
- package/src/ui/breadcrumbs/README.md +4 -4
- package/src/ui/button/README.md +26 -26
- package/src/ui/button.ts +1 -0
- package/src/ui/checkbox/README.md +59 -0
- package/src/ui/checkbox.ts +3 -0
- package/src/ui/combobox/README.md +58 -9
- package/src/ui/combobox/primitives/README.md +194 -0
- package/src/ui/combobox/primitives.ts +2 -0
- package/src/ui/dev/refresh.ts +2 -0
- package/src/ui/input/README.md +52 -0
- package/src/ui/input.ts +3 -0
- package/src/ui/listbox/README.md +9 -41
- package/src/ui/menu/README.md +55 -14
- package/src/ui/menu/primitives/README.md +161 -0
- package/src/ui/menu/primitives.ts +2 -0
- package/src/ui/popover/README.md +20 -39
- package/src/ui/radio/README.md +53 -0
- package/src/ui/radio.ts +3 -0
- package/src/ui/select/README.md +29 -19
- package/src/ui/select/primitives/README.md +117 -0
- package/src/ui/select/primitives.ts +2 -0
- package/src/ui/tabs/README.md +141 -0
- package/src/ui/tabs/primitives/README.md +141 -0
- package/src/ui/tabs/primitives.ts +2 -0
- package/src/ui/tabs.ts +2 -0
- package/src/ui/test/README.md +151 -60
- package/src/ui/toggle/README.md +56 -0
- package/src/ui/toggle/primitives/README.md +56 -0
- package/src/ui/toggle/primitives.ts +2 -0
- package/src/ui/toggle.ts +3 -0
- package/src/ui-hmr/README.md +119 -0
- package/{dist/ui/separator.js → src/ui-hmr/assets.ts} +1 -1
- package/src/ui-hmr/node.ts +3 -0
- package/src/ui-hmr/runtime/browser.ts +2 -0
- package/src/ui-hmr/runtime/server.ts +2 -0
- package/src/ui-hmr.ts +2 -0
- package/dist/ui/glyph.d.ts +0 -2
- package/dist/ui/glyph.d.ts.map +0 -1
- package/dist/ui/scroll-lock.d.ts +0 -2
- package/dist/ui/scroll-lock.d.ts.map +0 -1
- package/dist/ui/separator.d.ts +0 -2
- package/dist/ui/separator.d.ts.map +0 -1
- package/dist/ui/theme.d.ts +0 -2
- package/dist/ui/theme.d.ts.map +0 -1
- package/src/ui/glyph/README.md +0 -72
- package/src/ui/scroll-lock/README.md +0 -33
- package/src/ui/scroll-lock.ts +0 -2
- package/src/ui/separator.ts +0 -2
- package/src/ui/theme/README.md +0 -103
package/src/ui/popover/README.md
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# popover
|
|
2
2
|
|
|
3
3
|
`popover` is a low-level primitive for anchored, dismissible floating panels.
|
|
4
4
|
|
|
5
|
-
Use it for custom surfaces like filters, inspectors, and view options. Higher-level widgets like
|
|
5
|
+
Use it for custom surfaces like filters, inspectors, and view options. Higher-level widgets like menu, select, and combobox should build on top of it instead of exposing raw `popover.*` mixins directly.
|
|
6
6
|
|
|
7
|
-
## Usage
|
|
7
|
+
## Primitive Usage
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
import { popover } from 'remix/ui/popover'
|
|
10
|
+
import { on, type Handle } from 'remix/ui'
|
|
11
|
+
import * as popover from 'remix/ui/popover'
|
|
12
|
+
import { panelStyle } from './popover.styles'
|
|
14
13
|
|
|
15
14
|
export function ViewOptions(handle: Handle) {
|
|
16
15
|
let open = false
|
|
@@ -27,21 +26,20 @@ export function ViewOptions(handle: Handle) {
|
|
|
27
26
|
|
|
28
27
|
return () => (
|
|
29
28
|
<popover.Context>
|
|
30
|
-
<
|
|
31
|
-
endIcon={<Glyph name="chevronDown" />}
|
|
29
|
+
<button
|
|
32
30
|
mix={[
|
|
33
31
|
popover.anchor({ placement: 'bottom-end' }),
|
|
34
32
|
popover.focusOnHide(),
|
|
35
33
|
on('click', openPopover),
|
|
36
34
|
]}
|
|
37
|
-
|
|
35
|
+
type="button"
|
|
38
36
|
>
|
|
39
37
|
View options
|
|
40
|
-
</
|
|
38
|
+
</button>
|
|
41
39
|
|
|
42
40
|
<div
|
|
43
41
|
mix={[
|
|
44
|
-
|
|
42
|
+
panelStyle,
|
|
45
43
|
popover.surface({
|
|
46
44
|
open,
|
|
47
45
|
onHide() {
|
|
@@ -50,23 +48,17 @@ export function ViewOptions(handle: Handle) {
|
|
|
50
48
|
}),
|
|
51
49
|
]}
|
|
52
50
|
>
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<div mix={panelBody}>Panel content</div>
|
|
58
|
-
</div>
|
|
51
|
+
<button mix={[popover.focusOnShow(), on('click', closePopover)]} type="button">
|
|
52
|
+
Close
|
|
53
|
+
</button>
|
|
54
|
+
<div>Panel content</div>
|
|
59
55
|
</div>
|
|
60
56
|
</popover.Context>
|
|
61
57
|
)
|
|
62
58
|
}
|
|
63
|
-
|
|
64
|
-
let panelBody = css({
|
|
65
|
-
padding: '12px',
|
|
66
|
-
})
|
|
67
59
|
```
|
|
68
60
|
|
|
69
|
-
## `popover
|
|
61
|
+
## `remix/ui/popover`
|
|
70
62
|
|
|
71
63
|
### `popover.Context`
|
|
72
64
|
|
|
@@ -76,10 +68,6 @@ Provides shared coordination for one popover instance. Render the anchor, any fo
|
|
|
76
68
|
|
|
77
69
|
Registers the host element as the anchor for the current popover surface.
|
|
78
70
|
|
|
79
|
-
- Accepts standard `AnchorOptions`.
|
|
80
|
-
- The stored anchor controls where the surface is positioned when it opens.
|
|
81
|
-
- Apply it to the button or other element the surface should attach to.
|
|
82
|
-
|
|
83
71
|
### `popover.surface({ open, onHide, ... })`
|
|
84
72
|
|
|
85
73
|
Turns the host into the controlled popover surface.
|
|
@@ -99,13 +87,12 @@ Registers the element that should receive focus when the popover opens.
|
|
|
99
87
|
|
|
100
88
|
Registers the element that should receive focus again when the popover closes.
|
|
101
89
|
|
|
102
|
-
###
|
|
103
|
-
|
|
104
|
-
Default floating-surface presentation for popovers.
|
|
90
|
+
### Primitive Types
|
|
105
91
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
92
|
+
- `PopoverContext`: context object exposed by `popover.Context`.
|
|
93
|
+
- `PopoverProps`: provider props for one popover instance.
|
|
94
|
+
- `PopoverSurfaceOptions`: options accepted by `popover.surface(...)`.
|
|
95
|
+
- `PopoverHideRequest`: hide request passed to `onHide`.
|
|
109
96
|
|
|
110
97
|
## Behavior Notes
|
|
111
98
|
|
|
@@ -114,9 +101,3 @@ Default inner scroll container for popover content.
|
|
|
114
101
|
- `popover.focusOnShow()` wins on open when present.
|
|
115
102
|
- `popover.focusOnHide()` is used on close by default when focus restoration is enabled.
|
|
116
103
|
- `closeOnAnchorClick: false` keeps anchor clicks inside the current session, which is useful for input-driven popovers like comboboxes.
|
|
117
|
-
|
|
118
|
-
## When To Use Something Else
|
|
119
|
-
|
|
120
|
-
- Use `menu` for command surfaces.
|
|
121
|
-
- Use `listbox` or `select` for committed value picking.
|
|
122
|
-
- Use `combobox` for input-first popup selection.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# radio
|
|
2
|
+
|
|
3
|
+
`radio` is a style mixin for native radio inputs. It only owns radio visuals and the default `type="radio"` behavior for native `<input>` hosts.
|
|
4
|
+
|
|
5
|
+
## Primitive Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import radio from 'remix/ui/radio'
|
|
9
|
+
|
|
10
|
+
function ShippingSpeed() {
|
|
11
|
+
return () => (
|
|
12
|
+
<fieldset>
|
|
13
|
+
<label>
|
|
14
|
+
<input defaultChecked mix={radio()} name="shipping-speed" value="standard" />
|
|
15
|
+
Standard
|
|
16
|
+
</label>
|
|
17
|
+
<label>
|
|
18
|
+
<input mix={radio({ size: 'lg' })} name="shipping-speed" value="express" />
|
|
19
|
+
Express
|
|
20
|
+
</label>
|
|
21
|
+
</fieldset>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Compose app-owned styles around the primitive when a radio option needs local layout:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import radio from 'remix/ui/radio'
|
|
30
|
+
import { optionStyle } from './shipping.styles'
|
|
31
|
+
|
|
32
|
+
function ShippingOption() {
|
|
33
|
+
return () => (
|
|
34
|
+
<label mix={optionStyle}>
|
|
35
|
+
<input mix={radio()} name="shipping-speed" value="overnight" />
|
|
36
|
+
Overnight
|
|
37
|
+
</label>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## `remix/ui/radio`
|
|
43
|
+
|
|
44
|
+
- `radio(options)`: style mixin for native radio inputs or radio-like hosts.
|
|
45
|
+
- `RadioOptions`: accepts `size`.
|
|
46
|
+
- `RadioSize`: `'md'` or `'lg'`. Defaults to `'md'`.
|
|
47
|
+
|
|
48
|
+
## Behavior Notes
|
|
49
|
+
|
|
50
|
+
- `radio()` returns a mixin descriptor, so it composes with other mixins in the host element's `mix` prop.
|
|
51
|
+
- Native `<input>` hosts receive `type="radio"` unless an explicit `type` is provided.
|
|
52
|
+
- Radio state is native browser behavior. The mixin also styles `aria-checked="true"` and `data-state="checked"` for custom hosts.
|
|
53
|
+
- Disabled hosts use the shared disabled treatment through `disabled` or `aria-disabled="true"`.
|
package/src/ui/radio.ts
ADDED
package/src/ui/select/README.md
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
`Select` is a button-triggered popup value picker backed by `listbox` and `popover`. Use it when the user should choose one stable string value from a finite set.
|
|
4
4
|
|
|
5
|
-
## Usage
|
|
5
|
+
## Component Usage
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
|
-
import { Option, Select
|
|
8
|
+
import { Option, Select } from 'remix/ui/select'
|
|
9
|
+
import { onSelectChange } from 'remix/ui/select/primitives'
|
|
9
10
|
|
|
10
11
|
export function FrameworkSelect() {
|
|
11
12
|
return (
|
|
@@ -44,34 +45,34 @@ Use `textValue` when closed-trigger typeahead should match a different string fr
|
|
|
44
45
|
</Select>
|
|
45
46
|
```
|
|
46
47
|
|
|
48
|
+
## Primitive Usage
|
|
49
|
+
|
|
47
50
|
Use the lower-level primitives when the trigger or popup structure needs to be owned by another component. Keep the same provider, trigger, popover, list, option, and hidden-input relationship.
|
|
48
51
|
|
|
49
52
|
```tsx
|
|
50
53
|
import type { Handle } from 'remix/ui'
|
|
51
|
-
import * as button from 'remix/ui/button'
|
|
52
|
-
import * as listbox from 'remix/ui/listbox'
|
|
53
54
|
import * as popover from 'remix/ui/popover'
|
|
54
|
-
import
|
|
55
|
+
import { triggerStyle } from 'remix/ui/select'
|
|
56
|
+
import * as select from 'remix/ui/select/primitives'
|
|
57
|
+
import { listStyle, optionStyle, surfaceStyle } from './select.styles'
|
|
55
58
|
|
|
56
59
|
function SelectValue(handle: Handle) {
|
|
57
60
|
let context = handle.context.get(select.Context)
|
|
58
61
|
|
|
59
|
-
return () => <span
|
|
62
|
+
return () => <span>{context.displayedLabel}</span>
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
function IssueTypeSelect() {
|
|
63
66
|
return () => (
|
|
64
|
-
<select.Context defaultLabel="Select a type" name="issueType">
|
|
65
|
-
<button type="button" mix={[
|
|
67
|
+
<select.Context defaultLabel="Select a type" labelSwapDelayMs={100} name="issueType">
|
|
68
|
+
<button type="button" mix={[triggerStyle, select.trigger()]}>
|
|
66
69
|
<SelectValue />
|
|
67
70
|
</button>
|
|
68
71
|
<popover.Context>
|
|
69
|
-
<div mix={[
|
|
70
|
-
<div mix={[
|
|
71
|
-
<div mix={[
|
|
72
|
-
|
|
73
|
-
</div>
|
|
74
|
-
<div mix={[listbox.optionStyle, select.option({ label: 'Feature', value: 'feature' })]}>
|
|
72
|
+
<div mix={[surfaceStyle, select.popover()]}>
|
|
73
|
+
<div mix={[listStyle, select.list()]}>
|
|
74
|
+
<div mix={[optionStyle, select.option({ label: 'Bug', value: 'bug' })]}>Bug</div>
|
|
75
|
+
<div mix={[optionStyle, select.option({ label: 'Feature', value: 'feature' })]}>
|
|
75
76
|
Feature
|
|
76
77
|
</div>
|
|
77
78
|
</div>
|
|
@@ -83,15 +84,24 @@ function IssueTypeSelect() {
|
|
|
83
84
|
}
|
|
84
85
|
```
|
|
85
86
|
|
|
86
|
-
## `select
|
|
87
|
+
## `remix/ui/select`
|
|
87
88
|
|
|
88
89
|
- `Select`: composed trigger, popover, listbox, option list, and optional hidden input for form participation. Accepts `defaultLabel`, `defaultValue`, `disabled`, `name`, and button props.
|
|
89
|
-
- `Option`: option
|
|
90
|
+
- `Option`: option component that renders the standard check indicator and label slot. Accepts `label`, `value`, optional `disabled`, and optional `textValue`.
|
|
91
|
+
- `triggerStyle`: standard select trigger style for custom trigger composition.
|
|
92
|
+
- `SelectProps` and `SelectOptionProps`: public TypeScript props for the composed APIs.
|
|
93
|
+
|
|
94
|
+
## `remix/ui/select/primitives`
|
|
95
|
+
|
|
96
|
+
- `Context`: lower-level provider for custom composition. Accepts `defaultLabel`, `defaultValue`, `disabled`, `name`, and primitive-only `labelSwapDelayMs`; the label-swap delay defaults to `75` milliseconds.
|
|
97
|
+
- `trigger()`: wires the trigger button, open behavior, closed-trigger typeahead, and trigger ARIA attributes.
|
|
98
|
+
- `popover()`: wires the popover surface and keeps its min-width synced to the trigger before open.
|
|
99
|
+
- `list()`: wires the listbox root used inside the popover.
|
|
100
|
+
- `option(...)`: registers one selectable option. Accepts `label`, `value`, optional `disabled`, and optional `textValue`.
|
|
101
|
+
- `hiddenInput()`: mirrors the selected value into a hidden input for form participation.
|
|
90
102
|
- `onSelectChange(...)`: event mixin for the bubbling `SelectChangeEvent`.
|
|
91
|
-
- `Context`, `trigger()`, `popover()`, `list()`, `option(...)`, and `hiddenInput()`: lower-level composition primitives.
|
|
92
|
-
- `triggerStyle`: standard select trigger style.
|
|
93
103
|
- `SelectChangeEvent`: event with `value`, `label`, and `optionId`.
|
|
94
|
-
- `
|
|
104
|
+
- `SelectContextProps`, `SelectProps`, and `SelectOptionProps`: primitive prop types for custom composition.
|
|
95
105
|
|
|
96
106
|
## Behavior Notes
|
|
97
107
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# select
|
|
2
|
+
|
|
3
|
+
`Select` is a button-triggered popup value picker backed by `listbox` and `popover`. Use it when the user should choose one stable string value from a finite set.
|
|
4
|
+
|
|
5
|
+
## Component Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Option, Select } from 'remix/ui/select'
|
|
9
|
+
import { onSelectChange } from 'remix/ui/select/primitives'
|
|
10
|
+
|
|
11
|
+
export function FrameworkSelect() {
|
|
12
|
+
return (
|
|
13
|
+
<Select
|
|
14
|
+
defaultLabel="Select a framework"
|
|
15
|
+
defaultValue="remix"
|
|
16
|
+
name="framework"
|
|
17
|
+
mix={onSelectChange((event) => {
|
|
18
|
+
console.log(event.value, event.label, event.optionId)
|
|
19
|
+
})}
|
|
20
|
+
>
|
|
21
|
+
<Option label="Remix framework" value="remix">
|
|
22
|
+
Remix
|
|
23
|
+
</Option>
|
|
24
|
+
<Option disabled label="React Router framework" value="react-router">
|
|
25
|
+
React Router
|
|
26
|
+
</Option>
|
|
27
|
+
<Option label="React framework" value="react">
|
|
28
|
+
React
|
|
29
|
+
</Option>
|
|
30
|
+
</Select>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Use `textValue` when closed-trigger typeahead should match a different string from the visible label.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<Select defaultLabel="Select an environment">
|
|
39
|
+
<Option label="Production environment" value="production">
|
|
40
|
+
Production
|
|
41
|
+
</Option>
|
|
42
|
+
<Option label="Staging environment" textValue="beta" value="staging">
|
|
43
|
+
Staging
|
|
44
|
+
</Option>
|
|
45
|
+
</Select>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Primitive Usage
|
|
49
|
+
|
|
50
|
+
Use the lower-level primitives when the trigger or popup structure needs to be owned by another component. Keep the same provider, trigger, popover, list, option, and hidden-input relationship.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import type { Handle } from 'remix/ui'
|
|
54
|
+
import * as popover from 'remix/ui/popover'
|
|
55
|
+
import { triggerStyle } from 'remix/ui/select'
|
|
56
|
+
import * as select from 'remix/ui/select/primitives'
|
|
57
|
+
import { listStyle, optionStyle, surfaceStyle } from './select.styles'
|
|
58
|
+
|
|
59
|
+
function SelectValue(handle: Handle) {
|
|
60
|
+
let context = handle.context.get(select.Context)
|
|
61
|
+
|
|
62
|
+
return () => <span>{context.displayedLabel}</span>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function IssueTypeSelect() {
|
|
66
|
+
return () => (
|
|
67
|
+
<select.Context defaultLabel="Select a type" labelSwapDelayMs={100} name="issueType">
|
|
68
|
+
<button type="button" mix={[triggerStyle, select.trigger()]}>
|
|
69
|
+
<SelectValue />
|
|
70
|
+
</button>
|
|
71
|
+
<popover.Context>
|
|
72
|
+
<div mix={[surfaceStyle, select.popover()]}>
|
|
73
|
+
<div mix={[listStyle, select.list()]}>
|
|
74
|
+
<div mix={[optionStyle, select.option({ label: 'Bug', value: 'bug' })]}>Bug</div>
|
|
75
|
+
<div mix={[optionStyle, select.option({ label: 'Feature', value: 'feature' })]}>
|
|
76
|
+
Feature
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</popover.Context>
|
|
81
|
+
<input mix={select.hiddenInput()} />
|
|
82
|
+
</select.Context>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## `remix/ui/select`
|
|
88
|
+
|
|
89
|
+
- `Select`: composed trigger, popover, listbox, option list, and optional hidden input for form participation. Accepts `defaultLabel`, `defaultValue`, `disabled`, `name`, and button props.
|
|
90
|
+
- `Option`: option component that renders the standard check indicator and label slot. Accepts `label`, `value`, optional `disabled`, and optional `textValue`.
|
|
91
|
+
- `triggerStyle`: standard select trigger style for custom trigger composition.
|
|
92
|
+
- `SelectProps` and `SelectOptionProps`: public TypeScript props for the composed APIs.
|
|
93
|
+
|
|
94
|
+
## `remix/ui/select/primitives`
|
|
95
|
+
|
|
96
|
+
- `Context`: lower-level provider for custom composition. Accepts `defaultLabel`, `defaultValue`, `disabled`, `name`, and primitive-only `labelSwapDelayMs`; the label-swap delay defaults to `75` milliseconds.
|
|
97
|
+
- `trigger()`: wires the trigger button, open behavior, closed-trigger typeahead, and trigger ARIA attributes.
|
|
98
|
+
- `popover()`: wires the popover surface and keeps its min-width synced to the trigger before open.
|
|
99
|
+
- `list()`: wires the listbox root used inside the popover.
|
|
100
|
+
- `option(...)`: registers one selectable option. Accepts `label`, `value`, optional `disabled`, and optional `textValue`.
|
|
101
|
+
- `hiddenInput()`: mirrors the selected value into a hidden input for form participation.
|
|
102
|
+
- `onSelectChange(...)`: event mixin for the bubbling `SelectChangeEvent`.
|
|
103
|
+
- `SelectChangeEvent`: event with `value`, `label`, and `optionId`.
|
|
104
|
+
- `SelectContextProps`, `SelectProps`, and `SelectOptionProps`: primitive prop types for custom composition.
|
|
105
|
+
|
|
106
|
+
## Behavior Notes
|
|
107
|
+
|
|
108
|
+
- `defaultLabel` is displayed before selection settles. `defaultValue` selects the matching option without replacing the trigger label until a new selection commits.
|
|
109
|
+
- `Option.label` is the committed display label and event label. `children` are the rendered option contents.
|
|
110
|
+
- Click, `ArrowDown`, and `ArrowUp` open the popup. Focus moves into the list and Escape restores focus to the trigger through popover behavior.
|
|
111
|
+
- Reopening highlights the current selected value.
|
|
112
|
+
- The popup min-width syncs to the trigger width before opening.
|
|
113
|
+
- Closed-trigger typeahead selects a matching option immediately and supports option `textValue`.
|
|
114
|
+
- Selecting an option flashes it with `data-select-flash`, waits for the close transition and label delay, updates the displayed label, and dispatches `SelectChangeEvent`.
|
|
115
|
+
- Selecting the already-selected value updates state but does not dispatch a change event.
|
|
116
|
+
- `SelectChangeEvent` bubbles from the trigger when a trigger exists. It includes `value`, `label`, and `optionId`.
|
|
117
|
+
- Passing `name` renders a hidden input so the selected value participates in `FormData`; disabled selects disable the trigger and hidden input.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# tabs
|
|
2
|
+
|
|
3
|
+
`Tabs` renders a tab control with one active tab and matching panels. Use it when related views share the same page space and the owning component may either control the active tab or let the tabs context manage it.
|
|
4
|
+
|
|
5
|
+
## Component Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
|
|
9
|
+
|
|
10
|
+
export function ProjectTabs() {
|
|
11
|
+
return (
|
|
12
|
+
<Tabs defaultActiveTab="overview">
|
|
13
|
+
<TabList aria-label="Project sections">
|
|
14
|
+
<Tab name="overview">Overview</Tab>
|
|
15
|
+
<Tab name="activity">Activity</Tab>
|
|
16
|
+
<Tab name="settings">Settings</Tab>
|
|
17
|
+
</TabList>
|
|
18
|
+
|
|
19
|
+
<TabPanel name="overview">Project summary.</TabPanel>
|
|
20
|
+
<TabPanel name="activity">Recent changes.</TabPanel>
|
|
21
|
+
<TabPanel name="settings">Project settings.</TabPanel>
|
|
22
|
+
</Tabs>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Control the active tab when state should live in the owning component:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import type { Handle } from 'remix/ui'
|
|
31
|
+
import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
|
|
32
|
+
|
|
33
|
+
export function ControlledTabs(handle: Handle) {
|
|
34
|
+
let activeTab = 'overview'
|
|
35
|
+
|
|
36
|
+
return () => (
|
|
37
|
+
<Tabs
|
|
38
|
+
activeTab={activeTab}
|
|
39
|
+
onActiveTabChange={(nextActiveTab) => {
|
|
40
|
+
activeTab = nextActiveTab
|
|
41
|
+
void handle.update()
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<TabList aria-label="Project sections">
|
|
45
|
+
<Tab name="overview">Overview</Tab>
|
|
46
|
+
<Tab name="activity">Activity</Tab>
|
|
47
|
+
</TabList>
|
|
48
|
+
|
|
49
|
+
<TabPanel name="overview">Project summary.</TabPanel>
|
|
50
|
+
<TabPanel name="activity">Recent changes.</TabPanel>
|
|
51
|
+
</Tabs>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Listen for bubbling `TabsChangeEvent` events with `onTabsChange` from `remix/ui/tabs/primitives`.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { onTabsChange } from 'remix/ui/tabs/primitives'
|
|
60
|
+
import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
|
|
61
|
+
|
|
62
|
+
export function TrackedTabs() {
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
mix={[
|
|
66
|
+
onTabsChange((event) => {
|
|
67
|
+
console.log(event.previousActiveTab, event.activeTab)
|
|
68
|
+
}),
|
|
69
|
+
]}
|
|
70
|
+
>
|
|
71
|
+
<Tabs defaultActiveTab="overview">
|
|
72
|
+
<TabList aria-label="Project sections">
|
|
73
|
+
<Tab name="overview">Overview</Tab>
|
|
74
|
+
<Tab name="activity">Activity</Tab>
|
|
75
|
+
</TabList>
|
|
76
|
+
<TabPanel name="overview">Project summary.</TabPanel>
|
|
77
|
+
<TabPanel name="activity">Recent changes.</TabPanel>
|
|
78
|
+
</Tabs>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Primitive Usage
|
|
85
|
+
|
|
86
|
+
Use the lower-level primitives when app code owns the markup and styles:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import * as tabs from 'remix/ui/tabs/primitives'
|
|
90
|
+
import { listStyle, panelStyle, rootStyle, tabStyle } from './tabs.styles'
|
|
91
|
+
|
|
92
|
+
export function PrimitiveTabs() {
|
|
93
|
+
return (
|
|
94
|
+
<tabs.Context defaultActiveTab="overview">
|
|
95
|
+
<div mix={[rootStyle, tabs.root()]}>
|
|
96
|
+
<div mix={[listStyle, tabs.list()]} aria-label="Project sections">
|
|
97
|
+
<button mix={[tabStyle, tabs.tab({ name: 'overview' })]} type="button">
|
|
98
|
+
Overview
|
|
99
|
+
</button>
|
|
100
|
+
<button mix={[tabStyle, tabs.tab({ name: 'activity' })]} type="button">
|
|
101
|
+
Activity
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div mix={[panelStyle, tabs.panel({ name: 'overview' })]}>Project summary.</div>
|
|
106
|
+
<div mix={[panelStyle, tabs.panel({ name: 'activity' })]}>Recent changes.</div>
|
|
107
|
+
</div>
|
|
108
|
+
</tabs.Context>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## `remix/ui/tabs`
|
|
114
|
+
|
|
115
|
+
- `Tabs`: root component. Supports controlled `activeTab`, uncontrolled `defaultActiveTab`, `onActiveTabChange`, `disabled`, and `size: 'md' | 'lg'`.
|
|
116
|
+
- `TabList`: tablist wrapper that lays out the tabs in a track and applies the tablist primitive.
|
|
117
|
+
- `Tab`: button for one tab `name`. The active tab uses the toggle slider treatment and button-sized text. Pass `disabled` to prevent activation.
|
|
118
|
+
- `TabPanel`: panel for one tab `name`.
|
|
119
|
+
- `rootStyle`, `listStyle`, `tabStyle`, and `panelStyle`: flat style mixins used by the component markup.
|
|
120
|
+
- `onTabsChange` and `TabsChangeEvent`: re-exported primitive event helpers.
|
|
121
|
+
- `TabsProps`, `TabListProps`, `TabProps`, `TabPanelProps`, and `TabsSize`: public TypeScript types for the composed APIs.
|
|
122
|
+
|
|
123
|
+
## `remix/ui/tabs/primitives`
|
|
124
|
+
|
|
125
|
+
- `Context`: lower-level provider for custom tabs composition.
|
|
126
|
+
- `root()`: wires the root element and bubbling change events.
|
|
127
|
+
- `list()`: wires the tablist role and disabled state.
|
|
128
|
+
- `tab({ name, disabled })`: wires a tab button with selected state, ids, keyboard activation, and pointer activation.
|
|
129
|
+
- `panel({ name })`: wires the matching tab panel id, hidden state, inert state, and label relationship.
|
|
130
|
+
- `onTabsChange(...)`: event mixin for the bubbling `TabsChangeEvent`.
|
|
131
|
+
- `TabsChangeEvent`: bubbling event with `activeTab` and `previousActiveTab`.
|
|
132
|
+
- `TabsContextProps`, `TabsContextValue`, `TabsRegisteredTab`, `TabsActivationDirection`, `TabsRootOptions`, `TabListOptions`, `TabOptions`, and `TabPanelOptions`: primitive prop and option types for custom composition.
|
|
133
|
+
|
|
134
|
+
## Behavior Notes
|
|
135
|
+
|
|
136
|
+
- If neither `activeTab` nor `defaultActiveTab` is provided, the first enabled tab becomes active.
|
|
137
|
+
- Arrow keys activate the next or previous enabled tab. `Home` and `End` activate the first and last enabled tabs.
|
|
138
|
+
- `Enter`, `Space`, and pointer clicks activate the focused tab.
|
|
139
|
+
- Root `disabled` disables every tab. Tab `disabled` only disables that tab.
|
|
140
|
+
- Tabs and panels are linked with generated ids through `aria-controls`, `aria-labelledby`, and `aria-selected`.
|
|
141
|
+
- Inactive panels receive `hidden`, `inert`, and `data-state="inactive"`.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# tabs
|
|
2
|
+
|
|
3
|
+
`Tabs` renders a tab control with one active tab and matching panels. Use it when related views share the same page space and the owning component may either control the active tab or let the tabs context manage it.
|
|
4
|
+
|
|
5
|
+
## Component Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
|
|
9
|
+
|
|
10
|
+
export function ProjectTabs() {
|
|
11
|
+
return (
|
|
12
|
+
<Tabs defaultActiveTab="overview">
|
|
13
|
+
<TabList aria-label="Project sections">
|
|
14
|
+
<Tab name="overview">Overview</Tab>
|
|
15
|
+
<Tab name="activity">Activity</Tab>
|
|
16
|
+
<Tab name="settings">Settings</Tab>
|
|
17
|
+
</TabList>
|
|
18
|
+
|
|
19
|
+
<TabPanel name="overview">Project summary.</TabPanel>
|
|
20
|
+
<TabPanel name="activity">Recent changes.</TabPanel>
|
|
21
|
+
<TabPanel name="settings">Project settings.</TabPanel>
|
|
22
|
+
</Tabs>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Control the active tab when state should live in the owning component:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import type { Handle } from 'remix/ui'
|
|
31
|
+
import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
|
|
32
|
+
|
|
33
|
+
export function ControlledTabs(handle: Handle) {
|
|
34
|
+
let activeTab = 'overview'
|
|
35
|
+
|
|
36
|
+
return () => (
|
|
37
|
+
<Tabs
|
|
38
|
+
activeTab={activeTab}
|
|
39
|
+
onActiveTabChange={(nextActiveTab) => {
|
|
40
|
+
activeTab = nextActiveTab
|
|
41
|
+
void handle.update()
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<TabList aria-label="Project sections">
|
|
45
|
+
<Tab name="overview">Overview</Tab>
|
|
46
|
+
<Tab name="activity">Activity</Tab>
|
|
47
|
+
</TabList>
|
|
48
|
+
|
|
49
|
+
<TabPanel name="overview">Project summary.</TabPanel>
|
|
50
|
+
<TabPanel name="activity">Recent changes.</TabPanel>
|
|
51
|
+
</Tabs>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Listen for bubbling `TabsChangeEvent` events with `onTabsChange` from `remix/ui/tabs/primitives`.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { onTabsChange } from 'remix/ui/tabs/primitives'
|
|
60
|
+
import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
|
|
61
|
+
|
|
62
|
+
export function TrackedTabs() {
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
mix={[
|
|
66
|
+
onTabsChange((event) => {
|
|
67
|
+
console.log(event.previousActiveTab, event.activeTab)
|
|
68
|
+
}),
|
|
69
|
+
]}
|
|
70
|
+
>
|
|
71
|
+
<Tabs defaultActiveTab="overview">
|
|
72
|
+
<TabList aria-label="Project sections">
|
|
73
|
+
<Tab name="overview">Overview</Tab>
|
|
74
|
+
<Tab name="activity">Activity</Tab>
|
|
75
|
+
</TabList>
|
|
76
|
+
<TabPanel name="overview">Project summary.</TabPanel>
|
|
77
|
+
<TabPanel name="activity">Recent changes.</TabPanel>
|
|
78
|
+
</Tabs>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Primitive Usage
|
|
85
|
+
|
|
86
|
+
Use the lower-level primitives when app code owns the markup and styles:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import * as tabs from 'remix/ui/tabs/primitives'
|
|
90
|
+
import { listStyle, panelStyle, rootStyle, tabStyle } from './tabs.styles'
|
|
91
|
+
|
|
92
|
+
export function PrimitiveTabs() {
|
|
93
|
+
return (
|
|
94
|
+
<tabs.Context defaultActiveTab="overview">
|
|
95
|
+
<div mix={[rootStyle, tabs.root()]}>
|
|
96
|
+
<div mix={[listStyle, tabs.list()]} aria-label="Project sections">
|
|
97
|
+
<button mix={[tabStyle, tabs.tab({ name: 'overview' })]} type="button">
|
|
98
|
+
Overview
|
|
99
|
+
</button>
|
|
100
|
+
<button mix={[tabStyle, tabs.tab({ name: 'activity' })]} type="button">
|
|
101
|
+
Activity
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div mix={[panelStyle, tabs.panel({ name: 'overview' })]}>Project summary.</div>
|
|
106
|
+
<div mix={[panelStyle, tabs.panel({ name: 'activity' })]}>Recent changes.</div>
|
|
107
|
+
</div>
|
|
108
|
+
</tabs.Context>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## `remix/ui/tabs`
|
|
114
|
+
|
|
115
|
+
- `Tabs`: root component. Supports controlled `activeTab`, uncontrolled `defaultActiveTab`, `onActiveTabChange`, `disabled`, and `size: 'md' | 'lg'`.
|
|
116
|
+
- `TabList`: tablist wrapper that lays out the tabs in a track and applies the tablist primitive.
|
|
117
|
+
- `Tab`: button for one tab `name`. The active tab uses the toggle slider treatment and button-sized text. Pass `disabled` to prevent activation.
|
|
118
|
+
- `TabPanel`: panel for one tab `name`.
|
|
119
|
+
- `rootStyle`, `listStyle`, `tabStyle`, and `panelStyle`: flat style mixins used by the component markup.
|
|
120
|
+
- `onTabsChange` and `TabsChangeEvent`: re-exported primitive event helpers.
|
|
121
|
+
- `TabsProps`, `TabListProps`, `TabProps`, `TabPanelProps`, and `TabsSize`: public TypeScript types for the composed APIs.
|
|
122
|
+
|
|
123
|
+
## `remix/ui/tabs/primitives`
|
|
124
|
+
|
|
125
|
+
- `Context`: lower-level provider for custom tabs composition.
|
|
126
|
+
- `root()`: wires the root element and bubbling change events.
|
|
127
|
+
- `list()`: wires the tablist role and disabled state.
|
|
128
|
+
- `tab({ name, disabled })`: wires a tab button with selected state, ids, keyboard activation, and pointer activation.
|
|
129
|
+
- `panel({ name })`: wires the matching tab panel id, hidden state, inert state, and label relationship.
|
|
130
|
+
- `onTabsChange(...)`: event mixin for the bubbling `TabsChangeEvent`.
|
|
131
|
+
- `TabsChangeEvent`: bubbling event with `activeTab` and `previousActiveTab`.
|
|
132
|
+
- `TabsContextProps`, `TabsContextValue`, `TabsRegisteredTab`, `TabsActivationDirection`, `TabsRootOptions`, `TabListOptions`, `TabOptions`, and `TabPanelOptions`: primitive prop and option types for custom composition.
|
|
133
|
+
|
|
134
|
+
## Behavior Notes
|
|
135
|
+
|
|
136
|
+
- If neither `activeTab` nor `defaultActiveTab` is provided, the first enabled tab becomes active.
|
|
137
|
+
- Arrow keys activate the next or previous enabled tab. `Home` and `End` activate the first and last enabled tabs.
|
|
138
|
+
- `Enter`, `Space`, and pointer clicks activate the focused tab.
|
|
139
|
+
- Root `disabled` disables every tab. Tab `disabled` only disables that tab.
|
|
140
|
+
- Tabs and panels are linked with generated ids through `aria-controls`, `aria-labelledby`, and `aria-selected`.
|
|
141
|
+
- Inactive panels receive `hidden`, `inert`, and `data-state="inactive"`.
|