remix 3.0.0-beta.4 → 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 +73 -45
- package/src/data-table-mysql/README.md +25 -0
- package/src/data-table-postgres/README.md +26 -0
- 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/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/session/README.md
CHANGED
|
@@ -126,7 +126,7 @@ This will clear all session data from storage the next time it is saved. It also
|
|
|
126
126
|
|
|
127
127
|
Several strategies are provided out of the box for storing session data across requests, depending on your needs.
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
Session storage objects read and save cookie values. Use the `session` middleware with a signed `Cookie` to parse the incoming `Cookie` header, expose the session on request context, and serialize any saved value back into a `Set-Cookie` response header.
|
|
130
130
|
|
|
131
131
|
#### Filesystem Storage
|
|
132
132
|
|
package/src/ui/README.md
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
# ui
|
|
2
2
|
|
|
3
|
-
Runtime UI primitives for Remix apps, including the component runtime, server rendering, frame hydration, reusable mixins, first-party
|
|
3
|
+
Runtime UI primitives for Remix apps, including the component runtime, server rendering, frame hydration, reusable mixins, and headless first-party behavior primitives.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- Component runtime APIs for rendering, hydration, frame navigation, and JSX
|
|
8
8
|
- Server rendering APIs for streaming Remix UI trees and frames
|
|
9
9
|
- `mix` composition with event, ref, CSS, and animation helpers
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- `createTheme()` and `createGlyphSheet()` utilities for shared app styling and glyphs
|
|
10
|
+
- Headless behavior primitives for controls such as menus, listboxes, popovers, selects, and comboboxes
|
|
11
|
+
- Lower-level utilities for keyboard events, typeahead search, refs, attributes, and CSS transition timing
|
|
13
12
|
|
|
14
13
|
## Installation
|
|
15
14
|
|
|
@@ -19,188 +18,75 @@ npm i remix
|
|
|
19
18
|
|
|
20
19
|
## Usage
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
Compose behavior primitives with your own markup and styles:
|
|
23
22
|
|
|
24
23
|
```tsx
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
sm: '4px',
|
|
33
|
-
md: '8px',
|
|
34
|
-
lg: '12px',
|
|
35
|
-
xl: '16px',
|
|
36
|
-
xxl: '24px',
|
|
37
|
-
},
|
|
38
|
-
radius: {
|
|
39
|
-
none: '0px',
|
|
40
|
-
sm: '4px',
|
|
41
|
-
md: '8px',
|
|
42
|
-
lg: '12px',
|
|
43
|
-
xl: '16px',
|
|
44
|
-
full: '9999px',
|
|
45
|
-
},
|
|
46
|
-
fontSize: {
|
|
47
|
-
xxxs: '10px',
|
|
48
|
-
xxs: '11px',
|
|
49
|
-
xs: '12px',
|
|
50
|
-
sm: '14px',
|
|
51
|
-
md: '16px',
|
|
52
|
-
lg: '18px',
|
|
53
|
-
xl: '20px',
|
|
54
|
-
xxl: '28px',
|
|
55
|
-
},
|
|
56
|
-
lineHeight: {
|
|
57
|
-
tight: '1.2',
|
|
58
|
-
normal: '1.5',
|
|
59
|
-
relaxed: '1.7',
|
|
60
|
-
},
|
|
61
|
-
fontWeight: {
|
|
62
|
-
normal: '400',
|
|
63
|
-
medium: '500',
|
|
64
|
-
semibold: '600',
|
|
65
|
-
bold: '700',
|
|
66
|
-
},
|
|
67
|
-
shadow: {
|
|
68
|
-
xs: '0 1px 2px rgb(0 0 0 / 0.05)',
|
|
69
|
-
sm: '0 1px 3px rgb(0 0 0 / 0.10)',
|
|
70
|
-
md: '0 4px 10px rgb(0 0 0 / 0.12)',
|
|
71
|
-
lg: '0 10px 30px rgb(0 0 0 / 0.16)',
|
|
72
|
-
xl: '0 20px 50px rgb(0 0 0 / 0.20)',
|
|
73
|
-
},
|
|
74
|
-
zIndex: {
|
|
75
|
-
dropdown: '1000',
|
|
76
|
-
popover: '1100',
|
|
77
|
-
sticky: '1200',
|
|
78
|
-
overlay: '1300',
|
|
79
|
-
modal: '1400',
|
|
80
|
-
toast: '1500',
|
|
81
|
-
tooltip: '1600',
|
|
82
|
-
},
|
|
83
|
-
surface: {
|
|
84
|
-
lvl0: '#ffffff',
|
|
85
|
-
lvl1: '#f8fafc',
|
|
86
|
-
lvl2: '#f1f5f9',
|
|
87
|
-
lvl3: '#e5edf7',
|
|
88
|
-
lvl4: '#dbe6f4',
|
|
89
|
-
},
|
|
90
|
-
colors: {
|
|
91
|
-
text: {
|
|
92
|
-
primary: '#111827',
|
|
93
|
-
secondary: '#374151',
|
|
94
|
-
muted: '#6b7280',
|
|
95
|
-
link: '#2563eb',
|
|
96
|
-
},
|
|
97
|
-
border: {
|
|
98
|
-
subtle: '#e5e7eb',
|
|
99
|
-
default: '#d1d5db',
|
|
100
|
-
strong: '#9ca3af',
|
|
101
|
-
},
|
|
102
|
-
focus: {
|
|
103
|
-
ring: '#3b82f6',
|
|
104
|
-
},
|
|
105
|
-
overlay: {
|
|
106
|
-
scrim: 'rgb(0 0 0 / 0.45)',
|
|
107
|
-
},
|
|
108
|
-
action: {
|
|
109
|
-
primary: {
|
|
110
|
-
background: '#2563eb',
|
|
111
|
-
backgroundHover: '#1d4ed8',
|
|
112
|
-
backgroundActive: '#1e40af',
|
|
113
|
-
foreground: '#ffffff',
|
|
114
|
-
border: '#2563eb',
|
|
115
|
-
},
|
|
116
|
-
secondary: {
|
|
117
|
-
background: '#ffffff',
|
|
118
|
-
backgroundHover: '#f8fafc',
|
|
119
|
-
backgroundActive: '#f1f5f9',
|
|
120
|
-
foreground: '#111827',
|
|
121
|
-
border: '#d1d5db',
|
|
122
|
-
},
|
|
123
|
-
danger: {
|
|
124
|
-
background: '#dc2626',
|
|
125
|
-
backgroundHover: '#b91c1c',
|
|
126
|
-
backgroundActive: '#991b1b',
|
|
127
|
-
foreground: '#ffffff',
|
|
128
|
-
border: '#dc2626',
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
},
|
|
24
|
+
import { css } from 'remix/ui'
|
|
25
|
+
import * as popover from 'remix/ui/popover'
|
|
26
|
+
|
|
27
|
+
let triggerCss = css({
|
|
28
|
+
border: '1px solid #d1d5db',
|
|
29
|
+
borderRadius: '6px',
|
|
30
|
+
padding: '6px 10px',
|
|
132
31
|
})
|
|
133
|
-
```
|
|
134
32
|
|
|
135
|
-
|
|
33
|
+
let surfaceCss = css({
|
|
34
|
+
background: 'white',
|
|
35
|
+
border: '1px solid #d1d5db',
|
|
36
|
+
borderRadius: '6px',
|
|
37
|
+
padding: '8px',
|
|
38
|
+
})
|
|
136
39
|
|
|
137
|
-
|
|
138
|
-
|
|
40
|
+
function ViewOptions() {
|
|
41
|
+
let open = false
|
|
139
42
|
|
|
140
|
-
function Layout(handle: Handle<{ children: RemixNode }>) {
|
|
141
43
|
return () => (
|
|
142
|
-
<
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
44
|
+
<popover.Context>
|
|
45
|
+
<button
|
|
46
|
+
mix={[triggerCss, popover.anchor({ placement: 'bottom-end' }), popover.focusOnHide()]}
|
|
47
|
+
onClick={() => {
|
|
48
|
+
open = true
|
|
49
|
+
}}
|
|
50
|
+
type="button"
|
|
51
|
+
>
|
|
52
|
+
View options
|
|
53
|
+
</button>
|
|
54
|
+
<div
|
|
55
|
+
mix={[
|
|
56
|
+
surfaceCss,
|
|
57
|
+
popover.surface({
|
|
58
|
+
open,
|
|
59
|
+
onHide() {
|
|
60
|
+
open = false
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
]}
|
|
64
|
+
>
|
|
65
|
+
Panel content
|
|
66
|
+
</div>
|
|
67
|
+
</popover.Context>
|
|
148
68
|
)
|
|
149
69
|
}
|
|
150
70
|
```
|
|
151
71
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```tsx
|
|
155
|
-
import { css } from 'remix/ui'
|
|
156
|
-
import { theme } from 'remix/ui'
|
|
157
|
-
|
|
158
|
-
let card = css({
|
|
159
|
-
backgroundColor: theme.surface.lvl0,
|
|
160
|
-
color: theme.colors.text.primary,
|
|
161
|
-
border: `1px solid ${theme.colors.border.subtle}`,
|
|
162
|
-
borderRadius: theme.radius.md,
|
|
163
|
-
paddingInline: theme.space.md,
|
|
164
|
-
paddingBlock: theme.space.sm,
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
<div mix={card} />
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Render shared glyphs separately from the theme styles:
|
|
72
|
+
Button styling is available as a composable mixin:
|
|
171
73
|
|
|
172
74
|
```tsx
|
|
173
|
-
import
|
|
174
|
-
import { Button } from 'remix/ui/button'
|
|
175
|
-
import { Glyph } from 'remix/ui/glyph'
|
|
176
|
-
import { RMX_01, RMX_01_GLYPHS } from 'remix/ui/theme'
|
|
75
|
+
import button from 'remix/ui/button'
|
|
177
76
|
|
|
178
|
-
function
|
|
179
|
-
return () => (
|
|
180
|
-
<html>
|
|
181
|
-
<head>
|
|
182
|
-
<RMX_01 />
|
|
183
|
-
</head>
|
|
184
|
-
<body>
|
|
185
|
-
<RMX_01_GLYPHS />
|
|
186
|
-
<Button startIcon={<Glyph name="add" />} tone="primary">
|
|
187
|
-
New project
|
|
188
|
-
</Button>
|
|
189
|
-
{handle.props.children}
|
|
190
|
-
</body>
|
|
191
|
-
</html>
|
|
192
|
-
)
|
|
77
|
+
function Actions() {
|
|
78
|
+
return () => <button mix={button({ tone: 'primary' })}>Create project</button>
|
|
193
79
|
}
|
|
194
80
|
```
|
|
195
81
|
|
|
196
82
|
## Cascade Layers
|
|
197
83
|
|
|
198
|
-
Remix UI emits
|
|
84
|
+
Remix UI emits generated `css(...)` rules under the `rmx` cascade layer. Unlayered CSS outranks layered CSS, so use explicit layer order when mixing Remix UI with global styles.
|
|
199
85
|
|
|
200
|
-
Put layers that should lose to Remix UI before `rmx
|
|
86
|
+
Put layers that should lose to Remix UI before `rmx`:
|
|
201
87
|
|
|
202
88
|
```css
|
|
203
|
-
@layer base, rmx
|
|
89
|
+
@layer base, rmx;
|
|
204
90
|
|
|
205
91
|
@layer base {
|
|
206
92
|
button,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`Accordion` renders a disclosure set with one or more expandable items. Use it for grouped settings, FAQ sections, and dense panels where each item owns a trigger and content region.
|
|
4
4
|
|
|
5
|
-
## Usage
|
|
5
|
+
## Component Usage
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
8
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from 'remix/ui/accordion'
|
|
@@ -82,16 +82,11 @@ export function ControlledAccordion(handle: Handle) {
|
|
|
82
82
|
}
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
Listen for bubbling `AccordionChangeEvent` events with `onAccordionChange`.
|
|
85
|
+
Listen for bubbling `AccordionChangeEvent` events with `onAccordionChange` from `remix/ui/accordion/primitives`.
|
|
86
86
|
|
|
87
87
|
```tsx
|
|
88
|
-
import {
|
|
89
|
-
|
|
90
|
-
AccordionContent,
|
|
91
|
-
AccordionItem,
|
|
92
|
-
AccordionTrigger,
|
|
93
|
-
onAccordionChange,
|
|
94
|
-
} from 'remix/ui/accordion'
|
|
88
|
+
import { onAccordionChange } from 'remix/ui/accordion/primitives'
|
|
89
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from 'remix/ui/accordion'
|
|
95
90
|
|
|
96
91
|
export function TrackedAccordion() {
|
|
97
92
|
return (
|
|
@@ -124,7 +119,7 @@ Set `collapsible={false}` in single mode when the open item must stay open. The
|
|
|
124
119
|
</Accordion>
|
|
125
120
|
```
|
|
126
121
|
|
|
127
|
-
Use `headingLevel` to choose the heading
|
|
122
|
+
Use `headingLevel` to choose the heading element rendered around each trigger. The default level is `3`.
|
|
128
123
|
|
|
129
124
|
```tsx
|
|
130
125
|
<Accordion defaultValue="shipping" headingLevel={2}>
|
|
@@ -142,16 +137,57 @@ Pass `indicator={null}` to remove the default chevron, or pass a custom node to
|
|
|
142
137
|
<AccordionTrigger indicator={<span aria-hidden>+</span>}>Custom indicator</AccordionTrigger>
|
|
143
138
|
```
|
|
144
139
|
|
|
145
|
-
##
|
|
140
|
+
## Primitive Usage
|
|
141
|
+
|
|
142
|
+
Use the lower-level primitives when app code owns the accordion markup and styles:
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import * as accordion from 'remix/ui/accordion/primitives'
|
|
146
|
+
import { contentStyle, headingStyle, itemStyle, rootStyle, triggerStyle } from './accordion.styles'
|
|
147
|
+
|
|
148
|
+
export function PrimitiveAccordion() {
|
|
149
|
+
return (
|
|
150
|
+
<accordion.Context defaultValue="shipping">
|
|
151
|
+
<div mix={[rootStyle, accordion.root()]}>
|
|
152
|
+
<accordion.ItemContext value="shipping">
|
|
153
|
+
<div mix={[itemStyle, accordion.item()]}>
|
|
154
|
+
<h3 mix={headingStyle}>
|
|
155
|
+
<button mix={[triggerStyle, accordion.trigger()]} type="button">
|
|
156
|
+
Shipping
|
|
157
|
+
</button>
|
|
158
|
+
</h3>
|
|
159
|
+
<div mix={[contentStyle, accordion.content()]}>
|
|
160
|
+
Delivery windows and carrier defaults.
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</accordion.ItemContext>
|
|
164
|
+
</div>
|
|
165
|
+
</accordion.Context>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## `remix/ui/accordion`
|
|
146
171
|
|
|
147
172
|
- `Accordion`: root component. Defaults to single-item mode and supports controlled `value`, uncontrolled `defaultValue`, `onValueChange`, `disabled`, `headingLevel`, `collapsible`, and `type="multiple"`.
|
|
148
173
|
- `AccordionItem`: registers one accordion item by `value`. Pass `disabled` to prevent that item from opening or receiving keyboard focus.
|
|
149
174
|
- `AccordionTrigger`: heading-wrapped button for an item. It wires `aria-expanded`, `aria-controls`, keyboard navigation, and the default chevron indicator.
|
|
150
175
|
- `AccordionContent`: panel for an item. It wires the panel id, `aria-labelledby`, `aria-hidden`, inert state, and open/closed state attributes.
|
|
176
|
+
- `rootStyle`, `itemStyle`, `headingStyle`, `triggerStyle`, `indicatorStyle`, `panelStyle`, and `bodyStyle`: flat style mixins used by the component markup.
|
|
177
|
+
- `AccordionProps`, `AccordionSingleProps`, `AccordionMultipleProps`, `AccordionItemProps`, `AccordionTriggerProps`, and `AccordionContentProps`: public TypeScript props for the composed APIs.
|
|
178
|
+
|
|
179
|
+
## `remix/ui/accordion/primitives`
|
|
180
|
+
|
|
181
|
+
- `Context`: lower-level provider for custom accordion composition.
|
|
182
|
+
- `ItemContext`: lower-level provider for one item value.
|
|
183
|
+
- `root()`: wires the root element and bubbling change events.
|
|
184
|
+
- `item()`: wires one item wrapper.
|
|
185
|
+
- `trigger()`: wires the item trigger, keyboard navigation, and trigger ARIA attributes.
|
|
186
|
+
- `content()`: wires the item panel id, hidden state, inert state, and open/closed state attributes.
|
|
151
187
|
- `onAccordionChange(...)`: event mixin for the bubbling `AccordionChangeEvent`.
|
|
152
|
-
- `AccordionChangeEvent`: bubbling event
|
|
153
|
-
- `
|
|
154
|
-
- `
|
|
188
|
+
- `AccordionChangeEvent`: bubbling event with `value`, `itemValue`, and `accordionType`.
|
|
189
|
+
- `AccordionType`, `AccordionValue`, `AccordionSingleValue`, `AccordionMultipleValue`, and `AccordionHeadingLevel`: public TypeScript state and configuration types.
|
|
190
|
+
- `AccordionBaseContextProps`, `AccordionSingleContextProps`, `AccordionMultipleContextProps`, `AccordionContextProps`, `AccordionRootOptions`, `AccordionItemOptions`, `AccordionTriggerOptions`, and `AccordionContentOptions`: primitive prop and option types for custom composition.
|
|
155
191
|
|
|
156
192
|
## Behavior Notes
|
|
157
193
|
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# accordion
|
|
2
|
+
|
|
3
|
+
`Accordion` renders a disclosure set with one or more expandable items. Use it for grouped settings, FAQ sections, and dense panels where each item owns a trigger and content region.
|
|
4
|
+
|
|
5
|
+
## Component Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from 'remix/ui/accordion'
|
|
9
|
+
|
|
10
|
+
export function SettingsAccordion() {
|
|
11
|
+
return (
|
|
12
|
+
<Accordion defaultValue="account">
|
|
13
|
+
<AccordionItem value="account">
|
|
14
|
+
<AccordionTrigger>Account</AccordionTrigger>
|
|
15
|
+
<AccordionContent>Manage account preferences.</AccordionContent>
|
|
16
|
+
</AccordionItem>
|
|
17
|
+
|
|
18
|
+
<AccordionItem value="billing">
|
|
19
|
+
<AccordionTrigger>Billing</AccordionTrigger>
|
|
20
|
+
<AccordionContent>Review billing details.</AccordionContent>
|
|
21
|
+
</AccordionItem>
|
|
22
|
+
</Accordion>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use `type="multiple"` when more than one panel may stay open. `defaultValue` and `value` are arrays in multiple mode.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from 'remix/ui/accordion'
|
|
31
|
+
|
|
32
|
+
export function StatusAccordion() {
|
|
33
|
+
return (
|
|
34
|
+
<Accordion defaultValue={['api', 'alerts']} type="multiple">
|
|
35
|
+
<AccordionItem value="api">
|
|
36
|
+
<AccordionTrigger>API status checks</AccordionTrigger>
|
|
37
|
+
<AccordionContent>Review uptime checks and response time alerts.</AccordionContent>
|
|
38
|
+
</AccordionItem>
|
|
39
|
+
|
|
40
|
+
<AccordionItem disabled value="access">
|
|
41
|
+
<AccordionTrigger>Access control sync</AccordionTrigger>
|
|
42
|
+
<AccordionContent>This disabled item cannot be opened or focused.</AccordionContent>
|
|
43
|
+
</AccordionItem>
|
|
44
|
+
|
|
45
|
+
<AccordionItem value="alerts">
|
|
46
|
+
<AccordionTrigger>Alert routing</AccordionTrigger>
|
|
47
|
+
<AccordionContent>Confirm escalation rules and notification channels.</AccordionContent>
|
|
48
|
+
</AccordionItem>
|
|
49
|
+
</Accordion>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Control the open value when state should live in the owning component. Single mode uses `string | null`; multiple mode uses `string[]`.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import type { Handle } from 'remix/ui'
|
|
58
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from 'remix/ui/accordion'
|
|
59
|
+
|
|
60
|
+
export function ControlledAccordion(handle: Handle) {
|
|
61
|
+
let value: string | null = 'account'
|
|
62
|
+
|
|
63
|
+
return () => (
|
|
64
|
+
<Accordion
|
|
65
|
+
value={value}
|
|
66
|
+
onValueChange={(nextValue) => {
|
|
67
|
+
value = nextValue
|
|
68
|
+
void handle.update()
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<AccordionItem value="account">
|
|
72
|
+
<AccordionTrigger>Account</AccordionTrigger>
|
|
73
|
+
<AccordionContent>Manage account preferences.</AccordionContent>
|
|
74
|
+
</AccordionItem>
|
|
75
|
+
|
|
76
|
+
<AccordionItem value="billing">
|
|
77
|
+
<AccordionTrigger>Billing</AccordionTrigger>
|
|
78
|
+
<AccordionContent>Review billing details.</AccordionContent>
|
|
79
|
+
</AccordionItem>
|
|
80
|
+
</Accordion>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Listen for bubbling `AccordionChangeEvent` events with `onAccordionChange` from `remix/ui/accordion/primitives`.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { onAccordionChange } from 'remix/ui/accordion/primitives'
|
|
89
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from 'remix/ui/accordion'
|
|
90
|
+
|
|
91
|
+
export function TrackedAccordion() {
|
|
92
|
+
return (
|
|
93
|
+
<div
|
|
94
|
+
mix={[
|
|
95
|
+
onAccordionChange((event) => {
|
|
96
|
+
console.log(event.accordionType, event.itemValue, event.value)
|
|
97
|
+
}),
|
|
98
|
+
]}
|
|
99
|
+
>
|
|
100
|
+
<Accordion>
|
|
101
|
+
<AccordionItem value="account">
|
|
102
|
+
<AccordionTrigger>Account</AccordionTrigger>
|
|
103
|
+
<AccordionContent>Manage account preferences.</AccordionContent>
|
|
104
|
+
</AccordionItem>
|
|
105
|
+
</Accordion>
|
|
106
|
+
</div>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Set `collapsible={false}` in single mode when the open item must stay open. The locked-open trigger receives `aria-disabled`.
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
<Accordion collapsible={false} defaultValue="account">
|
|
115
|
+
<AccordionItem value="account">
|
|
116
|
+
<AccordionTrigger>Account</AccordionTrigger>
|
|
117
|
+
<AccordionContent>Manage account preferences.</AccordionContent>
|
|
118
|
+
</AccordionItem>
|
|
119
|
+
</Accordion>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Use `headingLevel` to choose the heading element rendered around each trigger. The default level is `3`.
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
<Accordion defaultValue="shipping" headingLevel={2}>
|
|
126
|
+
<AccordionItem value="shipping">
|
|
127
|
+
<AccordionTrigger>Shipping</AccordionTrigger>
|
|
128
|
+
<AccordionContent>Review shipping preferences.</AccordionContent>
|
|
129
|
+
</AccordionItem>
|
|
130
|
+
</Accordion>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Pass `indicator={null}` to remove the default chevron, or pass a custom node to replace it.
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<AccordionTrigger indicator={null}>No indicator</AccordionTrigger>
|
|
137
|
+
<AccordionTrigger indicator={<span aria-hidden>+</span>}>Custom indicator</AccordionTrigger>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Primitive Usage
|
|
141
|
+
|
|
142
|
+
Use the lower-level primitives when app code owns the accordion markup and styles:
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import * as accordion from 'remix/ui/accordion/primitives'
|
|
146
|
+
import { contentStyle, headingStyle, itemStyle, rootStyle, triggerStyle } from './accordion.styles'
|
|
147
|
+
|
|
148
|
+
export function PrimitiveAccordion() {
|
|
149
|
+
return (
|
|
150
|
+
<accordion.Context defaultValue="shipping">
|
|
151
|
+
<div mix={[rootStyle, accordion.root()]}>
|
|
152
|
+
<accordion.ItemContext value="shipping">
|
|
153
|
+
<div mix={[itemStyle, accordion.item()]}>
|
|
154
|
+
<h3 mix={headingStyle}>
|
|
155
|
+
<button mix={[triggerStyle, accordion.trigger()]} type="button">
|
|
156
|
+
Shipping
|
|
157
|
+
</button>
|
|
158
|
+
</h3>
|
|
159
|
+
<div mix={[contentStyle, accordion.content()]}>
|
|
160
|
+
Delivery windows and carrier defaults.
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</accordion.ItemContext>
|
|
164
|
+
</div>
|
|
165
|
+
</accordion.Context>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## `remix/ui/accordion`
|
|
171
|
+
|
|
172
|
+
- `Accordion`: root component. Defaults to single-item mode and supports controlled `value`, uncontrolled `defaultValue`, `onValueChange`, `disabled`, `headingLevel`, `collapsible`, and `type="multiple"`.
|
|
173
|
+
- `AccordionItem`: registers one accordion item by `value`. Pass `disabled` to prevent that item from opening or receiving keyboard focus.
|
|
174
|
+
- `AccordionTrigger`: heading-wrapped button for an item. It wires `aria-expanded`, `aria-controls`, keyboard navigation, and the default chevron indicator.
|
|
175
|
+
- `AccordionContent`: panel for an item. It wires the panel id, `aria-labelledby`, `aria-hidden`, inert state, and open/closed state attributes.
|
|
176
|
+
- `rootStyle`, `itemStyle`, `headingStyle`, `triggerStyle`, `indicatorStyle`, `panelStyle`, and `bodyStyle`: flat style mixins used by the component markup.
|
|
177
|
+
- `AccordionProps`, `AccordionSingleProps`, `AccordionMultipleProps`, `AccordionItemProps`, `AccordionTriggerProps`, and `AccordionContentProps`: public TypeScript props for the composed APIs.
|
|
178
|
+
|
|
179
|
+
## `remix/ui/accordion/primitives`
|
|
180
|
+
|
|
181
|
+
- `Context`: lower-level provider for custom accordion composition.
|
|
182
|
+
- `ItemContext`: lower-level provider for one item value.
|
|
183
|
+
- `root()`: wires the root element and bubbling change events.
|
|
184
|
+
- `item()`: wires one item wrapper.
|
|
185
|
+
- `trigger()`: wires the item trigger, keyboard navigation, and trigger ARIA attributes.
|
|
186
|
+
- `content()`: wires the item panel id, hidden state, inert state, and open/closed state attributes.
|
|
187
|
+
- `onAccordionChange(...)`: event mixin for the bubbling `AccordionChangeEvent`.
|
|
188
|
+
- `AccordionChangeEvent`: bubbling event with `value`, `itemValue`, and `accordionType`.
|
|
189
|
+
- `AccordionType`, `AccordionValue`, `AccordionSingleValue`, `AccordionMultipleValue`, and `AccordionHeadingLevel`: public TypeScript state and configuration types.
|
|
190
|
+
- `AccordionBaseContextProps`, `AccordionSingleContextProps`, `AccordionMultipleContextProps`, `AccordionContextProps`, `AccordionRootOptions`, `AccordionItemOptions`, `AccordionTriggerOptions`, and `AccordionContentOptions`: primitive prop and option types for custom composition.
|
|
191
|
+
|
|
192
|
+
## Behavior Notes
|
|
193
|
+
|
|
194
|
+
- Single mode stores one open value or `null`; multiple mode stores an array of open values.
|
|
195
|
+
- Single accordions are collapsible by default. Set `collapsible={false}` to keep the open item locked open.
|
|
196
|
+
- Root `disabled` disables every item. Item `disabled` only disables that item.
|
|
197
|
+
- Arrow keys move between enabled triggers. `Home` and `End` move to the first and last enabled triggers.
|
|
198
|
+
- Disabled items are skipped by keyboard navigation.
|
|
199
|
+
- Trigger and panel ids are generated and linked with `aria-controls`, `aria-labelledby`, and `aria-expanded`; closed panels receive `aria-hidden` and `inert`.
|
|
200
|
+
- `AccordionTrigger` renders inside an `h1`-`h6` element based on `headingLevel`.
|
|
201
|
+
- Each item and trigger receives `data-state="open"` or `data-state="closed"` for styling.
|
|
202
|
+
- `AccordionChangeEvent` bubbles from the root and includes `value`, `itemValue`, and `accordionType`.
|
package/src/ui/anchor/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`anchor` positions a floating element against an anchor element or viewport coordinates and keeps it constrained to the viewport. Use it for custom floating surfaces that need placement, flipping, offsets, and optional relative alignment.
|
|
4
4
|
|
|
5
|
-
## Usage
|
|
5
|
+
## Primitive Usage
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
8
|
import { anchor } from 'remix/ui/anchor'
|
|
@@ -55,7 +55,42 @@ Anchor to coordinates when the surface should open at a pointer location.
|
|
|
55
55
|
let cleanup = anchor(popover, { x: event.clientX, y: event.clientY }, { placement: 'bottom-start' })
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Keep presentation app-owned when the anchored element is rendered by your component:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { anchor } from 'remix/ui/anchor'
|
|
62
|
+
import { on, ref, type Handle } from 'remix/ui'
|
|
63
|
+
import { panelStyle } from './floating.styles'
|
|
64
|
+
|
|
65
|
+
export function AnchoredPanel(handle: Handle) {
|
|
66
|
+
let cleanup = () => {}
|
|
67
|
+
let trigger: HTMLElement | null = null
|
|
68
|
+
let panel: HTMLElement | null = null
|
|
69
|
+
|
|
70
|
+
function position() {
|
|
71
|
+
cleanup()
|
|
72
|
+
if (trigger && panel) {
|
|
73
|
+
cleanup = anchor(panel, trigger, { placement: 'bottom-start', offset: 8 })
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return () => (
|
|
78
|
+
<>
|
|
79
|
+
<button
|
|
80
|
+
mix={[ref((node) => (trigger = node as HTMLElement)), on('click', position)]}
|
|
81
|
+
type="button"
|
|
82
|
+
>
|
|
83
|
+
Open
|
|
84
|
+
</button>
|
|
85
|
+
<div data-panel mix={[panelStyle, ref((node) => (panel = node as HTMLElement))]}>
|
|
86
|
+
Panel
|
|
87
|
+
</div>
|
|
88
|
+
</>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## `remix/ui/anchor`
|
|
59
94
|
|
|
60
95
|
- `anchor(floatingElement, anchorTarget, options)`: positions `floatingElement` against an element or coordinate target, starts animation-frame polling for geometry changes, and returns a cleanup function.
|
|
61
96
|
- `AnchorOptions`: placement, inset, relative alignment, and offset options.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`Breadcrumbs` renders semantic breadcrumb navigation from a list of items. Use it when the page needs a compact path back through parent sections.
|
|
4
4
|
|
|
5
|
-
## Usage
|
|
5
|
+
## Component Usage
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
8
|
import { Breadcrumbs } from 'remix/ui/breadcrumbs'
|
|
@@ -32,13 +32,13 @@ Mark an earlier item as current when the page belongs to a parent section but th
|
|
|
32
32
|
/>
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Pass `separator` to replace the default chevron
|
|
35
|
+
Pass `separator` to replace the default chevron icon.
|
|
36
36
|
|
|
37
37
|
```tsx
|
|
38
38
|
<Breadcrumbs items={[{ href: '/', label: 'Home' }, { label: 'Breadcrumbs' }]} separator="/" />
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
## `breadcrumbs
|
|
41
|
+
## `remix/ui/breadcrumbs`
|
|
42
42
|
|
|
43
43
|
- `Breadcrumbs`: component that renders a `<nav>` with an ordered list of breadcrumb items.
|
|
44
44
|
- `BreadcrumbItem`: item shape with `label`, optional `href`, and optional `current`.
|
|
@@ -52,4 +52,4 @@ Pass `separator` to replace the default chevron glyph.
|
|
|
52
52
|
- An explicit current item wins over the last-item default.
|
|
53
53
|
- Current items render as text with `aria-current="page"`, even when they include `href`.
|
|
54
54
|
- Non-current items with `href` render as links; non-current items without `href` render as text.
|
|
55
|
-
- The default separator is
|
|
55
|
+
- The default separator is a chevron icon. Separators render between items and are hidden from assistive technology.
|