@remix-run/ui 0.2.0 → 0.3.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/dist/animation/demos/animation.demo.d.ts +5 -0
- package/dist/animation/demos/animation.demo.js +92 -0
- package/dist/animation/demos/animation.demo.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 +50 -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/spring.demo.d.ts +6 -0
- package/dist/animation/demos/spring.demo.js +304 -0
- package/dist/animation/demos/spring.demo.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/accordion/demos/card.demo.d.ts +1 -0
- package/dist/components/accordion/demos/card.demo.js +1 -0
- package/dist/components/accordion/demos/card.demo.js.map +1 -1
- package/dist/components/accordion/demos/multiple.demo.d.ts +1 -0
- package/dist/components/accordion/demos/multiple.demo.js +1 -0
- package/dist/components/accordion/demos/multiple.demo.js.map +1 -1
- package/dist/components/accordion/demos/overview.demo.d.ts +1 -0
- package/dist/components/accordion/demos/overview.demo.js +1 -0
- package/dist/components/accordion/demos/overview.demo.js.map +1 -1
- package/dist/components/anchor/anchor.d.ts +8 -1
- package/dist/components/anchor/anchor.js +30 -11
- package/dist/components/anchor/anchor.js.map +1 -1
- package/dist/components/breadcrumbs/{demos/basic.demo.d.ts → basic.demo.d.ts} +1 -0
- package/dist/components/breadcrumbs/{demos/basic.demo.js → basic.demo.js} +1 -0
- package/dist/components/breadcrumbs/basic.demo.js.map +1 -0
- package/dist/components/breadcrumbs/{demos/separator.demo.d.ts → separator.demo.d.ts} +1 -0
- package/dist/components/breadcrumbs/{demos/separator.demo.js → separator.demo.js} +1 -0
- package/dist/components/breadcrumbs/separator.demo.js.map +1 -0
- package/dist/components/button/demos/aliases.demo.d.ts +1 -0
- package/dist/components/button/demos/aliases.demo.js +1 -0
- package/dist/components/button/demos/aliases.demo.js.map +1 -1
- package/dist/components/button/demos/basic.demo.d.ts +1 -0
- package/dist/components/button/demos/basic.demo.js +1 -0
- package/dist/components/button/demos/basic.demo.js.map +1 -1
- package/dist/components/button/demos/states.demo.d.ts +1 -0
- package/dist/components/button/demos/states.demo.js +1 -0
- package/dist/components/button/demos/states.demo.js.map +1 -1
- package/dist/components/combobox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/combobox/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/combobox/overview.demo.js.map +1 -0
- package/dist/components/listbox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/listbox/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/listbox/overview.demo.js.map +1 -0
- package/dist/components/menu/demos/bubbling.demo.d.ts +1 -0
- package/dist/components/menu/demos/bubbling.demo.js +1 -0
- package/dist/components/menu/demos/bubbling.demo.js.map +1 -1
- package/dist/components/menu/demos/context-trigger.demo.d.ts +7 -0
- package/dist/components/menu/demos/context-trigger.demo.js +87 -0
- package/dist/components/menu/demos/context-trigger.demo.js.map +1 -0
- package/dist/components/menu/demos/overview.demo.d.ts +1 -0
- package/dist/components/menu/demos/overview.demo.js +1 -0
- package/dist/components/menu/demos/overview.demo.js.map +1 -1
- package/dist/components/menu/menu.d.ts +3 -0
- package/dist/components/menu/menu.js +55 -0
- package/dist/components/menu/menu.js.map +1 -1
- package/dist/components/popover/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/popover/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/popover/overview.demo.js.map +1 -0
- package/dist/components/popover/popover.d.ts +2 -2
- package/dist/components/popover/popover.js +11 -4
- package/dist/components/popover/popover.js.map +1 -1
- package/dist/components/select/{demos/deconstructed.demo.d.ts → deconstructed.demo.d.ts} +1 -0
- package/dist/components/select/{demos/deconstructed.demo.js → deconstructed.demo.js} +1 -0
- package/dist/components/select/deconstructed.demo.js.map +1 -0
- package/dist/components/select/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/select/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/select/overview.demo.js.map +1 -0
- package/dist/components/select/select.js +1 -1
- package/dist/components/select/select.js.map +1 -1
- 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/demos/basic.demo.d.ts +6 -0
- package/dist/runtime/demos/basic.demo.js +16 -0
- package/dist/runtime/demos/basic.demo.js.map +1 -0
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.d.ts +6 -0
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.js +77 -0
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.js.map +1 -0
- package/dist/runtime/demos/keyed-list.demo.d.ts +6 -0
- package/dist/runtime/demos/keyed-list.demo.js +62 -0
- package/dist/runtime/demos/keyed-list.demo.js.map +1 -0
- package/dist/runtime/demos/readme.demo.d.ts +5 -0
- package/dist/runtime/demos/readme.demo.js +396 -0
- package/dist/runtime/demos/readme.demo.js.map +1 -0
- 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/server/stream.js +3 -2
- package/dist/server/stream.js.map +1 -1
- package/dist/style/style.js +27 -10
- package/dist/style/style.js.map +1 -1
- package/package.json +3 -3
- package/src/animation/demos/animation.demo.tsx +194 -0
- 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 +65 -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/spring.demo.tsx +385 -0
- package/src/animation/demos/transition-options.tsx +24 -0
- package/src/animation/spring.ts +22 -0
- package/src/components/accordion/demos/card.demo.tsx +1 -0
- package/src/components/accordion/demos/multiple.demo.tsx +1 -0
- package/src/components/accordion/demos/overview.demo.tsx +1 -0
- package/src/components/anchor/README.md +11 -3
- package/src/components/anchor/anchor.ts +53 -12
- package/src/components/breadcrumbs/{demos/basic.demo.tsx → basic.demo.tsx} +1 -0
- package/src/components/breadcrumbs/{demos/separator.demo.tsx → separator.demo.tsx} +1 -0
- package/src/components/button/demos/aliases.demo.tsx +1 -0
- package/src/components/button/demos/basic.demo.tsx +1 -0
- package/src/components/button/demos/states.demo.tsx +1 -0
- package/src/components/combobox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/listbox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/menu/README.md +26 -2
- package/src/components/menu/demos/bubbling.demo.tsx +1 -0
- package/src/components/menu/demos/context-trigger.demo.tsx +133 -0
- package/src/components/menu/demos/overview.demo.tsx +1 -0
- package/src/components/menu/menu.tsx +73 -1
- package/src/components/popover/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/popover/popover.ts +18 -9
- package/src/components/select/{demos/deconstructed.demo.tsx → deconstructed.demo.tsx} +1 -0
- package/src/components/select/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/select/select.tsx +1 -1
- package/src/runtime/client-entries.ts +1 -5
- package/src/runtime/component.ts +3 -3
- package/src/runtime/demos/basic.demo.tsx +21 -0
- package/src/runtime/demos/controlled-uncontrolled-values.demo.tsx +177 -0
- package/src/runtime/demos/keyed-list.demo.tsx +94 -0
- package/src/runtime/demos/readme.demo.tsx +702 -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/server/stream.ts +7 -2
- package/src/style/style.ts +29 -10
- package/dist/components/breadcrumbs/demos/basic.demo.js.map +0 -1
- package/dist/components/breadcrumbs/demos/separator.demo.js.map +0 -1
- package/dist/components/combobox/demos/overview.demo.js.map +0 -1
- package/dist/components/listbox/demos/overview.demo.js.map +0 -1
- package/dist/components/popover/demos/overview.demo.js.map +0 -1
- package/dist/components/select/demos/deconstructed.demo.js.map +0 -1
- package/dist/components/select/demos/overview.demo.js.map +0 -1
|
@@ -68,6 +68,29 @@ Use `menuLabel` when the menu surface needs a different accessible label from th
|
|
|
68
68
|
</Menu>
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
Use `menu.contextTrigger()` with `menu.Context` and `MenuList` when a menu should open at the right-click location of an element.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import * as menu from 'remix/ui/menu'
|
|
75
|
+
import { MenuItem, MenuList } from 'remix/ui/menu'
|
|
76
|
+
|
|
77
|
+
export function FileContextMenu(handle: Handle) {
|
|
78
|
+
return () => (
|
|
79
|
+
<menu.Context label="File actions">
|
|
80
|
+
<div mix={menu.contextTrigger()} tabIndex={0}>
|
|
81
|
+
File.txt
|
|
82
|
+
</div>
|
|
83
|
+
<MenuList>
|
|
84
|
+
<MenuItem name="rename">Rename</MenuItem>
|
|
85
|
+
<MenuItem name="delete">Delete</MenuItem>
|
|
86
|
+
</MenuList>
|
|
87
|
+
</menu.Context>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Attach `onMenuSelect(...)` to `MenuList` or a shared ancestor when using lower-level context menu composition.
|
|
93
|
+
|
|
71
94
|
## `menu.*`
|
|
72
95
|
|
|
73
96
|
- `Menu`: composed trigger, popover, and list component for the common menu case.
|
|
@@ -77,13 +100,14 @@ Use `menuLabel` when the menu surface needs a different accessible label from th
|
|
|
77
100
|
- `onMenuSelect(...)`: event mixin for the bubbling `MenuSelectEvent`.
|
|
78
101
|
- `MenuSelectEvent`: bubbling event class whose `item` describes the selected item.
|
|
79
102
|
- `MenuSelectItem`: selected item shape with `checked`, `id`, `label`, `name`, `type`, and `value`.
|
|
80
|
-
- `menu.Context`, `menu.trigger()`, `menu.popover()`, `menu.list()`, `menu.item(...)`, and `menu.submenuTrigger(...)`: lower-level composition primitives.
|
|
103
|
+
- `menu.Context`, `menu.trigger()`, `menu.contextTrigger()`, `menu.popover()`, `menu.list()`, `menu.item(...)`, and `menu.submenuTrigger(...)`: lower-level composition primitives.
|
|
81
104
|
- `buttonStyle`, `popoverStyle`, `listStyle`, `itemStyle`, `itemSlotStyle`, `itemLabelStyle`, `itemGlyphStyle`, and `triggerGlyphStyle`: flat style mixins used by the wrappers.
|
|
82
|
-
- `MenuProps`, `MenuItemProps`, `MenuListProps`, `MenuProviderProps`, `MenuTriggerOptions`, `MenuItemOptions`, and `SubmenuProps`: public TypeScript props and option types.
|
|
105
|
+
- `MenuProps`, `MenuItemProps`, `MenuListProps`, `MenuProviderProps`, `MenuTriggerOptions`, `MenuContextTriggerOptions`, `MenuItemOptions`, and `SubmenuProps`: public TypeScript props and option types.
|
|
83
106
|
|
|
84
107
|
## Behavior Notes
|
|
85
108
|
|
|
86
109
|
- Click opens the root menu and focuses the list; clicking the trigger again closes it and restores focus.
|
|
110
|
+
- `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.
|
|
87
111
|
- `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.
|
|
88
112
|
- Keyboard navigation skips disabled items and does not wrap past the first or last enabled item.
|
|
89
113
|
- `Home` and `End` move to the first and last enabled item. Enter and Space activate the highlighted item.
|
|
@@ -4,6 +4,7 @@ import { Menu, MenuItem, onMenuSelect } from '@remix-run/ui/menu'
|
|
|
4
4
|
/**
|
|
5
5
|
* @name Menu Event Bubbling
|
|
6
6
|
* @description Item-level handlers fire before the root handler, letting individual items intercept events while others bubble up.
|
|
7
|
+
* @layout center
|
|
7
8
|
*/
|
|
8
9
|
export default function Example() {
|
|
9
10
|
return () => (
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { css, type Handle } from '@remix-run/ui'
|
|
2
|
+
import * as menu from '@remix-run/ui/menu'
|
|
3
|
+
import { MenuItem, MenuList, onMenuSelect, Submenu } from '@remix-run/ui/menu'
|
|
4
|
+
import { separatorStyle } from '@remix-run/ui/separator'
|
|
5
|
+
import { theme } from '@remix-run/ui/theme'
|
|
6
|
+
|
|
7
|
+
type FileAction = 'copyPath' | 'duplicate' | 'move' | 'rename' | 'reveal' | 'trash'
|
|
8
|
+
|
|
9
|
+
const actionLabelByName: Record<FileAction, string> = {
|
|
10
|
+
copyPath: 'Copied path',
|
|
11
|
+
duplicate: 'Duplicated file',
|
|
12
|
+
move: 'Moved file',
|
|
13
|
+
rename: 'Renamed file',
|
|
14
|
+
reveal: 'Revealed in Finder',
|
|
15
|
+
trash: 'Moved to trash',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @name Context Menu Trigger
|
|
20
|
+
* @description A lower-level menu composition that opens from right-click coordinates while keeping standard menu selection and submenu behavior.
|
|
21
|
+
* @layout center
|
|
22
|
+
*/
|
|
23
|
+
export default function Example(handle: Handle) {
|
|
24
|
+
let latestAction = 'Right-click the card.'
|
|
25
|
+
|
|
26
|
+
return () => (
|
|
27
|
+
<menu.Context label="File actions">
|
|
28
|
+
<div mix={layoutCss}>
|
|
29
|
+
<div tabIndex={0} mix={[fileCardCss, menu.contextTrigger()]}>
|
|
30
|
+
<span mix={fileIconCss}>TS</span>
|
|
31
|
+
<span mix={fileTextCss}>
|
|
32
|
+
<strong mix={fileNameCss}>context-menu.tsx</strong>
|
|
33
|
+
<span mix={fileMetaCss}>Right-click or press Shift+F10</span>
|
|
34
|
+
</span>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<p aria-live="polite" mix={statusCss}>
|
|
38
|
+
{latestAction}
|
|
39
|
+
</p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<MenuList
|
|
43
|
+
mix={onMenuSelect((event) => {
|
|
44
|
+
latestAction =
|
|
45
|
+
actionLabelByName[event.item.name as FileAction] ?? `Selected ${event.item.label}`
|
|
46
|
+
void handle.update()
|
|
47
|
+
})}
|
|
48
|
+
>
|
|
49
|
+
<MenuItem name="rename">Rename</MenuItem>
|
|
50
|
+
<MenuItem name="duplicate">Duplicate</MenuItem>
|
|
51
|
+
<MenuItem name="copyPath">Copy path</MenuItem>
|
|
52
|
+
<hr mix={separatorStyle} />
|
|
53
|
+
<Submenu label="Move to">
|
|
54
|
+
<MenuItem name="move" value="drafts">
|
|
55
|
+
Drafts
|
|
56
|
+
</MenuItem>
|
|
57
|
+
<MenuItem name="move" value="archive">
|
|
58
|
+
Archive
|
|
59
|
+
</MenuItem>
|
|
60
|
+
</Submenu>
|
|
61
|
+
<MenuItem name="reveal">Reveal in Finder</MenuItem>
|
|
62
|
+
<hr mix={separatorStyle} />
|
|
63
|
+
<MenuItem name="trash">Move to trash</MenuItem>
|
|
64
|
+
</MenuList>
|
|
65
|
+
</menu.Context>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const layoutCss = css({
|
|
70
|
+
display: 'grid',
|
|
71
|
+
justifyItems: 'start',
|
|
72
|
+
gap: theme.space.md,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const fileCardCss = css({
|
|
76
|
+
display: 'grid',
|
|
77
|
+
gridTemplateColumns: 'auto minmax(0, 1fr)',
|
|
78
|
+
alignItems: 'center',
|
|
79
|
+
gap: theme.space.md,
|
|
80
|
+
width: 'min(100%, 21rem)',
|
|
81
|
+
padding: theme.space.md,
|
|
82
|
+
border: `1px solid ${theme.colors.border.subtle}`,
|
|
83
|
+
borderRadius: theme.radius.lg,
|
|
84
|
+
backgroundColor: theme.surface.lvl1,
|
|
85
|
+
color: theme.colors.text.primary,
|
|
86
|
+
boxShadow: theme.shadow.xs,
|
|
87
|
+
cursor: 'context-menu',
|
|
88
|
+
userSelect: 'none',
|
|
89
|
+
'&:focus-visible': {
|
|
90
|
+
outline: `2px solid ${theme.colors.focus.ring}`,
|
|
91
|
+
outlineOffset: '2px',
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const fileIconCss = css({
|
|
96
|
+
display: 'inline-grid',
|
|
97
|
+
placeItems: 'center',
|
|
98
|
+
width: theme.control.height.lg,
|
|
99
|
+
height: theme.control.height.lg,
|
|
100
|
+
borderRadius: theme.radius.md,
|
|
101
|
+
backgroundColor: theme.colors.action.primary.background,
|
|
102
|
+
color: theme.colors.action.primary.foreground,
|
|
103
|
+
fontSize: theme.fontSize.xs,
|
|
104
|
+
fontWeight: theme.fontWeight.bold,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const fileTextCss = css({
|
|
108
|
+
display: 'grid',
|
|
109
|
+
gap: theme.space.px,
|
|
110
|
+
minWidth: 0,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const fileNameCss = css({
|
|
114
|
+
overflow: 'hidden',
|
|
115
|
+
textOverflow: 'ellipsis',
|
|
116
|
+
whiteSpace: 'nowrap',
|
|
117
|
+
fontSize: theme.fontSize.sm,
|
|
118
|
+
lineHeight: theme.lineHeight.normal,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const fileMetaCss = css({
|
|
122
|
+
color: theme.colors.text.secondary,
|
|
123
|
+
fontSize: theme.fontSize.xs,
|
|
124
|
+
lineHeight: theme.lineHeight.normal,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const statusCss = css({
|
|
128
|
+
margin: 0,
|
|
129
|
+
minHeight: theme.lineHeight.normal,
|
|
130
|
+
color: theme.colors.text.secondary,
|
|
131
|
+
fontSize: theme.fontSize.sm,
|
|
132
|
+
lineHeight: theme.lineHeight.normal,
|
|
133
|
+
})
|
|
@@ -5,6 +5,7 @@ import { separatorStyle } from '@remix-run/ui/separator'
|
|
|
5
5
|
/**
|
|
6
6
|
* @name Menu Overview
|
|
7
7
|
* @description A hierarchical menu with checkboxes, radio groups, submenus, and separators.
|
|
8
|
+
* @layout center
|
|
8
9
|
*/
|
|
9
10
|
export default function Example(handle: Handle) {
|
|
10
11
|
type Density = 'comfortable' | 'compact'
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type Props,
|
|
14
14
|
type RemixNode,
|
|
15
15
|
} from '@remix-run/ui'
|
|
16
|
-
import { type AnchorOptions } from '../anchor/anchor.ts'
|
|
16
|
+
import { type AnchorOptions, type AnchorPoint } from '../anchor/anchor.ts'
|
|
17
17
|
import * as button from '../button/button.tsx'
|
|
18
18
|
import { Glyph } from '../glyph/glyph.tsx'
|
|
19
19
|
import * as popover from '../popover/popover.ts'
|
|
@@ -250,6 +250,8 @@ export interface MenuProviderProps {
|
|
|
250
250
|
|
|
251
251
|
export interface MenuTriggerOptions extends AnchorOptions {}
|
|
252
252
|
|
|
253
|
+
export interface MenuContextTriggerOptions extends AnchorOptions {}
|
|
254
|
+
|
|
253
255
|
export interface MenuItemOptions {
|
|
254
256
|
checked?: boolean
|
|
255
257
|
disabled?: boolean
|
|
@@ -1008,6 +1010,74 @@ const triggerMixin: MixinFactory<HTMLElement, [options?: MenuTriggerOptions], El
|
|
|
1008
1010
|
]
|
|
1009
1011
|
})
|
|
1010
1012
|
|
|
1013
|
+
const contextTriggerMixin: MixinFactory<
|
|
1014
|
+
HTMLElement,
|
|
1015
|
+
[options?: MenuContextTriggerOptions],
|
|
1016
|
+
ElementProps
|
|
1017
|
+
> = createMixin<HTMLElement, [options?: MenuContextTriggerOptions], ElementProps>((handle) => {
|
|
1018
|
+
let context = handle.context.get(MenuProvider)
|
|
1019
|
+
let popoverContext = handle.context.get(popover.Context)
|
|
1020
|
+
let anchorPoint: AnchorPoint = { x: 0, y: 0, width: 0, height: 0 }
|
|
1021
|
+
|
|
1022
|
+
function setAnchorPoint(
|
|
1023
|
+
{ x, y, width = 0, height = 0 }: AnchorPoint,
|
|
1024
|
+
options: MenuContextTriggerOptions,
|
|
1025
|
+
) {
|
|
1026
|
+
anchorPoint.x = x
|
|
1027
|
+
anchorPoint.y = y
|
|
1028
|
+
anchorPoint.width = width
|
|
1029
|
+
anchorPoint.height = height
|
|
1030
|
+
popoverContext.anchor = {
|
|
1031
|
+
target: anchorPoint,
|
|
1032
|
+
options: {
|
|
1033
|
+
placement: 'bottom-start',
|
|
1034
|
+
...options,
|
|
1035
|
+
},
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
return (options = {}) => [
|
|
1040
|
+
attrs({
|
|
1041
|
+
id: handle.id,
|
|
1042
|
+
'aria-controls': context.listId,
|
|
1043
|
+
'aria-expanded': context.isOpen ? 'true' : 'false',
|
|
1044
|
+
'aria-haspopup': 'menu',
|
|
1045
|
+
}),
|
|
1046
|
+
ref((node: HTMLElement, signal) => {
|
|
1047
|
+
context.registerTrigger(node, handle.id)
|
|
1048
|
+
signal.addEventListener('abort', () => {
|
|
1049
|
+
context.unregisterTrigger(node)
|
|
1050
|
+
})
|
|
1051
|
+
}),
|
|
1052
|
+
on('contextmenu', (event) => {
|
|
1053
|
+
event.preventDefault()
|
|
1054
|
+
event.stopPropagation()
|
|
1055
|
+
setAnchorPoint({ x: event.clientX, y: event.clientY }, options)
|
|
1056
|
+
void context.open({ strategy: 'list' })
|
|
1057
|
+
}),
|
|
1058
|
+
on('keydown', (event) => {
|
|
1059
|
+
if (event.key !== 'ContextMenu' && !(event.shiftKey && event.key === 'F10')) {
|
|
1060
|
+
return
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
event.preventDefault()
|
|
1064
|
+
event.stopPropagation()
|
|
1065
|
+
|
|
1066
|
+
let rect = event.currentTarget.getBoundingClientRect()
|
|
1067
|
+
setAnchorPoint(
|
|
1068
|
+
{
|
|
1069
|
+
x: rect.left,
|
|
1070
|
+
y: rect.top,
|
|
1071
|
+
width: rect.width,
|
|
1072
|
+
height: rect.height,
|
|
1073
|
+
},
|
|
1074
|
+
options,
|
|
1075
|
+
)
|
|
1076
|
+
void context.open({ strategy: 'list' })
|
|
1077
|
+
}),
|
|
1078
|
+
]
|
|
1079
|
+
})
|
|
1080
|
+
|
|
1011
1081
|
const popoverMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<
|
|
1012
1082
|
HTMLElement,
|
|
1013
1083
|
[],
|
|
@@ -1394,6 +1464,7 @@ type MenuListChildProps = Omit<
|
|
|
1394
1464
|
>
|
|
1395
1465
|
|
|
1396
1466
|
export const Context = MenuProvider
|
|
1467
|
+
export const contextTrigger = contextTriggerMixin
|
|
1397
1468
|
export const item = itemMixin
|
|
1398
1469
|
export const list = listMixin
|
|
1399
1470
|
export { popoverMixin as popover }
|
|
@@ -1402,6 +1473,7 @@ export const trigger = triggerMixin
|
|
|
1402
1473
|
|
|
1403
1474
|
const menu = {
|
|
1404
1475
|
Context,
|
|
1476
|
+
contextTrigger,
|
|
1405
1477
|
item,
|
|
1406
1478
|
list,
|
|
1407
1479
|
popover: popoverMixin,
|
|
@@ -8,6 +8,7 @@ import { theme } from '@remix-run/ui/theme'
|
|
|
8
8
|
/**
|
|
9
9
|
* @name Popover Overview
|
|
10
10
|
* @description A popover panel anchored to a trigger button, containing form controls and a close action.
|
|
11
|
+
* @layout center
|
|
11
12
|
*/
|
|
12
13
|
export default function Example(handle: Handle) {
|
|
13
14
|
let open = false
|
|
@@ -9,7 +9,11 @@ import {
|
|
|
9
9
|
type MixinFactory,
|
|
10
10
|
type RemixNode,
|
|
11
11
|
} from '@remix-run/ui'
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
anchor as positionAnchor,
|
|
14
|
+
type AnchorOptions,
|
|
15
|
+
type AnchorTarget,
|
|
16
|
+
} from '../anchor/anchor.ts'
|
|
13
17
|
import { onOutsideClick } from '../../interactions/outside-click/outside-click-mixin.ts'
|
|
14
18
|
import { theme } from '../../theme/theme.ts'
|
|
15
19
|
import { lockScroll } from '../../utils/scroll-lock.ts'
|
|
@@ -26,7 +30,7 @@ export interface PopoverProps {
|
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
interface AnchorRef {
|
|
29
|
-
|
|
33
|
+
target: AnchorTarget
|
|
30
34
|
options: AnchorOptions
|
|
31
35
|
}
|
|
32
36
|
|
|
@@ -108,11 +112,15 @@ const anchorMixin: MixinFactory<HTMLElement, [options: AnchorOptions], ElementPr
|
|
|
108
112
|
let context = handle.context.get(PopoverProvider)
|
|
109
113
|
return (options) => {
|
|
110
114
|
handle.queueTask((node) => {
|
|
111
|
-
context.anchor = { node, options }
|
|
115
|
+
context.anchor = { target: node, options }
|
|
112
116
|
})
|
|
113
117
|
}
|
|
114
118
|
})
|
|
115
119
|
|
|
120
|
+
function anchorContains(anchor: AnchorRef | null, target: Node) {
|
|
121
|
+
return anchor?.target instanceof HTMLElement && anchor.target.contains(target)
|
|
122
|
+
}
|
|
123
|
+
|
|
116
124
|
const surfaceMixin: MixinFactory<HTMLElement, [options: PopoverSurfaceOptions], ElementProps> =
|
|
117
125
|
createMixin<HTMLElement, [options: PopoverSurfaceOptions]>((handle) => {
|
|
118
126
|
let openProp = false
|
|
@@ -147,11 +155,12 @@ const surfaceMixin: MixinFactory<HTMLElement, [options: PopoverSurfaceOptions],
|
|
|
147
155
|
|
|
148
156
|
on('beforetoggle', (event) => {
|
|
149
157
|
if (event.newState === 'open') {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
let anchor = context.anchor
|
|
159
|
+
if (!anchor) {
|
|
160
|
+
throw new Error('popover.surface() requires a registered anchor before opening')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
cleanupAnchor = positionAnchor(event.currentTarget, anchor.target, anchor.options)
|
|
155
164
|
unlockScroll = lockScroll()
|
|
156
165
|
} else if (event.newState === 'closed') {
|
|
157
166
|
cleanupAnchor()
|
|
@@ -180,7 +189,7 @@ const surfaceMixin: MixinFactory<HTMLElement, [options: PopoverSurfaceOptions],
|
|
|
180
189
|
options.onHide({ reason: 'outside-click', target })
|
|
181
190
|
},
|
|
182
191
|
(target) =>
|
|
183
|
-
options.closeOnAnchorClick === false &&
|
|
192
|
+
options.closeOnAnchorClick === false && anchorContains(context.anchor, target),
|
|
184
193
|
options.stopOutsideClickPropagation ?? true,
|
|
185
194
|
),
|
|
186
195
|
]
|
|
@@ -9,6 +9,7 @@ import { theme } from '@remix-run/ui/theme'
|
|
|
9
9
|
/**
|
|
10
10
|
* @name Select Deconstructed
|
|
11
11
|
* @description Build a fully custom select by composing the select, popover, and listbox primitives directly.
|
|
12
|
+
* @layout center
|
|
12
13
|
*/
|
|
13
14
|
export default function Example(handle: Handle) {
|
|
14
15
|
let label = 'Local'
|
|
@@ -156,7 +156,7 @@ function SelectProvider(handle: Handle<SelectContextProps, SelectContextValue>):
|
|
|
156
156
|
popoverContextRef.hideFocusTarget = triggerRef ?? null
|
|
157
157
|
popoverContextRef.anchor = triggerRef
|
|
158
158
|
? {
|
|
159
|
-
|
|
159
|
+
target: triggerRef,
|
|
160
160
|
options: getPopoverAnchorOptions(),
|
|
161
161
|
}
|
|
162
162
|
: null
|
|
@@ -47,7 +47,7 @@ export type EntryMetadata = {
|
|
|
47
47
|
*/
|
|
48
48
|
export type EntryComponent<props extends SerializableProps = {}, context = NoContext> = ((
|
|
49
49
|
handle: Handle<props, context>,
|
|
50
|
-
) => RenderFn
|
|
50
|
+
) => RenderFn) &
|
|
51
51
|
EntryMetadata
|
|
52
52
|
|
|
53
53
|
/**
|
|
@@ -86,10 +86,6 @@ export function clientEntry<props extends SerializableProps = {}, context = NoCo
|
|
|
86
86
|
entryId: string,
|
|
87
87
|
component: (handle: Handle<props, context>) => RenderFn,
|
|
88
88
|
): EntryComponent<props, context>
|
|
89
|
-
export function clientEntry<props extends SerializableProps = {}, context = NoContext>(
|
|
90
|
-
entryId: string,
|
|
91
|
-
component: (handle: Handle<Record<string, never>, context>) => RenderFn<props>,
|
|
92
|
-
): EntryComponent<props, context>
|
|
93
89
|
|
|
94
90
|
// Implementation
|
|
95
91
|
export function clientEntry(entryId: string, component: any): any {
|
package/src/runtime/component.ts
CHANGED
|
@@ -179,9 +179,9 @@ export type ComponentFn<Props = Record<string, never>, ContextValue = NoContext>
|
|
|
179
179
|
) => RenderFn
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
|
-
*
|
|
182
|
+
* Zero-argument render function returned by a component factory.
|
|
183
183
|
*/
|
|
184
|
-
export type RenderFn
|
|
184
|
+
export type RenderFn = () => RemixNode
|
|
185
185
|
|
|
186
186
|
export type { RemixNode } from './jsx.ts'
|
|
187
187
|
|
|
@@ -287,7 +287,7 @@ class ComponentRuntime<C = NoContext> implements ComponentHandle<C> {
|
|
|
287
287
|
this.#renderFn = renderFn
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
return [renderFn(
|
|
290
|
+
return [renderFn(), this.#dequeueTasks()]
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
remove = (): Array<() => void> => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { on, type Handle } from '@remix-run/ui'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @name Basic Counter
|
|
5
|
+
* @description The smallest interactive Remix UI counter from the standalone demos.
|
|
6
|
+
*/
|
|
7
|
+
export default function App(handle: Handle) {
|
|
8
|
+
let count = 0
|
|
9
|
+
return () => (
|
|
10
|
+
<button
|
|
11
|
+
mix={[
|
|
12
|
+
on('click', () => {
|
|
13
|
+
count++
|
|
14
|
+
handle.update()
|
|
15
|
+
}),
|
|
16
|
+
]}
|
|
17
|
+
>
|
|
18
|
+
Ye ol' counter: {count}
|
|
19
|
+
</button>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { on, type Handle } from '@remix-run/ui'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @name Controlled and Uncontrolled Values
|
|
5
|
+
* @description Compares controlled props with uncontrolled DOM values across rerenders and remounts.
|
|
6
|
+
*/
|
|
7
|
+
export default function App(handle: Handle) {
|
|
8
|
+
let controlledText = 'hello'
|
|
9
|
+
let controlledChecked = true
|
|
10
|
+
let controlledChoice = 'alpha'
|
|
11
|
+
let uncontrolledTextSnapshot = 'type to update this'
|
|
12
|
+
let uncontrolledCheckedSnapshot = true
|
|
13
|
+
let renderCount = 0
|
|
14
|
+
let uncontrolledVersion = 0
|
|
15
|
+
|
|
16
|
+
let rerender = () => {
|
|
17
|
+
renderCount++
|
|
18
|
+
handle.update()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let resetControlled = () => {
|
|
22
|
+
controlledText = 'hello'
|
|
23
|
+
controlledChecked = true
|
|
24
|
+
controlledChoice = 'alpha'
|
|
25
|
+
rerender()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let remountUncontrolled = () => {
|
|
29
|
+
uncontrolledVersion++
|
|
30
|
+
uncontrolledTextSnapshot = 'type to update this'
|
|
31
|
+
uncontrolledCheckedSnapshot = true
|
|
32
|
+
rerender()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return () => (
|
|
36
|
+
<main
|
|
37
|
+
style={{
|
|
38
|
+
fontFamily: 'system-ui, sans-serif',
|
|
39
|
+
maxWidth: '860px',
|
|
40
|
+
margin: '24px auto',
|
|
41
|
+
padding: '0 16px',
|
|
42
|
+
lineHeight: 1.45,
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
<h1>Controlled vs Uncontrolled Values</h1>
|
|
46
|
+
|
|
47
|
+
<p>
|
|
48
|
+
Render count: <strong>{renderCount}</strong>
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
<div style={{ display: 'flex', gap: '10px', marginBottom: '18px' }}>
|
|
52
|
+
<button mix={[on('click', rerender)]}>Force Re-render</button>
|
|
53
|
+
<button mix={[on('click', resetControlled)]}>Reset Controlled</button>
|
|
54
|
+
<button mix={[on('click', remountUncontrolled)]}>Remount Uncontrolled</button>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<section
|
|
58
|
+
style={{
|
|
59
|
+
border: '1px solid #d0d7de',
|
|
60
|
+
borderRadius: '8px',
|
|
61
|
+
padding: '12px',
|
|
62
|
+
marginBottom: '12px',
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<h2>Controlled</h2>
|
|
66
|
+
<p>
|
|
67
|
+
These values come from component state. The text input allows everything except digits,
|
|
68
|
+
and invalid input does not call update.
|
|
69
|
+
</p>
|
|
70
|
+
|
|
71
|
+
<label style={{ display: 'block', marginBottom: '8px' }}>
|
|
72
|
+
Text:
|
|
73
|
+
<input
|
|
74
|
+
style={{ marginLeft: '8px' }}
|
|
75
|
+
value={controlledText}
|
|
76
|
+
mix={[
|
|
77
|
+
on('input', (event) => {
|
|
78
|
+
let nextValue = event.currentTarget.value
|
|
79
|
+
if (/\d/.test(nextValue)) {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
controlledText = nextValue
|
|
83
|
+
rerender()
|
|
84
|
+
}),
|
|
85
|
+
]}
|
|
86
|
+
/>
|
|
87
|
+
</label>
|
|
88
|
+
|
|
89
|
+
<label style={{ display: 'block', marginBottom: '8px' }}>
|
|
90
|
+
<input
|
|
91
|
+
type="checkbox"
|
|
92
|
+
checked={controlledChecked}
|
|
93
|
+
mix={[
|
|
94
|
+
on('change', (event) => {
|
|
95
|
+
controlledChecked = event.currentTarget.checked
|
|
96
|
+
rerender()
|
|
97
|
+
}),
|
|
98
|
+
]}
|
|
99
|
+
/>{' '}
|
|
100
|
+
Checked
|
|
101
|
+
</label>
|
|
102
|
+
|
|
103
|
+
<label style={{ display: 'block', marginBottom: '8px' }}>
|
|
104
|
+
Choice:
|
|
105
|
+
<select
|
|
106
|
+
style={{ marginLeft: '8px' }}
|
|
107
|
+
value={controlledChoice}
|
|
108
|
+
mix={[
|
|
109
|
+
on('change', (event) => {
|
|
110
|
+
controlledChoice = event.currentTarget.value
|
|
111
|
+
rerender()
|
|
112
|
+
}),
|
|
113
|
+
]}
|
|
114
|
+
>
|
|
115
|
+
<option value="alpha">Alpha</option>
|
|
116
|
+
<option value="beta">Beta</option>
|
|
117
|
+
<option value="gamma">Gamma</option>
|
|
118
|
+
</select>
|
|
119
|
+
</label>
|
|
120
|
+
|
|
121
|
+
<div>
|
|
122
|
+
State snapshot: text=<code>{JSON.stringify(controlledText)}</code>, checked=
|
|
123
|
+
<code>{String(controlledChecked)}</code>, choice=
|
|
124
|
+
<code>{JSON.stringify(controlledChoice)}</code>
|
|
125
|
+
</div>
|
|
126
|
+
</section>
|
|
127
|
+
|
|
128
|
+
<section
|
|
129
|
+
key={`uncontrolled-${uncontrolledVersion}`}
|
|
130
|
+
style={{
|
|
131
|
+
border: '1px solid #d0d7de',
|
|
132
|
+
borderRadius: '8px',
|
|
133
|
+
padding: '12px',
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
<h2>Uncontrolled</h2>
|
|
137
|
+
<p>
|
|
138
|
+
These initialize from <code>defaultValue/defaultChecked</code> once and then keep their
|
|
139
|
+
own DOM state.
|
|
140
|
+
</p>
|
|
141
|
+
|
|
142
|
+
<label style={{ display: 'block', marginBottom: '8px' }}>
|
|
143
|
+
Text:
|
|
144
|
+
<input
|
|
145
|
+
style={{ marginLeft: '8px' }}
|
|
146
|
+
defaultValue="type to update this"
|
|
147
|
+
mix={[
|
|
148
|
+
on('input', (event) => {
|
|
149
|
+
uncontrolledTextSnapshot = event.currentTarget.value
|
|
150
|
+
rerender()
|
|
151
|
+
}),
|
|
152
|
+
]}
|
|
153
|
+
/>
|
|
154
|
+
</label>
|
|
155
|
+
|
|
156
|
+
<label style={{ display: 'block', marginBottom: '8px' }}>
|
|
157
|
+
<input
|
|
158
|
+
type="checkbox"
|
|
159
|
+
defaultChecked={true}
|
|
160
|
+
mix={[
|
|
161
|
+
on('change', (event) => {
|
|
162
|
+
uncontrolledCheckedSnapshot = event.currentTarget.checked
|
|
163
|
+
rerender()
|
|
164
|
+
}),
|
|
165
|
+
]}
|
|
166
|
+
/>{' '}
|
|
167
|
+
Checked
|
|
168
|
+
</label>
|
|
169
|
+
|
|
170
|
+
<div>
|
|
171
|
+
Last DOM snapshot: text=<code>{JSON.stringify(uncontrolledTextSnapshot)}</code>, checked=
|
|
172
|
+
<code>{String(uncontrolledCheckedSnapshot)}</code>
|
|
173
|
+
</div>
|
|
174
|
+
</section>
|
|
175
|
+
</main>
|
|
176
|
+
)
|
|
177
|
+
}
|