@remix-run/ui 0.2.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.d.ts → anchor/index.d.ts} +8 -1
- package/dist/{components/anchor/anchor.js → anchor/index.js} +31 -12
- package/dist/anchor/index.js.map +1 -0
- package/dist/animation/demos/aspect-ratio.d.ts +2 -0
- package/dist/animation/demos/aspect-ratio.js +69 -0
- package/dist/animation/demos/aspect-ratio.js.map +1 -0
- package/dist/animation/demos/bouncy-switch.d.ts +2 -0
- package/dist/animation/demos/bouncy-switch.js +36 -0
- package/dist/animation/demos/bouncy-switch.js.map +1 -0
- package/dist/animation/demos/color-interpolation.d.ts +1 -0
- package/dist/animation/demos/color-interpolation.js +53 -0
- package/dist/animation/demos/color-interpolation.js.map +1 -0
- package/dist/animation/demos/cube.d.ts +2 -0
- package/dist/animation/demos/cube.js +86 -0
- package/dist/animation/demos/cube.js.map +1 -0
- package/dist/animation/demos/default-animate.d.ts +2 -0
- package/dist/animation/demos/default-animate.js +89 -0
- package/dist/animation/demos/default-animate.js.map +1 -0
- package/dist/animation/demos/drag-release.d.ts +24 -0
- package/dist/animation/demos/drag-release.js +96 -0
- package/dist/animation/demos/drag-release.js.map +1 -0
- package/dist/animation/demos/enter.d.ts +1 -0
- package/dist/animation/demos/enter.js +19 -0
- package/dist/animation/demos/enter.js.map +1 -0
- package/dist/animation/demos/exit.d.ts +2 -0
- package/dist/animation/demos/exit.js +58 -0
- package/dist/animation/demos/exit.js.map +1 -0
- package/dist/animation/demos/flip-toggle.d.ts +2 -0
- package/dist/animation/demos/flip-toggle.js +37 -0
- package/dist/animation/demos/flip-toggle.js.map +1 -0
- package/dist/animation/demos/hold-to-confirm.d.ts +13 -0
- package/dist/animation/demos/hold-to-confirm.js +211 -0
- package/dist/animation/demos/hold-to-confirm.js.map +1 -0
- package/dist/animation/demos/html-content.d.ts +2 -0
- package/dist/animation/demos/html-content.js +30 -0
- package/dist/animation/demos/html-content.js.map +1 -0
- package/dist/animation/demos/interruptible-keyframes.d.ts +1 -0
- package/dist/animation/demos/interruptible-keyframes.js +48 -0
- package/dist/animation/demos/interruptible-keyframes.js.map +1 -0
- package/dist/animation/demos/keyframes.d.ts +1 -0
- package/dist/animation/demos/keyframes.js +24 -0
- package/dist/animation/demos/keyframes.js.map +1 -0
- package/dist/animation/demos/material-ripple.d.ts +2 -0
- package/dist/animation/demos/material-ripple.js +127 -0
- package/dist/animation/demos/material-ripple.js.map +1 -0
- package/dist/animation/demos/mixin-presence-list.d.ts +2 -0
- package/dist/animation/demos/mixin-presence-list.js +88 -0
- package/dist/animation/demos/mixin-presence-list.js.map +1 -0
- package/dist/animation/demos/mixin-reclaim.d.ts +2 -0
- package/dist/animation/demos/mixin-reclaim.js +90 -0
- package/dist/animation/demos/mixin-reclaim.js.map +1 -0
- package/dist/animation/demos/multi-state-badge.d.ts +2 -0
- package/dist/animation/demos/multi-state-badge.js +231 -0
- package/dist/animation/demos/multi-state-badge.js.map +1 -0
- package/dist/animation/demos/press.d.ts +2 -0
- package/dist/animation/demos/press.js +63 -0
- package/dist/animation/demos/press.js.map +1 -0
- package/dist/animation/demos/reordering.d.ts +2 -0
- package/dist/animation/demos/reordering.js +53 -0
- package/dist/animation/demos/reordering.js.map +1 -0
- package/dist/animation/demos/rolling-square.d.ts +2 -0
- package/dist/animation/demos/rolling-square.js +42 -0
- package/dist/animation/demos/rolling-square.js.map +1 -0
- package/dist/animation/demos/rotate.d.ts +1 -0
- package/dist/animation/demos/rotate.js +18 -0
- package/dist/animation/demos/rotate.js.map +1 -0
- package/dist/animation/demos/shared-layout.d.ts +2 -0
- package/dist/animation/demos/shared-layout.js +114 -0
- package/dist/animation/demos/shared-layout.js.map +1 -0
- package/dist/animation/demos/transition-options.d.ts +1 -0
- package/dist/animation/demos/transition-options.js +21 -0
- package/dist/animation/demos/transition-options.js.map +1 -0
- package/dist/animation/spring.d.ts +22 -0
- package/dist/animation/spring.js +22 -0
- package/dist/animation/spring.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} +6 -32
- package/dist/{components/menu/menu.js → menu/primitives.js} +117 -279
- package/dist/menu/primitives.js.map +1 -0
- package/dist/{components/popover/popover.d.ts → popover/index.d.ts} +3 -5
- package/dist/{components/popover/popover.js → popover/index.js} +15 -51
- 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/client-entries.d.ts +1 -2
- package/dist/runtime/client-entries.js.map +1 -1
- package/dist/runtime/component.d.ts +2 -2
- package/dist/runtime/component.js +1 -1
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/diff-dom.js +194 -21
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/frame.d.ts +7 -0
- package/dist/runtime/frame.js +176 -41
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/jsx.d.ts +2 -3
- package/dist/runtime/jsx.js.map +1 -1
- 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} +12 -64
- package/dist/select/primitives.js.map +1 -0
- package/dist/server/stream.js +5 -4
- 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/style/style.js +27 -10
- package/dist/style/style.js.map +1 -1
- 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 +74 -45
- 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 +48 -5
- package/src/{components/anchor/anchor.ts → anchor/index.ts} +53 -12
- package/src/animation/demos/aspect-ratio.tsx +120 -0
- package/src/animation/demos/bouncy-switch.tsx +47 -0
- package/src/animation/demos/color-interpolation.tsx +73 -0
- package/src/animation/demos/cube.tsx +116 -0
- package/src/animation/demos/default-animate.tsx +129 -0
- package/src/animation/demos/drag-release.ts +118 -0
- package/src/animation/demos/enter.tsx +22 -0
- package/src/animation/demos/exit.tsx +79 -0
- package/src/animation/demos/flip-toggle.tsx +46 -0
- package/src/animation/demos/hold-to-confirm.tsx +317 -0
- package/src/animation/demos/html-content.tsx +41 -0
- package/src/animation/demos/interruptible-keyframes.tsx +62 -0
- package/src/animation/demos/keyframes.tsx +29 -0
- package/src/animation/demos/material-ripple.tsx +158 -0
- package/src/animation/demos/mixin-presence-list.tsx +121 -0
- package/src/animation/demos/mixin-reclaim.tsx +125 -0
- package/src/animation/demos/multi-state-badge.tsx +426 -0
- package/src/animation/demos/press.tsx +67 -0
- package/src/animation/demos/reordering.tsx +68 -0
- package/src/animation/demos/rolling-square.tsx +57 -0
- package/src/animation/demos/rotate.tsx +21 -0
- package/src/animation/demos/shared-layout.tsx +211 -0
- package/src/animation/demos/transition-options.tsx +24 -0
- package/src/animation/spring.ts +22 -0
- 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/menu/README.md +161 -0
- package/src/menu/index.tsx +354 -0
- package/src/{components/menu/menu.tsx → menu/primitives.tsx} +117 -359
- package/src/{components/popover → popover}/README.md +20 -39
- package/src/{components/popover/popover.ts → popover/index.ts} +16 -61
- 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/client-entries.ts +1 -5
- package/src/runtime/component.ts +3 -3
- package/src/runtime/demos/readme.demo.tsx +704 -0
- package/src/runtime/diff-dom.ts +270 -20
- package/src/runtime/frame.ts +218 -41
- package/src/runtime/jsx.ts +6 -10
- 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} +11 -99
- package/src/server/stream.ts +10 -5
- 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/style/style.ts +29 -10
- 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/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 -6
- package/dist/components/accordion/demos/card.demo.js +0 -58
- package/dist/components/accordion/demos/card.demo.js.map +0 -1
- package/dist/components/accordion/demos/multiple.demo.d.ts +0 -6
- package/dist/components/accordion/demos/multiple.demo.js +0 -19
- package/dist/components/accordion/demos/multiple.demo.js.map +0 -1
- package/dist/components/accordion/demos/overview.demo.d.ts +0 -6
- package/dist/components/accordion/demos/overview.demo.js +0 -19
- package/dist/components/accordion/demos/overview.demo.js.map +0 -1
- package/dist/components/anchor/anchor.js.map +0 -1
- package/dist/components/breadcrumbs/breadcrumbs.js.map +0 -1
- package/dist/components/breadcrumbs/demos/basic.demo.d.ts +0 -5
- package/dist/components/breadcrumbs/demos/basic.demo.js +0 -14
- package/dist/components/breadcrumbs/demos/basic.demo.js.map +0 -1
- package/dist/components/breadcrumbs/demos/separator.demo.d.ts +0 -5
- package/dist/components/breadcrumbs/demos/separator.demo.js +0 -14
- package/dist/components/breadcrumbs/demos/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 -6
- package/dist/components/button/demos/aliases.demo.js +0 -18
- package/dist/components/button/demos/aliases.demo.js.map +0 -1
- package/dist/components/button/demos/basic.demo.d.ts +0 -6
- package/dist/components/button/demos/basic.demo.js +0 -19
- package/dist/components/button/demos/basic.demo.js.map +0 -1
- package/dist/components/button/demos/states.demo.d.ts +0 -6
- package/dist/components/button/demos/states.demo.js +0 -29
- package/dist/components/button/demos/states.demo.js.map +0 -1
- package/dist/components/combobox/combobox.js.map +0 -1
- package/dist/components/combobox/demos/overview.demo.d.ts +0 -6
- package/dist/components/combobox/demos/overview.demo.js +0 -153
- package/dist/components/combobox/demos/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/demos/overview.demo.d.ts +0 -6
- package/dist/components/listbox/demos/overview.demo.js +0 -64
- package/dist/components/listbox/demos/overview.demo.js.map +0 -1
- package/dist/components/listbox/listbox.js.map +0 -1
- package/dist/components/menu/demos/bubbling.demo.d.ts +0 -5
- package/dist/components/menu/demos/bubbling.demo.js +0 -15
- package/dist/components/menu/demos/bubbling.demo.js.map +0 -1
- package/dist/components/menu/demos/overview.demo.d.ts +0 -6
- package/dist/components/menu/demos/overview.demo.js +0 -32
- 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/demos/overview.demo.d.ts +0 -6
- package/dist/components/popover/demos/overview.demo.js +0 -64
- package/dist/components/popover/demos/overview.demo.js.map +0 -1
- package/dist/components/popover/popover.js.map +0 -1
- package/dist/components/select/demos/deconstructed.demo.d.ts +0 -6
- package/dist/components/select/demos/deconstructed.demo.js +0 -53
- package/dist/components/select/demos/deconstructed.demo.js.map +0 -1
- package/dist/components/select/demos/overview.demo.d.ts +0 -5
- package/dist/components/select/demos/overview.demo.js +0 -27
- package/dist/components/select/demos/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/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/components/accordion/accordion.tsx +0 -565
- package/src/components/accordion/demos/card.demo.tsx +0 -113
- package/src/components/accordion/demos/multiple.demo.tsx +0 -56
- package/src/components/accordion/demos/overview.demo.tsx +0 -56
- package/src/components/breadcrumbs/demos/basic.demo.tsx +0 -17
- package/src/components/breadcrumbs/demos/separator.demo.tsx +0 -18
- 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 -27
- package/src/components/button/demos/basic.demo.tsx +0 -30
- package/src/components/button/demos/states.demo.tsx +0 -52
- package/src/components/combobox/demos/overview.demo.tsx +0 -199
- package/src/components/glyph/README.md +0 -72
- package/src/components/glyph/glyph.tsx +0 -110
- package/src/components/listbox/demos/overview.demo.tsx +0 -101
- package/src/components/menu/README.md +0 -96
- package/src/components/menu/demos/bubbling.demo.tsx +0 -34
- package/src/components/menu/demos/overview.demo.tsx +0 -76
- package/src/components/popover/demos/overview.demo.tsx +0 -143
- package/src/components/select/demos/deconstructed.demo.tsx +0 -95
- package/src/components/select/demos/overview.demo.tsx +0 -51
- 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/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/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/{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
|
-
css,
|
|
4
|
-
createElement,
|
|
5
3
|
createMixin,
|
|
6
4
|
on,
|
|
7
5
|
ref,
|
|
8
|
-
type CSSMixinDescriptor,
|
|
9
6
|
type Dispatched,
|
|
10
7
|
type ElementProps,
|
|
11
8
|
type Handle,
|
|
@@ -15,13 +12,11 @@ import {
|
|
|
15
12
|
type RemixNode,
|
|
16
13
|
} from '@remix-run/ui'
|
|
17
14
|
|
|
18
|
-
import
|
|
19
|
-
import * as
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { waitForCssTransition } from '../../utils/wait-for-css-transition.ts'
|
|
24
|
-
import { wait } from '../../utils/wait.ts'
|
|
15
|
+
import * as listbox from '../listbox/index.ts'
|
|
16
|
+
import * as popover from '../popover/index.ts'
|
|
17
|
+
import { type SearchValue } from '../shared/typeahead.ts'
|
|
18
|
+
import { waitForCssTransition } from '../shared/wait-for-css-transition.ts'
|
|
19
|
+
import { wait } from '../shared/wait.ts'
|
|
25
20
|
|
|
26
21
|
const COMBOBOX_CHANGE_EVENT = 'rmx:combobox-change' as const
|
|
27
22
|
const INPUT_COMMIT_DELAY_MS = 50
|
|
@@ -33,48 +28,6 @@ type ComboboxChangeHandler<target extends HTMLElement> = (
|
|
|
33
28
|
|
|
34
29
|
type ShowReason = 'hint' | 'nav'
|
|
35
30
|
|
|
36
|
-
const comboboxPopoverCss: CSSMixinDescriptor = css({
|
|
37
|
-
opacity: 0,
|
|
38
|
-
'&:popover-open': {
|
|
39
|
-
opacity: 1,
|
|
40
|
-
},
|
|
41
|
-
'&:not(:popover-open)': {
|
|
42
|
-
pointerEvents: 'none',
|
|
43
|
-
},
|
|
44
|
-
'&[data-show-reason="nav"]:not(:popover-open)': {
|
|
45
|
-
transition: 'opacity 180ms ease-in, overlay 180ms ease-in, display 180ms ease-in',
|
|
46
|
-
transitionBehavior: 'allow-discrete',
|
|
47
|
-
},
|
|
48
|
-
'&[data-show-reason="hint"]:not(:popover-open)': {
|
|
49
|
-
transition: 'none',
|
|
50
|
-
transitionBehavior: 'normal',
|
|
51
|
-
},
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
const comboboxInputCss: CSSMixinDescriptor = css({
|
|
55
|
-
minHeight: theme.control.height.sm,
|
|
56
|
-
width: '100%',
|
|
57
|
-
paddingInline: theme.space.sm,
|
|
58
|
-
border: `0.5px solid ${theme.colors.border.default}`,
|
|
59
|
-
borderRadius: theme.radius.md,
|
|
60
|
-
backgroundColor: theme.surface.lvl0,
|
|
61
|
-
color: theme.colors.text.primary,
|
|
62
|
-
fontFamily: theme.fontFamily.sans,
|
|
63
|
-
fontSize: theme.fontSize.sm,
|
|
64
|
-
lineHeight: theme.lineHeight.normal,
|
|
65
|
-
boxShadow: 'inset 0 1px 0 rgb(255 255 255 / 0.7)',
|
|
66
|
-
'&:focus-visible': {
|
|
67
|
-
outline: `2px solid ${theme.colors.focus.ring}`,
|
|
68
|
-
outlineOffset: theme.space.none,
|
|
69
|
-
},
|
|
70
|
-
'&[data-surface-visible="true"][aria-activedescendant]:focus-visible': {
|
|
71
|
-
outline: 'none',
|
|
72
|
-
},
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
export const inputStyle = comboboxInputCss
|
|
76
|
-
export const popoverStyle = comboboxPopoverCss
|
|
77
|
-
|
|
78
31
|
declare global {
|
|
79
32
|
interface HTMLElementEventMap {
|
|
80
33
|
[COMBOBOX_CHANGE_EVENT]: ComboboxChangeEvent
|
|
@@ -989,42 +942,3 @@ export function onComboboxChange<target extends HTMLElement>(
|
|
|
989
942
|
): ReturnType<typeof on<target, typeof COMBOBOX_CHANGE_EVENT>> {
|
|
990
943
|
return on(COMBOBOX_CHANGE_EVENT, handler, captureBoolean)
|
|
991
944
|
}
|
|
992
|
-
|
|
993
|
-
export function Combobox(handle: Handle<ComboboxProps>): () => RemixNode {
|
|
994
|
-
return () => {
|
|
995
|
-
let { children, defaultValue, disabled, inputId, name, placeholder, ...divProps } = handle.props
|
|
996
|
-
|
|
997
|
-
return (
|
|
998
|
-
<combobox.Context defaultValue={defaultValue} disabled={disabled} name={name}>
|
|
999
|
-
<div {...divProps}>
|
|
1000
|
-
<input
|
|
1001
|
-
defaultValue={defaultValue ?? undefined}
|
|
1002
|
-
id={inputId}
|
|
1003
|
-
mix={[inputStyle, combobox.input()]}
|
|
1004
|
-
placeholder={placeholder}
|
|
1005
|
-
/>
|
|
1006
|
-
<div mix={[popover.surfaceStyle, popoverStyle, combobox.popover()]}>
|
|
1007
|
-
<div mix={[popover.contentStyle, listbox.listStyle, combobox.list()]}>{children}</div>
|
|
1008
|
-
</div>
|
|
1009
|
-
{name && <input mix={combobox.hiddenInput()} />}
|
|
1010
|
-
</div>
|
|
1011
|
-
</combobox.Context>
|
|
1012
|
-
)
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
export function ComboboxOption(handle: Handle<ComboboxOptionProps>): () => RemixNode {
|
|
1017
|
-
return () => {
|
|
1018
|
-
let { children, disabled, label, mix, searchValue, value, ...divProps } = handle.props
|
|
1019
|
-
|
|
1020
|
-
return (
|
|
1021
|
-
<div
|
|
1022
|
-
{...divProps}
|
|
1023
|
-
mix={[listbox.optionStyle, combobox.option({ disabled, label, searchValue, value }), mix]}
|
|
1024
|
-
>
|
|
1025
|
-
<Glyph mix={listbox.glyphStyle} name="check" />
|
|
1026
|
-
<span mix={listbox.labelStyle}>{children ?? label}</span>
|
|
1027
|
-
</div>
|
|
1028
|
-
)
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# input
|
|
2
|
+
|
|
3
|
+
`input` is a style mixin for text inputs. Use `input()` directly on standalone native inputs, or compose `input.root()` with `input.field()` when the input needs inline icons or controls.
|
|
4
|
+
|
|
5
|
+
## Primitive Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import input from 'remix/ui/input'
|
|
9
|
+
|
|
10
|
+
function ProductFilters() {
|
|
11
|
+
return () => (
|
|
12
|
+
<div>
|
|
13
|
+
<input mix={input()} placeholder="Limit" />
|
|
14
|
+
|
|
15
|
+
<div mix={input.root()}>
|
|
16
|
+
<SearchIcon />
|
|
17
|
+
<input mix={input.field()} placeholder="Search and filter products" />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Compose app-owned styles when a field needs local layout or adornments:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import input from 'remix/ui/input'
|
|
28
|
+
import { filterFieldStyle, filterRootStyle } from './filters.styles'
|
|
29
|
+
|
|
30
|
+
function SearchFilter() {
|
|
31
|
+
return () => (
|
|
32
|
+
<div mix={[filterRootStyle, input.root()]}>
|
|
33
|
+
<SearchIcon />
|
|
34
|
+
<input mix={[filterFieldStyle, input.field()]} placeholder="Search" />
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## `remix/ui/input`
|
|
41
|
+
|
|
42
|
+
- `input(options)`: styles a standalone native input. `size` may be `'md'` or `'lg'` and defaults to `'md'`.
|
|
43
|
+
- `input.root(options)`: styles a flex input frame for inline icons, buttons, and a child input.
|
|
44
|
+
- `input.field()`: styles the child native input inside `input.root()`.
|
|
45
|
+
- `InputOptions`: accepts `size`.
|
|
46
|
+
- `InputSize`: `'md'` or `'lg'`.
|
|
47
|
+
|
|
48
|
+
## Behavior Notes
|
|
49
|
+
|
|
50
|
+
- `input.root()` uses child selectors to size direct SVG children as presentational icons.
|
|
51
|
+
- Put the icon before or after the field to control its visual position.
|
|
52
|
+
- Focus and disabled states work through the native input; the root mirrors them with `:focus-within` and `:has(input:disabled)`.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { css } from '@remix-run/ui'
|
|
2
|
+
import type { CSSMixinDescriptor } from '@remix-run/ui'
|
|
3
|
+
import { controlFocusShadow } from '../shared/focus-styles.ts'
|
|
4
|
+
import type { CSSProps } from '../style/style.ts'
|
|
5
|
+
|
|
6
|
+
export type InputSize = 'md' | 'lg'
|
|
7
|
+
|
|
8
|
+
export interface InputOptions {
|
|
9
|
+
size?: InputSize
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type InputMixin = readonly [CSSMixinDescriptor, CSSMixinDescriptor]
|
|
13
|
+
|
|
14
|
+
interface InputFunction {
|
|
15
|
+
(options?: InputOptions): InputMixin
|
|
16
|
+
root(options?: InputOptions): InputMixin
|
|
17
|
+
field(): CSSMixinDescriptor
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const frameStyles = {
|
|
21
|
+
'--rmx-input-height': '32px',
|
|
22
|
+
'--rmx-input-padding-block': '6px',
|
|
23
|
+
'--rmx-input-padding-inline': '12px',
|
|
24
|
+
'--rmx-input-root-padding-inline': '8px',
|
|
25
|
+
'--rmx-input-gap': '6px',
|
|
26
|
+
'--rmx-input-icon-size': '16px',
|
|
27
|
+
'--rmx-input-icon-color': '#707070',
|
|
28
|
+
appearance: 'none',
|
|
29
|
+
margin: 0,
|
|
30
|
+
boxSizing: 'border-box',
|
|
31
|
+
display: 'block',
|
|
32
|
+
width: '100%',
|
|
33
|
+
minWidth: 0,
|
|
34
|
+
height: 'var(--rmx-input-height)',
|
|
35
|
+
paddingBlock: 'var(--rmx-input-padding-block)',
|
|
36
|
+
paddingInline: 'var(--rmx-input-padding-inline)',
|
|
37
|
+
border: 0,
|
|
38
|
+
borderRadius: '8px',
|
|
39
|
+
background: '#FFFFFF',
|
|
40
|
+
boxShadow:
|
|
41
|
+
'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 rgba(0, 0, 0, 0.12)',
|
|
42
|
+
color: '#101010',
|
|
43
|
+
fontFamily: '"Inter Variable", Inter, ui-sans-serif, system-ui, sans-serif',
|
|
44
|
+
fontStyle: 'normal',
|
|
45
|
+
fontWeight: 400,
|
|
46
|
+
fontSize: '13px',
|
|
47
|
+
lineHeight: '20px',
|
|
48
|
+
fontFeatureSettings: '"ss01" on, "cv01" on',
|
|
49
|
+
letterSpacing: 0,
|
|
50
|
+
textShadow: '0 1px 0 #FFFFFF',
|
|
51
|
+
'&::placeholder': {
|
|
52
|
+
color: '#B0B0B0',
|
|
53
|
+
opacity: 1,
|
|
54
|
+
},
|
|
55
|
+
'&:disabled, &[aria-disabled="true"]': {
|
|
56
|
+
opacity: 0.55,
|
|
57
|
+
},
|
|
58
|
+
} as const satisfies CSSProps
|
|
59
|
+
|
|
60
|
+
const baseStyle: CSSMixinDescriptor = css({
|
|
61
|
+
...frameStyles,
|
|
62
|
+
'&:focus-visible': {
|
|
63
|
+
outline: 0,
|
|
64
|
+
boxShadow: controlFocusShadow,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const rootBaseStyle: CSSMixinDescriptor = css({
|
|
69
|
+
...frameStyles,
|
|
70
|
+
display: 'flex',
|
|
71
|
+
alignItems: 'center',
|
|
72
|
+
gap: 'var(--rmx-input-gap)',
|
|
73
|
+
paddingInline: 'var(--rmx-input-root-padding-inline)',
|
|
74
|
+
'&:focus-within': {
|
|
75
|
+
boxShadow: controlFocusShadow,
|
|
76
|
+
},
|
|
77
|
+
'&:has(input:disabled), &:has(input[aria-disabled="true"])': {
|
|
78
|
+
opacity: 0.55,
|
|
79
|
+
},
|
|
80
|
+
'& > svg': {
|
|
81
|
+
flex: 'none',
|
|
82
|
+
width: 'var(--rmx-input-icon-size)',
|
|
83
|
+
height: 'var(--rmx-input-icon-size)',
|
|
84
|
+
color: 'var(--rmx-input-icon-color)',
|
|
85
|
+
pointerEvents: 'none',
|
|
86
|
+
},
|
|
87
|
+
'& > button': {
|
|
88
|
+
flex: 'none',
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const fieldBaseStyle: CSSMixinDescriptor = css({
|
|
93
|
+
appearance: 'none',
|
|
94
|
+
margin: 0,
|
|
95
|
+
boxSizing: 'border-box',
|
|
96
|
+
flex: '1 1 auto',
|
|
97
|
+
minWidth: 0,
|
|
98
|
+
width: '100%',
|
|
99
|
+
height: '20px',
|
|
100
|
+
padding: 0,
|
|
101
|
+
border: 0,
|
|
102
|
+
outline: 0,
|
|
103
|
+
background: 'transparent',
|
|
104
|
+
boxShadow: 'none',
|
|
105
|
+
color: '#101010',
|
|
106
|
+
font: 'inherit',
|
|
107
|
+
fontFeatureSettings: 'inherit',
|
|
108
|
+
letterSpacing: 'inherit',
|
|
109
|
+
textShadow: 'inherit',
|
|
110
|
+
'&::placeholder': {
|
|
111
|
+
color: '#B0B0B0',
|
|
112
|
+
opacity: 1,
|
|
113
|
+
},
|
|
114
|
+
'&:disabled, &[aria-disabled="true"]': {
|
|
115
|
+
opacity: 1,
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const sizeStyles = {
|
|
120
|
+
md: css({}),
|
|
121
|
+
lg: css({
|
|
122
|
+
'--rmx-input-height': '36px',
|
|
123
|
+
'--rmx-input-padding-block': '8px',
|
|
124
|
+
'--rmx-input-padding-inline': '14px',
|
|
125
|
+
'--rmx-input-root-padding-inline': '10px',
|
|
126
|
+
'--rmx-input-gap': '8px',
|
|
127
|
+
'--rmx-input-icon-size': '18px',
|
|
128
|
+
}),
|
|
129
|
+
} as const satisfies Record<InputSize, CSSMixinDescriptor>
|
|
130
|
+
|
|
131
|
+
function createInput(options: InputOptions = {}): InputMixin {
|
|
132
|
+
let { size = 'md' } = options
|
|
133
|
+
return [baseStyle, sizeStyles[size]]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function root(options: InputOptions = {}): InputMixin {
|
|
137
|
+
let { size = 'md' } = options
|
|
138
|
+
return [rootBaseStyle, sizeStyles[size]]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function field(): CSSMixinDescriptor {
|
|
142
|
+
return fieldBaseStyle
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const input: InputFunction = Object.assign(createInput, { root, field })
|
|
146
|
+
|
|
147
|
+
export default input
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# listbox
|
|
2
2
|
|
|
3
|
-
`listbox` is a headless option-list primitive for controlled selection and highlighting. Use it under components like
|
|
3
|
+
`listbox` is a headless option-list primitive for controlled selection and highlighting. Use it under components like select and combobox, or directly when you need custom listbox markup.
|
|
4
4
|
|
|
5
|
-
## Usage
|
|
5
|
+
## Primitive Usage
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
8
|
import type { Handle } from 'remix/ui'
|
|
9
|
-
import { Glyph } from 'remix/ui/glyph'
|
|
10
9
|
import * as listbox from 'remix/ui/listbox'
|
|
11
10
|
import type { ListboxValue } from 'remix/ui/listbox'
|
|
11
|
+
import { listStyle, optionStyle } from './listbox.styles'
|
|
12
12
|
|
|
13
13
|
function FrameworkListbox(handle: Handle) {
|
|
14
14
|
let value: ListboxValue = 'remix'
|
|
@@ -27,11 +27,10 @@ function FrameworkListbox(handle: Handle) {
|
|
|
27
27
|
void handle.update()
|
|
28
28
|
}}
|
|
29
29
|
>
|
|
30
|
-
<div aria-label="Frameworks" tabIndex={0} mix={[
|
|
30
|
+
<div aria-label="Frameworks" tabIndex={0} mix={[listStyle, listbox.list()]}>
|
|
31
31
|
{frameworks.map((option) => (
|
|
32
|
-
<div key={option.value} mix={[
|
|
33
|
-
|
|
34
|
-
<span mix={listbox.labelStyle}>{option.label}</span>
|
|
32
|
+
<div key={option.value} mix={[optionStyle, listbox.option(option)]}>
|
|
33
|
+
{option.label}
|
|
35
34
|
</div>
|
|
36
35
|
))}
|
|
37
36
|
</div>
|
|
@@ -52,6 +51,7 @@ Use `textValue` when the visible label is not the best string for typeahead sear
|
|
|
52
51
|
```tsx
|
|
53
52
|
<div
|
|
54
53
|
mix={[
|
|
54
|
+
optionStyle,
|
|
55
55
|
listbox.option({
|
|
56
56
|
label: 'Staging',
|
|
57
57
|
textValue: 'beta',
|
|
@@ -63,42 +63,13 @@ Use `textValue` when the visible label is not the best string for typeahead sear
|
|
|
63
63
|
</div>
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```tsx
|
|
69
|
-
import type { ListboxRef } from 'remix/ui/listbox'
|
|
70
|
-
|
|
71
|
-
let listboxRef: ListboxRef | undefined
|
|
72
|
-
|
|
73
|
-
function selectLastOption() {
|
|
74
|
-
listboxRef?.navigateLast()
|
|
75
|
-
void listboxRef?.selectActive()
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
;<listbox.Context
|
|
79
|
-
value={value}
|
|
80
|
-
activeValue={activeValue}
|
|
81
|
-
ref={(ref) => {
|
|
82
|
-
listboxRef = ref
|
|
83
|
-
}}
|
|
84
|
-
onSelect={(nextValue) => {
|
|
85
|
-
value = nextValue
|
|
86
|
-
}}
|
|
87
|
-
onHighlight={(nextActiveValue) => {
|
|
88
|
-
activeValue = nextActiveValue
|
|
89
|
-
}}
|
|
90
|
-
>
|
|
91
|
-
{/* listbox markup */}
|
|
92
|
-
</listbox.Context>
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## `listbox.*`
|
|
66
|
+
## `remix/ui/listbox`
|
|
96
67
|
|
|
97
68
|
- `listbox.Context`: provider for controlled `value` and `activeValue`, option registration, selection, highlighting, optional ref access, `flashSelection`, `selectionFlashAttribute`, and `onSelectSettled`.
|
|
98
69
|
- `listbox.list()`: mixin that wires `role="listbox"`, default `tabIndex={-1}`, keyboard navigation, focus scrolling, and typeahead highlighting.
|
|
99
70
|
- `listbox.option(options)`: mixin that registers an option with required `label` and `value`, optional `disabled` and `textValue`, and wires `role="option"`, id, selected, disabled, highlighted, mouse, and click behavior.
|
|
100
|
-
- `listStyle`, `optionStyle`, `glyphStyle`, and `labelStyle`: flat style mixins for standard listbox presentation.
|
|
101
71
|
- `ListboxValue`: selected or active value, represented as `string | null`.
|
|
72
|
+
- `ListboxContext` and `ListboxProviderProps`: provider context and prop types for controlled listboxes.
|
|
102
73
|
- `ListboxOption`: option input shape with `label`, `value`, optional `disabled`, and optional `textValue`.
|
|
103
74
|
- `ListboxRegisteredOption`: registered option metadata passed to callbacks and refs.
|
|
104
75
|
- `ListboxRef`: live ref object exposing active/selected options, option navigation, search matching, scrolling, and selection helpers.
|
|
@@ -108,8 +79,5 @@ function selectLastOption() {
|
|
|
108
79
|
- Selection and highlighting are controlled. `onSelect` and `onHighlight` notify the parent, but DOM state updates after the parent rerenders with new values.
|
|
109
80
|
- Disabled options are skipped by keyboard navigation, typeahead, mouse movement, and click selection.
|
|
110
81
|
- Arrow keys wrap through enabled options. `Home` and `End` move to enabled boundaries. `Enter` and Space select the active option.
|
|
111
|
-
- Mouse movement highlights enabled options. `mouseleave` clears the highlight when leaving the active option.
|
|
112
|
-
- `Tab` is prevented and highlights the first enabled option.
|
|
113
82
|
- Typeahead highlights the next matching enabled option without selecting it and supports `textValue`.
|
|
114
|
-
- Focus and keyboard navigation scroll the active option into view with nearest-edge alignment.
|
|
115
83
|
- `flashSelection` applies `selectionFlashAttribute` for 60ms, delays `onSelectSettled`, and ignores new highlight/select interactions until the flash completes.
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
attrs,
|
|
3
3
|
createMixin,
|
|
4
|
-
css,
|
|
5
4
|
on,
|
|
6
|
-
type CSSMixinDescriptor,
|
|
7
5
|
type ElementProps,
|
|
8
6
|
type Handle,
|
|
9
7
|
type MixinFactory,
|
|
10
8
|
type RemixNode,
|
|
11
9
|
} from '@remix-run/ui'
|
|
12
|
-
import { theme } from '../../theme/theme.ts'
|
|
13
10
|
import {
|
|
14
11
|
hiddenTypeahead,
|
|
15
12
|
matchNextItemBySearchText,
|
|
16
13
|
type SearchValue,
|
|
17
|
-
} from '
|
|
18
|
-
import { flashAttribute } from '
|
|
14
|
+
} from '../shared/typeahead.ts'
|
|
15
|
+
import { flashAttribute } from './flash-attribute.ts'
|
|
19
16
|
|
|
20
17
|
export type ListboxValue = string | null
|
|
21
18
|
|
|
@@ -69,88 +66,6 @@ export interface ListboxRegisteredOption extends ListboxOption {
|
|
|
69
66
|
|
|
70
67
|
interface RegisteredOption extends ListboxRegisteredOption {}
|
|
71
68
|
|
|
72
|
-
const listCss: CSSMixinDescriptor = css({
|
|
73
|
-
display: 'flex',
|
|
74
|
-
flexDirection: 'column',
|
|
75
|
-
outline: 'none',
|
|
76
|
-
userSelect: 'none',
|
|
77
|
-
WebkitUserSelect: 'none',
|
|
78
|
-
'--rmx-ui-item-inset': theme.space.sm,
|
|
79
|
-
'--rmx-ui-item-indicator-gap': theme.space.xs,
|
|
80
|
-
'--rmx-ui-item-indicator-width': theme.fontSize.sm,
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
const itemCss: CSSMixinDescriptor = css({
|
|
84
|
-
display: 'grid',
|
|
85
|
-
gridTemplateColumns: 'max-content minmax(0, 1fr)',
|
|
86
|
-
alignItems: 'center',
|
|
87
|
-
width: '100%',
|
|
88
|
-
minHeight: theme.control.height.md,
|
|
89
|
-
padding: `${theme.space.xs} ${theme.space.sm}`,
|
|
90
|
-
borderRadius: theme.radius.md,
|
|
91
|
-
backgroundColor: 'transparent',
|
|
92
|
-
color: theme.colors.text.primary,
|
|
93
|
-
fontFamily: theme.fontFamily.sans,
|
|
94
|
-
fontSize: theme.fontSize.sm,
|
|
95
|
-
fontWeight: theme.fontWeight.normal,
|
|
96
|
-
lineHeight: theme.lineHeight.normal,
|
|
97
|
-
textAlign: 'left',
|
|
98
|
-
userSelect: 'none',
|
|
99
|
-
WebkitUserSelect: 'none',
|
|
100
|
-
'&:focus': {
|
|
101
|
-
outline: 'none',
|
|
102
|
-
},
|
|
103
|
-
'&[data-highlighted="true"]': {
|
|
104
|
-
backgroundColor: theme.colors.action.primary.background,
|
|
105
|
-
color: theme.colors.action.primary.foreground,
|
|
106
|
-
},
|
|
107
|
-
'&[aria-disabled="true"]': {
|
|
108
|
-
opacity: 0.5,
|
|
109
|
-
},
|
|
110
|
-
scrollMarginBlock: theme.space.xs,
|
|
111
|
-
'--rmx-listbox-option-indicator-opacity': '0',
|
|
112
|
-
'&[hidden]': {
|
|
113
|
-
display: 'none',
|
|
114
|
-
},
|
|
115
|
-
'&[data-listbox-flash="true"], &[data-select-flash="true"], &[data-combobox-flash="true"]': {
|
|
116
|
-
backgroundColor: 'transparent',
|
|
117
|
-
color: theme.colors.text.primary,
|
|
118
|
-
},
|
|
119
|
-
'&[aria-selected="true"]': {
|
|
120
|
-
'--rmx-listbox-option-indicator-opacity': '1',
|
|
121
|
-
},
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
const itemGlyphCss: CSSMixinDescriptor = css({
|
|
125
|
-
gridColumn: '1',
|
|
126
|
-
display: 'inline-flex',
|
|
127
|
-
alignItems: 'center',
|
|
128
|
-
justifyContent: 'center',
|
|
129
|
-
width: '1em',
|
|
130
|
-
height: '1em',
|
|
131
|
-
color: 'currentColor',
|
|
132
|
-
flexShrink: 0,
|
|
133
|
-
opacity: 'var(--rmx-listbox-option-indicator-opacity)',
|
|
134
|
-
'& > svg': {
|
|
135
|
-
display: 'block',
|
|
136
|
-
width: '100%',
|
|
137
|
-
height: '100%',
|
|
138
|
-
},
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
const itemLabelCss: CSSMixinDescriptor = css({
|
|
142
|
-
display: 'inline-flex',
|
|
143
|
-
alignItems: 'center',
|
|
144
|
-
minWidth: 0,
|
|
145
|
-
paddingInline: theme.space.xs,
|
|
146
|
-
WebkitUserSelect: 'none',
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
export const listStyle = listCss
|
|
150
|
-
export const optionStyle = itemCss
|
|
151
|
-
export const glyphStyle = itemGlyphCss
|
|
152
|
-
export const labelStyle = itemLabelCss
|
|
153
|
-
|
|
154
69
|
function ListboxProvider(handle: Handle<ListboxProviderProps, ListboxContext>): () => RemixNode {
|
|
155
70
|
let options: RegisteredOption[] = []
|
|
156
71
|
let state: State = 'idle'
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# menu
|
|
2
|
+
|
|
3
|
+
`Menu` renders a button-triggered menu with keyboard navigation, checked items, selection events, and nested submenus. Use it for action menus and command groups.
|
|
4
|
+
|
|
5
|
+
## Component Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import type { Handle } from 'remix/ui'
|
|
9
|
+
import { css } from 'remix/ui'
|
|
10
|
+
import { Menu, MenuItem, Submenu } from 'remix/ui/menu'
|
|
11
|
+
import { onMenuSelect } from 'remix/ui/menu/primitives'
|
|
12
|
+
|
|
13
|
+
export function ViewMenu(handle: Handle) {
|
|
14
|
+
let wordWrap = false
|
|
15
|
+
let density = 'comfortable'
|
|
16
|
+
|
|
17
|
+
return () => (
|
|
18
|
+
<Menu
|
|
19
|
+
label="View"
|
|
20
|
+
mix={onMenuSelect((event) => {
|
|
21
|
+
if (event.item.name === 'wordWrap') {
|
|
22
|
+
wordWrap = event.item.checked ?? false
|
|
23
|
+
} else if (event.item.name === 'density' && event.item.value) {
|
|
24
|
+
density = event.item.value
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void handle.update()
|
|
28
|
+
})}
|
|
29
|
+
>
|
|
30
|
+
<MenuItem checked={wordWrap} name="wordWrap" type="checkbox">
|
|
31
|
+
Word wrap
|
|
32
|
+
</MenuItem>
|
|
33
|
+
<MenuItem disabled name="minimap">
|
|
34
|
+
Minimap
|
|
35
|
+
</MenuItem>
|
|
36
|
+
<hr mix={separatorStyle} />
|
|
37
|
+
<MenuItem checked={density === 'compact'} name="density" type="radio" value="compact">
|
|
38
|
+
Compact
|
|
39
|
+
</MenuItem>
|
|
40
|
+
<MenuItem checked={density === 'comfortable'} name="density" type="radio" value="comfortable">
|
|
41
|
+
Comfortable
|
|
42
|
+
</MenuItem>
|
|
43
|
+
<Submenu label="Zoom">
|
|
44
|
+
<MenuItem name="zoomIn" value="zoom-in">
|
|
45
|
+
Zoom in
|
|
46
|
+
</MenuItem>
|
|
47
|
+
<MenuItem name="zoomOut" value="zoom-out">
|
|
48
|
+
Zoom out
|
|
49
|
+
</MenuItem>
|
|
50
|
+
</Submenu>
|
|
51
|
+
</Menu>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let separatorStyle = css({
|
|
56
|
+
border: 0,
|
|
57
|
+
borderBlockStart: '1px solid #e5e7eb',
|
|
58
|
+
marginBlock: '4px',
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Use `label` or `searchValue` when the rendered item content is not the text that should be used for event labels or typeahead.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<MenuItem label="Open command palette" name="commandPalette" searchValue="palette">
|
|
66
|
+
Command palette
|
|
67
|
+
</MenuItem>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use `menuLabel` when the menu surface needs a different accessible label from the visible trigger.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
<Menu label="..." menuLabel="Project actions">
|
|
74
|
+
<MenuItem name="rename">Rename project</MenuItem>
|
|
75
|
+
</Menu>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use `menu.contextTrigger()` with `menu.Context` and `MenuList` when a menu should open at the right-click location of an element.
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { MenuItem, MenuList } from 'remix/ui/menu'
|
|
82
|
+
import * as menu from 'remix/ui/menu/primitives'
|
|
83
|
+
|
|
84
|
+
export function FileContextMenu(handle: Handle) {
|
|
85
|
+
return () => (
|
|
86
|
+
<menu.Context label="File actions">
|
|
87
|
+
<div mix={menu.contextTrigger()} tabIndex={0}>
|
|
88
|
+
File.txt
|
|
89
|
+
</div>
|
|
90
|
+
<MenuList>
|
|
91
|
+
<MenuItem name="rename">Rename</MenuItem>
|
|
92
|
+
<MenuItem name="delete">Delete</MenuItem>
|
|
93
|
+
</MenuList>
|
|
94
|
+
</menu.Context>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Attach `onMenuSelect(...)` from `remix/ui/menu/primitives` to `MenuList` or a shared ancestor when using lower-level context menu composition.
|
|
100
|
+
|
|
101
|
+
## Primitive Usage
|
|
102
|
+
|
|
103
|
+
Use only the lower-level primitives when app code owns the trigger, surface, and item markup:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import * as menu from 'remix/ui/menu/primitives'
|
|
107
|
+
import { itemStyle, listStyle, popoverStyle, triggerStyle } from './menu.styles'
|
|
108
|
+
|
|
109
|
+
export function PrimitiveMenu() {
|
|
110
|
+
return (
|
|
111
|
+
<menu.Context label="Project actions">
|
|
112
|
+
<button mix={[triggerStyle, menu.trigger()]} type="button">
|
|
113
|
+
Actions
|
|
114
|
+
</button>
|
|
115
|
+
<div mix={[popoverStyle, menu.popover()]}>
|
|
116
|
+
<div mix={[listStyle, menu.list()]}>
|
|
117
|
+
<div mix={[itemStyle, menu.item({ name: 'rename' })]}>Rename</div>
|
|
118
|
+
<div mix={[itemStyle, menu.item({ disabled: true, name: 'archive' })]}>Archive</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</menu.Context>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## `remix/ui/menu`
|
|
127
|
+
|
|
128
|
+
- `Menu`: composed trigger, popover, and list component for the common menu case.
|
|
129
|
+
- `MenuItem`: menu item component. Supports regular, checkbox, and radio item roles through `type`, `checked`, `name`, `value`, `label`, `disabled`, and `searchValue`.
|
|
130
|
+
- `Submenu`: nested menu component with its own trigger and child menu surface.
|
|
131
|
+
- `MenuList`: lower-level styled list component for custom composition inside `menu.Context`.
|
|
132
|
+
- `buttonStyle`, `popoverStyle`, `listStyle`, `itemStyle`, `itemSlotStyle`, `itemLabelStyle`, `itemIndicatorStyle`, and `triggerIndicatorStyle`: flat style mixins used by the component markup.
|
|
133
|
+
- `MenuProps`, `MenuItemProps`, `MenuListProps`, and `SubmenuProps`: public TypeScript props for the composed APIs.
|
|
134
|
+
|
|
135
|
+
## `remix/ui/menu/primitives`
|
|
136
|
+
|
|
137
|
+
- `Context`: lower-level provider for custom menu composition.
|
|
138
|
+
- `trigger()`: wires a button-style trigger to open the root menu.
|
|
139
|
+
- `contextTrigger()`: opens the root menu from a `contextmenu` event at pointer coordinates, or from keyboard context-menu shortcuts.
|
|
140
|
+
- `popover()`: wires the menu popover surface.
|
|
141
|
+
- `list()`: wires the menu list root, focus handling, keyboard navigation, and typeahead.
|
|
142
|
+
- `item(...)`: registers one menu item. Supports regular, checkbox, and radio roles through `type`, `checked`, `name`, `value`, `label`, `disabled`, and `searchValue`.
|
|
143
|
+
- `submenuTrigger(...)`: registers a menu item that opens a child menu.
|
|
144
|
+
- `onMenuSelect(...)`: event mixin for the bubbling `MenuSelectEvent`.
|
|
145
|
+
- `MenuSelectEvent`: bubbling event whose `item` describes the selected item.
|
|
146
|
+
- `MenuSelectItem`, `MenuProviderProps`, `MenuTriggerOptions`, `MenuContextTriggerOptions`, `MenuItemOptions`, and `SubmenuTriggerOptions`: public TypeScript event, prop, and option types for the primitives.
|
|
147
|
+
|
|
148
|
+
## Behavior Notes
|
|
149
|
+
|
|
150
|
+
- Click opens the root menu and focuses the list; clicking the trigger again closes it and restores focus.
|
|
151
|
+
- `menu.contextTrigger()` opens the root menu from a `contextmenu` event at the pointer coordinates and supports keyboard opening with the Context Menu key or Shift+F10.
|
|
152
|
+
- `ArrowDown` opens from the trigger at the first enabled item. `ArrowUp` opens at the last enabled item. Enter and Space open the menu with focus on the list.
|
|
153
|
+
- Keyboard navigation skips disabled items and does not wrap past the first or last enabled item.
|
|
154
|
+
- `Home` and `End` move to the first and last enabled item. Enter and Space activate the highlighted item.
|
|
155
|
+
- Printable keys use typeahead. Typeahead matches `searchValue` when provided and otherwise uses the item label.
|
|
156
|
+
- Submenus open with `ArrowRight`, close with `ArrowLeft`, and are anchored to the submenu trigger with `right-start` placement.
|
|
157
|
+
- Pointer movement highlights enabled items. Submenus open after a short focus/pointer delay and use hover aim so they stay open while moving toward the child surface.
|
|
158
|
+
- Selecting a regular item flashes that item. Selecting a checkbox or radio item flashes the committed checked state.
|
|
159
|
+
- Selection dispatches one bubbled `MenuSelectEvent`, closes the full menu tree, and restores focus to the root trigger.
|
|
160
|
+
- The composed `Menu` re-dispatches selection from its trigger so handlers on the `Menu` button and shared ancestors see the event once.
|
|
161
|
+
- `menuLabel`, `Submenu.menuLabel`, and `Submenu.listProps` let composed menus label and customize menu surfaces separately from trigger content.
|