@justanarthur/modo-ui 0.6.0-canary.8 → 0.6.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 +66 -164
- package/dist/base.css +4 -141
- package/dist/blocks/sidebar-app/index.js +26 -40
- package/dist/components/AI/input-message/index.js +16 -23
- package/dist/components/AI/thinking-indicator/index.js +6 -9
- package/dist/components/accordion/index.d.ts +11 -39
- package/dist/components/accordion/index.js +13 -18
- package/dist/components/badge/index.d.ts +11 -18
- package/dist/components/badge/index.js +7 -12
- package/dist/components/button/index.d.ts +17 -53
- package/dist/components/button/index.js +6 -11
- package/dist/components/card/index.d.ts +9 -32
- package/dist/components/card/index.js +11 -17
- package/dist/components/copy/index.d.ts +8 -41
- package/dist/components/copy/index.js +9 -15
- package/dist/components/inputs/checkbox/index.d.ts +9 -28
- package/dist/components/inputs/checkbox/index.js +13 -18
- package/dist/components/inputs/color-picker/index.d.ts +14 -22
- package/dist/components/inputs/color-picker/index.js +18 -26
- package/dist/components/inputs/combobox/index.d.ts +20 -85
- package/dist/components/inputs/combobox/index.js +18 -27
- package/dist/components/inputs/date-time/calendar/index.d.ts +18 -24
- package/dist/components/inputs/date-time/calendar/index.js +11 -15
- package/dist/components/inputs/date-time/date-picker/index.d.ts +16 -105
- package/dist/components/inputs/date-time/date-picker/index.js +25 -35
- package/dist/components/inputs/date-time/date-range-picker/index.d.ts +12 -106
- package/dist/components/inputs/date-time/date-range-picker/index.js +24 -35
- package/dist/components/inputs/date-time/duration-picker/index.d.ts +13 -66
- package/dist/components/inputs/date-time/duration-picker/index.js +24 -33
- package/dist/components/inputs/date-time/month-picker/index.d.ts +10 -60
- package/dist/components/inputs/date-time/month-picker/index.js +23 -32
- package/dist/components/inputs/date-time/time-picker/index.d.ts +10 -58
- package/dist/components/inputs/date-time/time-picker/index.js +24 -33
- package/dist/components/inputs/input/index.d.ts +13 -95
- package/dist/components/inputs/input/index.js +9 -18
- package/dist/components/inputs/radio/index.d.ts +5 -20
- package/dist/components/inputs/radio/index.js +12 -16
- package/dist/components/inputs/search/index.d.ts +3 -3
- package/dist/components/inputs/search/index.js +18 -26
- package/dist/components/inputs/select/index.d.ts +23 -99
- package/dist/components/inputs/select/index.js +14 -29
- package/dist/components/inputs/slider/index.d.ts +0 -9
- package/dist/components/inputs/slider/index.js +5 -8
- package/dist/components/inputs/switch/index.d.ts +2 -8
- package/dist/components/inputs/switch/index.js +12 -15
- package/dist/components/inputs/textarea/index.d.ts +1 -20
- package/dist/components/inputs/textarea/index.js +6 -9
- package/dist/components/navigation/sidebar/index.d.ts +21 -44
- package/dist/components/navigation/sidebar/index.js +31 -42
- package/dist/components/navigation/tabs/index.d.ts +2 -8
- package/dist/components/navigation/tabs/index.js +10 -15
- package/dist/components/navigation/tabs-subtle/index.js +10 -13
- package/dist/components/overlays/alert-dialog/index.d.ts +16 -135
- package/dist/components/overlays/alert-dialog/index.js +13 -23
- package/dist/components/overlays/command/index.d.ts +20 -25
- package/dist/components/overlays/command/index.js +17 -25
- package/dist/components/overlays/context-menu/index.d.ts +17 -39
- package/dist/components/overlays/context-menu/index.js +18 -29
- package/dist/components/overlays/dialog/index.d.ts +16 -42
- package/dist/components/overlays/dialog/index.js +12 -20
- package/dist/components/overlays/dropdown/index.d.ts +34 -304
- package/dist/components/overlays/dropdown/index.js +17 -44
- package/dist/components/overlays/popover/index.d.ts +12 -38
- package/dist/components/overlays/popover/index.js +9 -13
- package/dist/components/overlays/preview-card/index.js +8 -12
- package/dist/components/overlays/sheet/index.d.ts +8 -25
- package/dist/components/overlays/sheet/index.js +13 -19
- package/dist/components/overlays/toast/index.d.ts +50 -209
- package/dist/components/overlays/toast/index.js +10 -23
- package/dist/components/overlays/tooltip/index.d.ts +21 -123
- package/dist/components/overlays/tooltip/index.js +6 -16
- package/dist/components/table/index.d.ts +8 -63
- package/dist/components/table/index.js +9 -20
- package/dist/index.d.ts +1048 -2999
- package/dist/index.js +82 -231
- package/dist/preset.d.ts +0 -22
- package/dist/preset.js +2 -24
- package/dist/primitives/code/index.js +5 -8
- package/dist/primitives/craft/index.js +6 -10
- package/dist/primitives/fluid-hover/index.js +8 -10
- package/dist/primitives/morph/index.js +10 -16
- package/dist/primitives/motion/index.js +8 -11
- package/dist/primitives/scroll-area/index.js +3 -5
- package/dist/primitives/sizes/index.js +4 -6
- package/dist/primitives/surface/index.js +4 -6
- package/dist/shared/{components-4y816hwp.js → components-0q0sqfh4.js} +49 -27
- package/dist/shared/components-0sn7ypjw.js +61 -0
- package/dist/shared/{components-vc864c51.js → components-11da9ajg.js} +5 -8
- package/dist/shared/components-1434p1mh.js +123 -0
- package/dist/shared/{components-t2a3qmy9.js → components-1z6tdadk.js} +24 -40
- package/dist/shared/{components-qs4rkqsj.js → components-27nnvrwe.js} +14 -15
- package/dist/shared/{components-av5t26rc.js → components-2dxkv6zm.js} +7 -8
- package/dist/shared/{components-wqrvqq5p.js → components-2ecg8svx.js} +152 -234
- package/dist/shared/{components-3ydr2fqe.js → components-2ph6t61s.js} +34 -37
- package/dist/shared/{components-6gk118w3.js → components-39jq9va1.js} +24 -29
- package/dist/shared/components-3ky44908.js +9 -0
- package/dist/shared/{components-y1qvbawa.js → components-3mkepka5.js} +25 -28
- package/dist/shared/{components-xqszvkj4.js → components-4700p9kz.js} +22 -24
- package/dist/shared/{components-yhgq0qxx.js → components-59efxjd9.js} +12 -13
- package/dist/shared/{components-9j9abkct.js → components-5vgpnxgy.js} +27 -51
- package/dist/shared/{components-8tt3xbb5.js → components-5xc2j8jz.js} +18 -19
- package/dist/shared/{components-jymmaevv.js → components-6p8tfcd6.js} +29 -57
- package/dist/shared/{components-4nck8acq.js → components-7r3etddy.js} +54 -19
- package/dist/shared/{components-skfd7kff.js → components-8snj490h.js} +32 -78
- package/dist/shared/{components-ctqmge8t.js → components-b0eac83j.js} +30 -9
- package/dist/shared/{components-9sttq78t.js → components-b2wyq2cq.js} +59 -41
- package/dist/shared/{components-vkwq2k96.js → components-b3c5et5b.js} +38 -49
- package/dist/shared/{components-rrbpmfx4.js → components-c6s6b43g.js} +2 -3
- package/dist/shared/{components-pw06rbxq.js → components-chacnk61.js} +19 -19
- package/dist/shared/{components-71nbt03m.js → components-cv16mryv.js} +7 -26
- package/dist/shared/{components-eqze3y30.js → components-d6xtjht3.js} +30 -35
- package/dist/shared/{components-5y5shqfd.js → components-dk0gjr5a.js} +18 -19
- package/dist/shared/{components-khspp26b.js → components-fzrq6tr3.js} +34 -37
- package/dist/shared/{components-km7fhgbk.js → components-gc5vtq2f.js} +30 -66
- package/dist/shared/components-gf5s6ap9.js +42 -0
- package/dist/shared/{components-qqcmtrbv.js → components-gz8zf3ec.js} +55 -56
- package/dist/shared/{components-mattt5jp.js → components-h2ajmhwn.js} +35 -107
- package/dist/shared/{components-awra5sff.js → components-hqeq3w9q.js} +16 -24
- package/dist/shared/{components-5tzhqbcw.js → components-hw9yk32z.js} +24 -36
- package/dist/shared/components-hyq23phv.js +310 -0
- package/dist/shared/{components-8mt5bsv6.js → components-jbbmdr74.js} +22 -41
- package/dist/shared/{components-dcc02hs2.js → components-jjw69c4c.js} +4 -7
- package/dist/shared/{components-2syq4mwc.js → components-jthe1pwb.js} +420 -139
- package/dist/shared/{components-r7q1zm7r.js → components-jwst3vtj.js} +76 -224
- package/dist/shared/{components-qdm3yh26.js → components-kvr7f9hy.js} +10 -19
- package/dist/shared/{components-s8yqk2dc.js → components-kwa6mrf9.js} +11 -46
- package/dist/shared/{components-de99ykt7.js → components-men63rwj.js} +2 -3
- package/dist/shared/{components-6b0zb8z3.js → components-mjthm1kf.js} +9 -13
- package/dist/shared/{components-2c64r8c9.js → components-n42c79bz.js} +22 -23
- package/dist/shared/{components-tz1fv35x.js → components-n7mjr357.js} +2 -3
- package/dist/shared/components-ng72k97e.js +128 -0
- package/dist/shared/{components-dyae0y7h.js → components-pbmqctfa.js} +65 -169
- package/dist/shared/{components-px2zm7dc.js → components-q5xj5c16.js} +8 -9
- package/dist/shared/components-raknhppb.js +64 -0
- package/dist/shared/{components-8rb25gbe.js → components-rr7a6ja9.js} +27 -59
- package/dist/shared/{components-5012r6gr.js → components-s083f38z.js} +34 -164
- package/dist/shared/components-s5ngg32c.js +267 -0
- package/dist/shared/components-sn1rxx1d.js +143 -0
- package/dist/shared/{components-r8eqs7gz.js → components-ta7shw28.js} +4 -4
- package/dist/shared/{components-9as3rf68.js → components-vc7vq5nz.js} +3 -6
- package/dist/shared/{components-ft3zdc79.js → components-vfbhxf5r.js} +5 -6
- package/dist/shared/components-vynjtrk6.js +19 -0
- package/dist/shared/{components-w0xq6q9k.js → components-w1r67ag9.js} +149 -294
- package/dist/shared/{components-z0bqtvcp.js → components-w90endve.js} +72 -71
- package/dist/shared/{components-t5pk5nmc.js → components-wvp1m52g.js} +102 -149
- package/dist/shared/components-wy9jpqny.js +146 -0
- package/dist/shared/{components-5sxjeh6h.js → components-y9y0g8er.js} +28 -52
- package/dist/shared/components-ypp01w3p.js +986 -0
- package/dist/shared/{components-t50hrswb.js → components-zfnmn5d4.js} +36 -82
- package/dist/shared/{components-jqpefjsn.js → components-zrkzcayz.js} +6 -54
- package/dist/shared/{components-vf45j12c.js → components-zx6b6yby.js} +18 -4
- package/dist/styles.css +32 -320
- package/dist/tailwind.css +0 -16
- package/dist/tokens.css +0 -67
- package/package.json +4 -227
- package/DESIGN.md +0 -623
- package/blocks/sidebar-app/examples.mdx +0 -33
- package/blocks/sidebar-app/index.tsx +0 -218
- package/blocks/sidebar-app/inset-topbar.tsx +0 -25
- package/blocks/sidebar-app/menu-grid.ts +0 -26
- package/blocks/sidebar-app/nav-data.ts +0 -36
- package/blocks/sidebar-app/search-field.tsx +0 -48
- package/blocks/sidebar-app/user-footer.tsx +0 -67
- package/blocks/sidebar-app/workspace-header.tsx +0 -129
- package/components/AI/input-message/examples.mdx +0 -130
- package/components/AI/input-message/file-thumbnail.tsx +0 -173
- package/components/AI/input-message/index.tsx +0 -1385
- package/components/AI/thinking-indicator/examples.mdx +0 -27
- package/components/AI/thinking-indicator/index.tsx +0 -210
- package/components/accordion/examples.mdx +0 -233
- package/components/accordion/index.tsx +0 -1071
- package/components/avatar/examples.mdx +0 -95
- package/components/avatar/index.tsx +0 -129
- package/components/badge/change-demo.tsx +0 -29
- package/components/badge/examples.mdx +0 -124
- package/components/badge/index.tsx +0 -225
- package/components/badge/variants.ts +0 -73
- package/components/button/examples.mdx +0 -168
- package/components/button/index.tsx +0 -424
- package/components/button/loading-demo.tsx +0 -25
- package/components/button/variants.ts +0 -168
- package/components/card/examples.mdx +0 -251
- package/components/card/index.tsx +0 -1070
- package/components/copy/examples.mdx +0 -125
- package/components/copy/index.tsx +0 -662
- package/components/inputs/checkbox/examples.mdx +0 -87
- package/components/inputs/checkbox/index.tsx +0 -611
- package/components/inputs/checkbox/select-all-demo.tsx +0 -48
- package/components/inputs/color-picker/examples.mdx +0 -111
- package/components/inputs/color-picker/index.tsx +0 -1981
- package/components/inputs/color-picker/variants.ts +0 -401
- package/components/inputs/combobox/examples.mdx +0 -366
- package/components/inputs/combobox/index.tsx +0 -1527
- package/components/inputs/combobox/remote-demo.tsx +0 -105
- package/components/inputs/date-time/calendar/examples.mdx +0 -45
- package/components/inputs/date-time/calendar/index.tsx +0 -370
- package/components/inputs/date-time/calendar/use-month-window.ts +0 -173
- package/components/inputs/date-time/date-picker/examples.mdx +0 -243
- package/components/inputs/date-time/date-picker/index.tsx +0 -476
- package/components/inputs/date-time/date-picker/masked-date-input.tsx +0 -348
- package/components/inputs/date-time/date-range-picker/examples.mdx +0 -93
- package/components/inputs/date-time/date-range-picker/index.tsx +0 -525
- package/components/inputs/date-time/duration-picker/examples.mdx +0 -40
- package/components/inputs/date-time/duration-picker/index.tsx +0 -253
- package/components/inputs/date-time/month-picker/examples.mdx +0 -45
- package/components/inputs/date-time/month-picker/index.tsx +0 -254
- package/components/inputs/date-time/picker-shell.tsx +0 -581
- package/components/inputs/date-time/time-columns.tsx +0 -94
- package/components/inputs/date-time/time-picker/examples.mdx +0 -57
- package/components/inputs/date-time/time-picker/index.tsx +0 -227
- package/components/inputs/field/examples.mdx +0 -117
- package/components/inputs/field/index.tsx +0 -268
- package/components/inputs/file-dropzone/examples.mdx +0 -83
- package/components/inputs/file-dropzone/file-row.tsx +0 -201
- package/components/inputs/file-dropzone/index.tsx +0 -316
- package/components/inputs/file-dropzone/upload-demo.tsx +0 -67
- package/components/inputs/file-dropzone/variants.ts +0 -94
- package/components/inputs/input/examples.mdx +0 -166
- package/components/inputs/input/index.tsx +0 -494
- package/components/inputs/input/password-demo.tsx +0 -30
- package/components/inputs/label/examples.mdx +0 -67
- package/components/inputs/label/index.tsx +0 -85
- package/components/inputs/number-input/examples.mdx +0 -69
- package/components/inputs/number-input/index.tsx +0 -221
- package/components/inputs/radio/examples.mdx +0 -77
- package/components/inputs/radio/index.tsx +0 -636
- package/components/inputs/search/examples.mdx +0 -74
- package/components/inputs/search/index.tsx +0 -333
- package/components/inputs/select/examples.mdx +0 -296
- package/components/inputs/select/index.tsx +0 -1044
- package/components/inputs/select/variants.ts +0 -32
- package/components/inputs/slider/examples.mdx +0 -153
- package/components/inputs/slider/index.tsx +0 -1867
- package/components/inputs/switch/examples.mdx +0 -96
- package/components/inputs/switch/index.tsx +0 -504
- package/components/inputs/textarea/examples.mdx +0 -126
- package/components/inputs/textarea/index.tsx +0 -207
- package/components/navigation/navigation-menu/examples.mdx +0 -136
- package/components/navigation/navigation-menu/index.tsx +0 -704
- package/components/navigation/pagination/examples.mdx +0 -74
- package/components/navigation/pagination/index.tsx +0 -401
- package/components/navigation/sidebar/examples.mdx +0 -325
- package/components/navigation/sidebar/index.tsx +0 -453
- package/components/navigation/sidebar/sidebar-core.tsx +0 -1408
- package/components/navigation/sidebar/sidebar-menu.tsx +0 -1495
- package/components/navigation/sidebar/sidebar.css +0 -74
- package/components/navigation/sidebar/variants.ts +0 -55
- package/components/navigation/tabs/examples.mdx +0 -89
- package/components/navigation/tabs/index.tsx +0 -494
- package/components/navigation/tabs-subtle/examples.mdx +0 -49
- package/components/navigation/tabs-subtle/index.tsx +0 -512
- package/components/overlays/alert-dialog/examples.mdx +0 -123
- package/components/overlays/alert-dialog/index.tsx +0 -276
- package/components/overlays/alert-dialog/pending-demo.tsx +0 -36
- package/components/overlays/command/examples.mdx +0 -113
- package/components/overlays/command/index.tsx +0 -425
- package/components/overlays/context-menu/examples.mdx +0 -56
- package/components/overlays/context-menu/index.tsx +0 -279
- package/components/overlays/dialog/examples.mdx +0 -165
- package/components/overlays/dialog/index.tsx +0 -451
- package/components/overlays/dropdown/dropdown-search.tsx +0 -395
- package/components/overlays/dropdown/examples.mdx +0 -444
- package/components/overlays/dropdown/index.tsx +0 -1241
- package/components/overlays/dropdown/menu-item.tsx +0 -510
- package/components/overlays/dropdown/menu-parts.tsx +0 -725
- package/components/overlays/popover/anchored-demo.tsx +0 -59
- package/components/overlays/popover/examples.mdx +0 -203
- package/components/overlays/popover/index.tsx +0 -317
- package/components/overlays/preview-card/examples.mdx +0 -61
- package/components/overlays/preview-card/index.tsx +0 -204
- package/components/overlays/sheet/examples.mdx +0 -101
- package/components/overlays/sheet/index.tsx +0 -307
- package/components/overlays/toast/examples.mdx +0 -85
- package/components/overlays/toast/global-demo.tsx +0 -72
- package/components/overlays/toast/index.tsx +0 -544
- package/components/overlays/toast/manager.ts +0 -193
- package/components/overlays/tooltip/examples.mdx +0 -106
- package/components/overlays/tooltip/index.tsx +0 -539
- package/components/progress/examples.mdx +0 -59
- package/components/progress/index.tsx +0 -174
- package/components/progress/progress.css +0 -49
- package/components/progress/step-demo.tsx +0 -27
- package/components/table/examples.mdx +0 -188
- package/components/table/index.tsx +0 -535
- package/components/table/sort-demo.tsx +0 -61
- package/dist/components/avatar/index.d.ts +0 -46
- package/dist/components/avatar/index.js +0 -19
- package/dist/components/badge/variants.d.ts +0 -33
- package/dist/components/badge/variants.js +0 -10
- package/dist/components/button/variants.d.ts +0 -39
- package/dist/components/button/variants.js +0 -8
- package/dist/components/inputs/color-picker/variants.d.ts +0 -61
- package/dist/components/inputs/color-picker/variants.js +0 -28
- package/dist/components/inputs/field/index.d.ts +0 -116
- package/dist/components/inputs/field/index.js +0 -29
- package/dist/components/inputs/file-dropzone/index.d.ts +0 -167
- package/dist/components/inputs/file-dropzone/index.js +0 -33
- package/dist/components/inputs/file-dropzone/variants.d.ts +0 -35
- package/dist/components/inputs/file-dropzone/variants.js +0 -12
- package/dist/components/inputs/label/index.d.ts +0 -32
- package/dist/components/inputs/label/index.js +0 -11
- package/dist/components/inputs/number-input/index.d.ts +0 -87
- package/dist/components/inputs/number-input/index.js +0 -21
- package/dist/components/inputs/select/variants.d.ts +0 -5
- package/dist/components/inputs/select/variants.js +0 -6
- package/dist/components/navigation/navigation-menu/index.d.ts +0 -126
- package/dist/components/navigation/navigation-menu/index.js +0 -41
- package/dist/components/navigation/pagination/index.d.ts +0 -75
- package/dist/components/navigation/pagination/index.js +0 -41
- package/dist/components/navigation/sidebar/variants.d.ts +0 -25
- package/dist/components/navigation/sidebar/variants.js +0 -28
- package/dist/components/progress/index.d.ts +0 -56
- package/dist/components/progress/index.js +0 -15
- package/dist/docs/index.d.ts +0 -83
- package/dist/docs/index.js +0 -288
- package/dist/docs/vite.d.ts +0 -31
- package/dist/docs/vite.js +0 -45
- package/dist/lib/date-locale.d.ts +0 -26
- package/dist/lib/date-locale.js +0 -24
- package/dist/lib/field-classes.d.ts +0 -149
- package/dist/lib/field-classes.js +0 -41
- package/dist/lib/field-variants.d.ts +0 -47
- package/dist/lib/field-variants.js +0 -26
- package/dist/lib/fluid-hover-highlight.d.ts +0 -112
- package/dist/lib/fluid-hover-highlight.js +0 -17
- package/dist/lib/focus-ring.d.ts +0 -18
- package/dist/lib/focus-ring.js +0 -11
- package/dist/lib/goo-indicator.d.ts +0 -46
- package/dist/lib/goo-indicator.js +0 -14
- package/dist/lib/icon-context.d.ts +0 -30
- package/dist/lib/icon-context.js +0 -12
- package/dist/lib/inert.d.ts +0 -9
- package/dist/lib/inert.js +0 -8
- package/dist/lib/morph-layers.d.ts +0 -93
- package/dist/lib/morph-layers.js +0 -13
- package/dist/lib/morph-part.d.ts +0 -21
- package/dist/lib/morph-part.js +0 -12
- package/dist/lib/popup.d.ts +0 -20
- package/dist/lib/popup.js +0 -12
- package/dist/lib/reduced-motion.d.ts +0 -9
- package/dist/lib/reduced-motion.js +0 -6
- package/dist/lib/row-index.d.ts +0 -10
- package/dist/lib/row-index.js +0 -8
- package/dist/lib/row-order.d.ts +0 -28
- package/dist/lib/row-order.js +0 -10
- package/dist/lib/scroll-column.d.ts +0 -31
- package/dist/lib/scroll-column.js +0 -19
- package/dist/lib/shape-context.d.ts +0 -30
- package/dist/lib/shape-context.js +0 -13
- package/dist/lib/shape-radius.d.ts +0 -19
- package/dist/lib/shape-radius.js +0 -6
- package/dist/lib/size-context.d.ts +0 -94
- package/dist/lib/size-context.js +0 -20
- package/dist/lib/size-scale.d.ts +0 -79
- package/dist/lib/size-scale.js +0 -8
- package/dist/lib/slot.d.ts +0 -39
- package/dist/lib/slot.js +0 -14
- package/dist/lib/springs.d.ts +0 -44
- package/dist/lib/springs.js +0 -8
- package/dist/lib/surface-classes.d.ts +0 -19
- package/dist/lib/surface-classes.js +0 -10
- package/dist/lib/surface-context.d.ts +0 -9
- package/dist/lib/surface-context.js +0 -8
- package/dist/lib/use-controllable-state.d.ts +0 -2
- package/dist/lib/use-controllable-state.js +0 -6
- package/dist/lib/use-fluid-hover.d.ts +0 -138
- package/dist/lib/use-fluid-hover.js +0 -14
- package/dist/lib/use-is-mobile.d.ts +0 -2
- package/dist/lib/use-is-mobile.js +0 -6
- package/dist/lib/use-keyboard-nav-gate.d.ts +0 -20
- package/dist/lib/use-keyboard-nav-gate.js +0 -7
- package/dist/lib/use-merge-split.d.ts +0 -47
- package/dist/lib/use-merge-split.js +0 -15
- package/dist/lib/use-morph.d.ts +0 -101
- package/dist/lib/use-morph.js +0 -20
- package/dist/lib/use-touch-primary.d.ts +0 -2
- package/dist/lib/use-touch-primary.js +0 -6
- package/dist/lib/utils.d.ts +0 -3
- package/dist/lib/utils.js +0 -6
- package/dist/primitives/separator/index.d.ts +0 -25
- package/dist/primitives/separator/index.js +0 -9
- package/dist/primitives/skeleton/index.d.ts +0 -35
- package/dist/primitives/skeleton/index.js +0 -13
- package/dist/shared/components-124epb7f.js +0 -53
- package/dist/shared/components-1kab3j44.js +0 -337
- package/dist/shared/components-26snnbs9.js +0 -322
- package/dist/shared/components-34txk9rj.js +0 -35
- package/dist/shared/components-3h92d94m.js +0 -23
- package/dist/shared/components-4vfm04wq.js +0 -82
- package/dist/shared/components-54nq988h.js +0 -123
- package/dist/shared/components-7s7pykk5.js +0 -281
- package/dist/shared/components-7zkhz3r8.js +0 -399
- package/dist/shared/components-8v644a0s.js +0 -298
- package/dist/shared/components-a4evvq8h.js +0 -271
- package/dist/shared/components-adkarj8n.js +0 -35
- package/dist/shared/components-b1e239g3.js +0 -28
- package/dist/shared/components-b94t6qtj.js +0 -143
- package/dist/shared/components-c9yr4ed1.js +0 -44
- package/dist/shared/components-cwg1d0w6.js +0 -254
- package/dist/shared/components-d3shbwqq.js +0 -95
- package/dist/shared/components-dhgk6mbt.js +0 -16
- package/dist/shared/components-drxrtnp2.js +0 -25
- package/dist/shared/components-ej4r7wmj.js +0 -482
- package/dist/shared/components-f7h57m6t.js +0 -59
- package/dist/shared/components-fejdzvqc.js +0 -448
- package/dist/shared/components-gx4h5rbb.js +0 -40
- package/dist/shared/components-gyt7my41.js +0 -328
- package/dist/shared/components-h6p86be3.js +0 -19
- package/dist/shared/components-h83az0bz.js +0 -33
- package/dist/shared/components-hdzrqqpx.js +0 -55
- package/dist/shared/components-krtx3m5b.js +0 -84
- package/dist/shared/components-kswxnm3h.js +0 -24
- package/dist/shared/components-mpa2y6x8.js +0 -277
- package/dist/shared/components-mrbaxt8q.js +0 -62
- package/dist/shared/components-pt5ftm4p.js +0 -38
- package/dist/shared/components-rpw4md2b.js +0 -179
- package/dist/shared/components-rtadhd9j.js +0 -64
- package/dist/shared/components-tz4gaf1v.js +0 -23
- package/dist/shared/components-wbbckbw8.js +0 -34
- package/dist/shared/components-xdqmt6ed.js +0 -20
- package/dist/shared/components-xjkdvnbg.js +0 -107
- package/dist/shared/components-z3v0n0de.js +0 -11
- package/dist/shared/components-z6dfqtyt.js +0 -1545
- package/dist/tokens/colors.css +0 -230
- package/dist/tokens/motion.css +0 -40
- package/dist/tokens/radius.css +0 -10
- package/dist/tokens/spacing.css +0 -9
- package/dist/tokens/typography.css +0 -119
- package/docs/chrome.css +0 -120
- package/docs/chrome.tsx +0 -283
- package/docs/index.ts +0 -9
- package/docs/vite.ts +0 -85
- package/index.ts +0 -60
- package/lib/date-locale.ts +0 -195
- package/lib/field-classes.ts +0 -121
- package/lib/field-variants.ts +0 -117
- package/lib/fluid-hover-highlight.tsx +0 -139
- package/lib/focus-ring.tsx +0 -46
- package/lib/goo-indicator.tsx +0 -261
- package/lib/icon-context.tsx +0 -310
- package/lib/inert.ts +0 -22
- package/lib/morph-layers.tsx +0 -102
- package/lib/morph-part.tsx +0 -49
- package/lib/popup.ts +0 -52
- package/lib/reduced-motion.ts +0 -19
- package/lib/row-index.tsx +0 -29
- package/lib/row-order.tsx +0 -126
- package/lib/scroll-column.tsx +0 -273
- package/lib/shape-context.tsx +0 -138
- package/lib/shape-radius.ts +0 -67
- package/lib/size-context.tsx +0 -66
- package/lib/size-scale.ts +0 -115
- package/lib/slot.ts +0 -115
- package/lib/springs.ts +0 -62
- package/lib/surface-classes.ts +0 -58
- package/lib/surface-context.tsx +0 -21
- package/lib/use-controllable-state.ts +0 -44
- package/lib/use-fluid-hover.ts +0 -541
- package/lib/use-is-mobile.ts +0 -25
- package/lib/use-keyboard-nav-gate.ts +0 -45
- package/lib/use-merge-split.tsx +0 -209
- package/lib/use-morph.ts +0 -557
- package/lib/use-touch-primary.tsx +0 -43
- package/lib/utils.ts +0 -37
- package/preset.ts +0 -199
- package/primitives/code/examples.mdx +0 -27
- package/primitives/code/index.tsx +0 -80
- package/primitives/craft/demos.tsx +0 -173
- package/primitives/craft/examples.mdx +0 -142
- package/primitives/craft/index.tsx +0 -76
- package/primitives/fluid-hover/examples.mdx +0 -84
- package/primitives/fluid-hover/index.tsx +0 -316
- package/primitives/morph/examples.mdx +0 -139
- package/primitives/morph/index.tsx +0 -192
- package/primitives/motion/examples.mdx +0 -100
- package/primitives/motion/index.tsx +0 -160
- package/primitives/scroll-area/examples.mdx +0 -58
- package/primitives/scroll-area/index.tsx +0 -204
- package/primitives/separator/examples.mdx +0 -30
- package/primitives/separator/index.tsx +0 -53
- package/primitives/sizes/examples.mdx +0 -167
- package/primitives/sizes/index.tsx +0 -89
- package/primitives/skeleton/examples.mdx +0 -51
- package/primitives/skeleton/index.tsx +0 -72
- package/primitives/skeleton/skeleton.css +0 -15
- package/primitives/surface/examples.mdx +0 -173
- package/primitives/surface/index.tsx +0 -109
- package/primitives/surface/surface.css +0 -176
- /package/dist/shared/{components-3ey0hzpb.js → components-1v7rxytx.js} +0 -0
- /package/dist/shared/{components-jcnjay5g.js → components-ezxhrmer.js} +0 -0
|
@@ -1,1241 +0,0 @@
|
|
|
1
|
-
// biome-ignore-all lint: vendored upstream code keeps its own patterns (see the header)
|
|
2
|
-
/*
|
|
3
|
-
* Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
|
|
4
|
-
* `registry/base/dropdown.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
|
|
5
|
-
* MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
|
|
6
|
-
* Local modifications:
|
|
7
|
-
* - `"use client"` dropped; `framer-motion` → `motion/react`; `@/lib/*` and
|
|
8
|
-
* `@/hooks/*` → `../../../lib/*` (`SizeProvider` → `../../../primitives/sizes`);
|
|
9
|
-
* `@/lib/elevated` → `../../../primitives/surface`; `@/components/ui/scroll-area`
|
|
10
|
-
* → `../../../primitives/scroll-area`; `@/components/ui/fluid-hover-highlight` →
|
|
11
|
-
* `../../../lib/fluid-hover-highlight`; `@/components/ui/menu-item` and
|
|
12
|
-
* `@/components/ui/dropdown-search` → `./menu-item`, `./dropdown-search`
|
|
13
|
-
* (both vendored into this folder).
|
|
14
|
-
* - Uncontrolled selection (Base UI's contract) on `Dropdown` and
|
|
15
|
-
* `Dropdown.Content`: `defaultCheckedIndex` / `defaultCheckedIndices` plus
|
|
16
|
-
* `onCheckedIndexChange` / `onCheckedIndicesChange`, through
|
|
17
|
-
* `useControllableState`. With a `default*` the panel owns the selection,
|
|
18
|
-
* so rows need no `checked` or `onSelect` of their own. `checkedIndex` /
|
|
19
|
-
* `checkedIndices` keep working exactly as upstream.
|
|
20
|
-
* - Uncontrolled search: `Dropdown.Search` with `filter` hands the query to
|
|
21
|
-
* the panel, which drops the `Dropdown.Item` rows whose `label` doesn't
|
|
22
|
-
* match, re-indexes the survivors for fluid hover, and shows
|
|
23
|
-
* `Dropdown.Empty` only when nothing matched — the work the controlled
|
|
24
|
-
* form asks the consumer to do. Selection stays keyed on the authored
|
|
25
|
-
* index (`sourceIndex` on the row), so it survives re-indexing.
|
|
26
|
-
* - `DropdownProps` members re-declared one per line with the FF docs
|
|
27
|
-
* API-table descriptions (modo's props table lists only members declared in
|
|
28
|
-
* the interface body).
|
|
29
|
-
* - `items[next].focus()` guarded for `noUncheckedIndexedAccess`.
|
|
30
|
-
* - Compound statics `Dropdown.Menu/.Trigger/.Content/.Item/.Label/
|
|
31
|
-
* .Separator/.Search/.Empty` attached with `Object.assign`, typed through a
|
|
32
|
-
* `DropdownComponent` cast on the forwardRef. Upstream's named exports are
|
|
33
|
-
* kept.
|
|
34
|
-
* - modo item: TSDoc + examples from the FF "Dropdown" docs page (its
|
|
35
|
-
* "Create from the query" section is skipped — adding a row to the list is
|
|
36
|
-
* real consumer state, not something an uncontrolled panel can stand in
|
|
37
|
-
* for), and a default export.
|
|
38
|
-
* - Styling reads DS tokens (AGENTS.md styling): `text-[Npx]` →
|
|
39
|
-
* `text-<role>[-compact]`; the hex focus-ring fallback →
|
|
40
|
-
* `ring-focus-ring` / `border-focus-ring`; `duration-80|120|160` and
|
|
41
|
-
* tier-length JS durations → `duration-<tier>` / `spring.*`.
|
|
42
|
-
* - The popup menu morphs out of its trigger through the shared morph layer
|
|
43
|
-
* (`lib/use-morph.ts` + `MorphSurface`, goo by default on
|
|
44
|
-
* `spring.goo`; `Dropdown.Content` takes `from` / `effect` /
|
|
45
|
-
* `hideSource` / `tier`), the origin captured from `Menu.Root`'s
|
|
46
|
-
* `onOpenChange`. The `scaleY` motion wrapper and the `actionsRef` deferred
|
|
47
|
-
* unmount are gone: Menu ignores `actionsRef`, and the wrapper animated
|
|
48
|
-
* outside `Menu.Popup`, so Base UI unmounted the menu before its exit
|
|
49
|
-
* played. The popup paints its level through the morph's surface layers
|
|
50
|
-
* instead of `render={<Elevated/>}`, re-providing it with `SurfaceProvider`.
|
|
51
|
-
* - `renderMenuItem` passes the row's activation to the Menu item primitive's
|
|
52
|
-
* `onClick` (see menu-item.tsx), so Enter and Space pick a row instead of
|
|
53
|
-
* only closing the menu.
|
|
54
|
-
* - `effect="morph"` with `hideSource` opens the menu in place over its
|
|
55
|
-
* trigger: the positioner's `sideOffset` becomes the engine's `inPlaceOffset`.
|
|
56
|
-
* - shadcn DropdownMenu parity (local parts in `menu-parts.tsx`, numbering in
|
|
57
|
-
* `row-order.tsx`): children-based rows with an optional `index` (both
|
|
58
|
-
* panels provide `RowOrderContext`), `Dropdown.Group`, `Dropdown.Shortcut`,
|
|
59
|
-
* value-keyed `Dropdown.CheckboxItem` and `Dropdown.RadioGroup` /
|
|
60
|
-
* `Dropdown.RadioItem` (their checked rows join the merged selection
|
|
61
|
-
* background), and submenus (`Dropdown.Sub` / `.SubTrigger` /
|
|
62
|
-
* `.SubContent`). `Dropdown.Label` takes `inset` and names a surrounding
|
|
63
|
-
* group (`Menu.GroupLabel`); labels and separators take a place in the row
|
|
64
|
-
* order so selection runs never melt across them.
|
|
65
|
-
* - `Dropdown.Trigger` also takes Radix-style `asChild` (`lib/slot.ts`).
|
|
66
|
-
* - Radix-parity options, all optional: `Dropdown.Menu` forwards Base UI's
|
|
67
|
-
* change details (`reason`, `cancel()`) to `onOpenChange` and takes `modal`;
|
|
68
|
-
* `Dropdown.Content` / `Dropdown.SubContent` take `finalFocus`,
|
|
69
|
-
* `initialFocus` (modo's own: Base UI 1.4.1's Menu.Popup has none),
|
|
70
|
-
* `collisionPadding`, `positionerClassName`, `container`, and spread HTML
|
|
71
|
-
* attributes onto the popup (the panel's own handlers run after theirs);
|
|
72
|
-
* `Dropdown.Content` also takes `alignOffset` and `collisionBoundary`;
|
|
73
|
-
* `Dropdown.Item` takes `render` / `asChild` and `textValue`, the other row
|
|
74
|
-
* parts `textValue`.
|
|
75
|
-
* - `initialFocus={false}` only drops modo's own first-row focus. Base UI still
|
|
76
|
-
* focuses the first row on a keyboard open and the popup on a pointer open;
|
|
77
|
-
* that initial focus can't be suppressed.
|
|
78
|
-
* - `onKeyDownCapture` on `Dropdown.Content` / `Dropdown.SubContent` runs first.
|
|
79
|
-
* `preventDefault` there is harmless: navigation and typeahead still run.
|
|
80
|
-
* `stopPropagation` there stops Base UI's key handling and the panel's own
|
|
81
|
-
* arrow navigation (both bubble-phase); the panel's capture-phase bookkeeping
|
|
82
|
-
* (the keyboard ring, typing redirected into `Dropdown.Search`) still runs.
|
|
83
|
-
* - The popup panel shares `useMenuPanel` with `Dropdown.SubContent`: a
|
|
84
|
-
* keyboard focus ring gated on keyboard use (as in Select), the highlight
|
|
85
|
-
* pinned on a submenu trigger while its submenu is open, and handlers that
|
|
86
|
-
* ignore events bubbling up (through the portal) from a submenu. Its
|
|
87
|
-
* `renderMenuItem` is the shared `renderPopupItem`.
|
|
88
|
-
*/
|
|
89
|
-
|
|
90
|
-
import type { MenuRootChangeEventDetails, MenuTriggerProps } from '@base-ui/react/menu'
|
|
91
|
-
// Aliased: a bare `Menu` import makes the .d.ts bundler rename the `Menu` static too
|
|
92
|
-
import { Menu as BaseMenu } from '@base-ui/react/menu'
|
|
93
|
-
import { AnimatePresence, motion } from 'motion/react'
|
|
94
|
-
import {
|
|
95
|
-
Children,
|
|
96
|
-
type ComponentProps,
|
|
97
|
-
cloneElement,
|
|
98
|
-
createContext,
|
|
99
|
-
type ForwardRefExoticComponent,
|
|
100
|
-
forwardRef,
|
|
101
|
-
type HTMLAttributes,
|
|
102
|
-
isValidElement,
|
|
103
|
-
type ReactElement,
|
|
104
|
-
type ReactNode,
|
|
105
|
-
type RefAttributes,
|
|
106
|
-
type RefObject,
|
|
107
|
-
useCallback,
|
|
108
|
-
useContext,
|
|
109
|
-
useEffect,
|
|
110
|
-
useImperativeHandle,
|
|
111
|
-
useMemo,
|
|
112
|
-
useRef,
|
|
113
|
-
useState,
|
|
114
|
-
} from 'react'
|
|
115
|
-
import { FluidHoverHighlight } from '../../../lib/fluid-hover-highlight'
|
|
116
|
-
import { FocusRing } from '../../../lib/focus-ring'
|
|
117
|
-
import { MorphSurface } from '../../../lib/morph-layers'
|
|
118
|
-
import { isDisabledRow, popupScrollAreaClass, popupViewportClass } from '../../../lib/popup'
|
|
119
|
-
import { RowOrderContext, useRowOrder, useRowSlot } from '../../../lib/row-order'
|
|
120
|
-
import { shapeMap } from '../../../lib/shape-context'
|
|
121
|
-
import { type SizeVariant, useSize } from '../../../lib/size-context'
|
|
122
|
-
import { slotId } from '../../../lib/slot'
|
|
123
|
-
import { spring } from '../../../lib/springs'
|
|
124
|
-
import { SURFACE_BG, SURFACE_SHADOW } from '../../../lib/surface-classes'
|
|
125
|
-
import { SurfaceProvider, useSurface } from '../../../lib/surface-context'
|
|
126
|
-
import { useControllableState } from '../../../lib/use-controllable-state'
|
|
127
|
-
import { type ItemRect, useFluidHover } from '../../../lib/use-fluid-hover'
|
|
128
|
-
import { SelectionBackgrounds, useMergeSplitBlocks, useSelectionRuns } from '../../../lib/use-merge-split'
|
|
129
|
-
import { inPlaceOffset, type MorphOrigin, opensInPlace, useMorph, useMorphOrigin } from '../../../lib/use-morph'
|
|
130
|
-
import { cn } from '../../../lib/utils'
|
|
131
|
-
import { ScrollArea } from '../../../primitives/scroll-area'
|
|
132
|
-
import { SizeProvider } from '../../../primitives/sizes'
|
|
133
|
-
import { Elevated } from '../../../primitives/surface'
|
|
134
|
-
import {
|
|
135
|
-
DropdownEmpty,
|
|
136
|
-
DropdownFilterContext,
|
|
137
|
-
type DropdownFilterContextValue,
|
|
138
|
-
DropdownSearch,
|
|
139
|
-
DropdownSearchHostContext,
|
|
140
|
-
type DropdownSearchProps,
|
|
141
|
-
useDropdownSearchHost,
|
|
142
|
-
} from './dropdown-search'
|
|
143
|
-
import {
|
|
144
|
-
DropdownContext,
|
|
145
|
-
type DropdownContextValue,
|
|
146
|
-
DropdownShortcut,
|
|
147
|
-
type DropdownShortcutProps,
|
|
148
|
-
INSET_PX,
|
|
149
|
-
MenuItem,
|
|
150
|
-
type MenuItemProps,
|
|
151
|
-
type MenuItemRenderOptions,
|
|
152
|
-
useDropdown,
|
|
153
|
-
useDropdownMaybe,
|
|
154
|
-
} from './menu-item'
|
|
155
|
-
import {
|
|
156
|
-
chain,
|
|
157
|
-
DropdownCheckboxItem,
|
|
158
|
-
type DropdownCheckboxItemProps,
|
|
159
|
-
DropdownGroup,
|
|
160
|
-
DropdownGroupContext,
|
|
161
|
-
type DropdownGroupProps,
|
|
162
|
-
DropdownRadioGroup,
|
|
163
|
-
type DropdownRadioGroupProps,
|
|
164
|
-
DropdownRadioItem,
|
|
165
|
-
type DropdownRadioItemProps,
|
|
166
|
-
DropdownSub,
|
|
167
|
-
DropdownSubContent,
|
|
168
|
-
type DropdownSubContentProps,
|
|
169
|
-
type DropdownSubProps,
|
|
170
|
-
DropdownSubTrigger,
|
|
171
|
-
type DropdownSubTriggerProps,
|
|
172
|
-
type MenuPopupProps,
|
|
173
|
-
type MenuPortalProps,
|
|
174
|
-
mergeRefs,
|
|
175
|
-
ownEvent,
|
|
176
|
-
renderPopupItem,
|
|
177
|
-
useMenuPanel,
|
|
178
|
-
useValueChecked,
|
|
179
|
-
} from './menu-parts'
|
|
180
|
-
|
|
181
|
-
// Dropdown opts out of the global pill/rounded shape context — popover surfaces
|
|
182
|
-
// look cleaner with the smaller "rounded" radii regardless of how the rest of
|
|
183
|
-
// the UI is shaped (the heavy pill bubbling distorts perceived padding at this
|
|
184
|
-
// scale and produces the corner-shadow asymmetry).
|
|
185
|
-
const shape = shapeMap.rounded
|
|
186
|
-
|
|
187
|
-
// ---------------------------------------------------------------------------
|
|
188
|
-
// Panel context — shared by the inline Dropdown and the popup DropdownContent.
|
|
189
|
-
//
|
|
190
|
-
// The context object itself lives in menu-item.tsx so MenuItem resolves
|
|
191
|
-
// whichever dropdown provider actually wraps it, even when dropdowns built
|
|
192
|
-
// on different primitives render side by side. Re-exported here so the
|
|
193
|
-
// public dropdown API is unchanged.
|
|
194
|
-
// ---------------------------------------------------------------------------
|
|
195
|
-
|
|
196
|
-
export type { DropdownContextValue, MenuItemRenderOptions }
|
|
197
|
-
export { useDropdown, useDropdownMaybe }
|
|
198
|
-
|
|
199
|
-
/* "Nothing checked", so useControllableState always sees a defined value
|
|
200
|
-
while the caller drives the selection (`undefined` is what it reads as
|
|
201
|
-
"uncontrolled"). */
|
|
202
|
-
const NONE = -1
|
|
203
|
-
const NO_INDICES: number[] = []
|
|
204
|
-
/* Stable empty rect list, so a closed popup's merge/split pass gets the same
|
|
205
|
-
array every render instead of a fresh literal. */
|
|
206
|
-
const NO_RECTS: ItemRect[] = []
|
|
207
|
-
|
|
208
|
-
interface PanelSelectionProps {
|
|
209
|
-
checkedIndex?: number
|
|
210
|
-
defaultCheckedIndex?: number
|
|
211
|
-
onCheckedIndexChange?: (index: number | undefined) => void
|
|
212
|
-
checkedIndices?: number[]
|
|
213
|
-
defaultCheckedIndices?: number[]
|
|
214
|
-
onCheckedIndicesChange?: (indices: number[]) => void
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/*
|
|
218
|
-
* The panel's selection, in the two index spaces it lives in: every prop and
|
|
219
|
-
* callback speaks the AUTHORED index (what a row was written with), while the
|
|
220
|
-
* overlays and the row context speak the RENDERED index (what a filtering
|
|
221
|
-
* panel re-indexed the visible rows to). They are the same list unless a
|
|
222
|
-
* `Dropdown.Search filter` is dropping rows.
|
|
223
|
-
*/
|
|
224
|
-
function usePanelSelection(
|
|
225
|
-
{
|
|
226
|
-
checkedIndex,
|
|
227
|
-
defaultCheckedIndex,
|
|
228
|
-
onCheckedIndexChange,
|
|
229
|
-
checkedIndices,
|
|
230
|
-
defaultCheckedIndices,
|
|
231
|
-
onCheckedIndicesChange,
|
|
232
|
-
}: PanelSelectionProps,
|
|
233
|
-
sourceOf: number[],
|
|
234
|
-
filtering: boolean,
|
|
235
|
-
) {
|
|
236
|
-
const [single, setSingle] = useControllableState<number>(
|
|
237
|
-
defaultCheckedIndex === undefined ? (checkedIndex ?? NONE) : undefined,
|
|
238
|
-
defaultCheckedIndex ?? NONE,
|
|
239
|
-
index => onCheckedIndexChange?.(index === NONE ? undefined : index),
|
|
240
|
-
)
|
|
241
|
-
const [many, setMany] = useControllableState<number[]>(
|
|
242
|
-
defaultCheckedIndices === undefined ? (checkedIndices ?? NO_INDICES) : undefined,
|
|
243
|
-
defaultCheckedIndices ?? NO_INDICES,
|
|
244
|
-
indices => onCheckedIndicesChange?.(indices),
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
const multiple = checkedIndices != null || defaultCheckedIndices != null
|
|
248
|
-
// Only a `default*` makes the panel answer for rows that brought no
|
|
249
|
-
// `checked` of their own.
|
|
250
|
-
const selfManaged = defaultCheckedIndex !== undefined || defaultCheckedIndices !== undefined
|
|
251
|
-
|
|
252
|
-
const toggleIndex = useCallback(
|
|
253
|
-
(source: number) => {
|
|
254
|
-
if (multiple) {
|
|
255
|
-
setMany(prev =>
|
|
256
|
-
prev.includes(source) ? prev.filter(i => i !== source) : [...prev, source].sort((a, b) => a - b),
|
|
257
|
-
)
|
|
258
|
-
return
|
|
259
|
-
}
|
|
260
|
-
setSingle(source)
|
|
261
|
-
},
|
|
262
|
-
[multiple, setMany, setSingle],
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
const renderedIndex = useMemo(() => {
|
|
266
|
-
if (single === NONE) return undefined
|
|
267
|
-
const i = filtering ? sourceOf.indexOf(single) : single
|
|
268
|
-
return i === -1 ? undefined : i
|
|
269
|
-
}, [single, filtering, sourceOf])
|
|
270
|
-
|
|
271
|
-
const renderedIndices = useMemo(() => {
|
|
272
|
-
if (!multiple) return undefined
|
|
273
|
-
if (!filtering) return many
|
|
274
|
-
return many.map(s => sourceOf.indexOf(s)).filter(i => i !== -1)
|
|
275
|
-
}, [multiple, many, filtering, sourceOf])
|
|
276
|
-
|
|
277
|
-
return {
|
|
278
|
-
checkedIndex: renderedIndex,
|
|
279
|
-
checkedIndices: renderedIndices,
|
|
280
|
-
multiple,
|
|
281
|
-
selfManaged,
|
|
282
|
-
toggleIndex,
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
interface PanelRows {
|
|
287
|
-
/** What to render: the rows re-indexed and `Dropdown.Empty` dropped while
|
|
288
|
-
* anything matched, or the children untouched when nothing filters. */
|
|
289
|
-
content: ReactNode
|
|
290
|
-
/** Rendered index → authored index. Empty while nothing filters. */
|
|
291
|
-
sourceOf: number[]
|
|
292
|
-
filtering: boolean
|
|
293
|
-
/** The query to publish to a `Dropdown.Search filter`, or null. */
|
|
294
|
-
filterCtx: DropdownFilterContextValue | null
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/*
|
|
298
|
-
* Uncontrolled search. A `Dropdown.Search` with `filter` among the panel's
|
|
299
|
-
* direct children hands the panel the query; the panel then keeps only the
|
|
300
|
-
* rows (anything with a string `label` and a numeric `index` — MenuItem, or a
|
|
301
|
-
* wrapper that forwards both) whose label contains it, re-indexes them from 0
|
|
302
|
-
* for fluid hover, and stamps each with the index it was authored with so the
|
|
303
|
-
* selection survives. `Dropdown.Empty` children are held back and rendered
|
|
304
|
-
* only when no row matched.
|
|
305
|
-
*/
|
|
306
|
-
function usePanelRows(children: ReactNode): PanelRows {
|
|
307
|
-
const [query, setQuery] = useState('')
|
|
308
|
-
|
|
309
|
-
const filtering = useMemo(
|
|
310
|
-
() =>
|
|
311
|
-
Children.toArray(children).some(
|
|
312
|
-
child => isValidElement(child) && child.type === DropdownSearch && (child.props as { filter?: boolean }).filter,
|
|
313
|
-
),
|
|
314
|
-
[children],
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
const filterCtx = useMemo(() => ({ query, setQuery }), [query])
|
|
318
|
-
|
|
319
|
-
const { content, sourceOf } = useMemo(() => {
|
|
320
|
-
if (!filtering) return { content: children, sourceOf: NO_INDICES }
|
|
321
|
-
const needle = query.trim().toLowerCase()
|
|
322
|
-
const rendered: ReactNode[] = []
|
|
323
|
-
const empties: ReactNode[] = []
|
|
324
|
-
const sources: number[] = []
|
|
325
|
-
for (const child of Children.toArray(children)) {
|
|
326
|
-
if (!isValidElement(child)) {
|
|
327
|
-
rendered.push(child)
|
|
328
|
-
continue
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (child.type === DropdownEmpty) {
|
|
332
|
-
empties.push(child)
|
|
333
|
-
continue
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const rowProps = child.props as { label?: unknown; index?: unknown }
|
|
337
|
-
if (typeof rowProps.label !== 'string' || typeof rowProps.index !== 'number') {
|
|
338
|
-
rendered.push(child)
|
|
339
|
-
continue
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (needle && !rowProps.label.toLowerCase().includes(needle)) continue
|
|
343
|
-
|
|
344
|
-
rendered.push(
|
|
345
|
-
cloneElement(child as ReactElement<Record<string, unknown>>, {
|
|
346
|
-
index: sources.length,
|
|
347
|
-
sourceIndex: rowProps.index,
|
|
348
|
-
}),
|
|
349
|
-
)
|
|
350
|
-
sources.push(rowProps.index)
|
|
351
|
-
}
|
|
352
|
-
if (sources.length === 0) rendered.push(...empties)
|
|
353
|
-
return { content: rendered as ReactNode, sourceOf: sources }
|
|
354
|
-
}, [children, filtering, query])
|
|
355
|
-
|
|
356
|
-
return { content, sourceOf, filtering, filterCtx: filtering ? filterCtx : null }
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// ---------------------------------------------------------------------------
|
|
360
|
-
// Dropdown (inline panel)
|
|
361
|
-
//
|
|
362
|
-
// An always-rendered panel — no trigger, positioning, or dismissal. Because it
|
|
363
|
-
// sits statically in the page it does NOT claim popup menu semantics: the
|
|
364
|
-
// container is a plain role="group" (pass `aria-label` to name it). The real
|
|
365
|
-
// role="menu" lives on the popup DropdownContent below, which Base UI wires to
|
|
366
|
-
// a trigger. Consumers who hand-roll a trigger around the inline panel get
|
|
367
|
-
// grouping semantics rather than a falsely-announced popup menu.
|
|
368
|
-
// ---------------------------------------------------------------------------
|
|
369
|
-
|
|
370
|
-
interface DropdownProps extends HTMLAttributes<HTMLDivElement> {
|
|
371
|
-
/** `Dropdown.Item` rows, plus `Dropdown.Label` and `Dropdown.Separator`. */
|
|
372
|
-
children: ReactNode
|
|
373
|
-
/** The checked row: drives the animated selected background and the rows' radio semantics. */
|
|
374
|
-
checkedIndex?: number
|
|
375
|
-
/** Uncontrolled twin of `checkedIndex`: the panel owns the selection, so rows need no `checked` or `onSelect` of their own. */
|
|
376
|
-
defaultCheckedIndex?: number
|
|
377
|
-
/** Called with the newly checked row, or `undefined` when nothing is checked. */
|
|
378
|
-
onCheckedIndexChange?: (index: number | undefined) => void
|
|
379
|
-
/** Multiple selection: the checked rows. Rows become checkbox items and contiguous runs share one merged background (see Checkbox.Group). */
|
|
380
|
-
checkedIndices?: number[]
|
|
381
|
-
/** Uncontrolled twin of `checkedIndices`: the panel toggles rows itself. */
|
|
382
|
-
defaultCheckedIndices?: number[]
|
|
383
|
-
/** Called with every checked row after a toggle. */
|
|
384
|
-
onCheckedIndicesChange?: (indices: number[]) => void
|
|
385
|
-
/** Pins the panel's rows to one step of the size ladder (default 36px, compact 28px — see Sizes). Omitted, they follow the surrounding SizeProvider. */
|
|
386
|
-
size?: SizeVariant
|
|
387
|
-
/** Extra classes for the panel surface — it is 18rem wide by default. */
|
|
388
|
-
className?: string
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
type DropdownComponent = ForwardRefExoticComponent<DropdownProps & RefAttributes<HTMLDivElement>> & {
|
|
392
|
-
Menu: typeof DropdownMenu
|
|
393
|
-
Trigger: typeof DropdownTrigger
|
|
394
|
-
Content: typeof DropdownContent
|
|
395
|
-
Item: typeof MenuItem
|
|
396
|
-
Label: typeof DropdownLabel
|
|
397
|
-
Separator: typeof DropdownSeparator
|
|
398
|
-
Search: typeof DropdownSearch
|
|
399
|
-
Empty: typeof DropdownEmpty
|
|
400
|
-
Group: typeof DropdownGroup
|
|
401
|
-
Shortcut: typeof DropdownShortcut
|
|
402
|
-
CheckboxItem: typeof DropdownCheckboxItem
|
|
403
|
-
RadioGroup: typeof DropdownRadioGroup
|
|
404
|
-
RadioItem: typeof DropdownRadioItem
|
|
405
|
-
Sub: typeof DropdownSub
|
|
406
|
-
SubTrigger: typeof DropdownSubTrigger
|
|
407
|
-
SubContent: typeof DropdownSubContent
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* A menu in two forms: an inline panel that sits in the page, or a popup on
|
|
412
|
-
* any trigger you like.
|
|
413
|
-
*
|
|
414
|
-
* `Dropdown` is the inline panel — an elevated surface with the fluid hover
|
|
415
|
-
* background gliding between rows, a selected background that springs to the
|
|
416
|
-
* picked one, contiguous picks merging into a single block in multiple mode,
|
|
417
|
-
* and an animated focus ring. `Dropdown.Menu` + `Dropdown.Trigger` +
|
|
418
|
-
* `Dropdown.Content` is the same panel as a popup on Base UI's Menu, which
|
|
419
|
-
* brings the positioning, dismissal, roving highlight, typeahead and
|
|
420
|
-
* close-on-select; the popup oozes out of its trigger and back through the
|
|
421
|
-
* shared morph (see Morph), goo by default. Both take `checkedIndex` / `checkedIndices` to be driven
|
|
422
|
-
* from outside, or `defaultCheckedIndex` / `defaultCheckedIndices` to run
|
|
423
|
-
* themselves — and a `Dropdown.Search` marked `filter` makes the panel own
|
|
424
|
-
* the query too, keeping only the rows whose `label` matches and showing
|
|
425
|
-
* `Dropdown.Empty` when none do.
|
|
426
|
-
*
|
|
427
|
-
* Rows also come shadcn-style: children instead of a `label`, no `index` (a
|
|
428
|
-
* row takes its place in document order, so the hover, the keyboard ring and
|
|
429
|
-
* the selection backgrounds still find it, inside groups and behind
|
|
430
|
-
* conditions alike), `inset` to line up with rows that have an icon and a
|
|
431
|
-
* trailing `shortcut`. `Dropdown.CheckboxItem` and `Dropdown.RadioGroup` /
|
|
432
|
-
* `Dropdown.RadioItem` select by their own state and value instead of by
|
|
433
|
-
* index, their checked rows melting into one background like a multiple
|
|
434
|
-
* selection. A `Dropdown.Sub` opens a submenu from its trigger row (ArrowRight,
|
|
435
|
-
* a press, or a resting pointer) that grows out of that row and lifts above
|
|
436
|
-
* the menu, with a highlight and a keyboard ring of its own. Every popup
|
|
437
|
-
* lifts 2 surface levels off whatever it opens over, a Dialog included.
|
|
438
|
-
*
|
|
439
|
-
* Statics:
|
|
440
|
-
* - `Dropdown.Menu` — the popup root: `open`, `defaultOpen`, `onOpenChange`, `disabled`, `size`.
|
|
441
|
-
* - `Dropdown.Trigger` — the trigger: `render` or `asChild`.
|
|
442
|
-
* - `Dropdown.Content` — the popup panel: `side`, `align`, `sideOffset`, the
|
|
443
|
-
* index selection, and the morph options `from`, `effect`, `hideSource`, `tier`.
|
|
444
|
-
* - `Dropdown.Item` — a row: `label` or `children`, `index`, `icon`, `inset`,
|
|
445
|
-
* `shortcut`, `checked`, `onSelect`, `closeOnClick`, `disabled`.
|
|
446
|
-
* - `Dropdown.CheckboxItem` — a row toggling itself: `checked`, `defaultChecked`, `onCheckedChange`.
|
|
447
|
-
* - `Dropdown.RadioGroup` / `Dropdown.RadioItem` — one choice by value: `value`,
|
|
448
|
-
* `defaultValue`, `onValueChange` on the group, `value` on each row.
|
|
449
|
-
* - `Dropdown.Group` / `Dropdown.Label` — a named run of rows and its caption (`inset`).
|
|
450
|
-
* - `Dropdown.Separator` — a rule between runs.
|
|
451
|
-
* - `Dropdown.Shortcut` — a muted caption at a row's end.
|
|
452
|
-
* - `Dropdown.Sub` / `Dropdown.SubTrigger` / `Dropdown.SubContent` — a submenu:
|
|
453
|
-
* its state, its trigger row (`openOnHover`, `delay`), its panel (`side`,
|
|
454
|
-
* `align`, `sideOffset`, `alignOffset`, `from`, `effect`, `tier`).
|
|
455
|
-
* - `Dropdown.Search` / `Dropdown.Empty` — a filter field and its no-match row.
|
|
456
|
-
*
|
|
457
|
-
* @example {@include ./examples.mdx}
|
|
458
|
-
*/
|
|
459
|
-
const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
|
|
460
|
-
(
|
|
461
|
-
{
|
|
462
|
-
children,
|
|
463
|
-
checkedIndex,
|
|
464
|
-
defaultCheckedIndex,
|
|
465
|
-
onCheckedIndexChange,
|
|
466
|
-
checkedIndices,
|
|
467
|
-
defaultCheckedIndices,
|
|
468
|
-
onCheckedIndicesChange,
|
|
469
|
-
size,
|
|
470
|
-
className,
|
|
471
|
-
...props
|
|
472
|
-
},
|
|
473
|
-
ref,
|
|
474
|
-
) => {
|
|
475
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
476
|
-
const hover = useFluidHover(containerRef, { isItemDisabled: isDisabledRow })
|
|
477
|
-
const { activeIndex, setActiveIndex, itemRects, handlers, registerItem } = hover
|
|
478
|
-
|
|
479
|
-
const [focusedIndex, setFocusedIndex] = useState<number | null>(null)
|
|
480
|
-
const rowOrder = useRowOrder()
|
|
481
|
-
const { valueChecked, reportChecked } = useValueChecked()
|
|
482
|
-
|
|
483
|
-
const rows = usePanelRows(children)
|
|
484
|
-
const selection = usePanelSelection(
|
|
485
|
-
{
|
|
486
|
-
checkedIndex,
|
|
487
|
-
defaultCheckedIndex,
|
|
488
|
-
onCheckedIndexChange,
|
|
489
|
-
checkedIndices,
|
|
490
|
-
defaultCheckedIndices,
|
|
491
|
-
onCheckedIndicesChange,
|
|
492
|
-
},
|
|
493
|
-
rows.sourceOf,
|
|
494
|
-
rows.filtering,
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
const multiple = selection.multiple
|
|
498
|
-
const checkedRect = !multiple && selection.checkedIndex != null ? itemRects[selection.checkedIndex] : null
|
|
499
|
-
const focusRect = focusedIndex !== null ? itemRects[focusedIndex] : null
|
|
500
|
-
// Multiple: one merged block per contiguous run of checked rows, value-
|
|
501
|
-
// checked rows (CheckboxItem, RadioItem) included.
|
|
502
|
-
const runs = useSelectionRuns(
|
|
503
|
-
useMemo(
|
|
504
|
-
() => [...(selection.checkedIndices ?? NO_INDICES), ...valueChecked],
|
|
505
|
-
[selection.checkedIndices, valueChecked],
|
|
506
|
-
),
|
|
507
|
-
)
|
|
508
|
-
const blocks = useMergeSplitBlocks(runs, itemRects, shape.bgRadius)
|
|
509
|
-
const panelCtx = useMemo(
|
|
510
|
-
() => ({
|
|
511
|
-
registerItem,
|
|
512
|
-
activeIndex,
|
|
513
|
-
checkedIndex: selection.checkedIndex,
|
|
514
|
-
multiple,
|
|
515
|
-
checkedIndices: selection.checkedIndices,
|
|
516
|
-
selfManaged: selection.selfManaged,
|
|
517
|
-
toggleIndex: selection.toggleIndex,
|
|
518
|
-
reportChecked,
|
|
519
|
-
}),
|
|
520
|
-
[
|
|
521
|
-
registerItem,
|
|
522
|
-
activeIndex,
|
|
523
|
-
selection.checkedIndex,
|
|
524
|
-
multiple,
|
|
525
|
-
selection.checkedIndices,
|
|
526
|
-
selection.selfManaged,
|
|
527
|
-
selection.toggleIndex,
|
|
528
|
-
reportChecked,
|
|
529
|
-
],
|
|
530
|
-
)
|
|
531
|
-
const panel = (
|
|
532
|
-
<RowOrderContext.Provider value={rowOrder}>
|
|
533
|
-
<DropdownContext.Provider value={panelCtx}>
|
|
534
|
-
<DropdownFilterContext.Provider value={rows.filterCtx}>
|
|
535
|
-
<Elevated
|
|
536
|
-
offset={2}
|
|
537
|
-
shadowLevel={3}
|
|
538
|
-
ref={node => {
|
|
539
|
-
;(containerRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
540
|
-
if (typeof ref === 'function') ref(node)
|
|
541
|
-
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
542
|
-
}}
|
|
543
|
-
onMouseEnter={handlers.onMouseEnter}
|
|
544
|
-
onMouseMove={handlers.onMouseMove}
|
|
545
|
-
onMouseLeave={handlers.onMouseLeave}
|
|
546
|
-
onClick={handlers.onClick}
|
|
547
|
-
onFocus={e => {
|
|
548
|
-
const indexAttr = (e.target as HTMLElement)
|
|
549
|
-
.closest('[data-fluid-hover-index]')
|
|
550
|
-
?.getAttribute('data-fluid-hover-index')
|
|
551
|
-
if (indexAttr != null) {
|
|
552
|
-
const idx = Number(indexAttr)
|
|
553
|
-
setActiveIndex(idx)
|
|
554
|
-
setFocusedIndex((e.target as HTMLElement).matches(':focus-visible') ? idx : null)
|
|
555
|
-
}
|
|
556
|
-
}}
|
|
557
|
-
onBlur={e => {
|
|
558
|
-
if (containerRef.current?.contains(e.relatedTarget as Node)) return
|
|
559
|
-
setFocusedIndex(null)
|
|
560
|
-
setActiveIndex(null)
|
|
561
|
-
}}
|
|
562
|
-
onKeyDown={e => {
|
|
563
|
-
const items = Array.from(
|
|
564
|
-
containerRef.current?.querySelectorAll(
|
|
565
|
-
'[role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"]',
|
|
566
|
-
) ?? [],
|
|
567
|
-
) as HTMLElement[]
|
|
568
|
-
const currentIdx = items.indexOf(e.target as HTMLElement)
|
|
569
|
-
if (currentIdx === -1) return
|
|
570
|
-
|
|
571
|
-
if (['ArrowDown', 'ArrowUp', 'ArrowRight', 'ArrowLeft'].includes(e.key)) {
|
|
572
|
-
e.preventDefault()
|
|
573
|
-
const next = ['ArrowDown', 'ArrowRight'].includes(e.key)
|
|
574
|
-
? (currentIdx + 1) % items.length
|
|
575
|
-
: (currentIdx - 1 + items.length) % items.length
|
|
576
|
-
items[next]?.focus()
|
|
577
|
-
} else if (e.key === 'Home') {
|
|
578
|
-
e.preventDefault()
|
|
579
|
-
items[0]?.focus()
|
|
580
|
-
} else if (e.key === 'End') {
|
|
581
|
-
e.preventDefault()
|
|
582
|
-
items[items.length - 1]?.focus()
|
|
583
|
-
}
|
|
584
|
-
}}
|
|
585
|
-
role="group"
|
|
586
|
-
className={cn(`relative flex flex-col w-72 max-w-full p-1 select-none`, className)}
|
|
587
|
-
{...props}
|
|
588
|
-
>
|
|
589
|
-
{/* Selected backgrounds — merged runs in multiple mode and value-checked rows */}
|
|
590
|
-
<SelectionBackgrounds blocks={blocks} />
|
|
591
|
-
|
|
592
|
-
{/* Selected background */}
|
|
593
|
-
<AnimatePresence>
|
|
594
|
-
{checkedRect && (
|
|
595
|
-
<motion.div
|
|
596
|
-
className={`absolute ${shape.bg} bg-active pointer-events-none`}
|
|
597
|
-
initial={false}
|
|
598
|
-
animate={{
|
|
599
|
-
top: checkedRect.top,
|
|
600
|
-
left: checkedRect.left,
|
|
601
|
-
width: checkedRect.width,
|
|
602
|
-
height: checkedRect.height,
|
|
603
|
-
opacity: 1,
|
|
604
|
-
}}
|
|
605
|
-
exit={{ opacity: 0, transition: spring.moderate.exit }}
|
|
606
|
-
transition={{
|
|
607
|
-
...spring.moderate,
|
|
608
|
-
opacity: { duration: spring.fast.duration },
|
|
609
|
-
}}
|
|
610
|
-
/>
|
|
611
|
-
)}
|
|
612
|
-
</AnimatePresence>
|
|
613
|
-
|
|
614
|
-
{/* Hover background */}
|
|
615
|
-
<FluidHoverHighlight hover={hover} from={checkedRect} className={shape.bg} />
|
|
616
|
-
|
|
617
|
-
{/* Focus ring */}
|
|
618
|
-
<AnimatePresence>
|
|
619
|
-
{focusRect && (
|
|
620
|
-
<motion.div
|
|
621
|
-
className={`absolute ${shape.focusRing} pointer-events-none z-20 border border-focus-ring`}
|
|
622
|
-
initial={false}
|
|
623
|
-
animate={{
|
|
624
|
-
left: focusRect.left - 2,
|
|
625
|
-
top: focusRect.top - 2,
|
|
626
|
-
width: focusRect.width + 4,
|
|
627
|
-
height: focusRect.height + 4,
|
|
628
|
-
}}
|
|
629
|
-
exit={{ opacity: 0, transition: spring.fast.exit }}
|
|
630
|
-
transition={{
|
|
631
|
-
...spring.fast,
|
|
632
|
-
opacity: { duration: spring.fast.duration },
|
|
633
|
-
}}
|
|
634
|
-
/>
|
|
635
|
-
)}
|
|
636
|
-
</AnimatePresence>
|
|
637
|
-
|
|
638
|
-
{rows.content}
|
|
639
|
-
</Elevated>
|
|
640
|
-
</DropdownFilterContext.Provider>
|
|
641
|
-
</DropdownContext.Provider>
|
|
642
|
-
</RowOrderContext.Provider>
|
|
643
|
-
)
|
|
644
|
-
|
|
645
|
-
// A size prop pins every row in the panel to one ladder step.
|
|
646
|
-
return size ? <SizeProvider size={size}>{panel}</SizeProvider> : panel
|
|
647
|
-
},
|
|
648
|
-
) as DropdownComponent
|
|
649
|
-
|
|
650
|
-
Dropdown.displayName = 'Dropdown'
|
|
651
|
-
|
|
652
|
-
// ---------------------------------------------------------------------------
|
|
653
|
-
// DropdownMenu (popup root)
|
|
654
|
-
//
|
|
655
|
-
// Built on Base UI's Menu primitive, which owns the trigger wiring,
|
|
656
|
-
// positioning (collision flipping, anchor tracking), dismissal (outside
|
|
657
|
-
// press, focus-out, Escape), roving highlight, typeahead, and close-on-select.
|
|
658
|
-
// This layer keeps the fluid-hover overlays and the morph open/close
|
|
659
|
-
// (lib/use-morph.ts) — the same pattern as select.tsx.
|
|
660
|
-
// ---------------------------------------------------------------------------
|
|
661
|
-
|
|
662
|
-
interface DropdownMenuContextValue {
|
|
663
|
-
open: boolean
|
|
664
|
-
origin: RefObject<MorphOrigin>
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
const DropdownMenuContext = createContext<DropdownMenuContextValue | null>(null)
|
|
668
|
-
|
|
669
|
-
function useDropdownMenuContext() {
|
|
670
|
-
const ctx = useContext(DropdownMenuContext)
|
|
671
|
-
if (!ctx) throw new Error('DropdownMenu compound components must be inside <DropdownMenu>')
|
|
672
|
-
return ctx
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
interface DropdownMenuProps {
|
|
676
|
-
/** `Dropdown.Trigger` and `Dropdown.Content`. */
|
|
677
|
-
children: ReactNode
|
|
678
|
-
/** Controlled open state. Pair with `onOpenChange`. */
|
|
679
|
-
open?: boolean
|
|
680
|
-
/** Initial open state — uncontrolled. Defaults to `false`. */
|
|
681
|
-
defaultOpen?: boolean
|
|
682
|
-
/** Called when the menu opens or closes, with Base UI's event details: `reason` (why: `'escape-key'`, `'outside-press'`, `'focus-out'`, `'item-press'`, `'trigger-press'`…), `event`, and `cancel()`, which keeps the menu as it is. A callback that takes only `open` keeps working. */
|
|
683
|
-
onOpenChange?: (open: boolean, details: MenuRootChangeEventDetails) => void
|
|
684
|
-
/** Disables the menu. Defaults to `false`. */
|
|
685
|
-
disabled?: boolean
|
|
686
|
-
/** Locks the page scroll while the menu is open and keeps the pointer off everything outside it. Non-modal, the page keeps scrolling and the popup follows its trigger. Defaults to `false`. */
|
|
687
|
-
modal?: boolean
|
|
688
|
-
/** Pins trigger-side content and the portalled popup rows to one step of the size ladder (default 36px, compact 28px — see Sizes). Omitted, they follow the surrounding SizeProvider. */
|
|
689
|
-
size?: SizeVariant
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
function DropdownMenu({
|
|
693
|
-
children,
|
|
694
|
-
open: openProp,
|
|
695
|
-
defaultOpen = false,
|
|
696
|
-
onOpenChange,
|
|
697
|
-
disabled = false,
|
|
698
|
-
modal = false,
|
|
699
|
-
size,
|
|
700
|
-
}: DropdownMenuProps) {
|
|
701
|
-
const [internalOpen, setInternalOpen] = useState(defaultOpen)
|
|
702
|
-
const open = openProp !== undefined ? openProp : internalOpen
|
|
703
|
-
const { origin, capture } = useMorphOrigin()
|
|
704
|
-
|
|
705
|
-
const handleOpenChange = useCallback(
|
|
706
|
-
(next: boolean, details: MenuRootChangeEventDetails) => {
|
|
707
|
-
onOpenChange?.(next, details)
|
|
708
|
-
if (details.isCanceled) return
|
|
709
|
-
if (next) capture(details)
|
|
710
|
-
if (openProp === undefined) setInternalOpen(next)
|
|
711
|
-
},
|
|
712
|
-
[openProp, onOpenChange, capture],
|
|
713
|
-
)
|
|
714
|
-
|
|
715
|
-
const ctx = useMemo(() => ({ open, origin }), [open, origin])
|
|
716
|
-
|
|
717
|
-
// A size prop pins the whole compound (trigger content + portalled popup —
|
|
718
|
-
// React context crosses portals) to one ladder step.
|
|
719
|
-
const root = (
|
|
720
|
-
<DropdownMenuContext.Provider value={ctx}>
|
|
721
|
-
<BaseMenu.Root
|
|
722
|
-
open={open}
|
|
723
|
-
onOpenChange={handleOpenChange}
|
|
724
|
-
disabled={disabled}
|
|
725
|
-
// Non-modal by default: the page keeps scrolling and the Positioner
|
|
726
|
-
// tracks the anchor, so the popup follows its trigger instead of detaching.
|
|
727
|
-
modal={modal}
|
|
728
|
-
>
|
|
729
|
-
{children}
|
|
730
|
-
</BaseMenu.Root>
|
|
731
|
-
</DropdownMenuContext.Provider>
|
|
732
|
-
)
|
|
733
|
-
|
|
734
|
-
return size ? <SizeProvider size={size}>{root}</SizeProvider> : root
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
DropdownMenu.displayName = 'DropdownMenu'
|
|
738
|
-
|
|
739
|
-
// ---------------------------------------------------------------------------
|
|
740
|
-
// DropdownTrigger
|
|
741
|
-
//
|
|
742
|
-
// Base UI's Menu.Trigger under the library name. Composes via the `render`
|
|
743
|
-
// prop, or Radix-style `asChild` onto its single child, so any element can
|
|
744
|
-
// be the trigger:
|
|
745
|
-
//
|
|
746
|
-
// <DropdownTrigger render={<Button variant="secondary">Open</Button>} />
|
|
747
|
-
// <DropdownTrigger asChild><Button variant="secondary">Open</Button></DropdownTrigger>
|
|
748
|
-
// ---------------------------------------------------------------------------
|
|
749
|
-
|
|
750
|
-
type DropdownTriggerProps = MenuTriggerProps & {
|
|
751
|
-
/** Compose onto the single child instead of `render`. Defaults to `false`. */
|
|
752
|
-
asChild?: boolean
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
const DropdownTrigger = forwardRef<HTMLButtonElement, DropdownTriggerProps>(
|
|
756
|
-
({ asChild, render, children, ...props }, ref) =>
|
|
757
|
-
asChild && isValidElement(children) ? (
|
|
758
|
-
<BaseMenu.Trigger
|
|
759
|
-
ref={ref}
|
|
760
|
-
render={children as ReactElement<Record<string, unknown>>}
|
|
761
|
-
{...props}
|
|
762
|
-
id={slotId(children, props.id)}
|
|
763
|
-
/>
|
|
764
|
-
) : (
|
|
765
|
-
<BaseMenu.Trigger ref={ref} render={render} {...props} id={slotId(render, props.id)}>
|
|
766
|
-
{children}
|
|
767
|
-
</BaseMenu.Trigger>
|
|
768
|
-
),
|
|
769
|
-
)
|
|
770
|
-
|
|
771
|
-
DropdownTrigger.displayName = 'DropdownTrigger'
|
|
772
|
-
|
|
773
|
-
// ---------------------------------------------------------------------------
|
|
774
|
-
// DropdownContent (popup panel)
|
|
775
|
-
//
|
|
776
|
-
// Portal > Positioner > Popup carrying the exact inline-panel visuals: the
|
|
777
|
-
// surface (painted by the morph layer), fluid-hover overlays, animated
|
|
778
|
-
// selected background, and animated focus ring. Children are wrapped in a Menu.RadioGroup so
|
|
779
|
-
// radio-style MenuItems (boolean `checked`) get correct aria-checked from
|
|
780
|
-
// `checkedIndex`.
|
|
781
|
-
// ---------------------------------------------------------------------------
|
|
782
|
-
|
|
783
|
-
type MenuPositionerProps = ComponentProps<typeof BaseMenu.Positioner>
|
|
784
|
-
|
|
785
|
-
interface DropdownContentProps extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {
|
|
786
|
-
/** `Dropdown.Item` rows, plus `Dropdown.Label`, `Dropdown.Separator`, `Dropdown.Search`, `Dropdown.Empty`. */
|
|
787
|
-
children: ReactNode
|
|
788
|
-
/** Extra classes for the popup surface. */
|
|
789
|
-
className?: string
|
|
790
|
-
/** Classes for the portalled positioner element: a z utility here lifts the menu above other fixed layers (the positioner is `z-50`), e.g. `z-[60]`. */
|
|
791
|
-
positionerClassName?: string
|
|
792
|
-
/** Index of the checked item. Drives the animated selected background and
|
|
793
|
-
* the radio-group value announced to assistive tech. */
|
|
794
|
-
checkedIndex?: number
|
|
795
|
-
/** Uncontrolled twin of `checkedIndex`: the popup owns the selection, so rows need no `checked` or `onSelect` of their own. */
|
|
796
|
-
defaultCheckedIndex?: number
|
|
797
|
-
/** Called with the newly checked row, or `undefined` when nothing is checked. */
|
|
798
|
-
onCheckedIndexChange?: (index: number | undefined) => void
|
|
799
|
-
/** Multiple selection: the checked rows. Rows become checkbox items that
|
|
800
|
-
* keep the menu open when toggled, and contiguous runs share one merged
|
|
801
|
-
* background (see Checkbox.Group). */
|
|
802
|
-
checkedIndices?: number[]
|
|
803
|
-
/** Uncontrolled twin of `checkedIndices`: the popup toggles rows itself. */
|
|
804
|
-
defaultCheckedIndices?: number[]
|
|
805
|
-
/** Called with every checked row after a toggle. */
|
|
806
|
-
onCheckedIndicesChange?: (indices: number[]) => void
|
|
807
|
-
/** Side of the trigger the popup prefers. Defaults to `"bottom"`. */
|
|
808
|
-
side?: MenuPositionerProps['side']
|
|
809
|
-
/** Alignment against the trigger. Defaults to `"start"`. */
|
|
810
|
-
align?: MenuPositionerProps['align']
|
|
811
|
-
/** Gap to the trigger, in px. Ignored when the menu opens in place over its trigger (`effect="morph"` with `hideSource`). Defaults to `6`. */
|
|
812
|
-
sideOffset?: number
|
|
813
|
-
/** Shift along the trigger's edge, in px. Defaults to `0`. */
|
|
814
|
-
alignOffset?: MenuPositionerProps['alignOffset']
|
|
815
|
-
/** Space to keep between the menu and the edge of the collision boundary, in px (a number, or per side). Defaults to `5`. */
|
|
816
|
-
collisionPadding?: MenuPositionerProps['collisionPadding']
|
|
817
|
-
/** The element or rectangle the menu stays inside when it flips and shifts. Defaults to `'clipping-ancestors'`. */
|
|
818
|
-
collisionBoundary?: MenuPositionerProps['collisionBoundary']
|
|
819
|
-
/** The element the portalled menu mounts into, e.g. a Dialog's popup so it stays inside its focus trap. Defaults to `document.body`. */
|
|
820
|
-
container?: MenuPortalProps['container']
|
|
821
|
-
/** Where focus goes once the menu has closed: `false` leaves it, a ref moves it there (a text field after a pick), a function picks by how it closed. Defaults to `undefined`: Base UI's own return to the trigger. */
|
|
822
|
-
finalFocus?: MenuPopupProps['finalFocus']
|
|
823
|
-
/** An element to focus once the menu has opened, in place of the first row, or `false` to drop modo's own first-row focus. Base UI 1.4.1's Menu.Popup has no such option, so this is modo's: it acts a frame after the open. `false` does not silence Base UI's own initial focus, which still lands on the first row after a keyboard open and on the popup after a pointer open. The ref may be a fresh object each render: it is read when the menu opens, and only a change to or from `false` re-runs it. Defaults to `undefined`: the first row (the search field when there is one). */
|
|
824
|
-
initialFocus?: boolean | RefObject<HTMLElement | null>
|
|
825
|
-
/** Key handler on the popup, in the capture phase, ahead of the panel's own. `preventDefault` is harmless: navigation and typeahead still run. `stopPropagation` stops Base UI's key handling and the panel's arrow navigation; the keyboard ring and typing redirected into `Dropdown.Search` still run. Defaults to `undefined`. */
|
|
826
|
-
onKeyDownCapture?: HTMLAttributes<HTMLDivElement>['onKeyDownCapture']
|
|
827
|
-
/** Where the menu grows from (see Morph): the trigger, the pointer, its own center, a viewport edge, or a ref to any element. Defaults to `'trigger'`. */
|
|
828
|
-
from?: 'trigger' | 'pointer' | 'center' | 'top' | 'right' | 'bottom' | 'left' | RefObject<HTMLElement | null>
|
|
829
|
-
/** How it grows (see Morph): with the liquid goo neck, a plain morph, a slide or a fade. Defaults to `'goo'`. */
|
|
830
|
-
effect?: 'goo' | 'morph' | 'slide' | 'fade'
|
|
831
|
-
/** Hide the trigger while open, so it reads as turning into the menu. Defaults to `false`. */
|
|
832
|
-
hideSource?: boolean
|
|
833
|
-
/** Spring tier of a plain morph, slide or fade; goo runs on its own `spring.goo`. Defaults to `'moderate'`. */
|
|
834
|
-
tier?: 'moderate' | 'slow'
|
|
835
|
-
/** Keep the rows mounted (hidden) while the menu is closed, so rows that run their own state — `Dropdown.CheckboxItem defaultChecked`, `Dropdown.RadioGroup defaultValue`, an uncontrolled `Dropdown.Sub` — keep it across opens. Defaults to `false`. */
|
|
836
|
-
keepMounted?: boolean
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
const DropdownContent = forwardRef<HTMLDivElement, DropdownContentProps>(
|
|
840
|
-
(
|
|
841
|
-
{
|
|
842
|
-
className,
|
|
843
|
-
children,
|
|
844
|
-
checkedIndex,
|
|
845
|
-
defaultCheckedIndex,
|
|
846
|
-
onCheckedIndexChange,
|
|
847
|
-
checkedIndices,
|
|
848
|
-
defaultCheckedIndices,
|
|
849
|
-
onCheckedIndicesChange,
|
|
850
|
-
side = 'bottom',
|
|
851
|
-
align = 'start',
|
|
852
|
-
sideOffset = 6,
|
|
853
|
-
alignOffset,
|
|
854
|
-
collisionPadding,
|
|
855
|
-
collisionBoundary,
|
|
856
|
-
container,
|
|
857
|
-
finalFocus,
|
|
858
|
-
initialFocus,
|
|
859
|
-
positionerClassName,
|
|
860
|
-
from,
|
|
861
|
-
effect,
|
|
862
|
-
hideSource,
|
|
863
|
-
tier = 'moderate',
|
|
864
|
-
keepMounted = false,
|
|
865
|
-
onKeyDownCapture,
|
|
866
|
-
onMouseEnter,
|
|
867
|
-
onMouseMove,
|
|
868
|
-
onMouseLeave,
|
|
869
|
-
onClick,
|
|
870
|
-
onFocus,
|
|
871
|
-
onBlur,
|
|
872
|
-
...popupProps
|
|
873
|
-
},
|
|
874
|
-
ref,
|
|
875
|
-
) => {
|
|
876
|
-
const { open, origin } = useDropdownMenuContext()
|
|
877
|
-
const morph = useMorph(open, origin, { from, effect, hideSource, tier })
|
|
878
|
-
useImperativeHandle(ref, () => morph.popup as HTMLDivElement, [morph.popup])
|
|
879
|
-
// Lifts 2 levels off its substrate with a fixed shadow (see Elevated).
|
|
880
|
-
const level = Math.min(useSurface() + 2, 8)
|
|
881
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
882
|
-
|
|
883
|
-
const panel = useMenuPanel(containerRef, open)
|
|
884
|
-
const { hover, focusRow, setFocusedIndex } = panel
|
|
885
|
-
const { setActiveIndex, itemRects, handlers, registerItem, remeasure } = hover
|
|
886
|
-
|
|
887
|
-
const rows = usePanelRows(children)
|
|
888
|
-
const selection = usePanelSelection(
|
|
889
|
-
{
|
|
890
|
-
checkedIndex,
|
|
891
|
-
defaultCheckedIndex,
|
|
892
|
-
onCheckedIndexChange,
|
|
893
|
-
checkedIndices,
|
|
894
|
-
defaultCheckedIndices,
|
|
895
|
-
onCheckedIndicesChange,
|
|
896
|
-
},
|
|
897
|
-
rows.sourceOf,
|
|
898
|
-
rows.filtering,
|
|
899
|
-
)
|
|
900
|
-
|
|
901
|
-
// An optional DropdownSearch child: typing on a focused row is
|
|
902
|
-
// redirected into the field. (The field takes focus itself, a frame
|
|
903
|
-
// after the primitive's own open autofocus.)
|
|
904
|
-
const {
|
|
905
|
-
host: searchHost,
|
|
906
|
-
hasSearch,
|
|
907
|
-
searchMounted,
|
|
908
|
-
onKeyDownCapture: redirectTypingToSearch,
|
|
909
|
-
isSearchField,
|
|
910
|
-
highlightFirst,
|
|
911
|
-
} = useDropdownSearchHost(open, { containerRef, setActiveIndex })
|
|
912
|
-
|
|
913
|
-
// Open ready to act: focus the first enabled row (a mounted search field
|
|
914
|
-
// takes focus itself instead). A frame after the primitive's own open
|
|
915
|
-
// autofocus, which lands on the popup for pointer opens.
|
|
916
|
-
// Read through a ref: a fresh `{ current }` object each render must not re-run the effect.
|
|
917
|
-
const initialFocusRef = useRef(initialFocus)
|
|
918
|
-
initialFocusRef.current = initialFocus
|
|
919
|
-
const keepsFocus = initialFocus === false
|
|
920
|
-
useEffect(() => {
|
|
921
|
-
if (!open || keepsFocus) return
|
|
922
|
-
let inner: number | undefined
|
|
923
|
-
const outer = requestAnimationFrame(() => {
|
|
924
|
-
inner = requestAnimationFrame(() => {
|
|
925
|
-
const target = initialFocusRef.current
|
|
926
|
-
if (typeof target === 'object') return target.current?.focus()
|
|
927
|
-
if (hasSearch()) return
|
|
928
|
-
const rows = containerRef.current
|
|
929
|
-
if (!rows || (rows.contains(document.activeElement) && document.activeElement !== rows)) return
|
|
930
|
-
const first = rows.querySelector<HTMLElement>(
|
|
931
|
-
'[role="menuitem"]:not([aria-disabled="true"]), [role="menuitemradio"]:not([aria-disabled="true"]), [role="menuitemcheckbox"]:not([aria-disabled="true"])',
|
|
932
|
-
)
|
|
933
|
-
first?.focus()
|
|
934
|
-
})
|
|
935
|
-
})
|
|
936
|
-
return () => {
|
|
937
|
-
cancelAnimationFrame(outer)
|
|
938
|
-
if (inner !== undefined) cancelAnimationFrame(inner)
|
|
939
|
-
}
|
|
940
|
-
}, [open, hasSearch, keepsFocus])
|
|
941
|
-
|
|
942
|
-
// The popup keeps its rows registered between opens, so their rects
|
|
943
|
-
// were taken while it was hidden: re-measure once it is open and laid out.
|
|
944
|
-
useEffect(() => {
|
|
945
|
-
if (!open) return
|
|
946
|
-
remeasure()
|
|
947
|
-
}, [open, remeasure])
|
|
948
|
-
|
|
949
|
-
const multiple = selection.multiple
|
|
950
|
-
const checkedRect = !multiple && selection.checkedIndex != null ? itemRects[selection.checkedIndex] : null
|
|
951
|
-
// Multiple: one merged block per contiguous run of checked rows, value-
|
|
952
|
-
// checked rows (CheckboxItem, RadioItem) included.
|
|
953
|
-
const runs = useSelectionRuns(
|
|
954
|
-
useMemo(
|
|
955
|
-
() => [...(selection.checkedIndices ?? NO_INDICES), ...panel.valueChecked],
|
|
956
|
-
[selection.checkedIndices, panel.valueChecked],
|
|
957
|
-
),
|
|
958
|
-
)
|
|
959
|
-
const blocks = useMergeSplitBlocks(runs, open ? itemRects : NO_RECTS, shape.bgRadius)
|
|
960
|
-
// Inside the popup, Base UI's Menu.Item / Menu.RadioItem own the role,
|
|
961
|
-
// aria-checked, tabIndex, roving highlight, typeahead, and Enter/Space/
|
|
962
|
-
// click activation. Keyboard activation reaches only the primitive's own
|
|
963
|
-
// onClick, so the row's activation rides there. The render div carries the
|
|
964
|
-
// Fluid Functionalism visuals and the fluid-hover registration.
|
|
965
|
-
// (`renderPopupItem`, shared with Dropdown.SubContent.)
|
|
966
|
-
|
|
967
|
-
const contentCtx = useMemo(
|
|
968
|
-
() => ({
|
|
969
|
-
registerItem,
|
|
970
|
-
activeIndex: panel.lit,
|
|
971
|
-
checkedIndex: selection.checkedIndex,
|
|
972
|
-
multiple,
|
|
973
|
-
checkedIndices: selection.checkedIndices,
|
|
974
|
-
inMenu: true,
|
|
975
|
-
renderMenuItem: renderPopupItem,
|
|
976
|
-
selfManaged: selection.selfManaged,
|
|
977
|
-
toggleIndex: selection.toggleIndex,
|
|
978
|
-
pin: panel.pin,
|
|
979
|
-
reportChecked: panel.reportChecked,
|
|
980
|
-
}),
|
|
981
|
-
[
|
|
982
|
-
registerItem,
|
|
983
|
-
panel.lit,
|
|
984
|
-
selection.checkedIndex,
|
|
985
|
-
multiple,
|
|
986
|
-
selection.checkedIndices,
|
|
987
|
-
selection.selfManaged,
|
|
988
|
-
selection.toggleIndex,
|
|
989
|
-
panel.pin,
|
|
990
|
-
panel.reportChecked,
|
|
991
|
-
],
|
|
992
|
-
)
|
|
993
|
-
|
|
994
|
-
return (
|
|
995
|
-
<BaseMenu.Portal keepMounted={keepMounted} container={container}>
|
|
996
|
-
<BaseMenu.Positioner
|
|
997
|
-
side={side}
|
|
998
|
-
align={align}
|
|
999
|
-
sideOffset={opensInPlace({ effect, hideSource }) ? inPlaceOffset : sideOffset}
|
|
1000
|
-
alignOffset={alignOffset}
|
|
1001
|
-
collisionPadding={collisionPadding}
|
|
1002
|
-
collisionBoundary={collisionBoundary}
|
|
1003
|
-
className={cn('z-50 outline-none', positionerClassName)}
|
|
1004
|
-
>
|
|
1005
|
-
<DropdownContext.Provider value={contentCtx}>
|
|
1006
|
-
<DropdownFilterContext.Provider value={rows.filterCtx}>
|
|
1007
|
-
<DropdownSearchHostContext.Provider value={searchHost}>
|
|
1008
|
-
<BaseMenu.Popup
|
|
1009
|
-
{...popupProps}
|
|
1010
|
-
ref={morph.popupRef}
|
|
1011
|
-
finalFocus={finalFocus}
|
|
1012
|
-
onKeyDownCapture={chain(onKeyDownCapture, e => {
|
|
1013
|
-
// A submenu's keys bubble here through the portal.
|
|
1014
|
-
if (!ownEvent(e)) return
|
|
1015
|
-
panel.trackKeyboardNav(e)
|
|
1016
|
-
redirectTypingToSearch(e)
|
|
1017
|
-
})}
|
|
1018
|
-
onMouseEnter={chain(onMouseEnter, () => {
|
|
1019
|
-
handlers.onMouseEnter()
|
|
1020
|
-
setFocusedIndex(null)
|
|
1021
|
-
})}
|
|
1022
|
-
onMouseMove={chain(onMouseMove, e => ownEvent(e) && handlers.onMouseMove(e))}
|
|
1023
|
-
onClick={chain(onClick, e => ownEvent(e) && handlers.onClick(e))}
|
|
1024
|
-
onMouseLeave={chain(onMouseLeave, () => {
|
|
1025
|
-
handlers.onMouseLeave()
|
|
1026
|
-
// The pointer's session is over; a focused search field
|
|
1027
|
-
// gets its first-row highlight back.
|
|
1028
|
-
if (isSearchField(document.activeElement)) highlightFirst()
|
|
1029
|
-
})}
|
|
1030
|
-
onFocus={chain(onFocus, e => {
|
|
1031
|
-
// Keyboard navigation moves the hover background, and the
|
|
1032
|
-
// ring once the keyboard earned it (useKeyboardNavGate).
|
|
1033
|
-
if (!ownEvent(e) || focusRow(e)) return
|
|
1034
|
-
if (isSearchField(e.target)) {
|
|
1035
|
-
// The search field: the first row (what Enter picks)
|
|
1036
|
-
// carries the highlight while it has focus.
|
|
1037
|
-
highlightFirst()
|
|
1038
|
-
} else if (e.target !== e.currentTarget) {
|
|
1039
|
-
// Focus moved to some other non-row inside the popup: no
|
|
1040
|
-
// row is highlighted any more. The popup focusing itself
|
|
1041
|
-
// (pointer leaving a row) doesn't count.
|
|
1042
|
-
setActiveIndex(null)
|
|
1043
|
-
}
|
|
1044
|
-
})}
|
|
1045
|
-
onBlur={chain(onBlur, e => {
|
|
1046
|
-
// The popup itself takes focus when the pointer leaves a row; only a
|
|
1047
|
-
// departure from the whole popup ends the hover session.
|
|
1048
|
-
if (!ownEvent(e) || e.currentTarget.contains(e.relatedTarget as Node)) return
|
|
1049
|
-
setFocusedIndex(null)
|
|
1050
|
-
setActiveIndex(null)
|
|
1051
|
-
})}
|
|
1052
|
-
className="relative select-none outline-none"
|
|
1053
|
-
>
|
|
1054
|
-
<SurfaceProvider value={level}>
|
|
1055
|
-
<MorphSurface
|
|
1056
|
-
morph={morph}
|
|
1057
|
-
bg={SURFACE_BG[level]}
|
|
1058
|
-
shadow={SURFACE_SHADOW[3]}
|
|
1059
|
-
radius={shape.container}
|
|
1060
|
-
className={cn(
|
|
1061
|
-
// min-w tracks the trigger via the Positioner's
|
|
1062
|
-
// --anchor-width var.
|
|
1063
|
-
`flex flex-col w-72 max-w-full min-w-[var(--anchor-width)] max-h-[min(480px,var(--available-height))] overflow-hidden ${shape.container}`,
|
|
1064
|
-
className,
|
|
1065
|
-
)}
|
|
1066
|
-
>
|
|
1067
|
-
{/* The list scrolls inside a ScrollArea; this wrapper is the rows'
|
|
1068
|
-
offsetParent, so the overlays scroll with them. */}
|
|
1069
|
-
<ScrollArea
|
|
1070
|
-
className={popupScrollAreaClass}
|
|
1071
|
-
viewportClassName={cn(popupViewportClass, !searchMounted && 'scroll-fade')}
|
|
1072
|
-
>
|
|
1073
|
-
<div ref={containerRef} className="relative flex flex-col p-1">
|
|
1074
|
-
{/* Selected backgrounds — merged runs in multiple mode and value-checked rows */}
|
|
1075
|
-
<SelectionBackgrounds blocks={blocks} />
|
|
1076
|
-
|
|
1077
|
-
{/* Selected background */}
|
|
1078
|
-
<AnimatePresence>
|
|
1079
|
-
{checkedRect && (
|
|
1080
|
-
<motion.div
|
|
1081
|
-
className={`absolute ${shape.bg} bg-active pointer-events-none`}
|
|
1082
|
-
initial={false}
|
|
1083
|
-
animate={{
|
|
1084
|
-
top: checkedRect.top,
|
|
1085
|
-
left: checkedRect.left,
|
|
1086
|
-
width: checkedRect.width,
|
|
1087
|
-
height: checkedRect.height,
|
|
1088
|
-
opacity: 1,
|
|
1089
|
-
}}
|
|
1090
|
-
exit={{ opacity: 0, transition: spring.moderate.exit }}
|
|
1091
|
-
transition={{
|
|
1092
|
-
...spring.moderate,
|
|
1093
|
-
opacity: { duration: spring.fast.duration },
|
|
1094
|
-
}}
|
|
1095
|
-
/>
|
|
1096
|
-
)}
|
|
1097
|
-
</AnimatePresence>
|
|
1098
|
-
|
|
1099
|
-
{/* Hover background (pinned on an open submenu's trigger) */}
|
|
1100
|
-
<FluidHoverHighlight hover={panel.shownHover} from={checkedRect} className={shape.bg} />
|
|
1101
|
-
|
|
1102
|
-
{/* Focus ring, keyboard only */}
|
|
1103
|
-
<FocusRing rect={open ? panel.focusRect : null} shape="rounded" />
|
|
1104
|
-
|
|
1105
|
-
{/* display: contents keeps items direct flex children of the
|
|
1106
|
-
wrapper so fluid hover measurement and gap layout still work,
|
|
1107
|
-
while the group provides the radio value context. */}
|
|
1108
|
-
<RowOrderContext.Provider value={panel.rowOrder}>
|
|
1109
|
-
<BaseMenu.RadioGroup value={selection.checkedIndex ?? null} className="contents">
|
|
1110
|
-
{rows.content}
|
|
1111
|
-
</BaseMenu.RadioGroup>
|
|
1112
|
-
</RowOrderContext.Provider>
|
|
1113
|
-
</div>
|
|
1114
|
-
</ScrollArea>
|
|
1115
|
-
</MorphSurface>
|
|
1116
|
-
</SurfaceProvider>
|
|
1117
|
-
</BaseMenu.Popup>
|
|
1118
|
-
</DropdownSearchHostContext.Provider>
|
|
1119
|
-
</DropdownFilterContext.Provider>
|
|
1120
|
-
</DropdownContext.Provider>
|
|
1121
|
-
</BaseMenu.Positioner>
|
|
1122
|
-
</BaseMenu.Portal>
|
|
1123
|
-
)
|
|
1124
|
-
},
|
|
1125
|
-
)
|
|
1126
|
-
|
|
1127
|
-
DropdownContent.displayName = 'DropdownContent'
|
|
1128
|
-
|
|
1129
|
-
// ---------------------------------------------------------------------------
|
|
1130
|
-
// DropdownLabel
|
|
1131
|
-
// ---------------------------------------------------------------------------
|
|
1132
|
-
|
|
1133
|
-
interface DropdownLabelProps extends HTMLAttributes<HTMLDivElement> {
|
|
1134
|
-
/** The caption. */
|
|
1135
|
-
children?: ReactNode
|
|
1136
|
-
/** Indents the caption to the label column of rows with an icon. Defaults to `false`. */
|
|
1137
|
-
inset?: boolean
|
|
1138
|
-
/** Extra classes. */
|
|
1139
|
-
className?: string
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
const DropdownLabel = forwardRef<HTMLDivElement, DropdownLabelProps>(({ className, inset, ...props }, ref) => {
|
|
1143
|
-
// Group labels are the caption role of the type scale — see /docs/sizes.
|
|
1144
|
-
const size = useSize()
|
|
1145
|
-
const own = useRef<HTMLDivElement>(null)
|
|
1146
|
-
// A place in the row order, so a selection run never melts across it.
|
|
1147
|
-
useRowSlot(own, undefined, true)
|
|
1148
|
-
const classes = cn(
|
|
1149
|
-
'py-1.5 shrink-0 text-muted-foreground',
|
|
1150
|
-
inset ? INSET_PX[size.variant] : 'px-2',
|
|
1151
|
-
size.variant === 'compact' ? 'text-caption-compact' : 'text-caption',
|
|
1152
|
-
className,
|
|
1153
|
-
)
|
|
1154
|
-
// Inside a Dropdown.Group it names the group.
|
|
1155
|
-
return useContext(DropdownGroupContext) ? (
|
|
1156
|
-
<BaseMenu.GroupLabel ref={mergeRefs(own, ref)} className={classes} {...props} />
|
|
1157
|
-
) : (
|
|
1158
|
-
<div ref={mergeRefs(own, ref)} className={classes} {...props} />
|
|
1159
|
-
)
|
|
1160
|
-
})
|
|
1161
|
-
|
|
1162
|
-
DropdownLabel.displayName = 'DropdownLabel'
|
|
1163
|
-
|
|
1164
|
-
// ---------------------------------------------------------------------------
|
|
1165
|
-
// DropdownSeparator
|
|
1166
|
-
// ---------------------------------------------------------------------------
|
|
1167
|
-
|
|
1168
|
-
const DropdownSeparator = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => {
|
|
1169
|
-
const own = useRef<HTMLDivElement>(null)
|
|
1170
|
-
useRowSlot(own, undefined, true)
|
|
1171
|
-
return (
|
|
1172
|
-
<div
|
|
1173
|
-
ref={mergeRefs(own, ref)}
|
|
1174
|
-
role="separator"
|
|
1175
|
-
className={cn('my-1 -mx-1 h-px shrink-0 bg-border/60', className)}
|
|
1176
|
-
{...props}
|
|
1177
|
-
/>
|
|
1178
|
-
)
|
|
1179
|
-
})
|
|
1180
|
-
|
|
1181
|
-
DropdownSeparator.displayName = 'DropdownSeparator'
|
|
1182
|
-
|
|
1183
|
-
Object.assign(Dropdown, {
|
|
1184
|
-
Menu: DropdownMenu,
|
|
1185
|
-
Trigger: DropdownTrigger,
|
|
1186
|
-
Content: DropdownContent,
|
|
1187
|
-
Item: MenuItem,
|
|
1188
|
-
Label: DropdownLabel,
|
|
1189
|
-
Separator: DropdownSeparator,
|
|
1190
|
-
Search: DropdownSearch,
|
|
1191
|
-
Empty: DropdownEmpty,
|
|
1192
|
-
Group: DropdownGroup,
|
|
1193
|
-
Shortcut: DropdownShortcut,
|
|
1194
|
-
CheckboxItem: DropdownCheckboxItem,
|
|
1195
|
-
RadioGroup: DropdownRadioGroup,
|
|
1196
|
-
RadioItem: DropdownRadioItem,
|
|
1197
|
-
Sub: DropdownSub,
|
|
1198
|
-
SubTrigger: DropdownSubTrigger,
|
|
1199
|
-
SubContent: DropdownSubContent,
|
|
1200
|
-
})
|
|
1201
|
-
|
|
1202
|
-
// DropdownContextValue and MenuItemRenderOptions are already re-exported
|
|
1203
|
-
// above next to their import — repeating them here is a duplicate-export
|
|
1204
|
-
// build error.
|
|
1205
|
-
export type {
|
|
1206
|
-
DropdownCheckboxItemProps,
|
|
1207
|
-
DropdownContentProps,
|
|
1208
|
-
DropdownGroupProps,
|
|
1209
|
-
DropdownLabelProps,
|
|
1210
|
-
DropdownMenuProps,
|
|
1211
|
-
DropdownProps,
|
|
1212
|
-
DropdownRadioGroupProps,
|
|
1213
|
-
DropdownRadioItemProps,
|
|
1214
|
-
DropdownSearchProps,
|
|
1215
|
-
DropdownShortcutProps,
|
|
1216
|
-
DropdownSubContentProps,
|
|
1217
|
-
DropdownSubProps,
|
|
1218
|
-
DropdownSubTriggerProps,
|
|
1219
|
-
DropdownTriggerProps,
|
|
1220
|
-
MenuItemProps,
|
|
1221
|
-
}
|
|
1222
|
-
export {
|
|
1223
|
-
Dropdown,
|
|
1224
|
-
DropdownCheckboxItem,
|
|
1225
|
-
DropdownContent,
|
|
1226
|
-
DropdownEmpty,
|
|
1227
|
-
DropdownGroup,
|
|
1228
|
-
DropdownLabel,
|
|
1229
|
-
DropdownMenu,
|
|
1230
|
-
DropdownRadioGroup,
|
|
1231
|
-
DropdownRadioItem,
|
|
1232
|
-
DropdownSearch,
|
|
1233
|
-
DropdownSeparator,
|
|
1234
|
-
DropdownShortcut,
|
|
1235
|
-
DropdownSub,
|
|
1236
|
-
DropdownSubContent,
|
|
1237
|
-
DropdownSubTrigger,
|
|
1238
|
-
DropdownTrigger,
|
|
1239
|
-
MenuItem,
|
|
1240
|
-
}
|
|
1241
|
-
export default Dropdown
|