remix 3.0.0-beta.3 → 3.0.0-beta.5
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/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/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/{glyph.js → menu/primitives.js} +1 -1
- 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/{theme.js → tabs/primitives.js} +1 -1
- package/dist/ui/tabs.d.ts +2 -0
- package/dist/ui/tabs.d.ts.map +1 -0
- package/{src/ui/glyph.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/package.json +76 -47
- package/src/assert/README.md +11 -4
- package/src/data-table-mysql/README.md +25 -0
- package/src/data-table-postgres/README.md +26 -0
- package/src/fetch-router/README.md +115 -35
- package/src/form-data-parser/README.md +4 -4
- package/src/mime/README.md +8 -1
- package/src/node-fetch-server/README.md +28 -2
- package/src/route-pattern/README.md +51 -6
- package/src/session/README.md +1 -1
- package/src/test/README.md +27 -0
- package/src/ui/README.md +50 -164
- 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/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/{dist/ui/scroll-lock.js → src/ui/menu/primitives.ts} +1 -1
- 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/{dist/ui/separator.js → src/ui/tabs/primitives.ts} +1 -1
- package/src/ui/{theme.ts → tabs.ts} +1 -1
- 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/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/button/README.md
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
# button
|
|
2
2
|
|
|
3
|
-
`button` is
|
|
3
|
+
`button` is a style mixin for pill-shaped action controls. It owns only button-like visual styling and the default `type="button"` behavior for native `<button>` hosts.
|
|
4
4
|
|
|
5
|
-
## Usage
|
|
5
|
+
## Primitive Usage
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
|
-
import
|
|
9
|
-
import * as button from 'remix/ui/button'
|
|
10
|
-
import { Glyph } from 'remix/ui/glyph'
|
|
8
|
+
import button from 'remix/ui/button'
|
|
11
9
|
|
|
12
10
|
function Actions() {
|
|
13
|
-
return (
|
|
11
|
+
return () => (
|
|
14
12
|
<div>
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
</
|
|
18
|
-
|
|
19
|
-
<a href="/projects" mix={[button.baseStyle, button.secondaryStyle]}>
|
|
20
|
-
<span mix={button.labelStyle}>View projects</span>
|
|
21
|
-
<Glyph mix={button.iconStyle} name="chevronRight" />
|
|
22
|
-
</a>
|
|
13
|
+
<button mix={button()}>Edit order</button>
|
|
14
|
+
<button mix={button({ size: 'lg', tone: 'primary' })}>Add product</button>
|
|
15
|
+
<button mix={button({ tone: 'ghost' })}>Cancel</button>
|
|
23
16
|
</div>
|
|
24
17
|
)
|
|
25
18
|
}
|
|
26
19
|
```
|
|
27
20
|
|
|
28
|
-
|
|
21
|
+
Compose app-owned styles around the primitive when a control needs local layout or state styling:
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- `button.labelStyle`: inline label slot with the standard button spacing.
|
|
34
|
-
- `button.iconStyle`: icon slot sizing and `aria-hidden` defaults for decorative icons.
|
|
23
|
+
```tsx
|
|
24
|
+
import button from 'remix/ui/button'
|
|
25
|
+
import { toolbarButtonStyle } from './toolbar.styles'
|
|
35
26
|
|
|
36
|
-
|
|
27
|
+
function ToolbarAction() {
|
|
28
|
+
return () => <button mix={[toolbarButtonStyle, button({ tone: 'ghost' })]}>Archive</button>
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## `remix/ui/button`
|
|
37
33
|
|
|
38
|
-
- `button
|
|
39
|
-
- `
|
|
40
|
-
-
|
|
34
|
+
- `button(options)`: style mixin for native buttons or button-like hosts.
|
|
35
|
+
- `ButtonOptions`: accepts `size` and `tone`.
|
|
36
|
+
- `ButtonSize`: `'md'` or `'lg'`. Defaults to `'md'`.
|
|
37
|
+
- `ButtonTone`: `'neutral'`, `'primary'`, or `'ghost'`. Defaults to `'neutral'`.
|
|
41
38
|
|
|
42
|
-
##
|
|
39
|
+
## Behavior Notes
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
- `button()` returns a mixin descriptor, so it composes with other mixins in the host element's `mix` prop.
|
|
42
|
+
- Native `<button>` hosts receive `type="button"` unless an explicit `type` is provided.
|
|
43
|
+
- Non-button hosts receive styling only.
|
|
44
|
+
- Disabled hosts use the shared disabled treatment through `disabled` or `aria-disabled="true"`.
|
package/src/ui/button.ts
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# checkbox
|
|
2
|
+
|
|
3
|
+
`checkbox` is a style mixin for native checkbox inputs. It only owns checkbox visuals, an optional visual state, and the default `type="checkbox"` behavior for native `<input>` hosts.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import checkbox from 'remix/ui/checkbox'
|
|
9
|
+
|
|
10
|
+
function VisibilityToggle() {
|
|
11
|
+
return () => (
|
|
12
|
+
<label>
|
|
13
|
+
<input defaultChecked mix={checkbox()} name="visibility" value="archived" />
|
|
14
|
+
Show archived items
|
|
15
|
+
</label>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Use `size: 'lg'` when the surrounding UI needs a larger visual control:
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
<input mix={checkbox({ size: 'lg' })} />
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For mixed state, app code owns the checkbox state and the native `indeterminate` property:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import checkbox from 'remix/ui/checkbox'
|
|
30
|
+
|
|
31
|
+
function PermissionParent() {
|
|
32
|
+
let state: 'checked' | 'mixed' | 'unchecked' = 'mixed'
|
|
33
|
+
|
|
34
|
+
return () => (
|
|
35
|
+
<input
|
|
36
|
+
checked={state === 'checked'}
|
|
37
|
+
indeterminate={state === 'mixed'}
|
|
38
|
+
mix={checkbox({ state })}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## `remix/ui/checkbox`
|
|
45
|
+
|
|
46
|
+
- `checkbox(options)`: style mixin for native checkbox inputs or checkbox-like hosts.
|
|
47
|
+
- `CheckboxOptions`: accepts `size` and `state`.
|
|
48
|
+
- `CheckboxSize`: `'md'` or `'lg'`. Defaults to `'md'`.
|
|
49
|
+
- `CheckboxState`: `'checked'`, `'mixed'`, or `'unchecked'`.
|
|
50
|
+
|
|
51
|
+
## Behavior Notes
|
|
52
|
+
|
|
53
|
+
- `checkbox()` returns a mixin descriptor, so it composes with other mixins in the host element's `mix` prop.
|
|
54
|
+
- Native `<input>` hosts receive `type="checkbox"` unless an explicit `type` is provided.
|
|
55
|
+
- Native input state is browser-owned through `checked`, `defaultChecked`, and user interaction.
|
|
56
|
+
- The optional `state` option adds `aria-checked` and `data-state` for app-owned mixed state or custom hosts.
|
|
57
|
+
- Checked styles apply through `:checked`, `aria-checked="true"`, or `data-state="checked"`.
|
|
58
|
+
- Mixed styles apply through `:indeterminate`, `indeterminate`, `aria-checked="mixed"`, or `data-state="mixed"`.
|
|
59
|
+
- Disabled hosts use the shared disabled treatment through `disabled` or `aria-disabled="true"`.
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# combobox
|
|
2
2
|
|
|
3
|
-
`Combobox` is the input-first popup value picker for `remix/ui`.
|
|
3
|
+
`Combobox` is the input-first popup value picker for `remix/ui/combobox`.
|
|
4
4
|
|
|
5
5
|
Use it when the user should type draft text, filter a popup list, and still commit one stable form value. If you just need a button-triggered picker, use `Select` instead.
|
|
6
6
|
|
|
7
|
-
## Usage
|
|
7
|
+
## Component Usage
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
10
10
|
import { css, type Handle } from 'remix/ui'
|
|
11
|
-
import { Combobox, ComboboxOption
|
|
11
|
+
import { Combobox, ComboboxOption } from 'remix/ui/combobox'
|
|
12
|
+
import { onComboboxChange } from 'remix/ui/combobox/primitives'
|
|
12
13
|
|
|
13
14
|
let airports = [
|
|
14
15
|
{
|
|
@@ -58,7 +59,39 @@ let root = css({
|
|
|
58
59
|
})
|
|
59
60
|
```
|
|
60
61
|
|
|
61
|
-
##
|
|
62
|
+
## Primitive Usage
|
|
63
|
+
|
|
64
|
+
Use the lower-level primitives when app code owns the input, popover, list, and option markup:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import * as combobox from 'remix/ui/combobox/primitives'
|
|
68
|
+
import { inputStyle, listStyle, optionStyle, popoverStyle } from './combobox.styles'
|
|
69
|
+
|
|
70
|
+
let frameworks = [
|
|
71
|
+
{ label: 'Remix', searchValue: ['remix', 'rmx'], value: 'remix' },
|
|
72
|
+
{ label: 'React Router', value: 'react-router' },
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
export function PrimitiveCombobox() {
|
|
76
|
+
return (
|
|
77
|
+
<combobox.Context name="framework">
|
|
78
|
+
<input mix={[inputStyle, combobox.input()]} placeholder="Search frameworks" />
|
|
79
|
+
<div mix={[popoverStyle, combobox.popover()]}>
|
|
80
|
+
<div mix={[listStyle, combobox.list()]}>
|
|
81
|
+
{frameworks.map((option) => (
|
|
82
|
+
<div key={option.value} mix={[optionStyle, combobox.option(option)]}>
|
|
83
|
+
{option.label}
|
|
84
|
+
</div>
|
|
85
|
+
))}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<input mix={combobox.hiddenInput()} />
|
|
89
|
+
</combobox.Context>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## `remix/ui/combobox`
|
|
62
95
|
|
|
63
96
|
### `Combobox`
|
|
64
97
|
|
|
@@ -66,7 +99,7 @@ The convenience component.
|
|
|
66
99
|
|
|
67
100
|
- Renders the text input, popover surface, listbox root, and hidden form input.
|
|
68
101
|
- Dispatches a bubbled custom event that `onComboboxChange(...)` listens for when the committed value changes.
|
|
69
|
-
- Accepts `defaultValue`, `disabled`, `inputId`, `name`, and `
|
|
102
|
+
- Accepts `children`, `defaultValue`, `disabled`, `inputId`, `name`, `placeholder`, and root `div` props.
|
|
70
103
|
|
|
71
104
|
### `ComboboxOption`
|
|
72
105
|
|
|
@@ -74,21 +107,31 @@ The default option row for `Combobox`.
|
|
|
74
107
|
|
|
75
108
|
- Uses the shared listbox option visuals.
|
|
76
109
|
- Accepts `label`, `value`, optional `searchValue`, and optional `disabled`.
|
|
110
|
+
- Renders `children` when provided, otherwise renders `label`.
|
|
77
111
|
- `searchValue` can be a string or string array for aliases like airport codes, abbreviations, or alternate labels.
|
|
78
112
|
|
|
113
|
+
### Style and Prop Exports
|
|
114
|
+
|
|
115
|
+
- `inputStyle`: default combobox input style.
|
|
116
|
+
- `popoverStyle`: default combobox popover behavior style.
|
|
117
|
+
- `ComboboxProps` and `ComboboxOptionProps`: public TypeScript props for the composed APIs.
|
|
118
|
+
|
|
119
|
+
## `remix/ui/combobox/primitives`
|
|
120
|
+
|
|
79
121
|
### `onComboboxChange(...)`
|
|
80
122
|
|
|
81
|
-
The listener mixin for bubbled committed-value changes.
|
|
123
|
+
The listener mixin from `remix/ui/combobox/primitives` for bubbled committed-value changes.
|
|
82
124
|
|
|
83
125
|
The event object includes:
|
|
84
126
|
|
|
85
127
|
- `event.value`: the committed value or `null`
|
|
86
128
|
- `event.label`: the committed option label or `null`
|
|
87
129
|
- `event.optionId`: the generated option id or `null`
|
|
130
|
+
- `ComboboxChangeEvent`: the event class dispatched for committed value changes.
|
|
88
131
|
|
|
89
132
|
### `combobox.Context`
|
|
90
133
|
|
|
91
|
-
The lower-level coordinator for custom combobox composition.
|
|
134
|
+
The lower-level coordinator from `remix/ui/combobox/primitives` for custom combobox composition.
|
|
92
135
|
|
|
93
136
|
It wraps the shared `popover` and `listbox` contexts and owns the draft text, committed value, popup state, and selection timing.
|
|
94
137
|
|
|
@@ -106,7 +149,7 @@ Turns the host into the combobox popover surface.
|
|
|
106
149
|
|
|
107
150
|
- Uses the shared popover primitive.
|
|
108
151
|
- Keeps anchor clicks inside the session so the input stays interactive while open.
|
|
109
|
-
- Applies the combobox open/close reason contract used by `
|
|
152
|
+
- Applies the combobox open/close reason contract used by `popoverStyle`.
|
|
110
153
|
|
|
111
154
|
### `combobox.list()`
|
|
112
155
|
|
|
@@ -126,6 +169,12 @@ Mirrors the committed value into a hidden input for forms.
|
|
|
126
169
|
|
|
127
170
|
Apply it to an `<input type="hidden" />` inside the same `combobox.Context`.
|
|
128
171
|
|
|
172
|
+
### Primitive Types
|
|
173
|
+
|
|
174
|
+
- `ComboboxOpenStrategy`: initial active-option strategy when the popup opens.
|
|
175
|
+
- `ComboboxHandle`: imperative ref for reading or updating the combobox value and draft label.
|
|
176
|
+
- `ComboboxContextProps`, `ComboboxProps`, `ComboboxOptionOptions`, and `ComboboxOptionProps`: primitive prop and option types for custom composition.
|
|
177
|
+
|
|
129
178
|
## Behavior Notes
|
|
130
179
|
|
|
131
180
|
- Typing opens the popup in hint mode when there are matches.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# combobox
|
|
2
|
+
|
|
3
|
+
`Combobox` is the input-first popup value picker for `remix/ui/combobox`.
|
|
4
|
+
|
|
5
|
+
Use it when the user should type draft text, filter a popup list, and still commit one stable form value. If you just need a button-triggered picker, use `Select` instead.
|
|
6
|
+
|
|
7
|
+
## Component Usage
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { css, type Handle } from 'remix/ui'
|
|
11
|
+
import { Combobox, ComboboxOption } from 'remix/ui/combobox'
|
|
12
|
+
import { onComboboxChange } from 'remix/ui/combobox/primitives'
|
|
13
|
+
|
|
14
|
+
let airports = [
|
|
15
|
+
{
|
|
16
|
+
label: 'Los Angeles International',
|
|
17
|
+
searchValue: ['lax', 'los angeles', 'los angeles international'],
|
|
18
|
+
value: 'LAX',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: 'John F. Kennedy International',
|
|
22
|
+
searchValue: ['jfk', 'new york', 'john f. kennedy international'],
|
|
23
|
+
value: 'JFK',
|
|
24
|
+
},
|
|
25
|
+
] as const
|
|
26
|
+
|
|
27
|
+
export default function AirportField(handle: Handle) {
|
|
28
|
+
let value: string | null = null
|
|
29
|
+
|
|
30
|
+
return () => (
|
|
31
|
+
<div mix={root}>
|
|
32
|
+
<Combobox
|
|
33
|
+
inputId="airport"
|
|
34
|
+
mix={onComboboxChange((event) => {
|
|
35
|
+
value = event.value
|
|
36
|
+
void handle.update()
|
|
37
|
+
})}
|
|
38
|
+
name="airport"
|
|
39
|
+
placeholder="Search airports or codes"
|
|
40
|
+
>
|
|
41
|
+
{airports.map((airport) => (
|
|
42
|
+
<ComboboxOption
|
|
43
|
+
key={airport.value}
|
|
44
|
+
label={airport.label}
|
|
45
|
+
searchValue={airport.searchValue}
|
|
46
|
+
value={airport.value}
|
|
47
|
+
/>
|
|
48
|
+
))}
|
|
49
|
+
</Combobox>
|
|
50
|
+
|
|
51
|
+
<p>{`value=${value ?? 'null'}`}</p>
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let root = css({
|
|
57
|
+
display: 'grid',
|
|
58
|
+
gap: '8px',
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Primitive Usage
|
|
63
|
+
|
|
64
|
+
Use the lower-level primitives when app code owns the input, popover, list, and option markup:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import * as combobox from 'remix/ui/combobox/primitives'
|
|
68
|
+
import { inputStyle, listStyle, optionStyle, popoverStyle } from './combobox.styles'
|
|
69
|
+
|
|
70
|
+
let frameworks = [
|
|
71
|
+
{ label: 'Remix', searchValue: ['remix', 'rmx'], value: 'remix' },
|
|
72
|
+
{ label: 'React Router', value: 'react-router' },
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
export function PrimitiveCombobox() {
|
|
76
|
+
return (
|
|
77
|
+
<combobox.Context name="framework">
|
|
78
|
+
<input mix={[inputStyle, combobox.input()]} placeholder="Search frameworks" />
|
|
79
|
+
<div mix={[popoverStyle, combobox.popover()]}>
|
|
80
|
+
<div mix={[listStyle, combobox.list()]}>
|
|
81
|
+
{frameworks.map((option) => (
|
|
82
|
+
<div key={option.value} mix={[optionStyle, combobox.option(option)]}>
|
|
83
|
+
{option.label}
|
|
84
|
+
</div>
|
|
85
|
+
))}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<input mix={combobox.hiddenInput()} />
|
|
89
|
+
</combobox.Context>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## `remix/ui/combobox`
|
|
95
|
+
|
|
96
|
+
### `Combobox`
|
|
97
|
+
|
|
98
|
+
The convenience component.
|
|
99
|
+
|
|
100
|
+
- Renders the text input, popover surface, listbox root, and hidden form input.
|
|
101
|
+
- Dispatches a bubbled custom event that `onComboboxChange(...)` listens for when the committed value changes.
|
|
102
|
+
- Accepts `children`, `defaultValue`, `disabled`, `inputId`, `name`, `placeholder`, and root `div` props.
|
|
103
|
+
|
|
104
|
+
### `ComboboxOption`
|
|
105
|
+
|
|
106
|
+
The default option row for `Combobox`.
|
|
107
|
+
|
|
108
|
+
- Uses the shared listbox option visuals.
|
|
109
|
+
- Accepts `label`, `value`, optional `searchValue`, and optional `disabled`.
|
|
110
|
+
- Renders `children` when provided, otherwise renders `label`.
|
|
111
|
+
- `searchValue` can be a string or string array for aliases like airport codes, abbreviations, or alternate labels.
|
|
112
|
+
|
|
113
|
+
### Style and Prop Exports
|
|
114
|
+
|
|
115
|
+
- `inputStyle`: default combobox input style.
|
|
116
|
+
- `popoverStyle`: default combobox popover behavior style.
|
|
117
|
+
- `ComboboxProps` and `ComboboxOptionProps`: public TypeScript props for the composed APIs.
|
|
118
|
+
|
|
119
|
+
## `remix/ui/combobox/primitives`
|
|
120
|
+
|
|
121
|
+
### `onComboboxChange(...)`
|
|
122
|
+
|
|
123
|
+
The listener mixin from `remix/ui/combobox/primitives` for bubbled committed-value changes.
|
|
124
|
+
|
|
125
|
+
The event object includes:
|
|
126
|
+
|
|
127
|
+
- `event.value`: the committed value or `null`
|
|
128
|
+
- `event.label`: the committed option label or `null`
|
|
129
|
+
- `event.optionId`: the generated option id or `null`
|
|
130
|
+
- `ComboboxChangeEvent`: the event class dispatched for committed value changes.
|
|
131
|
+
|
|
132
|
+
### `combobox.Context`
|
|
133
|
+
|
|
134
|
+
The lower-level coordinator from `remix/ui/combobox/primitives` for custom combobox composition.
|
|
135
|
+
|
|
136
|
+
It wraps the shared `popover` and `listbox` contexts and owns the draft text, committed value, popup state, and selection timing.
|
|
137
|
+
|
|
138
|
+
### `combobox.input()`
|
|
139
|
+
|
|
140
|
+
Turns the host input into the combobox input.
|
|
141
|
+
|
|
142
|
+
- Keeps focus on the input during list navigation and pointer selection.
|
|
143
|
+
- Wires `role="combobox"`, `aria-expanded`, `aria-controls`, and `aria-activedescendant`.
|
|
144
|
+
- Opens from typing, click, and arrow-key navigation.
|
|
145
|
+
|
|
146
|
+
### `combobox.popover()`
|
|
147
|
+
|
|
148
|
+
Turns the host into the combobox popover surface.
|
|
149
|
+
|
|
150
|
+
- Uses the shared popover primitive.
|
|
151
|
+
- Keeps anchor clicks inside the session so the input stays interactive while open.
|
|
152
|
+
- Applies the combobox open/close reason contract used by `popoverStyle`.
|
|
153
|
+
|
|
154
|
+
### `combobox.list()`
|
|
155
|
+
|
|
156
|
+
Turns the host into the popup listbox root and applies the generated list id.
|
|
157
|
+
|
|
158
|
+
### `combobox.option(options)`
|
|
159
|
+
|
|
160
|
+
Registers one option with the combobox and listbox layers.
|
|
161
|
+
|
|
162
|
+
- Accepts `label`, `value`, optional `searchValue`, and optional `disabled`.
|
|
163
|
+
- Hides non-matching options from the current draft filter.
|
|
164
|
+
- Prevents pointer selection from blurring the input before the click commits.
|
|
165
|
+
|
|
166
|
+
### `combobox.hiddenInput()`
|
|
167
|
+
|
|
168
|
+
Mirrors the committed value into a hidden input for forms.
|
|
169
|
+
|
|
170
|
+
Apply it to an `<input type="hidden" />` inside the same `combobox.Context`.
|
|
171
|
+
|
|
172
|
+
### Primitive Types
|
|
173
|
+
|
|
174
|
+
- `ComboboxOpenStrategy`: initial active-option strategy when the popup opens.
|
|
175
|
+
- `ComboboxHandle`: imperative ref for reading or updating the combobox value and draft label.
|
|
176
|
+
- `ComboboxContextProps`, `ComboboxProps`, `ComboboxOptionOptions`, and `ComboboxOptionProps`: primitive prop and option types for custom composition.
|
|
177
|
+
|
|
178
|
+
## Behavior Notes
|
|
179
|
+
|
|
180
|
+
- Typing opens the popup in hint mode when there are matches.
|
|
181
|
+
- If typing leaves no matches, the popup closes immediately without the navigation fade-out.
|
|
182
|
+
- Selecting from the list flashes the option, then closes the popup and finally commits the visible input label.
|
|
183
|
+
- Typing clears the committed value immediately; the hidden form value becomes empty until the user commits again.
|
|
184
|
+
- Blur commits an exact `label` or `searchValue` match. A non-matching blur clears the draft text and committed value.
|
|
185
|
+
- `Escape` keeps exact-match draft text but clears non-matching draft text and selection.
|
|
186
|
+
- Disabled options can stay visible in filtered results, but they are skipped by keyboard navigation and selection.
|
|
187
|
+
|
|
188
|
+
## When To Use Something Else
|
|
189
|
+
|
|
190
|
+
Use `Select` when you want the ordinary button-triggered single-select control.
|
|
191
|
+
|
|
192
|
+
Use `listbox` when you need listbox semantics without an editable text input.
|
|
193
|
+
|
|
194
|
+
Use `popover` directly for custom floating panels that are not value-picking controls.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# input
|
|
2
|
+
|
|
3
|
+
`input` is a style mixin for text inputs. Use `input()` directly on standalone native inputs, or compose `input.root()` with `input.field()` when the input needs inline icons or controls.
|
|
4
|
+
|
|
5
|
+
## Primitive Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import input from 'remix/ui/input'
|
|
9
|
+
|
|
10
|
+
function ProductFilters() {
|
|
11
|
+
return () => (
|
|
12
|
+
<div>
|
|
13
|
+
<input mix={input()} placeholder="Limit" />
|
|
14
|
+
|
|
15
|
+
<div mix={input.root()}>
|
|
16
|
+
<SearchIcon />
|
|
17
|
+
<input mix={input.field()} placeholder="Search and filter products" />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Compose app-owned styles when a field needs local layout or adornments:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import input from 'remix/ui/input'
|
|
28
|
+
import { filterFieldStyle, filterRootStyle } from './filters.styles'
|
|
29
|
+
|
|
30
|
+
function SearchFilter() {
|
|
31
|
+
return () => (
|
|
32
|
+
<div mix={[filterRootStyle, input.root()]}>
|
|
33
|
+
<SearchIcon />
|
|
34
|
+
<input mix={[filterFieldStyle, input.field()]} placeholder="Search" />
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## `remix/ui/input`
|
|
41
|
+
|
|
42
|
+
- `input(options)`: styles a standalone native input. `size` may be `'md'` or `'lg'` and defaults to `'md'`.
|
|
43
|
+
- `input.root(options)`: styles a flex input frame for inline icons, buttons, and a child input.
|
|
44
|
+
- `input.field()`: styles the child native input inside `input.root()`.
|
|
45
|
+
- `InputOptions`: accepts `size`.
|
|
46
|
+
- `InputSize`: `'md'` or `'lg'`.
|
|
47
|
+
|
|
48
|
+
## Behavior Notes
|
|
49
|
+
|
|
50
|
+
- `input.root()` uses child selectors to size direct SVG children as presentational icons.
|
|
51
|
+
- Put the icon before or after the field to control its visual position.
|
|
52
|
+
- Focus and disabled states work through the native input; the root mirrors them with `:focus-within` and `:has(input:disabled)`.
|
package/src/ui/input.ts
ADDED
package/src/ui/listbox/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# listbox
|
|
2
2
|
|
|
3
|
-
`listbox` is a headless option-list primitive for controlled selection and highlighting. Use it under components like
|
|
3
|
+
`listbox` is a headless option-list primitive for controlled selection and highlighting. Use it under components like select and combobox, or directly when you need custom listbox markup.
|
|
4
4
|
|
|
5
|
-
## Usage
|
|
5
|
+
## Primitive Usage
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
8
|
import type { Handle } from 'remix/ui'
|
|
9
|
-
import { Glyph } from 'remix/ui/glyph'
|
|
10
9
|
import * as listbox from 'remix/ui/listbox'
|
|
11
10
|
import type { ListboxValue } from 'remix/ui/listbox'
|
|
11
|
+
import { listStyle, optionStyle } from './listbox.styles'
|
|
12
12
|
|
|
13
13
|
function FrameworkListbox(handle: Handle) {
|
|
14
14
|
let value: ListboxValue = 'remix'
|
|
@@ -27,11 +27,10 @@ function FrameworkListbox(handle: Handle) {
|
|
|
27
27
|
void handle.update()
|
|
28
28
|
}}
|
|
29
29
|
>
|
|
30
|
-
<div aria-label="Frameworks" tabIndex={0} mix={[
|
|
30
|
+
<div aria-label="Frameworks" tabIndex={0} mix={[listStyle, listbox.list()]}>
|
|
31
31
|
{frameworks.map((option) => (
|
|
32
|
-
<div key={option.value} mix={[
|
|
33
|
-
|
|
34
|
-
<span mix={listbox.labelStyle}>{option.label}</span>
|
|
32
|
+
<div key={option.value} mix={[optionStyle, listbox.option(option)]}>
|
|
33
|
+
{option.label}
|
|
35
34
|
</div>
|
|
36
35
|
))}
|
|
37
36
|
</div>
|
|
@@ -52,6 +51,7 @@ Use `textValue` when the visible label is not the best string for typeahead sear
|
|
|
52
51
|
```tsx
|
|
53
52
|
<div
|
|
54
53
|
mix={[
|
|
54
|
+
optionStyle,
|
|
55
55
|
listbox.option({
|
|
56
56
|
label: 'Staging',
|
|
57
57
|
textValue: 'beta',
|
|
@@ -63,42 +63,13 @@ Use `textValue` when the visible label is not the best string for typeahead sear
|
|
|
63
63
|
</div>
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```tsx
|
|
69
|
-
import type { ListboxRef } from 'remix/ui/listbox'
|
|
70
|
-
|
|
71
|
-
let listboxRef: ListboxRef | undefined
|
|
72
|
-
|
|
73
|
-
function selectLastOption() {
|
|
74
|
-
listboxRef?.navigateLast()
|
|
75
|
-
void listboxRef?.selectActive()
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
;<listbox.Context
|
|
79
|
-
value={value}
|
|
80
|
-
activeValue={activeValue}
|
|
81
|
-
ref={(ref) => {
|
|
82
|
-
listboxRef = ref
|
|
83
|
-
}}
|
|
84
|
-
onSelect={(nextValue) => {
|
|
85
|
-
value = nextValue
|
|
86
|
-
}}
|
|
87
|
-
onHighlight={(nextActiveValue) => {
|
|
88
|
-
activeValue = nextActiveValue
|
|
89
|
-
}}
|
|
90
|
-
>
|
|
91
|
-
{/* listbox markup */}
|
|
92
|
-
</listbox.Context>
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## `listbox.*`
|
|
66
|
+
## `remix/ui/listbox`
|
|
96
67
|
|
|
97
68
|
- `listbox.Context`: provider for controlled `value` and `activeValue`, option registration, selection, highlighting, optional ref access, `flashSelection`, `selectionFlashAttribute`, and `onSelectSettled`.
|
|
98
69
|
- `listbox.list()`: mixin that wires `role="listbox"`, default `tabIndex={-1}`, keyboard navigation, focus scrolling, and typeahead highlighting.
|
|
99
70
|
- `listbox.option(options)`: mixin that registers an option with required `label` and `value`, optional `disabled` and `textValue`, and wires `role="option"`, id, selected, disabled, highlighted, mouse, and click behavior.
|
|
100
|
-
- `listStyle`, `optionStyle`, `glyphStyle`, and `labelStyle`: flat style mixins for standard listbox presentation.
|
|
101
71
|
- `ListboxValue`: selected or active value, represented as `string | null`.
|
|
72
|
+
- `ListboxContext` and `ListboxProviderProps`: provider context and prop types for controlled listboxes.
|
|
102
73
|
- `ListboxOption`: option input shape with `label`, `value`, optional `disabled`, and optional `textValue`.
|
|
103
74
|
- `ListboxRegisteredOption`: registered option metadata passed to callbacks and refs.
|
|
104
75
|
- `ListboxRef`: live ref object exposing active/selected options, option navigation, search matching, scrolling, and selection helpers.
|
|
@@ -108,8 +79,5 @@ function selectLastOption() {
|
|
|
108
79
|
- Selection and highlighting are controlled. `onSelect` and `onHighlight` notify the parent, but DOM state updates after the parent rerenders with new values.
|
|
109
80
|
- Disabled options are skipped by keyboard navigation, typeahead, mouse movement, and click selection.
|
|
110
81
|
- Arrow keys wrap through enabled options. `Home` and `End` move to enabled boundaries. `Enter` and Space select the active option.
|
|
111
|
-
- Mouse movement highlights enabled options. `mouseleave` clears the highlight when leaving the active option.
|
|
112
|
-
- `Tab` is prevented and highlights the first enabled option.
|
|
113
82
|
- Typeahead highlights the next matching enabled option without selecting it and supports `textValue`.
|
|
114
|
-
- Focus and keyboard navigation scroll the active option into view with nearest-edge alignment.
|
|
115
83
|
- `flashSelection` applies `selectionFlashAttribute` for 60ms, delays `onSelectSettled`, and ignores new highlight/select interactions until the flash completes.
|