@remix-run/ui 0.3.0 → 0.4.0
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 +50 -164
- package/dist/accordion/index.d.ts +46 -0
- package/dist/accordion/index.js +142 -0
- package/dist/accordion/index.js.map +1 -0
- package/dist/accordion/primitives.d.ts +104 -0
- package/dist/accordion/primitives.js +312 -0
- package/dist/accordion/primitives.js.map +1 -0
- package/dist/{components/anchor/anchor.js → anchor/index.js} +1 -1
- package/dist/anchor/index.js.map +1 -0
- package/dist/animation/demos/reordering.js +9 -6
- package/dist/animation/demos/reordering.js.map +1 -1
- package/dist/{components/breadcrumbs/breadcrumbs.js → breadcrumbs/index.js} +19 -19
- package/dist/breadcrumbs/index.js.map +1 -0
- package/dist/button/index.d.ts +15 -0
- package/dist/button/index.js +128 -0
- package/dist/button/index.js.map +1 -0
- package/dist/checkbox/index.d.ts +14 -0
- package/dist/checkbox/index.js +125 -0
- package/dist/checkbox/index.js.map +1 -0
- package/dist/combobox/index.d.ts +21 -0
- package/dist/combobox/index.js +58 -0
- package/dist/combobox/index.js.map +1 -0
- package/dist/{components/combobox/combobox.d.ts → combobox/primitives.d.ts} +3 -7
- package/dist/{components/combobox/combobox.js → combobox/primitives.js} +8 -61
- package/dist/combobox/primitives.js.map +1 -0
- package/dist/input/index.d.ts +13 -0
- package/dist/input/index.js +121 -0
- package/dist/input/index.js.map +1 -0
- package/dist/listbox/flash-attribute.js.map +1 -0
- package/dist/{components/listbox/listbox.d.ts → listbox/index.d.ts} +2 -6
- package/dist/{components/listbox/listbox.js → listbox/index.js} +4 -82
- package/dist/listbox/index.js.map +1 -0
- package/dist/menu/hover-aim.js.map +1 -0
- package/dist/menu/index.d.ts +42 -0
- package/dist/menu/index.js +250 -0
- package/dist/menu/index.js.map +1 -0
- package/dist/{components/menu/menu.d.ts → menu/primitives.d.ts} +3 -32
- package/dist/{components/menu/menu.js → menu/primitives.js} +97 -314
- package/dist/menu/primitives.js.map +1 -0
- package/dist/{components/popover/popover.d.ts → popover/index.d.ts} +2 -4
- package/dist/{components/popover/popover.js → popover/index.js} +5 -48
- package/dist/popover/index.js.map +1 -0
- package/dist/{interactions/outside-click/outside-click-mixin.js → popover/outside-click.js} +1 -1
- package/dist/popover/outside-click.js.map +1 -0
- package/dist/popover/scroll-lock.d.ts +1 -0
- package/dist/{utils → popover}/scroll-lock.js +0 -18
- package/dist/popover/scroll-lock.js.map +1 -0
- package/dist/radio/index.d.ts +12 -0
- package/dist/radio/index.js +77 -0
- package/dist/radio/index.js.map +1 -0
- package/dist/runtime/reconcile.js +11 -5
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/vdom.js +3 -0
- package/dist/runtime/vdom.js.map +1 -1
- package/dist/select/index.d.ts +19 -0
- package/dist/select/index.js +95 -0
- package/dist/select/index.js.map +1 -0
- package/dist/select/keydown.js.map +1 -0
- package/dist/{components/select/select.d.ts → select/primitives.d.ts} +4 -6
- package/dist/{components/select/select.js → select/primitives.js} +11 -63
- package/dist/select/primitives.js.map +1 -0
- package/dist/server/stream.js +2 -2
- package/dist/server/stream.js.map +1 -1
- package/dist/shared/focus-styles.d.ts +1 -0
- package/dist/shared/focus-styles.js +2 -0
- package/dist/shared/focus-styles.js.map +1 -0
- package/dist/shared/icons.d.ts +9 -0
- package/dist/shared/icons.js +42 -0
- package/dist/shared/icons.js.map +1 -0
- package/dist/shared/listbox-popover-styles.d.ts +6 -0
- package/dist/shared/listbox-popover-styles.js +137 -0
- package/dist/shared/listbox-popover-styles.js.map +1 -0
- package/dist/shared/style-values.d.ts +78 -0
- package/dist/{theme/presets/rmx-01/index.js → shared/style-values.js} +3 -34
- package/dist/shared/style-values.js.map +1 -0
- package/dist/{interactions/typeahead/typeahead-mixin.js → shared/typeahead.js} +1 -1
- package/dist/shared/typeahead.js.map +1 -0
- package/dist/shared/wait-for-css-transition.js.map +1 -0
- package/dist/shared/wait.js.map +1 -0
- package/dist/tabs/index.d.ts +32 -0
- package/dist/tabs/index.js +157 -0
- package/dist/tabs/index.js.map +1 -0
- package/dist/tabs/primitives.d.ts +57 -0
- package/dist/tabs/primitives.js +238 -0
- package/dist/tabs/primitives.js.map +1 -0
- package/dist/toggle/index.d.ts +9 -0
- package/dist/toggle/index.js +108 -0
- package/dist/toggle/index.js.map +1 -0
- package/dist/toggle/primitives.d.ts +29 -0
- package/dist/toggle/primitives.js +127 -0
- package/dist/toggle/primitives.js.map +1 -0
- package/package.json +73 -44
- package/src/{components/accordion → accordion}/README.md +50 -14
- package/src/accordion/index.tsx +285 -0
- package/src/accordion/primitives.tsx +512 -0
- package/src/{components/anchor → anchor}/README.md +37 -2
- package/src/animation/demos/reordering.tsx +9 -6
- package/src/{components/breadcrumbs → breadcrumbs}/README.md +4 -4
- package/src/{components/breadcrumbs/breadcrumbs.tsx → breadcrumbs/index.tsx} +18 -18
- package/src/button/README.md +44 -0
- package/src/button/index.ts +168 -0
- package/src/checkbox/README.md +59 -0
- package/src/checkbox/index.ts +162 -0
- package/src/{components/combobox → combobox}/README.md +58 -9
- package/src/combobox/index.tsx +112 -0
- package/src/{components/combobox/combobox.tsx → combobox/primitives.tsx} +5 -91
- package/src/input/README.md +52 -0
- package/src/input/index.ts +147 -0
- package/src/{components/listbox → listbox}/README.md +9 -41
- package/src/{components/listbox/listbox.ts → listbox/index.ts} +2 -87
- package/src/{components/menu → menu}/README.md +55 -14
- package/src/menu/index.tsx +354 -0
- package/src/{components/menu/menu.tsx → menu/primitives.tsx} +99 -413
- package/src/{components/popover → popover}/README.md +20 -39
- package/src/{components/popover/popover.ts → popover/index.ts} +3 -57
- package/src/{utils → popover}/scroll-lock.ts +0 -28
- package/src/radio/README.md +53 -0
- package/src/radio/index.ts +100 -0
- package/src/runtime/demos/readme.demo.tsx +16 -14
- package/src/runtime/reconcile.ts +11 -5
- package/src/runtime/vdom.ts +3 -0
- package/src/{components/select → select}/README.md +29 -19
- package/src/select/index.tsx +159 -0
- package/src/{components/select/select.tsx → select/primitives.tsx} +10 -98
- package/src/server/stream.ts +3 -3
- package/src/shared/focus-styles.ts +2 -0
- package/src/shared/icons.tsx +62 -0
- package/src/shared/listbox-popover-styles.ts +146 -0
- package/src/shared/style-values.ts +165 -0
- package/src/tabs/README.md +141 -0
- package/src/tabs/index.tsx +244 -0
- package/src/tabs/primitives.ts +365 -0
- package/src/toggle/README.md +56 -0
- package/src/toggle/index.tsx +134 -0
- package/src/toggle/primitives.ts +192 -0
- package/dist/animation/demos/animation.demo.d.ts +0 -5
- package/dist/animation/demos/animation.demo.js +0 -92
- package/dist/animation/demos/animation.demo.js.map +0 -1
- package/dist/animation/demos/spring.demo.d.ts +0 -6
- package/dist/animation/demos/spring.demo.js +0 -304
- package/dist/animation/demos/spring.demo.js.map +0 -1
- package/dist/components/accordion/accordion.d.ts +0 -92
- package/dist/components/accordion/accordion.js +0 -335
- package/dist/components/accordion/accordion.js.map +0 -1
- package/dist/components/accordion/demos/card.demo.d.ts +0 -7
- package/dist/components/accordion/demos/card.demo.js +0 -59
- package/dist/components/accordion/demos/card.demo.js.map +0 -1
- package/dist/components/accordion/demos/multiple.demo.d.ts +0 -7
- package/dist/components/accordion/demos/multiple.demo.js +0 -20
- package/dist/components/accordion/demos/multiple.demo.js.map +0 -1
- package/dist/components/accordion/demos/overview.demo.d.ts +0 -7
- package/dist/components/accordion/demos/overview.demo.js +0 -20
- package/dist/components/accordion/demos/overview.demo.js.map +0 -1
- package/dist/components/anchor/anchor.js.map +0 -1
- package/dist/components/breadcrumbs/basic.demo.d.ts +0 -6
- package/dist/components/breadcrumbs/basic.demo.js +0 -15
- package/dist/components/breadcrumbs/basic.demo.js.map +0 -1
- package/dist/components/breadcrumbs/breadcrumbs.js.map +0 -1
- package/dist/components/breadcrumbs/separator.demo.d.ts +0 -6
- package/dist/components/breadcrumbs/separator.demo.js +0 -15
- package/dist/components/breadcrumbs/separator.demo.js.map +0 -1
- package/dist/components/button/button.d.ts +0 -105
- package/dist/components/button/button.js +0 -207
- package/dist/components/button/button.js.map +0 -1
- package/dist/components/button/demos/aliases.demo.d.ts +0 -7
- package/dist/components/button/demos/aliases.demo.js +0 -19
- package/dist/components/button/demos/aliases.demo.js.map +0 -1
- package/dist/components/button/demos/basic.demo.d.ts +0 -7
- package/dist/components/button/demos/basic.demo.js +0 -20
- package/dist/components/button/demos/basic.demo.js.map +0 -1
- package/dist/components/button/demos/states.demo.d.ts +0 -7
- package/dist/components/button/demos/states.demo.js +0 -30
- package/dist/components/button/demos/states.demo.js.map +0 -1
- package/dist/components/combobox/combobox.js.map +0 -1
- package/dist/components/combobox/overview.demo.d.ts +0 -7
- package/dist/components/combobox/overview.demo.js +0 -154
- package/dist/components/combobox/overview.demo.js.map +0 -1
- package/dist/components/glyph/glyph.d.ts +0 -14
- package/dist/components/glyph/glyph.js +0 -65
- package/dist/components/glyph/glyph.js.map +0 -1
- package/dist/components/listbox/listbox.js.map +0 -1
- package/dist/components/listbox/overview.demo.d.ts +0 -7
- package/dist/components/listbox/overview.demo.js +0 -65
- package/dist/components/listbox/overview.demo.js.map +0 -1
- package/dist/components/menu/demos/bubbling.demo.d.ts +0 -6
- package/dist/components/menu/demos/bubbling.demo.js +0 -16
- package/dist/components/menu/demos/bubbling.demo.js.map +0 -1
- package/dist/components/menu/demos/context-trigger.demo.d.ts +0 -7
- package/dist/components/menu/demos/context-trigger.demo.js +0 -87
- package/dist/components/menu/demos/context-trigger.demo.js.map +0 -1
- package/dist/components/menu/demos/overview.demo.d.ts +0 -7
- package/dist/components/menu/demos/overview.demo.js +0 -33
- package/dist/components/menu/demos/overview.demo.js.map +0 -1
- package/dist/components/menu/hover-aim.js.map +0 -1
- package/dist/components/menu/menu.js.map +0 -1
- package/dist/components/popover/overview.demo.d.ts +0 -7
- package/dist/components/popover/overview.demo.js +0 -65
- package/dist/components/popover/overview.demo.js.map +0 -1
- package/dist/components/popover/popover.js.map +0 -1
- package/dist/components/select/deconstructed.demo.d.ts +0 -7
- package/dist/components/select/deconstructed.demo.js +0 -54
- package/dist/components/select/deconstructed.demo.js.map +0 -1
- package/dist/components/select/overview.demo.d.ts +0 -6
- package/dist/components/select/overview.demo.js +0 -28
- package/dist/components/select/overview.demo.js.map +0 -1
- package/dist/components/select/select.js.map +0 -1
- package/dist/components/separator/separator.d.ts +0 -2
- package/dist/components/separator/separator.js +0 -15
- package/dist/components/separator/separator.js.map +0 -1
- package/dist/components/tabs/tabs.d.ts +0 -78
- package/dist/components/tabs/tabs.js +0 -339
- package/dist/components/tabs/tabs.js.map +0 -1
- package/dist/interactions/keydown/keydown.js.map +0 -1
- package/dist/interactions/outside-click/outside-click-mixin.js.map +0 -1
- package/dist/interactions/typeahead/typeahead-mixin.js.map +0 -1
- package/dist/runtime/demos/basic.demo.d.ts +0 -6
- package/dist/runtime/demos/basic.demo.js +0 -16
- package/dist/runtime/demos/basic.demo.js.map +0 -1
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.d.ts +0 -6
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.js +0 -77
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.js.map +0 -1
- package/dist/runtime/demos/keyed-list.demo.d.ts +0 -6
- package/dist/runtime/demos/keyed-list.demo.js +0 -62
- package/dist/runtime/demos/keyed-list.demo.js.map +0 -1
- package/dist/runtime/demos/readme.demo.d.ts +0 -5
- package/dist/runtime/demos/readme.demo.js +0 -396
- package/dist/runtime/demos/readme.demo.js.map +0 -1
- package/dist/theme/contract.d.ts +0 -61
- package/dist/theme/contract.js +0 -131
- package/dist/theme/contract.js.map +0 -1
- package/dist/theme/glyph-contract.d.ts +0 -11
- package/dist/theme/glyph-contract.js +0 -34
- package/dist/theme/glyph-contract.js.map +0 -1
- package/dist/theme/presets/rmx-01/glyphs.d.ts +0 -2
- package/dist/theme/presets/rmx-01/glyphs.js +0 -252
- package/dist/theme/presets/rmx-01/glyphs.js.map +0 -1
- package/dist/theme/presets/rmx-01/index.d.ts +0 -4
- package/dist/theme/presets/rmx-01/index.js.map +0 -1
- package/dist/theme/runtime.d.ts +0 -2
- package/dist/theme/runtime.js +0 -84
- package/dist/theme/runtime.js.map +0 -1
- package/dist/theme/theme.d.ts +0 -6
- package/dist/theme/theme.js +0 -5
- package/dist/theme/theme.js.map +0 -1
- package/dist/utils/flash-attribute.js.map +0 -1
- package/dist/utils/scroll-lock.d.ts +0 -3
- package/dist/utils/scroll-lock.js.map +0 -1
- package/dist/utils/wait-for-css-transition.js.map +0 -1
- package/dist/utils/wait.js.map +0 -1
- package/src/animation/demos/animation.demo.tsx +0 -194
- package/src/animation/demos/spring.demo.tsx +0 -385
- package/src/components/accordion/accordion.tsx +0 -565
- package/src/components/accordion/demos/card.demo.tsx +0 -114
- package/src/components/accordion/demos/multiple.demo.tsx +0 -57
- package/src/components/accordion/demos/overview.demo.tsx +0 -57
- package/src/components/breadcrumbs/basic.demo.tsx +0 -18
- package/src/components/breadcrumbs/separator.demo.tsx +0 -19
- package/src/components/button/README.md +0 -44
- package/src/components/button/button.tsx +0 -286
- package/src/components/button/demos/aliases.demo.tsx +0 -28
- package/src/components/button/demos/basic.demo.tsx +0 -31
- package/src/components/button/demos/states.demo.tsx +0 -53
- package/src/components/combobox/overview.demo.tsx +0 -200
- package/src/components/glyph/README.md +0 -72
- package/src/components/glyph/glyph.tsx +0 -110
- package/src/components/listbox/overview.demo.tsx +0 -102
- package/src/components/menu/demos/bubbling.demo.tsx +0 -35
- package/src/components/menu/demos/context-trigger.demo.tsx +0 -133
- package/src/components/menu/demos/overview.demo.tsx +0 -77
- package/src/components/popover/overview.demo.tsx +0 -144
- package/src/components/select/deconstructed.demo.tsx +0 -96
- package/src/components/select/overview.demo.tsx +0 -52
- package/src/components/separator/separator.ts +0 -19
- package/src/components/tabs/README.md +0 -105
- package/src/components/tabs/tabs.tsx +0 -504
- package/src/runtime/demos/basic.demo.tsx +0 -21
- package/src/runtime/demos/controlled-uncontrolled-values.demo.tsx +0 -177
- package/src/runtime/demos/keyed-list.demo.tsx +0 -94
- package/src/theme/README.md +0 -103
- package/src/theme/contract.ts +0 -215
- package/src/theme/glyph-contract.ts +0 -59
- package/src/theme/presets/rmx-01/glyphs.tsx +0 -308
- package/src/theme/presets/rmx-01/index.ts +0 -123
- package/src/theme/runtime.ts +0 -119
- package/src/theme/theme.ts +0 -15
- package/src/utils/scroll-lock/README.md +0 -33
- /package/dist/{components/anchor/anchor.d.ts → anchor/index.d.ts} +0 -0
- /package/dist/{components/breadcrumbs/breadcrumbs.d.ts → breadcrumbs/index.d.ts} +0 -0
- /package/dist/{utils → listbox}/flash-attribute.d.ts +0 -0
- /package/dist/{utils → listbox}/flash-attribute.js +0 -0
- /package/dist/{components/menu → menu}/hover-aim.d.ts +0 -0
- /package/dist/{components/menu → menu}/hover-aim.js +0 -0
- /package/dist/{interactions/outside-click/outside-click-mixin.d.ts → popover/outside-click.d.ts} +0 -0
- /package/dist/{interactions/keydown → select}/keydown.d.ts +0 -0
- /package/dist/{interactions/keydown → select}/keydown.js +0 -0
- /package/dist/{interactions/typeahead/typeahead-mixin.d.ts → shared/typeahead.d.ts} +0 -0
- /package/dist/{utils → shared}/wait-for-css-transition.d.ts +0 -0
- /package/dist/{utils → shared}/wait-for-css-transition.js +0 -0
- /package/dist/{utils → shared}/wait.d.ts +0 -0
- /package/dist/{utils → shared}/wait.js +0 -0
- /package/src/{components/anchor/anchor.ts → anchor/index.ts} +0 -0
- /package/src/{utils → listbox}/flash-attribute.ts +0 -0
- /package/src/{components/menu → menu}/hover-aim.ts +0 -0
- /package/src/{interactions/outside-click/outside-click-mixin.ts → popover/outside-click.ts} +0 -0
- /package/src/{interactions/keydown → select}/keydown.ts +0 -0
- /package/src/{interactions/typeahead/typeahead-mixin.ts → shared/typeahead.ts} +0 -0
- /package/src/{utils → shared}/wait-for-css-transition.ts +0 -0
- /package/src/{utils → shared}/wait.ts +0 -0
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
attrs,
|
|
3
|
-
createElement,
|
|
4
3
|
createMixin,
|
|
5
|
-
css,
|
|
6
4
|
on,
|
|
7
5
|
ref,
|
|
8
|
-
type CSSMixinDescriptor,
|
|
9
6
|
type Dispatched,
|
|
10
7
|
type ElementProps,
|
|
11
8
|
type Handle,
|
|
@@ -14,19 +11,16 @@ import {
|
|
|
14
11
|
type Props,
|
|
15
12
|
type RemixNode,
|
|
16
13
|
} from '@remix-run/ui'
|
|
17
|
-
import * as
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { waitForCssTransition } from '../../utils/wait-for-css-transition.ts'
|
|
25
|
-
import { wait } from '../../utils/wait.ts'
|
|
26
|
-
import type { AnchorOptions } from '../anchor/anchor.ts'
|
|
14
|
+
import * as listbox from '../listbox/index.ts'
|
|
15
|
+
import * as popover from '../popover/index.ts'
|
|
16
|
+
import { hiddenTypeahead } from '../shared/typeahead.ts'
|
|
17
|
+
import { waitForCssTransition } from '../shared/wait-for-css-transition.ts'
|
|
18
|
+
import { wait } from '../shared/wait.ts'
|
|
19
|
+
import { onKeyDown } from './keydown.ts'
|
|
20
|
+
import type { AnchorOptions } from '../anchor/index.ts'
|
|
27
21
|
|
|
28
22
|
const SELECT_CHANGE_EVENT = 'rmx:select-change' as const
|
|
29
|
-
const
|
|
23
|
+
const DEFAULT_LABEL_SWAP_DELAY_MS = 75
|
|
30
24
|
|
|
31
25
|
type SelectChangeHandler<target extends HTMLElement> = (
|
|
32
26
|
event: Dispatched<SelectChangeEvent, target>,
|
|
@@ -65,6 +59,7 @@ export interface SelectContextProps {
|
|
|
65
59
|
defaultLabel: string
|
|
66
60
|
defaultValue?: string | null
|
|
67
61
|
disabled?: boolean
|
|
62
|
+
labelSwapDelayMs?: number
|
|
68
63
|
name?: string
|
|
69
64
|
}
|
|
70
65
|
|
|
@@ -236,7 +231,7 @@ function SelectProvider(handle: Handle<SelectContextProps, SelectContextValue>):
|
|
|
236
231
|
}
|
|
237
232
|
|
|
238
233
|
await Promise.all([handle.update(), waitForCssTransition(surfaceRef, handle.signal)])
|
|
239
|
-
await wait(
|
|
234
|
+
await wait(handle.props.labelSwapDelayMs ?? DEFAULT_LABEL_SWAP_DELAY_MS)
|
|
240
235
|
if (handle.signal.aborted) return
|
|
241
236
|
displayedLabel = selectedLabel
|
|
242
237
|
state = 'closed'
|
|
@@ -495,41 +490,6 @@ const hiddenInputMixin: MixinFactory<HTMLInputElement, [], ElementProps> = creat
|
|
|
495
490
|
})
|
|
496
491
|
})
|
|
497
492
|
|
|
498
|
-
const selectTriggerCss: CSSMixinDescriptor = css({
|
|
499
|
-
minHeight: theme.control.height.sm,
|
|
500
|
-
width: '100%',
|
|
501
|
-
paddingInline: theme.space.md,
|
|
502
|
-
paddingInlineEnd: theme.space.sm,
|
|
503
|
-
display: 'grid',
|
|
504
|
-
gridTemplateColumns: 'minmax(0, 1fr) auto',
|
|
505
|
-
alignItems: 'center',
|
|
506
|
-
gap: theme.space.sm,
|
|
507
|
-
borderRadius: theme.radius.md,
|
|
508
|
-
backgroundImage: 'none',
|
|
509
|
-
border: '0.5px solid transparent',
|
|
510
|
-
boxShadow: 'none',
|
|
511
|
-
fontSize: theme.fontSize.xs,
|
|
512
|
-
textAlign: 'left',
|
|
513
|
-
backgroundColor: theme.surface.lvl3,
|
|
514
|
-
color: theme.colors.text.secondary,
|
|
515
|
-
'&:hover, &:focus-visible, &[aria-expanded="true"], &[aria-expanded="true"]:hover, &[aria-expanded="true"]:focus-visible':
|
|
516
|
-
{
|
|
517
|
-
backgroundColor: theme.surface.lvl4,
|
|
518
|
-
color: theme.colors.text.primary,
|
|
519
|
-
},
|
|
520
|
-
'&:active': {
|
|
521
|
-
backgroundColor: theme.surface.lvl3,
|
|
522
|
-
},
|
|
523
|
-
'&:focus-visible': {
|
|
524
|
-
outline: `2px solid ${theme.colors.focus.ring}`,
|
|
525
|
-
outlineOffset: '2px',
|
|
526
|
-
},
|
|
527
|
-
'&:disabled': {
|
|
528
|
-
opacity: 0.6,
|
|
529
|
-
},
|
|
530
|
-
})
|
|
531
|
-
|
|
532
|
-
export const triggerStyle = selectTriggerCss
|
|
533
493
|
export const Context = SelectProvider
|
|
534
494
|
export const hiddenInput = hiddenInputMixin
|
|
535
495
|
export const list = listMixin
|
|
@@ -552,51 +512,3 @@ export function onSelectChange<target extends HTMLElement>(
|
|
|
552
512
|
): ReturnType<typeof on<target, typeof SELECT_CHANGE_EVENT>> {
|
|
553
513
|
return on(SELECT_CHANGE_EVENT, handler, captureBoolean)
|
|
554
514
|
}
|
|
555
|
-
|
|
556
|
-
function SelectLabel(handle: Handle): () => RemixNode {
|
|
557
|
-
let context = getSelectContext(handle)
|
|
558
|
-
|
|
559
|
-
return () => <span mix={button.labelStyle}>{context.displayedLabel}</span>
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
export function Select(handle: Handle<SelectProps>): () => RemixNode {
|
|
563
|
-
return () => {
|
|
564
|
-
let { children, defaultLabel, defaultValue, disabled, name, mix, ...buttonProps } = handle.props
|
|
565
|
-
|
|
566
|
-
return (
|
|
567
|
-
<select.Context
|
|
568
|
-
defaultLabel={defaultLabel}
|
|
569
|
-
defaultValue={defaultValue}
|
|
570
|
-
disabled={disabled}
|
|
571
|
-
name={name}
|
|
572
|
-
>
|
|
573
|
-
<button {...buttonProps} mix={[button.baseStyle, triggerStyle, select.trigger(), mix]}>
|
|
574
|
-
<SelectLabel />
|
|
575
|
-
<Glyph mix={button.iconStyle} name="chevronVertical" />
|
|
576
|
-
</button>
|
|
577
|
-
<popover.Context>
|
|
578
|
-
<div mix={[popover.surfaceStyle, select.popover()]}>
|
|
579
|
-
<div mix={[popover.contentStyle, listbox.listStyle, select.list()]}>{children}</div>
|
|
580
|
-
</div>
|
|
581
|
-
</popover.Context>
|
|
582
|
-
{name && <input mix={select.hiddenInput()} />}
|
|
583
|
-
</select.Context>
|
|
584
|
-
)
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
export function Option(handle: Handle<SelectOptionProps>): () => RemixNode {
|
|
589
|
-
return () => {
|
|
590
|
-
let { label, value, disabled, textValue, children, mix, ...divProps } = handle.props
|
|
591
|
-
|
|
592
|
-
return (
|
|
593
|
-
<div
|
|
594
|
-
{...divProps}
|
|
595
|
-
mix={[listbox.optionStyle, select.option({ value, label, disabled, textValue }), mix]}
|
|
596
|
-
>
|
|
597
|
-
<Glyph mix={listbox.glyphStyle} name="check" />
|
|
598
|
-
<span mix={listbox.labelStyle}>{children ?? handle.props.label}</span>
|
|
599
|
-
</div>
|
|
600
|
-
)
|
|
601
|
-
}
|
|
602
|
-
}
|
package/src/server/stream.ts
CHANGED
|
@@ -1052,17 +1052,17 @@ async function resolveClientEntries(
|
|
|
1052
1052
|
): Promise<void> {
|
|
1053
1053
|
if (context.unresolvedHydrationData.size === 0) return
|
|
1054
1054
|
|
|
1055
|
-
let resolvedEntries = new Map<
|
|
1055
|
+
let resolvedEntries = new Map<EntryComponent, ResolvedClientEntry>()
|
|
1056
1056
|
|
|
1057
1057
|
for (let [hydrationId, unresolvedHydrationData] of context.unresolvedHydrationData) {
|
|
1058
1058
|
let { entryId, component, props } = unresolvedHydrationData
|
|
1059
|
-
let resolvedEntry = resolvedEntries.get(
|
|
1059
|
+
let resolvedEntry = resolvedEntries.get(component)
|
|
1060
1060
|
if (!resolvedEntry) {
|
|
1061
1061
|
resolvedEntry = resolveClientEntry
|
|
1062
1062
|
? await Promise.resolve(resolveClientEntry(entryId, component))
|
|
1063
1063
|
: resolveDefaultClientEntry(entryId, component)
|
|
1064
1064
|
validateResolvedClientEntry(entryId, resolvedEntry)
|
|
1065
|
-
resolvedEntries.set(
|
|
1065
|
+
resolvedEntries.set(component, resolvedEntry)
|
|
1066
1066
|
}
|
|
1067
1067
|
|
|
1068
1068
|
context.hydrationData.set(hydrationId, {
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const controlFocusShadow =
|
|
2
|
+
'0 2px 3px -1px rgba(0, 0, 0, 0.04), 0 3px 4px -1.5px rgba(0, 0, 0, 0.04), 0 4px 5px -2px rgba(0, 0, 0, 0.04), 0 0 0 1px #3573F6, 0 0 0 4px rgba(53, 115, 246, 0.1), 0 6px 32px 4px rgba(53, 115, 246, 0.08), inset 0 0 8px 1px rgba(53, 115, 246, 0.05)'
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createElement } from '@remix-run/ui'
|
|
2
|
+
import type { Handle, Props, RemixElement } from '@remix-run/ui'
|
|
3
|
+
|
|
4
|
+
type IconProps = Omit<Props<'svg'>, 'children'>
|
|
5
|
+
|
|
6
|
+
function icon(
|
|
7
|
+
handle: Handle<IconProps>,
|
|
8
|
+
children: RemixElement | readonly RemixElement[],
|
|
9
|
+
): RemixElement {
|
|
10
|
+
let hiddenByDefault =
|
|
11
|
+
handle.props['aria-hidden'] === undefined &&
|
|
12
|
+
handle.props['aria-label'] === undefined &&
|
|
13
|
+
handle.props['aria-labelledby'] === undefined
|
|
14
|
+
|
|
15
|
+
return createElement(
|
|
16
|
+
'svg',
|
|
17
|
+
{
|
|
18
|
+
...handle.props,
|
|
19
|
+
'aria-hidden': hiddenByDefault ? true : handle.props['aria-hidden'],
|
|
20
|
+
fill: handle.props.fill ?? 'none',
|
|
21
|
+
viewBox: handle.props.viewBox ?? '0 0 16 16',
|
|
22
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
23
|
+
},
|
|
24
|
+
children,
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function strokedPath(d: string): RemixElement {
|
|
29
|
+
return createElement('path', {
|
|
30
|
+
d,
|
|
31
|
+
fill: 'none',
|
|
32
|
+
stroke: 'currentColor',
|
|
33
|
+
strokeLinecap: 'round',
|
|
34
|
+
strokeLinejoin: 'round',
|
|
35
|
+
strokeWidth: '1.5',
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function AddIcon(handle: Handle<IconProps>): () => RemixElement {
|
|
40
|
+
return () => icon(handle, strokedPath('M8 3.25v9.5M3.25 8h9.5'))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function CheckIcon(handle: Handle<IconProps>): () => RemixElement {
|
|
44
|
+
return () => icon(handle, strokedPath('m3.5 8.25 2.75 2.75L12.5 4.75'))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ChevronDownIcon(handle: Handle<IconProps>): () => RemixElement {
|
|
48
|
+
return () => icon(handle, strokedPath('m3.75 6.25 4.25 4 4.25-4'))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function ChevronRightIcon(handle: Handle<IconProps>): () => RemixElement {
|
|
52
|
+
return () => icon(handle, strokedPath('m6 4 4 4-4 4'))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function ChevronVerticalIcon(handle: Handle<IconProps>): () => RemixElement {
|
|
56
|
+
return () =>
|
|
57
|
+
icon(handle, [strokedPath('m3.75 6.5 4.25-4 4.25 4'), strokedPath('m3.75 9.5 4.25 4 4.25-4')])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function SearchIcon(handle: Handle<IconProps>): () => RemixElement {
|
|
61
|
+
return () => icon(handle, strokedPath('M7.25 12.25a5 5 0 1 1 0-10 5 5 0 0 1 0 10Zm3.54-1.46 3 3'))
|
|
62
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { css, type CSSMixinDescriptor } from '@remix-run/ui'
|
|
2
|
+
|
|
3
|
+
import { componentStyleValues as styles } from './style-values.ts'
|
|
4
|
+
|
|
5
|
+
const popupViewportClampMaxHeight = '50dvh'
|
|
6
|
+
|
|
7
|
+
const popoverSurfaceTransitionCss: CSSMixinDescriptor = css({
|
|
8
|
+
opacity: 0,
|
|
9
|
+
'&:popover-open': {
|
|
10
|
+
opacity: 1,
|
|
11
|
+
},
|
|
12
|
+
'&:not(:popover-open)': {
|
|
13
|
+
pointerEvents: 'none',
|
|
14
|
+
transition: 'opacity 180ms ease-in, overlay 180ms ease-in, display 180ms ease-in',
|
|
15
|
+
transitionBehavior: 'allow-discrete',
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const popoverSurfaceCss: CSSMixinDescriptor = css({
|
|
20
|
+
position: 'fixed',
|
|
21
|
+
inset: 'auto',
|
|
22
|
+
margin: 0,
|
|
23
|
+
padding: styles.space.none,
|
|
24
|
+
display: 'flex',
|
|
25
|
+
flexDirection: 'column',
|
|
26
|
+
minHeight: 0,
|
|
27
|
+
minWidth: '12rem',
|
|
28
|
+
maxWidth: `min(24rem, calc(100vw - (${styles.space.lg} * 2)))`,
|
|
29
|
+
maxHeight: popupViewportClampMaxHeight,
|
|
30
|
+
border: `1px solid ${styles.colors.border.subtle}`,
|
|
31
|
+
borderRadius: styles.radius.lg,
|
|
32
|
+
backgroundColor: styles.surface.lvl0,
|
|
33
|
+
color: styles.colors.text.primary,
|
|
34
|
+
overflow: 'hidden',
|
|
35
|
+
boxShadow: `${styles.shadow.xs}, ${styles.shadow.md}`,
|
|
36
|
+
'&::backdrop': {
|
|
37
|
+
background: 'transparent',
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const listCss: CSSMixinDescriptor = css({
|
|
42
|
+
display: 'flex',
|
|
43
|
+
flexDirection: 'column',
|
|
44
|
+
flex: '1 1 auto',
|
|
45
|
+
minHeight: 0,
|
|
46
|
+
paddingBlock: styles.space.xs,
|
|
47
|
+
paddingInline: styles.space.none,
|
|
48
|
+
overflow: 'auto',
|
|
49
|
+
overscrollBehavior: 'contain',
|
|
50
|
+
outline: 'none',
|
|
51
|
+
userSelect: 'none',
|
|
52
|
+
WebkitUserSelect: 'none',
|
|
53
|
+
'--rmx-ui-item-inset': `calc(${styles.space.sm} + ${styles.space.xs})`,
|
|
54
|
+
'--rmx-ui-item-indicator-gap': styles.space.xs,
|
|
55
|
+
'--rmx-ui-item-indicator-width': styles.fontSize.md,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const itemCss: CSSMixinDescriptor = css({
|
|
59
|
+
display: 'flex',
|
|
60
|
+
alignItems: 'center',
|
|
61
|
+
width: '100%',
|
|
62
|
+
minWidth: 0,
|
|
63
|
+
minHeight: styles.control.height.md,
|
|
64
|
+
boxSizing: 'border-box',
|
|
65
|
+
position: 'relative',
|
|
66
|
+
isolation: 'isolate',
|
|
67
|
+
paddingInline: `calc(${styles.space.sm} + ${styles.space.xs})`,
|
|
68
|
+
color: styles.colors.text.primary,
|
|
69
|
+
fontFamily: styles.fontFamily.sans,
|
|
70
|
+
fontSize: styles.fontSize.sm,
|
|
71
|
+
fontWeight: styles.fontWeight.normal,
|
|
72
|
+
lineHeight: styles.lineHeight.normal,
|
|
73
|
+
textAlign: 'left',
|
|
74
|
+
userSelect: 'none',
|
|
75
|
+
WebkitUserSelect: 'none',
|
|
76
|
+
'&::before': {
|
|
77
|
+
content: '""',
|
|
78
|
+
position: 'absolute',
|
|
79
|
+
insetBlock: 0,
|
|
80
|
+
insetInline: styles.space.xs,
|
|
81
|
+
borderRadius: styles.radius.md,
|
|
82
|
+
backgroundColor: 'transparent',
|
|
83
|
+
pointerEvents: 'none',
|
|
84
|
+
zIndex: -1,
|
|
85
|
+
},
|
|
86
|
+
'&:focus': {
|
|
87
|
+
outline: 'none',
|
|
88
|
+
},
|
|
89
|
+
'&[data-highlighted="true"]': {
|
|
90
|
+
color: styles.colors.action.primary.foreground,
|
|
91
|
+
},
|
|
92
|
+
'&[data-highlighted="true"]::before': {
|
|
93
|
+
backgroundColor: styles.colors.action.primary.background,
|
|
94
|
+
},
|
|
95
|
+
'&[aria-disabled="true"]': {
|
|
96
|
+
opacity: 0.5,
|
|
97
|
+
},
|
|
98
|
+
scrollMarginBlock: styles.space.xs,
|
|
99
|
+
'--rmx-listbox-option-indicator-opacity': '0',
|
|
100
|
+
'&[hidden]': {
|
|
101
|
+
display: 'none',
|
|
102
|
+
},
|
|
103
|
+
'&[data-listbox-flash="true"], &[data-select-flash="true"], &[data-combobox-flash="true"]': {
|
|
104
|
+
color: styles.colors.text.primary,
|
|
105
|
+
},
|
|
106
|
+
'&[data-listbox-flash="true"]::before, &[data-select-flash="true"]::before, &[data-combobox-flash="true"]::before':
|
|
107
|
+
{
|
|
108
|
+
backgroundColor: 'transparent',
|
|
109
|
+
},
|
|
110
|
+
'&[aria-selected="true"]': {
|
|
111
|
+
'--rmx-listbox-option-indicator-opacity': '1',
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const itemIndicatorCss: CSSMixinDescriptor = css({
|
|
116
|
+
display: 'inline-flex',
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
justifyContent: 'center',
|
|
119
|
+
flex: '0 0 var(--rmx-ui-item-indicator-width)',
|
|
120
|
+
width: 'var(--rmx-ui-item-indicator-width)',
|
|
121
|
+
minWidth: 0,
|
|
122
|
+
height: 'var(--rmx-ui-item-indicator-width)',
|
|
123
|
+
marginInlineEnd: 'var(--rmx-ui-item-indicator-gap)',
|
|
124
|
+
overflow: 'hidden',
|
|
125
|
+
color: 'currentColor',
|
|
126
|
+
opacity: 'var(--rmx-listbox-option-indicator-opacity)',
|
|
127
|
+
'& > svg': {
|
|
128
|
+
display: 'block',
|
|
129
|
+
width: '100%',
|
|
130
|
+
height: '100%',
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const itemLabelCss: CSSMixinDescriptor = css({
|
|
135
|
+
display: 'inline-flex',
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
flex: '1 1 auto',
|
|
138
|
+
minWidth: 0,
|
|
139
|
+
WebkitUserSelect: 'none',
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
export const popoverSurfaceStyle = [popoverSurfaceCss, popoverSurfaceTransitionCss] as const
|
|
143
|
+
export const listboxIndicatorStyle = itemIndicatorCss
|
|
144
|
+
export const listboxLabelStyle = itemLabelCss
|
|
145
|
+
export const listboxListStyle = listCss
|
|
146
|
+
export const listboxOptionStyle = itemCss
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
type ComponentActionColors = {
|
|
2
|
+
readonly background: string
|
|
3
|
+
readonly backgroundHover: string
|
|
4
|
+
readonly backgroundActive: string
|
|
5
|
+
readonly foreground: string
|
|
6
|
+
readonly border: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type ComponentStyleValues = {
|
|
10
|
+
readonly space: {
|
|
11
|
+
readonly none: string
|
|
12
|
+
readonly xs: string
|
|
13
|
+
readonly sm: string
|
|
14
|
+
readonly md: string
|
|
15
|
+
readonly lg: string
|
|
16
|
+
}
|
|
17
|
+
readonly radius: {
|
|
18
|
+
readonly md: string
|
|
19
|
+
readonly lg: string
|
|
20
|
+
readonly xl: string
|
|
21
|
+
readonly full: string
|
|
22
|
+
}
|
|
23
|
+
readonly fontFamily: {
|
|
24
|
+
readonly sans: string
|
|
25
|
+
}
|
|
26
|
+
readonly fontSize: {
|
|
27
|
+
readonly xs: string
|
|
28
|
+
readonly sm: string
|
|
29
|
+
readonly md: string
|
|
30
|
+
}
|
|
31
|
+
readonly lineHeight: {
|
|
32
|
+
readonly normal: string
|
|
33
|
+
readonly relaxed: string
|
|
34
|
+
}
|
|
35
|
+
readonly fontWeight: {
|
|
36
|
+
readonly normal: string
|
|
37
|
+
readonly medium: string
|
|
38
|
+
}
|
|
39
|
+
readonly control: {
|
|
40
|
+
readonly height: {
|
|
41
|
+
readonly sm: string
|
|
42
|
+
readonly md: string
|
|
43
|
+
readonly lg: string
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
readonly surface: {
|
|
47
|
+
readonly lvl0: string
|
|
48
|
+
readonly lvl1: string
|
|
49
|
+
readonly lvl2: string
|
|
50
|
+
readonly lvl3: string
|
|
51
|
+
readonly lvl4: string
|
|
52
|
+
}
|
|
53
|
+
readonly shadow: {
|
|
54
|
+
readonly xs: string
|
|
55
|
+
readonly sm: string
|
|
56
|
+
readonly md: string
|
|
57
|
+
}
|
|
58
|
+
readonly colors: {
|
|
59
|
+
readonly text: {
|
|
60
|
+
readonly primary: string
|
|
61
|
+
readonly secondary: string
|
|
62
|
+
readonly muted: string
|
|
63
|
+
}
|
|
64
|
+
readonly border: {
|
|
65
|
+
readonly subtle: string
|
|
66
|
+
readonly default: string
|
|
67
|
+
}
|
|
68
|
+
readonly focus: {
|
|
69
|
+
readonly ring: string
|
|
70
|
+
}
|
|
71
|
+
readonly action: {
|
|
72
|
+
readonly primary: ComponentActionColors
|
|
73
|
+
readonly secondary: ComponentActionColors
|
|
74
|
+
readonly danger: ComponentActionColors
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const componentStyleValues: ComponentStyleValues = {
|
|
80
|
+
space: {
|
|
81
|
+
none: '0px',
|
|
82
|
+
xs: '4px',
|
|
83
|
+
sm: '8px',
|
|
84
|
+
md: '12px',
|
|
85
|
+
lg: '16px',
|
|
86
|
+
},
|
|
87
|
+
radius: {
|
|
88
|
+
md: '8px',
|
|
89
|
+
lg: '12px',
|
|
90
|
+
xl: '16px',
|
|
91
|
+
full: '9999px',
|
|
92
|
+
},
|
|
93
|
+
fontSize: {
|
|
94
|
+
xs: '12px',
|
|
95
|
+
sm: '13px',
|
|
96
|
+
md: '14px',
|
|
97
|
+
},
|
|
98
|
+
fontFamily: {
|
|
99
|
+
sans: '"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
100
|
+
},
|
|
101
|
+
lineHeight: {
|
|
102
|
+
normal: '1.45',
|
|
103
|
+
relaxed: '1.65',
|
|
104
|
+
},
|
|
105
|
+
fontWeight: {
|
|
106
|
+
normal: '400',
|
|
107
|
+
medium: '500',
|
|
108
|
+
},
|
|
109
|
+
control: {
|
|
110
|
+
height: {
|
|
111
|
+
sm: '28px',
|
|
112
|
+
md: '32px',
|
|
113
|
+
lg: '36px',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
surface: {
|
|
117
|
+
lvl0: '#ffffff',
|
|
118
|
+
lvl1: '#f8f8f8',
|
|
119
|
+
lvl2: '#f5f5f5',
|
|
120
|
+
lvl3: '#f3f3f3',
|
|
121
|
+
lvl4: '#efefef',
|
|
122
|
+
},
|
|
123
|
+
shadow: {
|
|
124
|
+
xs: '0 1px 1px rgb(0 0 0 / 0.05)',
|
|
125
|
+
sm: '0 1px 2px rgb(0 0 0 / 0.07)',
|
|
126
|
+
md: '0 6px 18px rgb(0 0 0 / 0.08)',
|
|
127
|
+
},
|
|
128
|
+
colors: {
|
|
129
|
+
text: {
|
|
130
|
+
primary: '#151515',
|
|
131
|
+
secondary: '#4f4f4f',
|
|
132
|
+
muted: '#6d6d6d',
|
|
133
|
+
},
|
|
134
|
+
border: {
|
|
135
|
+
subtle: '#e7e7e7',
|
|
136
|
+
default: '#d1d1d1',
|
|
137
|
+
},
|
|
138
|
+
focus: {
|
|
139
|
+
ring: '#1A72FF',
|
|
140
|
+
},
|
|
141
|
+
action: {
|
|
142
|
+
primary: {
|
|
143
|
+
background: '#1A72FF',
|
|
144
|
+
backgroundHover: '#1463e0',
|
|
145
|
+
backgroundActive: '#0f55c9',
|
|
146
|
+
foreground: 'rgb(255 255 255 / 0.92)',
|
|
147
|
+
border: '#1A72FF',
|
|
148
|
+
},
|
|
149
|
+
secondary: {
|
|
150
|
+
background: '#ffffff',
|
|
151
|
+
backgroundHover: '#fbfbfb',
|
|
152
|
+
backgroundActive: '#f3f3f3',
|
|
153
|
+
foreground: '#202020',
|
|
154
|
+
border: '#d1d1d1',
|
|
155
|
+
},
|
|
156
|
+
danger: {
|
|
157
|
+
background: '#FF3000',
|
|
158
|
+
backgroundHover: '#e12b00',
|
|
159
|
+
backgroundActive: '#c52600',
|
|
160
|
+
foreground: 'rgb(255 255 255 / 0.92)',
|
|
161
|
+
border: '#FF3000',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# tabs
|
|
2
|
+
|
|
3
|
+
`Tabs` renders a tab control with one active tab and matching panels. Use it when related views share the same page space and the owning component may either control the active tab or let the tabs context manage it.
|
|
4
|
+
|
|
5
|
+
## Component Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
|
|
9
|
+
|
|
10
|
+
export function ProjectTabs() {
|
|
11
|
+
return (
|
|
12
|
+
<Tabs defaultActiveTab="overview">
|
|
13
|
+
<TabList aria-label="Project sections">
|
|
14
|
+
<Tab name="overview">Overview</Tab>
|
|
15
|
+
<Tab name="activity">Activity</Tab>
|
|
16
|
+
<Tab name="settings">Settings</Tab>
|
|
17
|
+
</TabList>
|
|
18
|
+
|
|
19
|
+
<TabPanel name="overview">Project summary.</TabPanel>
|
|
20
|
+
<TabPanel name="activity">Recent changes.</TabPanel>
|
|
21
|
+
<TabPanel name="settings">Project settings.</TabPanel>
|
|
22
|
+
</Tabs>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Control the active tab when state should live in the owning component:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import type { Handle } from 'remix/ui'
|
|
31
|
+
import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
|
|
32
|
+
|
|
33
|
+
export function ControlledTabs(handle: Handle) {
|
|
34
|
+
let activeTab = 'overview'
|
|
35
|
+
|
|
36
|
+
return () => (
|
|
37
|
+
<Tabs
|
|
38
|
+
activeTab={activeTab}
|
|
39
|
+
onActiveTabChange={(nextActiveTab) => {
|
|
40
|
+
activeTab = nextActiveTab
|
|
41
|
+
void handle.update()
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<TabList aria-label="Project sections">
|
|
45
|
+
<Tab name="overview">Overview</Tab>
|
|
46
|
+
<Tab name="activity">Activity</Tab>
|
|
47
|
+
</TabList>
|
|
48
|
+
|
|
49
|
+
<TabPanel name="overview">Project summary.</TabPanel>
|
|
50
|
+
<TabPanel name="activity">Recent changes.</TabPanel>
|
|
51
|
+
</Tabs>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Listen for bubbling `TabsChangeEvent` events with `onTabsChange` from `remix/ui/tabs/primitives`.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { onTabsChange } from 'remix/ui/tabs/primitives'
|
|
60
|
+
import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
|
|
61
|
+
|
|
62
|
+
export function TrackedTabs() {
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
mix={[
|
|
66
|
+
onTabsChange((event) => {
|
|
67
|
+
console.log(event.previousActiveTab, event.activeTab)
|
|
68
|
+
}),
|
|
69
|
+
]}
|
|
70
|
+
>
|
|
71
|
+
<Tabs defaultActiveTab="overview">
|
|
72
|
+
<TabList aria-label="Project sections">
|
|
73
|
+
<Tab name="overview">Overview</Tab>
|
|
74
|
+
<Tab name="activity">Activity</Tab>
|
|
75
|
+
</TabList>
|
|
76
|
+
<TabPanel name="overview">Project summary.</TabPanel>
|
|
77
|
+
<TabPanel name="activity">Recent changes.</TabPanel>
|
|
78
|
+
</Tabs>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Primitive Usage
|
|
85
|
+
|
|
86
|
+
Use the lower-level primitives when app code owns the markup and styles:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import * as tabs from 'remix/ui/tabs/primitives'
|
|
90
|
+
import { listStyle, panelStyle, rootStyle, tabStyle } from './tabs.styles'
|
|
91
|
+
|
|
92
|
+
export function PrimitiveTabs() {
|
|
93
|
+
return (
|
|
94
|
+
<tabs.Context defaultActiveTab="overview">
|
|
95
|
+
<div mix={[rootStyle, tabs.root()]}>
|
|
96
|
+
<div mix={[listStyle, tabs.list()]} aria-label="Project sections">
|
|
97
|
+
<button mix={[tabStyle, tabs.tab({ name: 'overview' })]} type="button">
|
|
98
|
+
Overview
|
|
99
|
+
</button>
|
|
100
|
+
<button mix={[tabStyle, tabs.tab({ name: 'activity' })]} type="button">
|
|
101
|
+
Activity
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div mix={[panelStyle, tabs.panel({ name: 'overview' })]}>Project summary.</div>
|
|
106
|
+
<div mix={[panelStyle, tabs.panel({ name: 'activity' })]}>Recent changes.</div>
|
|
107
|
+
</div>
|
|
108
|
+
</tabs.Context>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## `remix/ui/tabs`
|
|
114
|
+
|
|
115
|
+
- `Tabs`: root component. Supports controlled `activeTab`, uncontrolled `defaultActiveTab`, `onActiveTabChange`, `disabled`, and `size: 'md' | 'lg'`.
|
|
116
|
+
- `TabList`: tablist wrapper that lays out the tabs in a track and applies the tablist primitive.
|
|
117
|
+
- `Tab`: button for one tab `name`. The active tab uses the toggle slider treatment and button-sized text. Pass `disabled` to prevent activation.
|
|
118
|
+
- `TabPanel`: panel for one tab `name`.
|
|
119
|
+
- `rootStyle`, `listStyle`, `tabStyle`, and `panelStyle`: flat style mixins used by the component markup.
|
|
120
|
+
- `onTabsChange` and `TabsChangeEvent`: re-exported primitive event helpers.
|
|
121
|
+
- `TabsProps`, `TabListProps`, `TabProps`, `TabPanelProps`, and `TabsSize`: public TypeScript types for the composed APIs.
|
|
122
|
+
|
|
123
|
+
## `remix/ui/tabs/primitives`
|
|
124
|
+
|
|
125
|
+
- `Context`: lower-level provider for custom tabs composition.
|
|
126
|
+
- `root()`: wires the root element and bubbling change events.
|
|
127
|
+
- `list()`: wires the tablist role and disabled state.
|
|
128
|
+
- `tab({ name, disabled })`: wires a tab button with selected state, ids, keyboard activation, and pointer activation.
|
|
129
|
+
- `panel({ name })`: wires the matching tab panel id, hidden state, inert state, and label relationship.
|
|
130
|
+
- `onTabsChange(...)`: event mixin for the bubbling `TabsChangeEvent`.
|
|
131
|
+
- `TabsChangeEvent`: bubbling event with `activeTab` and `previousActiveTab`.
|
|
132
|
+
- `TabsContextProps`, `TabsContextValue`, `TabsRegisteredTab`, `TabsActivationDirection`, `TabsRootOptions`, `TabListOptions`, `TabOptions`, and `TabPanelOptions`: primitive prop and option types for custom composition.
|
|
133
|
+
|
|
134
|
+
## Behavior Notes
|
|
135
|
+
|
|
136
|
+
- If neither `activeTab` nor `defaultActiveTab` is provided, the first enabled tab becomes active.
|
|
137
|
+
- Arrow keys activate the next or previous enabled tab. `Home` and `End` activate the first and last enabled tabs.
|
|
138
|
+
- `Enter`, `Space`, and pointer clicks activate the focused tab.
|
|
139
|
+
- Root `disabled` disables every tab. Tab `disabled` only disables that tab.
|
|
140
|
+
- Tabs and panels are linked with generated ids through `aria-controls`, `aria-labelledby`, and `aria-selected`.
|
|
141
|
+
- Inactive panels receive `hidden`, `inert`, and `data-state="inactive"`.
|