@remix-run/ui 0.1.0 → 0.1.2
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 +24 -3
- package/dist/animation/animate-layout-mixin.js +66 -66
- package/dist/animation/animate-layout-mixin.js.map +1 -1
- package/dist/components/accordion/accordion.d.ts +3 -3
- package/dist/components/accordion/accordion.js +0 -2
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/accordion/demos/card.demo.d.ts +6 -0
- package/dist/components/accordion/demos/card.demo.js +58 -0
- package/dist/components/accordion/demos/card.demo.js.map +1 -0
- package/dist/components/accordion/demos/multiple.demo.d.ts +6 -0
- package/dist/components/accordion/demos/multiple.demo.js +19 -0
- package/dist/components/accordion/demos/multiple.demo.js.map +1 -0
- package/dist/components/accordion/demos/overview.demo.d.ts +6 -0
- package/dist/components/accordion/demos/overview.demo.js +19 -0
- package/dist/components/accordion/demos/overview.demo.js.map +1 -0
- package/dist/components/breadcrumbs/breadcrumbs.js +0 -2
- package/dist/components/breadcrumbs/breadcrumbs.js.map +1 -1
- package/dist/components/breadcrumbs/demos/basic.demo.d.ts +5 -0
- package/dist/components/breadcrumbs/demos/basic.demo.js +14 -0
- package/dist/components/breadcrumbs/demos/basic.demo.js.map +1 -0
- package/dist/components/breadcrumbs/demos/separator.demo.d.ts +5 -0
- package/dist/components/breadcrumbs/demos/separator.demo.js +14 -0
- package/dist/components/breadcrumbs/demos/separator.demo.js.map +1 -0
- package/dist/components/button/button.d.ts +82 -0
- package/dist/components/button/button.js +60 -2
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/button/demos/aliases.demo.d.ts +6 -0
- package/dist/components/button/demos/aliases.demo.js +18 -0
- package/dist/components/button/demos/aliases.demo.js.map +1 -0
- package/dist/components/button/demos/basic.demo.d.ts +6 -0
- package/dist/components/button/demos/basic.demo.js +19 -0
- package/dist/components/button/demos/basic.demo.js.map +1 -0
- package/dist/components/button/demos/states.demo.d.ts +6 -0
- package/dist/components/button/demos/states.demo.js +29 -0
- package/dist/components/button/demos/states.demo.js.map +1 -0
- package/dist/components/combobox/combobox.d.ts +3 -3
- package/dist/components/combobox/combobox.js +0 -2
- package/dist/components/combobox/combobox.js.map +1 -1
- package/dist/components/combobox/demos/overview.demo.d.ts +6 -0
- package/dist/components/combobox/demos/overview.demo.js +153 -0
- package/dist/components/combobox/demos/overview.demo.js.map +1 -0
- package/dist/components/listbox/demos/overview.demo.d.ts +6 -0
- package/dist/components/listbox/demos/overview.demo.js +64 -0
- package/dist/components/listbox/demos/overview.demo.js.map +1 -0
- package/dist/components/listbox/listbox.d.ts +1 -6
- package/dist/components/listbox/listbox.js +21 -33
- package/dist/components/listbox/listbox.js.map +1 -1
- package/dist/components/menu/demos/bubbling.demo.d.ts +5 -0
- package/dist/components/menu/demos/bubbling.demo.js +15 -0
- package/dist/components/menu/demos/bubbling.demo.js.map +1 -0
- package/dist/components/menu/demos/overview.demo.d.ts +6 -0
- package/dist/components/menu/demos/overview.demo.js +32 -0
- package/dist/components/menu/demos/overview.demo.js.map +1 -0
- package/dist/components/menu/menu.d.ts +5 -14
- package/dist/components/menu/menu.js +23 -38
- package/dist/components/menu/menu.js.map +1 -1
- package/dist/components/popover/demos/overview.demo.d.ts +6 -0
- package/dist/components/popover/demos/overview.demo.js +64 -0
- package/dist/components/popover/demos/overview.demo.js.map +1 -0
- package/dist/components/select/demos/deconstructed.demo.d.ts +6 -0
- package/dist/components/select/demos/deconstructed.demo.js +53 -0
- package/dist/components/select/demos/deconstructed.demo.js.map +1 -0
- package/dist/components/select/demos/overview.demo.d.ts +5 -0
- package/dist/components/select/demos/overview.demo.js +27 -0
- package/dist/components/select/demos/overview.demo.js.map +1 -0
- package/dist/components/select/select.d.ts +3 -3
- package/dist/components/select/select.js +17 -26
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/tabs/tabs.d.ts +3 -3
- package/dist/components/tabs/tabs.js +0 -2
- package/dist/components/tabs/tabs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/component.d.ts +8 -5
- package/dist/runtime/component.js +84 -80
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/core/attributes.d.ts +12 -0
- package/dist/runtime/core/attributes.js +85 -0
- package/dist/runtime/core/attributes.js.map +1 -0
- package/dist/runtime/core/children.d.ts +7 -0
- package/dist/runtime/core/children.js +30 -0
- package/dist/runtime/core/children.js.map +1 -0
- package/dist/runtime/core/props.d.ts +9 -0
- package/dist/runtime/core/props.js +169 -0
- package/dist/runtime/core/props.js.map +1 -0
- package/dist/runtime/core/vnode.d.ts +3 -0
- package/dist/runtime/core/vnode.js +39 -0
- package/dist/runtime/core/vnode.js.map +1 -0
- package/dist/runtime/create-element.js +2 -1
- package/dist/runtime/create-element.js.map +1 -1
- package/dist/runtime/diff-dom.js +47 -14
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/diff-props.d.ts +1 -2
- package/dist/runtime/diff-props.js +2 -181
- package/dist/runtime/diff-props.js.map +1 -1
- package/dist/runtime/dom.d.ts +4 -0
- package/dist/runtime/frame.d.ts +23 -2
- package/dist/runtime/frame.js +51 -52
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/jsx.js +2 -27
- package/dist/runtime/jsx.js.map +1 -1
- package/dist/runtime/mixins/mixin.js +9 -4
- package/dist/runtime/mixins/mixin.js.map +1 -1
- package/dist/runtime/mixins/on-mixin.d.ts +15 -4
- package/dist/runtime/mixins/on-mixin.js +9 -2
- package/dist/runtime/mixins/on-mixin.js.map +1 -1
- package/dist/runtime/navigation.d.ts +6 -0
- package/dist/runtime/navigation.js +16 -3
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/reconcile.js +444 -181
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/run.d.ts +17 -2
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/scheduler.js +10 -0
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/stream-protocol.d.ts +8 -0
- package/dist/runtime/stream-protocol.js +20 -0
- package/dist/runtime/stream-protocol.js.map +1 -0
- package/dist/runtime/to-vnode.js +17 -18
- package/dist/runtime/to-vnode.js.map +1 -1
- package/dist/runtime/vnode.d.ts +5 -4
- package/dist/runtime/vnode.js +2 -3
- package/dist/runtime/vnode.js.map +1 -1
- package/dist/server/stream.js +80 -106
- package/dist/server/stream.js.map +1 -1
- package/dist/style/layers.d.ts +2 -0
- package/dist/style/layers.js +3 -0
- package/dist/style/layers.js.map +1 -0
- package/dist/style/stylesheet.d.ts +3 -1
- package/dist/style/stylesheet.js +69 -8
- package/dist/style/stylesheet.js.map +1 -1
- package/dist/theme/glyph-contract.d.ts +1 -1
- package/dist/theme/runtime.js +4 -1
- package/dist/theme/runtime.js.map +1 -1
- package/package.json +6 -7
- package/src/animation/README.md +314 -0
- package/src/animation/animate-layout-mixin.ts +79 -71
- package/src/components/accordion/README.md +166 -0
- package/src/components/accordion/accordion.tsx +8 -10
- package/src/components/accordion/demos/card.demo.tsx +113 -0
- package/src/components/accordion/demos/multiple.demo.tsx +56 -0
- package/src/components/accordion/demos/overview.demo.tsx +56 -0
- package/src/components/anchor/README.md +153 -0
- package/src/components/breadcrumbs/README.md +55 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +0 -2
- package/src/components/breadcrumbs/demos/basic.demo.tsx +17 -0
- package/src/components/breadcrumbs/demos/separator.demo.tsx +18 -0
- package/src/components/button/README.md +4 -4
- package/src/components/button/button.tsx +88 -2
- package/src/components/button/demos/aliases.demo.tsx +27 -0
- package/src/components/button/demos/basic.demo.tsx +30 -0
- package/src/components/button/demos/states.demo.tsx +52 -0
- package/src/components/combobox/combobox.tsx +8 -10
- package/src/components/combobox/demos/overview.demo.tsx +199 -0
- package/src/components/glyph/README.md +72 -0
- package/src/components/listbox/README.md +115 -0
- package/src/components/listbox/demos/overview.demo.tsx +101 -0
- package/src/components/listbox/listbox.ts +23 -32
- package/src/components/menu/README.md +96 -0
- package/src/components/menu/demos/bubbling.demo.tsx +34 -0
- package/src/components/menu/demos/overview.demo.tsx +76 -0
- package/src/components/menu/menu.tsx +35 -41
- package/src/components/popover/README.md +3 -3
- package/src/components/popover/demos/overview.demo.tsx +143 -0
- package/src/components/select/README.md +107 -0
- package/src/components/select/demos/deconstructed.demo.tsx +95 -0
- package/src/components/select/demos/overview.demo.tsx +51 -0
- package/src/components/select/select.tsx +28 -28
- package/src/components/tabs/tabs.tsx +10 -5
- package/src/index.ts +1 -1
- package/src/runtime/component.ts +94 -82
- package/src/runtime/core/attributes.ts +97 -0
- package/src/runtime/core/children.ts +42 -0
- package/src/runtime/core/props.ts +185 -0
- package/src/runtime/core/vnode.ts +48 -0
- package/src/runtime/create-element.ts +2 -1
- package/src/runtime/diff-dom.ts +59 -12
- package/src/runtime/diff-props.ts +2 -187
- package/src/runtime/dom.ts +4 -0
- package/src/runtime/frame.ts +84 -61
- package/src/runtime/jsx.ts +2 -30
- package/src/runtime/mixins/mixin.ts +9 -4
- package/src/runtime/mixins/on-mixin.ts +20 -12
- package/src/runtime/navigation.ts +24 -3
- package/src/runtime/reconcile.ts +552 -186
- package/src/runtime/run.ts +17 -1
- package/src/runtime/scheduler.ts +7 -0
- package/src/runtime/stream-protocol.ts +27 -0
- package/src/runtime/to-vnode.ts +15 -17
- package/src/runtime/vnode.ts +6 -9
- package/src/server/README.md +88 -0
- package/src/server/stream.ts +105 -117
- package/src/style/layers.ts +2 -0
- package/src/style/stylesheet.ts +75 -10
- package/src/test/README.md +107 -0
- package/src/test/utils.ts +3 -1
- package/src/theme/README.md +103 -0
- package/src/theme/runtime.ts +4 -1
- package/src/utils/scroll-lock/README.md +33 -0
- package/src/test/setup.ts +0 -7
|
@@ -0,0 +1,166 @@
|
|
|
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
|
+
## 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`.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import {
|
|
89
|
+
Accordion,
|
|
90
|
+
AccordionContent,
|
|
91
|
+
AccordionItem,
|
|
92
|
+
AccordionTrigger,
|
|
93
|
+
onAccordionChange,
|
|
94
|
+
} from 'remix/ui/accordion'
|
|
95
|
+
|
|
96
|
+
export function TrackedAccordion() {
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
mix={[
|
|
100
|
+
onAccordionChange((event) => {
|
|
101
|
+
console.log(event.accordionType, event.itemValue, event.value)
|
|
102
|
+
}),
|
|
103
|
+
]}
|
|
104
|
+
>
|
|
105
|
+
<Accordion>
|
|
106
|
+
<AccordionItem value="account">
|
|
107
|
+
<AccordionTrigger>Account</AccordionTrigger>
|
|
108
|
+
<AccordionContent>Manage account preferences.</AccordionContent>
|
|
109
|
+
</AccordionItem>
|
|
110
|
+
</Accordion>
|
|
111
|
+
</div>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Set `collapsible={false}` in single mode when the open item must stay open. The locked-open trigger receives `aria-disabled`.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
<Accordion collapsible={false} defaultValue="account">
|
|
120
|
+
<AccordionItem value="account">
|
|
121
|
+
<AccordionTrigger>Account</AccordionTrigger>
|
|
122
|
+
<AccordionContent>Manage account preferences.</AccordionContent>
|
|
123
|
+
</AccordionItem>
|
|
124
|
+
</Accordion>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Use `headingLevel` to choose the heading wrapper rendered around each trigger. The default level is `3`.
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
<Accordion defaultValue="shipping" headingLevel={2}>
|
|
131
|
+
<AccordionItem value="shipping">
|
|
132
|
+
<AccordionTrigger>Shipping</AccordionTrigger>
|
|
133
|
+
<AccordionContent>Review shipping preferences.</AccordionContent>
|
|
134
|
+
</AccordionItem>
|
|
135
|
+
</Accordion>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Pass `indicator={null}` to remove the default chevron, or pass a custom node to replace it.
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<AccordionTrigger indicator={null}>No indicator</AccordionTrigger>
|
|
142
|
+
<AccordionTrigger indicator={<span aria-hidden>+</span>}>Custom indicator</AccordionTrigger>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## `accordion.*`
|
|
146
|
+
|
|
147
|
+
- `Accordion`: root component. Defaults to single-item mode and supports controlled `value`, uncontrolled `defaultValue`, `onValueChange`, `disabled`, `headingLevel`, `collapsible`, and `type="multiple"`.
|
|
148
|
+
- `AccordionItem`: registers one accordion item by `value`. Pass `disabled` to prevent that item from opening or receiving keyboard focus.
|
|
149
|
+
- `AccordionTrigger`: heading-wrapped button for an item. It wires `aria-expanded`, `aria-controls`, keyboard navigation, and the default chevron indicator.
|
|
150
|
+
- `AccordionContent`: panel for an item. It wires the panel id, `aria-labelledby`, `aria-hidden`, inert state, and open/closed state attributes.
|
|
151
|
+
- `onAccordionChange(...)`: event mixin for the bubbling `AccordionChangeEvent`.
|
|
152
|
+
- `AccordionChangeEvent`: bubbling event class with `value`, `itemValue`, and `accordionType`.
|
|
153
|
+
- `AccordionProps`, `AccordionSingleProps`, `AccordionMultipleProps`, `AccordionItemProps`, `AccordionTriggerProps`, and `AccordionContentProps`: public TypeScript props.
|
|
154
|
+
- `rootStyle`, `itemStyle`, `triggerStyle`, `indicatorStyle`, `panelStyle`, and `bodyStyle`: flat style mixins used by the component wrappers.
|
|
155
|
+
|
|
156
|
+
## Behavior Notes
|
|
157
|
+
|
|
158
|
+
- Single mode stores one open value or `null`; multiple mode stores an array of open values.
|
|
159
|
+
- Single accordions are collapsible by default. Set `collapsible={false}` to keep the open item locked open.
|
|
160
|
+
- Root `disabled` disables every item. Item `disabled` only disables that item.
|
|
161
|
+
- Arrow keys move between enabled triggers. `Home` and `End` move to the first and last enabled triggers.
|
|
162
|
+
- Disabled items are skipped by keyboard navigation.
|
|
163
|
+
- Trigger and panel ids are generated and linked with `aria-controls`, `aria-labelledby`, and `aria-expanded`; closed panels receive `aria-hidden` and `inert`.
|
|
164
|
+
- `AccordionTrigger` renders inside an `h1`-`h6` element based on `headingLevel`.
|
|
165
|
+
- Each item and trigger receives `data-state="open"` or `data-state="closed"` for styling.
|
|
166
|
+
- `AccordionChangeEvent` bubbles from the root and includes `value`, `itemValue`, and `accordionType`.
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// @jsxRuntime classic
|
|
2
|
-
// @jsx createElement
|
|
3
1
|
import {
|
|
4
2
|
css,
|
|
5
3
|
createElement,
|
|
@@ -9,6 +7,7 @@ import {
|
|
|
9
7
|
type Handle,
|
|
10
8
|
type Props,
|
|
11
9
|
type RemixNode,
|
|
10
|
+
type Dispatched,
|
|
12
11
|
} from '@remix-run/ui'
|
|
13
12
|
|
|
14
13
|
import { spring } from '@remix-run/ui/animation'
|
|
@@ -17,8 +16,8 @@ import { theme } from '../../theme/theme.ts'
|
|
|
17
16
|
|
|
18
17
|
const ACCORDION_CHANGE_EVENT = 'rmx:accordion-change' as const
|
|
19
18
|
|
|
20
|
-
type AccordionChangeHandler = (
|
|
21
|
-
event: AccordionChangeEvent,
|
|
19
|
+
type AccordionChangeHandler<target extends HTMLElement> = (
|
|
20
|
+
event: Dispatched<AccordionChangeEvent, target>,
|
|
22
21
|
signal: AbortSignal,
|
|
23
22
|
) => void | Promise<void>
|
|
24
23
|
|
|
@@ -416,12 +415,11 @@ function AccordionImpl(handle: Handle<AccordionProps, AccordionContext>) {
|
|
|
416
415
|
}
|
|
417
416
|
}
|
|
418
417
|
|
|
419
|
-
export function onAccordionChange
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
)
|
|
418
|
+
export function onAccordionChange<target extends HTMLElement>(
|
|
419
|
+
handler: AccordionChangeHandler<target>,
|
|
420
|
+
captureBoolean?: boolean,
|
|
421
|
+
) {
|
|
422
|
+
return on(ACCORDION_CHANGE_EVENT, handler, captureBoolean)
|
|
425
423
|
}
|
|
426
424
|
|
|
427
425
|
export const Accordion = AccordionImpl
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { css } from '@remix-run/ui'
|
|
2
|
+
import {
|
|
3
|
+
Accordion,
|
|
4
|
+
AccordionContent,
|
|
5
|
+
AccordionItem,
|
|
6
|
+
AccordionTrigger,
|
|
7
|
+
} from '@remix-run/ui/accordion'
|
|
8
|
+
import { theme } from '@remix-run/ui/theme'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @name Accordion in a Card
|
|
12
|
+
* @description The Accordion can sit inside a card when the surrounding surface needs stronger grouping.
|
|
13
|
+
* @order 3
|
|
14
|
+
*/
|
|
15
|
+
export default function Example() {
|
|
16
|
+
return () => (
|
|
17
|
+
<article mix={cardCss}>
|
|
18
|
+
<div mix={cardHeaderCss}>
|
|
19
|
+
<p mix={eyebrowCss}>Project settings</p>
|
|
20
|
+
<h3 mix={titleCss}>Deployment policies</h3>
|
|
21
|
+
<p mix={descriptionCss}>
|
|
22
|
+
The Accordion can sit inside a card when the surrounding surface needs stronger grouping.
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div mix={accordionInsetCss}>
|
|
27
|
+
<Accordion defaultValue="reviews">
|
|
28
|
+
<AccordionItem value="reviews">
|
|
29
|
+
<AccordionTrigger>Required approvals</AccordionTrigger>
|
|
30
|
+
<AccordionContent>
|
|
31
|
+
<p mix={bodyTextCss}>
|
|
32
|
+
Require one reviewer for routine changes and two reviewers for dependency, auth, or
|
|
33
|
+
billing-related changes.
|
|
34
|
+
</p>
|
|
35
|
+
</AccordionContent>
|
|
36
|
+
</AccordionItem>
|
|
37
|
+
|
|
38
|
+
<AccordionItem value="windows">
|
|
39
|
+
<AccordionTrigger>Release windows</AccordionTrigger>
|
|
40
|
+
<AccordionContent>
|
|
41
|
+
<p mix={bodyTextCss}>
|
|
42
|
+
Schedule production deploys on weekdays before 3 PM so incidents and rollback work
|
|
43
|
+
stay inside staffed hours.
|
|
44
|
+
</p>
|
|
45
|
+
</AccordionContent>
|
|
46
|
+
</AccordionItem>
|
|
47
|
+
|
|
48
|
+
<AccordionItem value="rollback">
|
|
49
|
+
<AccordionTrigger>Rollback policy</AccordionTrigger>
|
|
50
|
+
<AccordionContent>
|
|
51
|
+
<p mix={bodyTextCss}>
|
|
52
|
+
Keep a rollback target ready for every release and treat rollback preparation as
|
|
53
|
+
part of the normal change checklist.
|
|
54
|
+
</p>
|
|
55
|
+
</AccordionContent>
|
|
56
|
+
</AccordionItem>
|
|
57
|
+
</Accordion>
|
|
58
|
+
</div>
|
|
59
|
+
</article>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const cardCss = css({
|
|
64
|
+
display: 'flex',
|
|
65
|
+
flexDirection: 'column',
|
|
66
|
+
gap: theme.space.md,
|
|
67
|
+
padding: theme.space.lg,
|
|
68
|
+
border: `1px solid ${theme.colors.border.subtle}`,
|
|
69
|
+
borderRadius: theme.radius.lg,
|
|
70
|
+
backgroundColor: theme.surface.lvl0,
|
|
71
|
+
boxShadow: theme.shadow.xs,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const cardHeaderCss = css({
|
|
75
|
+
display: 'flex',
|
|
76
|
+
flexDirection: 'column',
|
|
77
|
+
gap: theme.space.sm,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const accordionInsetCss = css({
|
|
81
|
+
paddingInline: theme.space.lg,
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const eyebrowCss = css({
|
|
85
|
+
margin: 0,
|
|
86
|
+
fontSize: theme.fontSize.xxxs,
|
|
87
|
+
fontWeight: theme.fontWeight.semibold,
|
|
88
|
+
letterSpacing: theme.letterSpacing.meta,
|
|
89
|
+
textTransform: 'uppercase',
|
|
90
|
+
color: theme.colors.text.muted,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const titleCss = css({
|
|
94
|
+
margin: 0,
|
|
95
|
+
fontSize: theme.fontSize.lg,
|
|
96
|
+
lineHeight: theme.lineHeight.tight,
|
|
97
|
+
fontWeight: theme.fontWeight.semibold,
|
|
98
|
+
color: theme.colors.text.primary,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const descriptionCss = css({
|
|
102
|
+
margin: 0,
|
|
103
|
+
fontSize: theme.fontSize.sm,
|
|
104
|
+
lineHeight: theme.lineHeight.relaxed,
|
|
105
|
+
color: theme.colors.text.secondary,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const bodyTextCss = css({
|
|
109
|
+
margin: 0,
|
|
110
|
+
fontSize: theme.fontSize.sm,
|
|
111
|
+
lineHeight: theme.lineHeight.relaxed,
|
|
112
|
+
color: theme.colors.text.secondary,
|
|
113
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { css } from '@remix-run/ui'
|
|
2
|
+
import {
|
|
3
|
+
Accordion,
|
|
4
|
+
AccordionContent,
|
|
5
|
+
AccordionItem,
|
|
6
|
+
AccordionTrigger,
|
|
7
|
+
} from '@remix-run/ui/accordion'
|
|
8
|
+
import { theme } from '@remix-run/ui/theme'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @order 2
|
|
12
|
+
* @name Accordion Multiple Open
|
|
13
|
+
* @description Multiple mode allows several sections to stay open simultaneously, useful for operational checklists and dashboards.
|
|
14
|
+
*/
|
|
15
|
+
export default function Example() {
|
|
16
|
+
return () => (
|
|
17
|
+
<Accordion defaultValue={['api', 'alerts']} type="multiple">
|
|
18
|
+
<AccordionItem value="api">
|
|
19
|
+
<AccordionTrigger>API status checks</AccordionTrigger>
|
|
20
|
+
<AccordionContent>
|
|
21
|
+
<p mix={bodyTextCss}>
|
|
22
|
+
Multiple mode works well for operational checklists and dashboards where several
|
|
23
|
+
sections often need to stay open together.
|
|
24
|
+
</p>
|
|
25
|
+
</AccordionContent>
|
|
26
|
+
</AccordionItem>
|
|
27
|
+
|
|
28
|
+
<AccordionItem disabled value="access">
|
|
29
|
+
<AccordionTrigger>Access control sync</AccordionTrigger>
|
|
30
|
+
<AccordionContent>
|
|
31
|
+
<p mix={bodyTextCss}>
|
|
32
|
+
This section is intentionally disabled to show how one unavailable item should read
|
|
33
|
+
inside an otherwise active list.
|
|
34
|
+
</p>
|
|
35
|
+
</AccordionContent>
|
|
36
|
+
</AccordionItem>
|
|
37
|
+
|
|
38
|
+
<AccordionItem value="alerts">
|
|
39
|
+
<AccordionTrigger>Alert routing</AccordionTrigger>
|
|
40
|
+
<AccordionContent>
|
|
41
|
+
<p mix={bodyTextCss}>
|
|
42
|
+
Disabled items should feel clearly unavailable without changing the overall visual model
|
|
43
|
+
of the disclosure list.
|
|
44
|
+
</p>
|
|
45
|
+
</AccordionContent>
|
|
46
|
+
</AccordionItem>
|
|
47
|
+
</Accordion>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const bodyTextCss = css({
|
|
52
|
+
margin: 0,
|
|
53
|
+
fontSize: theme.fontSize.sm,
|
|
54
|
+
lineHeight: theme.lineHeight.relaxed,
|
|
55
|
+
color: theme.colors.text.secondary,
|
|
56
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { css } from '@remix-run/ui'
|
|
2
|
+
import {
|
|
3
|
+
Accordion,
|
|
4
|
+
AccordionContent,
|
|
5
|
+
AccordionItem,
|
|
6
|
+
AccordionTrigger,
|
|
7
|
+
} from '@remix-run/ui/accordion'
|
|
8
|
+
import { theme } from '@remix-run/ui/theme'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @name Accordion Overview
|
|
12
|
+
* @description A single-open disclosure list that keeps settings, billing, or notification rules in one calm section.
|
|
13
|
+
* @order 1
|
|
14
|
+
*/
|
|
15
|
+
export default function Example() {
|
|
16
|
+
return () => (
|
|
17
|
+
<Accordion defaultValue="account">
|
|
18
|
+
<AccordionItem value="account">
|
|
19
|
+
<AccordionTrigger>Account defaults</AccordionTrigger>
|
|
20
|
+
<AccordionContent>
|
|
21
|
+
<p mix={bodyTextCss}>
|
|
22
|
+
Keep billing contacts, email summaries, and workspace naming rules in one calm
|
|
23
|
+
disclosure list without adding another card layer.
|
|
24
|
+
</p>
|
|
25
|
+
</AccordionContent>
|
|
26
|
+
</AccordionItem>
|
|
27
|
+
|
|
28
|
+
<AccordionItem value="billing">
|
|
29
|
+
<AccordionTrigger>Billing schedule</AccordionTrigger>
|
|
30
|
+
<AccordionContent>
|
|
31
|
+
<p mix={bodyTextCss}>
|
|
32
|
+
Review invoice timing, payment methods, and renewal reminders with the same spacing and
|
|
33
|
+
typography used elsewhere in the system.
|
|
34
|
+
</p>
|
|
35
|
+
</AccordionContent>
|
|
36
|
+
</AccordionItem>
|
|
37
|
+
|
|
38
|
+
<AccordionItem value="notifications">
|
|
39
|
+
<AccordionTrigger>Notification rules</AccordionTrigger>
|
|
40
|
+
<AccordionContent>
|
|
41
|
+
<p mix={bodyTextCss}>
|
|
42
|
+
Use single mode when only one details panel should stay open at a time in a compact
|
|
43
|
+
settings or details view.
|
|
44
|
+
</p>
|
|
45
|
+
</AccordionContent>
|
|
46
|
+
</AccordionItem>
|
|
47
|
+
</Accordion>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const bodyTextCss = css({
|
|
52
|
+
margin: 0,
|
|
53
|
+
fontSize: theme.fontSize.sm,
|
|
54
|
+
lineHeight: theme.lineHeight.relaxed,
|
|
55
|
+
color: theme.colors.text.secondary,
|
|
56
|
+
})
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# anchor
|
|
2
|
+
|
|
3
|
+
`anchor` positions a floating element against an anchor element and keeps it constrained to the viewport. Use it for custom floating surfaces that need placement, flipping, offsets, and optional relative alignment.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { anchor } from 'remix/ui/anchor'
|
|
9
|
+
|
|
10
|
+
let trigger = document.querySelector<HTMLButtonElement>('[data-trigger]')
|
|
11
|
+
let panel = document.querySelector<HTMLElement>('[data-panel]')
|
|
12
|
+
|
|
13
|
+
if (trigger && panel) {
|
|
14
|
+
let cleanup = anchor(panel, trigger, {
|
|
15
|
+
placement: 'bottom-end',
|
|
16
|
+
offset: 8,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
// Later, when the surface closes or unmounts:
|
|
20
|
+
cleanup()
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Use the returned cleanup function with the lifecycle that owns the floating element. For native popovers, position on open and clean up on close.
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { anchor } from 'remix/ui/anchor'
|
|
28
|
+
|
|
29
|
+
let cleanupAnchor = () => {}
|
|
30
|
+
|
|
31
|
+
button.addEventListener('click', () => {
|
|
32
|
+
popover.showPopover()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
popover.addEventListener('beforetoggle', (event) => {
|
|
36
|
+
let toggleEvent = event as ToggleEvent
|
|
37
|
+
|
|
38
|
+
if (toggleEvent.newState === 'open') {
|
|
39
|
+
cleanupAnchor()
|
|
40
|
+
cleanupAnchor = anchor(popover, button, {
|
|
41
|
+
placement: 'bottom-start',
|
|
42
|
+
offset: 4,
|
|
43
|
+
})
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
cleanupAnchor()
|
|
48
|
+
cleanupAnchor = () => {}
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## `anchor.*`
|
|
53
|
+
|
|
54
|
+
- `anchor(floatingElement, anchorElement, options)`: positions `floatingElement` against `anchorElement`, starts animation-frame polling for geometry changes, and returns a cleanup function.
|
|
55
|
+
- `AnchorOptions`: placement, inset, relative alignment, and offset options.
|
|
56
|
+
- `AnchorPlacement`: exported placement names for the main sides and top/bottom start/end alignment.
|
|
57
|
+
|
|
58
|
+
## Placements
|
|
59
|
+
|
|
60
|
+
Default placement is `bottom`. Use start/end variants to align an edge instead of centering the floating element on the anchor.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
anchor(panel, trigger, { placement: 'bottom' })
|
|
64
|
+
anchor(panel, trigger, { placement: 'bottom-start' })
|
|
65
|
+
anchor(panel, trigger, { placement: 'bottom-end' })
|
|
66
|
+
anchor(panel, trigger, { placement: 'top' })
|
|
67
|
+
anchor(panel, trigger, { placement: 'left' })
|
|
68
|
+
anchor(panel, trigger, { placement: 'right' })
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The positioning logic can also handle left/right start/end placements through `AnchorOptions['placement']`.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
anchor(panel, trigger, { placement: 'right-start' })
|
|
75
|
+
anchor(panel, trigger, { placement: 'left-end' })
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
When the requested placement would overflow the viewport, `anchor` flips to the opposite side and writes the final placement to `data-anchor-placement`.
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
let cleanup = anchor(panel, trigger, {
|
|
82
|
+
placement: 'bottom-start',
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
panel.dataset.anchorPlacement
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Offsets
|
|
89
|
+
|
|
90
|
+
Use `offset` for distance along the placement axis. Use `offsetX` and `offsetY` for independent adjustment after placement is resolved.
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
anchor(panel, trigger, {
|
|
94
|
+
placement: 'bottom-start',
|
|
95
|
+
offset: 8,
|
|
96
|
+
offsetX: 4,
|
|
97
|
+
offsetY: -2,
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Offsets may be numbers or functions that receive the floating element.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
anchor(panel, trigger, {
|
|
105
|
+
placement: 'bottom-start',
|
|
106
|
+
offset: (floating) => floating.offsetHeight / 10,
|
|
107
|
+
offsetX: (floating) => floating.offsetWidth / 20,
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Inset Positioning
|
|
112
|
+
|
|
113
|
+
Pass `inset: true` to align the floating element inside the anchor edge instead of outside it. This is useful for surfaces that should visually cover or line up with the trigger.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
anchor(panel, trigger, {
|
|
117
|
+
placement: 'bottom-start',
|
|
118
|
+
inset: true,
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Relative Alignment
|
|
123
|
+
|
|
124
|
+
Use `relativeTo` when a child inside the floating element should align to the anchor instead of the floating element's outer box. The value is a selector scoped to the floating element.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
anchor(listbox, trigger, {
|
|
128
|
+
placement: 'bottom-start',
|
|
129
|
+
relativeTo: '[role="option"][aria-selected="true"]',
|
|
130
|
+
})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Combine `relativeTo` with `inset` for selected-option popovers where the selected option should sit over the trigger.
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
anchor(listbox, trigger, {
|
|
137
|
+
placement: 'left',
|
|
138
|
+
inset: true,
|
|
139
|
+
relativeTo: '[aria-selected="true"]',
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Behavior Notes
|
|
144
|
+
|
|
145
|
+
- Default placement is below the anchor.
|
|
146
|
+
- Supported placements include top, bottom, left, right, top/bottom start/end variants, and left/right start/end placements through `AnchorOptions['placement']`.
|
|
147
|
+
- The floating element flips when the requested placement would overflow the viewport and records the final placement in `data-anchor-placement`.
|
|
148
|
+
- Oversized floating elements are constrained with max dimensions and remain inside the viewport padding.
|
|
149
|
+
- Oversized inset surfaces with `relativeTo` preserve alignment by scrolling the nearest scrollable descendant when possible.
|
|
150
|
+
- `offset`, `offsetX`, and `offsetY` may be numbers or functions that receive the floating element.
|
|
151
|
+
- `relativeTo` lets a surface align to an inner element, which is useful for selected options inside popovers.
|
|
152
|
+
- `anchor` polls on animation frames for anchor or floating geometry changes and repositions when either changes.
|
|
153
|
+
- The returned cleanup function cancels animation-frame polling.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# breadcrumbs
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Breadcrumbs } from 'remix/ui/breadcrumbs'
|
|
9
|
+
|
|
10
|
+
export function ProjectBreadcrumbs() {
|
|
11
|
+
return (
|
|
12
|
+
<Breadcrumbs
|
|
13
|
+
items={[
|
|
14
|
+
{ href: '/', label: 'Home' },
|
|
15
|
+
{ href: '/projects', label: 'Projects' },
|
|
16
|
+
{ label: 'Roadmap' },
|
|
17
|
+
]}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Mark an earlier item as current when the page belongs to a parent section but the final crumb is still useful context. The current item always renders as text with `aria-current="page"`.
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
<Breadcrumbs
|
|
27
|
+
items={[
|
|
28
|
+
{ href: '/', label: 'Home' },
|
|
29
|
+
{ current: true, href: '/components', label: 'Components' },
|
|
30
|
+
{ label: 'Breadcrumbs' },
|
|
31
|
+
]}
|
|
32
|
+
/>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Pass `separator` to replace the default chevron glyph.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<Breadcrumbs items={[{ href: '/', label: 'Home' }, { label: 'Breadcrumbs' }]} separator="/" />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## `breadcrumbs.*`
|
|
42
|
+
|
|
43
|
+
- `Breadcrumbs`: component that renders a `<nav>` with an ordered list of breadcrumb items.
|
|
44
|
+
- `BreadcrumbItem`: item shape with `label`, optional `href`, and optional `current`.
|
|
45
|
+
- `BreadcrumbsProps`: nav props plus required `items` and optional `separator`.
|
|
46
|
+
|
|
47
|
+
## Behavior Notes
|
|
48
|
+
|
|
49
|
+
- The default `aria-label` is `"Breadcrumb"` unless an `aria-label` prop is provided.
|
|
50
|
+
- Breadcrumb items render inside an ordered list.
|
|
51
|
+
- The last item is treated as current when no item has `current: true`.
|
|
52
|
+
- An explicit current item wins over the last-item default.
|
|
53
|
+
- Current items render as text with `aria-current="page"`, even when they include `href`.
|
|
54
|
+
- Non-current items with `href` render as links; non-current items without `href` render as text.
|
|
55
|
+
- The default separator is the `chevronRight` glyph. Separators render between items and are hidden from assistive technology.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Breadcrumbs } from '@remix-run/ui/breadcrumbs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @name Breadcrumbs Basic
|
|
5
|
+
* @description A basic breadcrumb trail linking back through the page hierarchy.
|
|
6
|
+
*/
|
|
7
|
+
export default function Example() {
|
|
8
|
+
return () => (
|
|
9
|
+
<Breadcrumbs
|
|
10
|
+
items={[
|
|
11
|
+
{ href: '/', label: 'Home' },
|
|
12
|
+
{ href: '/components', label: 'Components' },
|
|
13
|
+
{ label: 'Breadcrumbs' },
|
|
14
|
+
]}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Breadcrumbs } from '@remix-run/ui/breadcrumbs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @name Breadcrumbs with Separator
|
|
5
|
+
* @description Pass a custom separator string to override the default chevron icon.
|
|
6
|
+
*/
|
|
7
|
+
export default function Example() {
|
|
8
|
+
return () => (
|
|
9
|
+
<Breadcrumbs
|
|
10
|
+
items={[
|
|
11
|
+
{ href: '/', label: 'Workspace' },
|
|
12
|
+
{ href: '/projects', label: 'Projects' },
|
|
13
|
+
{ label: 'RMX_01' },
|
|
14
|
+
]}
|
|
15
|
+
separator="/"
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|