@justanarthur/modo-ui 0.6.0-canary.9 → 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-c7rwx0ap.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-c74gf16d.js → components-1z6tdadk.js} +24 -40
- package/dist/shared/{components-bae8sbjx.js → components-27nnvrwe.js} +14 -15
- package/dist/shared/{components-qh1cvj6p.js → components-2dxkv6zm.js} +7 -8
- package/dist/shared/{components-y0kfy2g4.js → components-2ecg8svx.js} +152 -234
- package/dist/shared/{components-c5w5zk0s.js → components-2ph6t61s.js} +34 -37
- package/dist/shared/{components-4j3t39dd.js → components-39jq9va1.js} +24 -29
- package/dist/shared/components-3ky44908.js +9 -0
- package/dist/shared/{components-syynwm1r.js → components-3mkepka5.js} +25 -28
- package/dist/shared/{components-wgtjt2af.js → components-4700p9kz.js} +22 -24
- package/dist/shared/{components-yhgq0qxx.js → components-59efxjd9.js} +12 -13
- package/dist/shared/{components-91zw3d9f.js → components-5vgpnxgy.js} +27 -51
- package/dist/shared/{components-kz5q11pp.js → components-5xc2j8jz.js} +18 -19
- package/dist/shared/{components-zkk3fmw8.js → components-6p8tfcd6.js} +29 -57
- package/dist/shared/{components-4nck8acq.js → components-7r3etddy.js} +54 -19
- package/dist/shared/{components-svkt577j.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-pv1dyrtn.js → components-b3c5et5b.js} +38 -49
- package/dist/shared/{components-rrbpmfx4.js → components-c6s6b43g.js} +2 -3
- package/dist/shared/{components-pwayrkw9.js → components-chacnk61.js} +19 -19
- package/dist/shared/{components-71nbt03m.js → components-cv16mryv.js} +7 -26
- package/dist/shared/{components-hgv6tygd.js → components-d6xtjht3.js} +30 -35
- package/dist/shared/{components-z8fedth5.js → components-dk0gjr5a.js} +18 -19
- package/dist/shared/{components-cet3jpa0.js → components-fzrq6tr3.js} +34 -37
- package/dist/shared/{components-cnead5qm.js → components-gc5vtq2f.js} +30 -66
- package/dist/shared/components-gf5s6ap9.js +42 -0
- package/dist/shared/{components-0h78g3b5.js → components-gz8zf3ec.js} +55 -56
- package/dist/shared/{components-mattt5jp.js → components-h2ajmhwn.js} +35 -107
- package/dist/shared/{components-m06yq8wk.js → components-hqeq3w9q.js} +23 -38
- package/dist/shared/{components-7qx17na9.js → components-hw9yk32z.js} +24 -36
- package/dist/shared/components-hyq23phv.js +310 -0
- package/dist/shared/{components-9jmjnkqd.js → components-jbbmdr74.js} +22 -41
- package/dist/shared/{components-dcc02hs2.js → components-jjw69c4c.js} +4 -7
- package/dist/shared/{components-303jf96s.js → components-jthe1pwb.js} +420 -139
- package/dist/shared/{components-dtca4s6b.js → components-jwst3vtj.js} +76 -224
- package/dist/shared/{components-dc5q749x.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-qx27excf.js → components-mjthm1kf.js} +9 -13
- package/dist/shared/{components-1e64t8aj.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-w2jqa6hx.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-2a7hcand.js → components-rr7a6ja9.js} +27 -59
- package/dist/shared/{components-005jt2nz.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-p0fm0bab.js → components-w1r67ag9.js} +149 -294
- package/dist/shared/{components-xd3emmy4.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-yxmxn2jp.js → components-y9y0g8er.js} +28 -52
- package/dist/shared/components-ypp01w3p.js +986 -0
- package/dist/shared/{components-czdvj1a8.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-01w9qcfa.js +0 -277
- package/dist/shared/components-124epb7f.js +0 -53
- package/dist/shared/components-26snnbs9.js +0 -322
- package/dist/shared/components-2krsc300.js +0 -254
- package/dist/shared/components-2qbqqpgq.js +0 -1545
- 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-868v7scv.js +0 -399
- package/dist/shared/components-8jrk5eye.js +0 -448
- 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-bsqynhaj.js +0 -179
- package/dist/shared/components-c9yr4ed1.js +0 -44
- 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-f7h57m6t.js +0 -59
- package/dist/shared/components-g3eafs70.js +0 -337
- 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-mhza2wdn.js +0 -482
- package/dist/shared/components-mrbaxt8q.js +0 -62
- package/dist/shared/components-pt5ftm4p.js +0 -38
- package/dist/shared/components-rtadhd9j.js +0 -64
- package/dist/shared/components-tv7b8tet.js +0 -271
- package/dist/shared/components-tz4gaf1v.js +0 -23
- package/dist/shared/components-wbbckbw8.js +0 -34
- package/dist/shared/components-wknae3m4.js +0 -298
- 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/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 -575
- 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,1044 +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/select.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`.
|
|
12
|
-
* - `SelectProps` members re-declared one per line with the FF docs API-table
|
|
13
|
-
* descriptions (modo's props table lists only members declared in the
|
|
14
|
-
* interface body, and drops members whose type spans lines).
|
|
15
|
-
* - Uncontrolled selection was already upstream (`defaultValue` seeds the
|
|
16
|
-
* internal value, `onValueChange` still fires while controlled) — verified,
|
|
17
|
-
* kept as is rather than rerouted through `useControllableState`.
|
|
18
|
-
* - Compound statics `Select.Trigger/.Content/.Item/.Group/.Label/.Separator`
|
|
19
|
-
* attached as expando properties on the root function (TS infers them, so
|
|
20
|
-
* `<Select.Item>` typechecks); upstream's named exports are kept.
|
|
21
|
-
* - modo item: TSDoc + examples from the FF "Select" docs page, and a default
|
|
22
|
-
* export.
|
|
23
|
-
* - Styling reads DS tokens (AGENTS.md styling): `text-[Npx]` →
|
|
24
|
-
* `text-<role>[-compact]`; the hex focus-ring fallback →
|
|
25
|
-
* `ring-focus-ring` / `border-focus-ring`; `duration-80|120|160` and
|
|
26
|
-
* tier-length JS durations → `duration-<tier>` / `spring.*`.
|
|
27
|
-
* - The popup morphs out of its trigger through the shared morph layer
|
|
28
|
-
* (`lib/use-morph.ts` + `MorphSurface`, goo by default on
|
|
29
|
-
* `spring.goo`; `Select.Content` takes `from` / `effect` /
|
|
30
|
-
* `hideSource` / `tier`). The `scaleY` motion wrapper and the `actionsRef`
|
|
31
|
-
* deferred unmount (with its fallback timer) are gone: the morph holds Base
|
|
32
|
-
* UI's unmount itself. The popup paints its level through the morph's
|
|
33
|
-
* surface layers instead of `render={<Elevated/>}`, re-providing the level
|
|
34
|
-
* with `SurfaceProvider` as `Elevated` did.
|
|
35
|
-
* - `effect="morph"` with `hideSource` opens the menu in place over its
|
|
36
|
-
* trigger: the positioner's `sideOffset` becomes the engine's `inPlaceOffset`.
|
|
37
|
-
* - `Select.Item`'s `index` is optional: rows take their place in document
|
|
38
|
-
* order (`lib/row-order.tsx`), inside groups, conditionals and maps alike;
|
|
39
|
-
* the checked row is found by its own index rather than its DOM position.
|
|
40
|
-
* A row's label may be any node (the trigger shows it, through Base UI's
|
|
41
|
-
* `items`), with `textValue` for typeahead.
|
|
42
|
-
* - `Select.Value` (local): the trigger's value slot as a part, so a trigger
|
|
43
|
-
* can take it as children (shadcn's `<SelectValue placeholder>`).
|
|
44
|
-
* - `Select.Content` takes `side` / `align` / `sideOffset` (local).
|
|
45
|
-
* - The keyboard focus ring is the shared `FocusRing` (`lib/focus-ring.tsx`,
|
|
46
|
-
* pinned to the popup's rounded shape), so it also snaps under reduced motion.
|
|
47
|
-
* - `triggerVariants` moved to `variants.ts` (server-callable, no JSX or hooks)
|
|
48
|
-
* and re-exported here.
|
|
49
|
-
* - Root `open` / `defaultOpen` / `onOpenChange(open, details)` (local): the 300ms
|
|
50
|
-
* pick acknowledgment runs only while the open state is uncontrolled; a
|
|
51
|
-
* controlled select follows the caller's `open` and reports every request.
|
|
52
|
-
* - `Select.Content` takes `alignOffset` / `collisionPadding` (Positioner),
|
|
53
|
-
* `finalFocus` (Popup) and rest HTML props (Popup). `initialFocus` is not
|
|
54
|
-
* offered: Base UI's select popup always focuses the picked row itself.
|
|
55
|
-
* - `Select.Trigger` / `Select.Item` take `render` / `asChild` (`lib/slot.ts`);
|
|
56
|
-
* the trigger takes `wrapperClassName` for its outer div. `Select.Value`
|
|
57
|
-
* and `Select.Group` forward their ref.
|
|
58
|
-
*/
|
|
59
|
-
|
|
60
|
-
import { Select as SelectPrimitive } from '@base-ui/react/select'
|
|
61
|
-
import type { VariantProps } from 'class-variance-authority'
|
|
62
|
-
import { AnimatePresence, motion } from 'motion/react'
|
|
63
|
-
import {
|
|
64
|
-
Children,
|
|
65
|
-
type ComponentProps,
|
|
66
|
-
cloneElement,
|
|
67
|
-
createContext,
|
|
68
|
-
forwardRef,
|
|
69
|
-
type HTMLAttributes,
|
|
70
|
-
isValidElement,
|
|
71
|
-
type ReactElement,
|
|
72
|
-
type ReactNode,
|
|
73
|
-
type Ref,
|
|
74
|
-
useCallback,
|
|
75
|
-
useContext,
|
|
76
|
-
useEffect,
|
|
77
|
-
useImperativeHandle,
|
|
78
|
-
useMemo,
|
|
79
|
-
useRef,
|
|
80
|
-
useState,
|
|
81
|
-
version,
|
|
82
|
-
} from 'react'
|
|
83
|
-
import { FluidHoverHighlight } from '../../../lib/fluid-hover-highlight'
|
|
84
|
-
import { FocusRing } from '../../../lib/focus-ring'
|
|
85
|
-
import type { IconComponent } from '../../../lib/icon-context'
|
|
86
|
-
import { MorphSurface } from '../../../lib/morph-layers'
|
|
87
|
-
import { isDisabledRow, popupScrollAreaClass, popupViewportClass } from '../../../lib/popup'
|
|
88
|
-
import { RowOrderContext, useRowOrder, useRowSlot } from '../../../lib/row-order'
|
|
89
|
-
import { shapeMap, useShape } from '../../../lib/shape-context'
|
|
90
|
-
import { type SizeVariant, useSize } from '../../../lib/size-context'
|
|
91
|
-
import { type SlotProps, slotId, slotRender } from '../../../lib/slot'
|
|
92
|
-
import { spring } from '../../../lib/springs'
|
|
93
|
-
import { SURFACE_BG, SURFACE_SHADOW } from '../../../lib/surface-classes'
|
|
94
|
-
import { SurfaceProvider, useSurface } from '../../../lib/surface-context'
|
|
95
|
-
import { useFluidHover, useRegisterFluidHoverItem } from '../../../lib/use-fluid-hover'
|
|
96
|
-
import { useKeyboardNavGate } from '../../../lib/use-keyboard-nav-gate'
|
|
97
|
-
import { inPlaceOffset, type MorphOrigin, opensInPlace, useMorph, useMorphOrigin } from '../../../lib/use-morph'
|
|
98
|
-
import { cn } from '../../../lib/utils'
|
|
99
|
-
import { ScrollArea } from '../../../primitives/scroll-area'
|
|
100
|
-
import { SizeProvider } from '../../../primitives/sizes'
|
|
101
|
-
import { triggerVariants } from './variants'
|
|
102
|
-
|
|
103
|
-
// ---------------------------------------------------------------------------
|
|
104
|
-
// Select context
|
|
105
|
-
//
|
|
106
|
-
// Built on Base UI's Select primitive, which owns positioning (collision
|
|
107
|
-
// flipping, anchor tracking), dismissal (outside press, focus-out, Escape
|
|
108
|
-
// nesting inside dialogs), list keyboard navigation + typeahead, combobox
|
|
109
|
-
// ARIA, and the hidden form input. This layer keeps the
|
|
110
|
-
// fluid-hover overlays, the morph open/close animation (lib/use-morph.ts),
|
|
111
|
-
// and the animated checkmark.
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
|
|
114
|
-
// How long a selection holds the popup open before closing, so the
|
|
115
|
-
// acknowledgment — the checkmark drawing in and the selected background
|
|
116
|
-
// springing to the picked row — is visible instead of being cut off by the
|
|
117
|
-
// ~60ms close fade. Escape and outside presses still close immediately.
|
|
118
|
-
const selectionAckMs = 300
|
|
119
|
-
|
|
120
|
-
interface SelectContextValue {
|
|
121
|
-
value: string
|
|
122
|
-
open: boolean
|
|
123
|
-
origin: React.RefObject<MorphOrigin>
|
|
124
|
-
/** Local: whether the picked row's label is plain text (trimmed and truncated) or a node (laid out as is). */
|
|
125
|
-
textLabel: boolean
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const SelectContext = createContext<SelectContextValue | null>(null)
|
|
129
|
-
|
|
130
|
-
function useSelectContext() {
|
|
131
|
-
const ctx = useContext(SelectContext)
|
|
132
|
-
if (!ctx) throw new Error('Select compound components must be inside <Select>')
|
|
133
|
-
return ctx
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Content context for fluid hover
|
|
137
|
-
interface SelectContentContextValue {
|
|
138
|
-
registerItem: (index: number, element: HTMLElement | null) => void
|
|
139
|
-
activeIndex: number | null
|
|
140
|
-
checkedIndex?: number
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const SelectContentContext = createContext<SelectContentContextValue | null>(null)
|
|
144
|
-
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
|
-
// Select (root)
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
|
-
|
|
149
|
-
// The trigger follows the global pill/rounded shape; the popup does not.
|
|
150
|
-
// Like Dropdown and Combobox, the list keeps the smaller "rounded" radii
|
|
151
|
-
// whatever the rest of the UI is shaped: pill corners on a popover distort
|
|
152
|
-
// its padding and break the concentric fit of the rows' hover and selection
|
|
153
|
-
// backgrounds inside it.
|
|
154
|
-
const popupShape = shapeMap.rounded
|
|
155
|
-
|
|
156
|
-
// An element's ref: `props.ref` on React 19, `element.ref` on 18. Reading `element.ref` warns on 19, so it
|
|
157
|
-
// is only touched when `props.ref` is absent (React 18 never has it).
|
|
158
|
-
function elementRef(el: ReactElement | undefined): Ref<HTMLDivElement> | undefined {
|
|
159
|
-
if (!el) return undefined
|
|
160
|
-
// React 19 keeps the ref in props, React 18 on the element; each warns when read the other way.
|
|
161
|
-
if (parseInt(version, 10) >= 19) return (el.props as { ref?: Ref<HTMLDivElement> }).ref
|
|
162
|
-
return (el as unknown as { ref?: Ref<HTMLDivElement> }).ref
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const ownChildren = (el: ReactElement) => (el.props as { children?: ReactNode }).children
|
|
166
|
-
|
|
167
|
-
// With `asChild` a row's children are the element that becomes the row, and its own children are
|
|
168
|
-
// the label; a `render` element's own children are the label when the row has none.
|
|
169
|
-
function itemLabel(asChild: boolean | undefined, render: ReactElement | undefined, children: ReactNode) {
|
|
170
|
-
if (asChild && isValidElement(children)) return ownChildren(children)
|
|
171
|
-
return children === undefined && render ? ownChildren(render) : children
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Walk the children tree collecting `{ value, label }` pairs from SelectItem
|
|
176
|
-
* elements. Passed to Base UI's `items` prop so the trigger can resolve the
|
|
177
|
-
* label of an initial value before the popup has ever mounted (items only
|
|
178
|
-
* render while open). The label is the row's children, whatever node they
|
|
179
|
-
* are; a row with none shows its raw value.
|
|
180
|
-
*/
|
|
181
|
-
function collectSelectItems(node: ReactNode, out: { value: string; label: ReactNode }[] = []) {
|
|
182
|
-
Children.forEach(node, child => {
|
|
183
|
-
if (!isValidElement(child)) return
|
|
184
|
-
const props = child.props as { value?: unknown; children?: ReactNode; asChild?: boolean; render?: ReactElement }
|
|
185
|
-
if (typeof props.value === 'string') {
|
|
186
|
-
out.push({
|
|
187
|
-
value: props.value,
|
|
188
|
-
label: itemLabel(props.asChild, props.render, props.children) ?? props.value,
|
|
189
|
-
})
|
|
190
|
-
} else if (props.children) {
|
|
191
|
-
collectSelectItems(props.children, out)
|
|
192
|
-
}
|
|
193
|
-
})
|
|
194
|
-
return out
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** Base UI's change details for the select popup: `reason`, `event`, `trigger`, `cancel()`. */
|
|
198
|
-
type SelectOpenChangeDetails = SelectPrimitive.Root.ChangeEventDetails
|
|
199
|
-
|
|
200
|
-
interface SelectProps {
|
|
201
|
-
/** `Select.Trigger` and `Select.Content`. */
|
|
202
|
-
children: ReactNode
|
|
203
|
-
/** Controlled selected value. Pair with `onValueChange`. */
|
|
204
|
-
value?: string
|
|
205
|
-
/** Uncontrolled default value — the select then keeps the selection itself. */
|
|
206
|
-
defaultValue?: string
|
|
207
|
-
/** Called when the selected value changes. */
|
|
208
|
-
onValueChange?: (value: string) => void
|
|
209
|
-
/** Controlled open state. Pair with `onOpenChange`. A controlled select follows it exactly: it closes only when you say so, and the 300ms pick acknowledgment is skipped. */
|
|
210
|
-
open?: boolean
|
|
211
|
-
/** Uncontrolled initial open state. Defaults to `false`. */
|
|
212
|
-
defaultOpen?: boolean
|
|
213
|
-
/** Called when the popup asks to open or close, with Base UI's change details (`reason`, `event`, `trigger`, `cancel()`). Uncontrolled, a pick reports its close once the 300ms acknowledgment ends. */
|
|
214
|
-
onOpenChange?: (open: boolean, details: SelectOpenChangeDetails) => void
|
|
215
|
-
/** Disables the entire select. Defaults to `false`. */
|
|
216
|
-
disabled?: boolean
|
|
217
|
-
/** Name for form submission (renders a hidden native input). */
|
|
218
|
-
name?: string
|
|
219
|
-
/** Marks the select as required in forms. Defaults to `false`. */
|
|
220
|
-
required?: boolean
|
|
221
|
-
/** Pins trigger and popup to one step of the size ladder (default 36px, compact 28px — see Sizes). Omitted, both follow the surrounding SizeProvider. */
|
|
222
|
-
size?: SizeVariant
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* An animated select menu: fluid hover on the rows, a popup that oozes out of
|
|
227
|
-
* its trigger, and a checkmark that draws itself as the selection lands.
|
|
228
|
-
*
|
|
229
|
-
* Built on Base UI's Select — positioning and collision flipping, dismissal,
|
|
230
|
-
* typeahead, combobox ARIA and the hidden form input all come from the
|
|
231
|
-
* primitive. This layer adds the magnetic hover background, the selected
|
|
232
|
-
* background that springs from the old row to the picked one, a keyboard
|
|
233
|
-
* focus ring that only appears once you actually use the keyboard, and the
|
|
234
|
-
* popup's open and close through the shared morph (see Morph), goo by
|
|
235
|
-
* default. A pick holds the popup open for 300ms so that acknowledgment is
|
|
236
|
-
* seen; Escape and outside presses close at once. Uncontrolled through
|
|
237
|
-
* `defaultValue`, controlled through `value` + `onValueChange`. The popup's
|
|
238
|
-
* open state follows the same rule: `defaultOpen`, or `open` +
|
|
239
|
-
* `onOpenChange(open, details)`. A controlled `open` is obeyed exactly, so
|
|
240
|
-
* a handler may refuse a close; it skips the 300ms hold, which only runs
|
|
241
|
-
* while the select keeps its own open state.
|
|
242
|
-
*
|
|
243
|
-
* Rows need no `index`: they take their place in document order, inside a
|
|
244
|
-
* `Select.Group` or out of a `map`. The trigger shows the picked row's
|
|
245
|
-
* label, whatever node it is (an icon and text, a badge), and a row's
|
|
246
|
-
* `textValue` is what typeahead matches when its children aren't plain text.
|
|
247
|
-
*
|
|
248
|
-
* Statics:
|
|
249
|
-
* - `Select.Trigger` — the field: `variant`, `icon`, `placeholder`, `error`;
|
|
250
|
-
* children replace its value slot (a `Select.Value`). `render` / `asChild`
|
|
251
|
-
* swap the button for your own element (it brings its own look); an
|
|
252
|
-
* element with children of its own drops the icon, `placeholder` and
|
|
253
|
-
* chevron too, one without keeps them;
|
|
254
|
-
* `wrapperClassName` styles the div that holds the `error` message.
|
|
255
|
-
* - `Select.Value` — the value slot: the picked row's label, else the muted
|
|
256
|
-
* `placeholder`.
|
|
257
|
-
* - `Select.Content` — the popup: `side`, `align`, `sideOffset`, `alignOffset`,
|
|
258
|
-
* `collisionPadding`, `finalFocus`, the morph options `from`, `effect`,
|
|
259
|
-
* `hideSource`, `tier`, and HTML attributes (`id`, `aria-*`, `data-*`) for
|
|
260
|
-
* the popup element.
|
|
261
|
-
* - `Select.Item` — one row: `value`, optional `index`, `textValue`, `icon`,
|
|
262
|
-
* `disabled`; its children are the label. `render` / `asChild` make the
|
|
263
|
-
* row your own element, a link for instance.
|
|
264
|
-
* - `Select.Group`, `Select.Label`, `Select.Separator` — sections, their
|
|
265
|
-
* captions and hairlines between them.
|
|
266
|
-
*
|
|
267
|
-
* @example {@include ./examples.mdx}
|
|
268
|
-
*/
|
|
269
|
-
function Select({
|
|
270
|
-
children,
|
|
271
|
-
value,
|
|
272
|
-
defaultValue,
|
|
273
|
-
onValueChange,
|
|
274
|
-
open: openProp,
|
|
275
|
-
defaultOpen = false,
|
|
276
|
-
onOpenChange,
|
|
277
|
-
disabled = false,
|
|
278
|
-
name,
|
|
279
|
-
required,
|
|
280
|
-
size,
|
|
281
|
-
}: SelectProps) {
|
|
282
|
-
const [internalValue, setInternalValue] = useState(defaultValue ?? '')
|
|
283
|
-
const [internalOpen, setInternalOpen] = useState(defaultOpen)
|
|
284
|
-
const { origin, capture } = useMorphOrigin()
|
|
285
|
-
const currentValue = value !== undefined ? value : internalValue
|
|
286
|
-
const controlledOpen = openProp !== undefined
|
|
287
|
-
const open = controlledOpen ? openProp : internalOpen
|
|
288
|
-
|
|
289
|
-
const items = useMemo(() => collectSelectItems(children), [children])
|
|
290
|
-
|
|
291
|
-
const handleValueChange = useCallback(
|
|
292
|
-
(next: string | null) => {
|
|
293
|
-
const v = next ?? ''
|
|
294
|
-
if (value === undefined) setInternalValue(v)
|
|
295
|
-
onValueChange?.(v)
|
|
296
|
-
},
|
|
297
|
-
[value, onValueChange],
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
const ackTimeoutRef = useRef<number | null>(null)
|
|
301
|
-
const cancelAckClose = useCallback(() => {
|
|
302
|
-
if (ackTimeoutRef.current !== null) {
|
|
303
|
-
clearTimeout(ackTimeoutRef.current)
|
|
304
|
-
ackTimeoutRef.current = null
|
|
305
|
-
}
|
|
306
|
-
}, [])
|
|
307
|
-
useEffect(() => cancelAckClose, [cancelAckClose])
|
|
308
|
-
|
|
309
|
-
const onOpenChangeRef = useRef(onOpenChange)
|
|
310
|
-
onOpenChangeRef.current = onOpenChange
|
|
311
|
-
const controlledOpenRef = useRef(controlledOpen)
|
|
312
|
-
controlledOpenRef.current = controlledOpen
|
|
313
|
-
|
|
314
|
-
// Picking an item acknowledges before closing: the close is deferred by
|
|
315
|
-
// selectionAckMs so the checkmark draw and the selected background's spring
|
|
316
|
-
// to the picked row are seen. Every other close reason (Escape, outside
|
|
317
|
-
// press, trigger toggle, focus-out) closes immediately and cancels any
|
|
318
|
-
// pending acknowledgment; re-picking within the window restarts it. A
|
|
319
|
-
// controlled `open` is the caller's: every request is reported at once and
|
|
320
|
-
// nothing is deferred.
|
|
321
|
-
const handleOpenChange = useCallback(
|
|
322
|
-
(nextOpen: boolean, eventDetails: SelectOpenChangeDetails) => {
|
|
323
|
-
if (nextOpen) capture(eventDetails)
|
|
324
|
-
cancelAckClose()
|
|
325
|
-
if (!nextOpen && eventDetails.reason === 'item-press' && !controlledOpenRef.current) {
|
|
326
|
-
ackTimeoutRef.current = window.setTimeout(() => {
|
|
327
|
-
ackTimeoutRef.current = null
|
|
328
|
-
setInternalOpen(false)
|
|
329
|
-
onOpenChangeRef.current?.(false, eventDetails)
|
|
330
|
-
}, selectionAckMs)
|
|
331
|
-
return
|
|
332
|
-
}
|
|
333
|
-
if (!controlledOpenRef.current) setInternalOpen(nextOpen)
|
|
334
|
-
onOpenChangeRef.current?.(nextOpen, eventDetails)
|
|
335
|
-
},
|
|
336
|
-
[cancelAckClose, capture],
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
const picked = items.find(item => item.value === currentValue)?.label
|
|
340
|
-
const textLabel = picked === undefined || typeof picked === 'string' || typeof picked === 'number'
|
|
341
|
-
const ctx = useMemo(() => ({ value: currentValue, open, origin, textLabel }), [currentValue, open, origin, textLabel])
|
|
342
|
-
|
|
343
|
-
// A size prop pins the whole compound (trigger + portalled popup — React
|
|
344
|
-
// context crosses portals) to one step of the ladder.
|
|
345
|
-
const root = (
|
|
346
|
-
<SelectContext.Provider value={ctx}>
|
|
347
|
-
<SelectPrimitive.Root
|
|
348
|
-
// Always controlled; "" (no selection) maps to Base UI's null.
|
|
349
|
-
value={currentValue === '' ? null : currentValue}
|
|
350
|
-
onValueChange={handleValueChange}
|
|
351
|
-
open={open}
|
|
352
|
-
onOpenChange={handleOpenChange}
|
|
353
|
-
items={items}
|
|
354
|
-
disabled={disabled}
|
|
355
|
-
name={name}
|
|
356
|
-
required={required}
|
|
357
|
-
// Non-modal: the page keeps scrolling and the Positioner tracks the
|
|
358
|
-
// anchor, so the popup follows its trigger instead of detaching.
|
|
359
|
-
modal={false}
|
|
360
|
-
>
|
|
361
|
-
{children}
|
|
362
|
-
</SelectPrimitive.Root>
|
|
363
|
-
</SelectContext.Provider>
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
return size ? <SizeProvider size={size}>{root}</SizeProvider> : root
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
Select.displayName = 'Select'
|
|
370
|
-
|
|
371
|
-
// ---------------------------------------------------------------------------
|
|
372
|
-
// SelectValue
|
|
373
|
-
// ---------------------------------------------------------------------------
|
|
374
|
-
|
|
375
|
-
interface SelectValueProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'placeholder'> {
|
|
376
|
-
/** Shown, muted, while nothing is selected. Defaults to `"Select…"`. */
|
|
377
|
-
placeholder?: ReactNode
|
|
378
|
-
/** Extra classes for the value text. */
|
|
379
|
-
className?: string
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/** The trigger's value slot: the picked row's label (its children), else the muted placeholder. */
|
|
383
|
-
const SelectValue = forwardRef<HTMLSpanElement, SelectValueProps>(
|
|
384
|
-
({ placeholder = 'Select…', className, ...props }, ref) => {
|
|
385
|
-
const { textLabel } = useSelectContext()
|
|
386
|
-
return (
|
|
387
|
-
<SelectPrimitive.Value
|
|
388
|
-
ref={ref}
|
|
389
|
-
placeholder={placeholder}
|
|
390
|
-
// py-1/-my-1: truncate's overflow:hidden clips at the padding
|
|
391
|
-
// box, and the trimmed box excludes ascenders/descenders — the
|
|
392
|
-
// padding gives glyphs room while the negative margin keeps the
|
|
393
|
-
// trimmed layout box.
|
|
394
|
-
className={cn(
|
|
395
|
-
'min-w-0 flex-1 text-left data-[placeholder]:text-muted-foreground',
|
|
396
|
-
// A node label (a badge, an icon and text) keeps its own box: trimming
|
|
397
|
-
// and clipping it would cut it top and bottom.
|
|
398
|
-
textLabel ? 'truncate [text-box:trim-both_cap_alphabetic] py-1 -my-1' : 'flex items-center',
|
|
399
|
-
className,
|
|
400
|
-
)}
|
|
401
|
-
{...props}
|
|
402
|
-
/>
|
|
403
|
-
)
|
|
404
|
-
},
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
SelectValue.displayName = 'SelectValue'
|
|
408
|
-
|
|
409
|
-
// ---------------------------------------------------------------------------
|
|
410
|
-
// SelectTrigger
|
|
411
|
-
// ---------------------------------------------------------------------------
|
|
412
|
-
|
|
413
|
-
interface SelectTriggerProps extends Omit<SlotProps, 'children' | 'placeholder'>, VariantProps<typeof triggerVariants> {
|
|
414
|
-
/** Replaces the built-in value slot, typically with a `Select.Value` (shadcn's `<SelectValue placeholder>`). The icon and chevron stay. With `asChild`, the single child element that becomes the trigger instead, and `render` takes an element the same way: that element replaces the whole field. If it has children of its own, they are the entire body, so the `icon`, `placeholder` and chevron are not rendered and the field look, including the `error` border tint, is not applied (the `error` message and `aria-invalid` remain). If it has none, it still gets the value slot, `icon` and chevron, unstyled. */
|
|
415
|
-
children?: ReactNode
|
|
416
|
-
/** Visual style of the trigger. Defaults to `"bordered"`. */
|
|
417
|
-
variant?: 'bordered' | 'borderless'
|
|
418
|
-
/** Optional icon displayed before the value. */
|
|
419
|
-
icon?: IconComponent
|
|
420
|
-
/** Text shown when no value is selected. Defaults to `"Select…"`. */
|
|
421
|
-
placeholder?: string
|
|
422
|
-
/** Error message shown below the trigger; also tints the border and sets `aria-invalid`. */
|
|
423
|
-
error?: string
|
|
424
|
-
/** Size override for the trigger alone. Prefer the `size` prop on `<Select>` (or a surrounding SizeProvider) so the popup matches. */
|
|
425
|
-
size?: SizeVariant
|
|
426
|
-
/** Extra classes for the trigger button. The field has a minimum width of 160px (128px compact); pass `min-w-0` to lift that floor. */
|
|
427
|
-
className?: string
|
|
428
|
-
/** Extra classes for the div around the trigger, which holds the `error` message under it. It is a plain flex column, so `contents` makes it vanish from the layout. */
|
|
429
|
-
wrapperClassName?: string
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const SelectTrigger = forwardRef<HTMLButtonElement, SelectTriggerProps>(
|
|
433
|
-
(
|
|
434
|
-
{
|
|
435
|
-
className,
|
|
436
|
-
wrapperClassName,
|
|
437
|
-
variant,
|
|
438
|
-
icon: Icon,
|
|
439
|
-
placeholder = 'Select…',
|
|
440
|
-
error,
|
|
441
|
-
size,
|
|
442
|
-
render,
|
|
443
|
-
asChild,
|
|
444
|
-
children,
|
|
445
|
-
...props
|
|
446
|
-
},
|
|
447
|
-
ref,
|
|
448
|
-
) => {
|
|
449
|
-
const shape = useShape()
|
|
450
|
-
const sizeClasses = useSize(size)
|
|
451
|
-
const compact = sizeClasses.variant === 'compact'
|
|
452
|
-
// The element a `render` / `asChild` caller brings is the whole trigger, look included: it gets only
|
|
453
|
-
// the `group` class, so the field's border, size, padding and `error` border tint are not applied
|
|
454
|
-
// (the `error` message below it and `aria-invalid` still are). What goes inside depends on the
|
|
455
|
-
// element's own children:
|
|
456
|
-
// - it has some: they are the whole body and replace the field's, so the icon, `placeholder` and
|
|
457
|
-
// chevron are not rendered (add a `Select.Value` among them to show the picked label);
|
|
458
|
-
// - it has none: it gets the field's body, `icon` (if any), value slot with `placeholder`, and
|
|
459
|
-
// chevron, in an unstyled trigger.
|
|
460
|
-
const el = slotRender(render, asChild, asChild ? children : undefined)
|
|
461
|
-
const content = el ? itemLabel(asChild, render, children) : children
|
|
462
|
-
|
|
463
|
-
return (
|
|
464
|
-
<div className={cn('flex flex-col gap-1', wrapperClassName)}>
|
|
465
|
-
<SelectPrimitive.Trigger
|
|
466
|
-
ref={ref}
|
|
467
|
-
render={el}
|
|
468
|
-
aria-invalid={!!error || undefined}
|
|
469
|
-
className={
|
|
470
|
-
el
|
|
471
|
-
? cn('group', className)
|
|
472
|
-
: cn(
|
|
473
|
-
triggerVariants({ variant }),
|
|
474
|
-
sizeClasses.control,
|
|
475
|
-
sizeClasses.text,
|
|
476
|
-
sizeClasses.px,
|
|
477
|
-
sizeClasses.gap,
|
|
478
|
-
compact ? 'min-w-[128px]' : 'min-w-[160px]',
|
|
479
|
-
shape.input,
|
|
480
|
-
error && 'border-destructive/50 hover:border-destructive/50',
|
|
481
|
-
className,
|
|
482
|
-
)
|
|
483
|
-
}
|
|
484
|
-
{...props}
|
|
485
|
-
id={slotId(el, props.id)}
|
|
486
|
-
>
|
|
487
|
-
<span className={cn('flex items-center min-w-0 flex-1', sizeClasses.gap)}>
|
|
488
|
-
{Icon && (
|
|
489
|
-
<Icon
|
|
490
|
-
size={sizeClasses.icon}
|
|
491
|
-
strokeWidth={1.5}
|
|
492
|
-
className="shrink-0 text-muted-foreground transition-[color,stroke-width] duration-fast group-hover:text-foreground group-hover:stroke-[2]"
|
|
493
|
-
/>
|
|
494
|
-
)}
|
|
495
|
-
{content ?? <SelectValue placeholder={placeholder} />}
|
|
496
|
-
</span>
|
|
497
|
-
|
|
498
|
-
<svg
|
|
499
|
-
width={sizeClasses.icon}
|
|
500
|
-
height={sizeClasses.icon}
|
|
501
|
-
viewBox="0 0 24 24"
|
|
502
|
-
fill="none"
|
|
503
|
-
stroke="currentColor"
|
|
504
|
-
strokeWidth={2}
|
|
505
|
-
strokeLinecap="round"
|
|
506
|
-
strokeLinejoin="round"
|
|
507
|
-
className="shrink-0 text-muted-foreground transition-colors duration-fast group-hover:text-foreground"
|
|
508
|
-
>
|
|
509
|
-
<path d="M6 9l6 6 6-6" />
|
|
510
|
-
</svg>
|
|
511
|
-
</SelectPrimitive.Trigger>
|
|
512
|
-
{error && <span className="text-caption text-destructive pl-3">{error}</span>}
|
|
513
|
-
</div>
|
|
514
|
-
)
|
|
515
|
-
},
|
|
516
|
-
)
|
|
517
|
-
|
|
518
|
-
SelectTrigger.displayName = 'SelectTrigger'
|
|
519
|
-
|
|
520
|
-
// ---------------------------------------------------------------------------
|
|
521
|
-
// SelectContent
|
|
522
|
-
// ---------------------------------------------------------------------------
|
|
523
|
-
|
|
524
|
-
type SelectPositionerProps = ComponentProps<typeof SelectPrimitive.Positioner>
|
|
525
|
-
type SelectPopupProps = ComponentProps<typeof SelectPrimitive.Popup>
|
|
526
|
-
|
|
527
|
-
// Handlers the popup drives itself (fluid hover, the keyboard ring gate): not passed through.
|
|
528
|
-
type PopupOwnHandlers =
|
|
529
|
-
| 'onKeyDownCapture'
|
|
530
|
-
| 'onMouseEnter'
|
|
531
|
-
| 'onMouseMove'
|
|
532
|
-
| 'onMouseLeave'
|
|
533
|
-
| 'onClick'
|
|
534
|
-
| 'onFocus'
|
|
535
|
-
| 'onBlur'
|
|
536
|
-
|
|
537
|
-
interface SelectContentProps extends Omit<HTMLAttributes<HTMLDivElement>, PopupOwnHandlers | 'className' | 'children'> {
|
|
538
|
-
/** 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'`. */
|
|
539
|
-
from?: 'trigger' | 'pointer' | 'center' | 'top' | 'right' | 'bottom' | 'left' | React.RefObject<HTMLElement | null>
|
|
540
|
-
/** How it grows (see Morph): with the liquid goo neck, a plain morph, a slide or a fade. Defaults to `'goo'`. */
|
|
541
|
-
effect?: 'goo' | 'morph' | 'slide' | 'fade'
|
|
542
|
-
/** Hide the trigger while open, so it reads as turning into the menu. Defaults to `false`. */
|
|
543
|
-
hideSource?: boolean
|
|
544
|
-
/** Spring tier of a plain morph, slide or fade; goo runs on its own `spring.goo`. Defaults to `'moderate'`. */
|
|
545
|
-
tier?: 'moderate' | 'slow'
|
|
546
|
-
/** Side of the trigger the menu prefers; it flips when there is no room. Defaults to `"bottom"`. */
|
|
547
|
-
side?: 'top' | 'bottom' | 'left' | 'right'
|
|
548
|
-
/** Alignment against the trigger. Defaults to `"start"`. */
|
|
549
|
-
align?: 'start' | 'center' | 'end'
|
|
550
|
-
/** Gap to the trigger, in px. Ignored when the menu opens in place over the trigger (`effect="morph"` with `hideSource`). Defaults to `6`. */
|
|
551
|
-
sideOffset?: number
|
|
552
|
-
/** Shift along the aligned edge in px, after `align` (a number, or Base UI's function of side, align and sizes). Defaults to `0`. */
|
|
553
|
-
alignOffset?: SelectPositionerProps['alignOffset']
|
|
554
|
-
/** Space kept between the menu and the edge of its collision boundary, in px or per side. Defaults to `5`. */
|
|
555
|
-
collisionPadding?: SelectPositionerProps['collisionPadding']
|
|
556
|
-
/** Where focus goes when the menu closes: a ref, `false` to leave it, `true` for the default, or a function of how it closed (`'mouse'`, `'touch'`, `'pen'`, `'keyboard'`). Defaults to the trigger. */
|
|
557
|
-
finalFocus?: SelectPopupProps['finalFocus']
|
|
558
|
-
/** Additional classes for the menu container. */
|
|
559
|
-
className?: string
|
|
560
|
-
/** `Select.Item`, `Select.Group`, `Select.Label`, `Select.Separator`. */
|
|
561
|
-
children: ReactNode
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const SelectContent = forwardRef<HTMLDivElement, SelectContentProps>(
|
|
565
|
-
(
|
|
566
|
-
{
|
|
567
|
-
from,
|
|
568
|
-
effect,
|
|
569
|
-
hideSource,
|
|
570
|
-
tier = 'moderate',
|
|
571
|
-
side = 'bottom',
|
|
572
|
-
align = 'start',
|
|
573
|
-
sideOffset = 6,
|
|
574
|
-
alignOffset,
|
|
575
|
-
collisionPadding,
|
|
576
|
-
finalFocus,
|
|
577
|
-
className,
|
|
578
|
-
children,
|
|
579
|
-
...props
|
|
580
|
-
},
|
|
581
|
-
ref,
|
|
582
|
-
) => {
|
|
583
|
-
const { open, value, origin } = useSelectContext()
|
|
584
|
-
const rowOrder = useRowOrder()
|
|
585
|
-
const morph = useMorph(open, origin, {
|
|
586
|
-
from,
|
|
587
|
-
effect,
|
|
588
|
-
hideSource,
|
|
589
|
-
tier,
|
|
590
|
-
// A custom `finalFocus` places focus itself; the morph's own return to the trigger would undo it.
|
|
591
|
-
returnFocus: typeof finalFocus === 'boolean' ? finalFocus : finalFocus === undefined,
|
|
592
|
-
})
|
|
593
|
-
useImperativeHandle(ref, () => morph.popup as HTMLDivElement, [morph.popup])
|
|
594
|
-
// Lifts 2 levels off its substrate with a fixed shadow (see Elevated).
|
|
595
|
-
const level = Math.min(useSurface() + 2, 8)
|
|
596
|
-
const shape = popupShape
|
|
597
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
598
|
-
|
|
599
|
-
const hover = useFluidHover(containerRef, { isItemDisabled: isDisabledRow })
|
|
600
|
-
const { activeIndex, setActiveIndex, itemRects, isMeasured, handlers, registerItem, remeasure } = hover
|
|
601
|
-
|
|
602
|
-
const [focusedIndex, setFocusedIndex] = useState<number | null>(null)
|
|
603
|
-
|
|
604
|
-
// Keyboard focus ring gate: seeded from the trigger's :focus-visible at
|
|
605
|
-
// open, earned by navigation keys inside the popup.
|
|
606
|
-
const { keyboardNavRef, trackKeyboardNav } = useKeyboardNavGate(open)
|
|
607
|
-
const [checkedIndex, setCheckedIndex] = useState<number | undefined>(undefined)
|
|
608
|
-
|
|
609
|
-
// Fresh rects once per open. Measuring is the hook's job — it owns the
|
|
610
|
-
// one coalesced pass that item registration and container resizes both
|
|
611
|
-
// feed into, and a second pass from elsewhere is what used to land a
|
|
612
|
-
// corrected rect on an already-mounted overlay. The popup keeps its items
|
|
613
|
-
// registered while it sits hidden between opens, so registration alone
|
|
614
|
-
// would never trigger a fresh pass on reopen.
|
|
615
|
-
useEffect(() => {
|
|
616
|
-
if (!open) return
|
|
617
|
-
remeasure()
|
|
618
|
-
}, [open, remeasure])
|
|
619
|
-
|
|
620
|
-
// Detect the checked row. Deliberately does NOT remeasure on a value
|
|
621
|
-
// change while open: the rows haven't moved, so the published rects stay
|
|
622
|
-
// trustworthy and only checkedIndex switches — which lets the selected
|
|
623
|
-
// marker spring from the old row to the picked one (the selection
|
|
624
|
-
// acknowledgment) instead of unmounting and snapping.
|
|
625
|
-
useEffect(() => {
|
|
626
|
-
if (!open) return
|
|
627
|
-
// Double rAF: first waits for React commit, second for layout
|
|
628
|
-
let inner: number
|
|
629
|
-
const outer = requestAnimationFrame(() => {
|
|
630
|
-
inner = requestAnimationFrame(() => {
|
|
631
|
-
const container = containerRef.current
|
|
632
|
-
if (container) {
|
|
633
|
-
// The row's own index: rows may be numbered out of DOM order (an explicit `index`).
|
|
634
|
-
const row = Array.from(container.querySelectorAll<HTMLElement>('[data-fluid-hover-index]')).find(
|
|
635
|
-
el => el.getAttribute('data-value') === value,
|
|
636
|
-
)
|
|
637
|
-
setCheckedIndex(row ? Number(row.getAttribute('data-fluid-hover-index')) : undefined)
|
|
638
|
-
}
|
|
639
|
-
})
|
|
640
|
-
})
|
|
641
|
-
return () => {
|
|
642
|
-
cancelAnimationFrame(outer)
|
|
643
|
-
cancelAnimationFrame(inner)
|
|
644
|
-
}
|
|
645
|
-
}, [open, value])
|
|
646
|
-
|
|
647
|
-
// Reset every overlay index as the close begins. checkedIndex otherwise
|
|
648
|
-
// lags one open behind value (picking an item closes the popup before the
|
|
649
|
-
// effect above re-syncs it), and a leftover activeIndex is worse: Base UI
|
|
650
|
-
// keeps the popup mounted through the exit tween, so on reopen the hover
|
|
651
|
-
// pill would still be sitting on the previously active row and spring from
|
|
652
|
-
// there to the row that auto-focus lands on.
|
|
653
|
-
useEffect(() => {
|
|
654
|
-
if (open) return
|
|
655
|
-
setCheckedIndex(undefined)
|
|
656
|
-
setActiveIndex(null)
|
|
657
|
-
setFocusedIndex(null)
|
|
658
|
-
}, [open, setActiveIndex])
|
|
659
|
-
|
|
660
|
-
// Overlays read rects only once the hook reports the item set fully
|
|
661
|
-
// measured. Positioning one from an incomplete pass mounts it at the wrong
|
|
662
|
-
// row, and the correcting pass then springs it across the list.
|
|
663
|
-
const checkedRect = isMeasured && checkedIndex != null ? itemRects[checkedIndex] : null
|
|
664
|
-
const focusRect = isMeasured && focusedIndex !== null ? itemRects[focusedIndex] : null
|
|
665
|
-
|
|
666
|
-
const contentCtx = useMemo(
|
|
667
|
-
() => ({ registerItem, activeIndex, checkedIndex }),
|
|
668
|
-
[registerItem, activeIndex, checkedIndex],
|
|
669
|
-
)
|
|
670
|
-
|
|
671
|
-
return (
|
|
672
|
-
<SelectPrimitive.Portal>
|
|
673
|
-
<SelectPrimitive.Positioner
|
|
674
|
-
side={side}
|
|
675
|
-
align={align}
|
|
676
|
-
sideOffset={opensInPlace({ effect, hideSource }) ? inPlaceOffset : sideOffset}
|
|
677
|
-
alignOffset={alignOffset}
|
|
678
|
-
collisionPadding={collisionPadding}
|
|
679
|
-
alignItemWithTrigger={false}
|
|
680
|
-
className="z-50 outline-none"
|
|
681
|
-
>
|
|
682
|
-
<SelectContentContext.Provider value={contentCtx}>
|
|
683
|
-
<SelectPrimitive.Popup
|
|
684
|
-
ref={morph.popupRef}
|
|
685
|
-
finalFocus={finalFocus}
|
|
686
|
-
{...props}
|
|
687
|
-
// Capture phase: the primitive moves focus during its own keydown
|
|
688
|
-
// handling, so the nav flag must be set before then.
|
|
689
|
-
onKeyDownCapture={trackKeyboardNav}
|
|
690
|
-
onMouseEnter={() => {
|
|
691
|
-
handlers.onMouseEnter()
|
|
692
|
-
setFocusedIndex(null)
|
|
693
|
-
}}
|
|
694
|
-
onMouseMove={handlers.onMouseMove}
|
|
695
|
-
onMouseLeave={handlers.onMouseLeave}
|
|
696
|
-
onClick={handlers.onClick}
|
|
697
|
-
onFocus={e => {
|
|
698
|
-
const indexAttr = (e.target as HTMLElement)
|
|
699
|
-
.closest('[data-fluid-hover-index]')
|
|
700
|
-
?.getAttribute('data-fluid-hover-index')
|
|
701
|
-
if (indexAttr != null) {
|
|
702
|
-
const idx = Number(indexAttr)
|
|
703
|
-
setActiveIndex(idx)
|
|
704
|
-
setFocusedIndex(
|
|
705
|
-
keyboardNavRef.current && (e.target as HTMLElement).matches(':focus-visible') ? idx : null,
|
|
706
|
-
)
|
|
707
|
-
}
|
|
708
|
-
}}
|
|
709
|
-
onBlur={e => {
|
|
710
|
-
// The popup itself takes focus when the pointer leaves a row; only a
|
|
711
|
-
// departure from the whole popup ends the hover session.
|
|
712
|
-
if (e.currentTarget.contains(e.relatedTarget as Node)) return
|
|
713
|
-
setFocusedIndex(null)
|
|
714
|
-
setActiveIndex(null)
|
|
715
|
-
}}
|
|
716
|
-
className="relative select-none outline-none"
|
|
717
|
-
>
|
|
718
|
-
<SurfaceProvider value={level}>
|
|
719
|
-
<MorphSurface
|
|
720
|
-
morph={morph}
|
|
721
|
-
bg={SURFACE_BG[level]}
|
|
722
|
-
shadow={SURFACE_SHADOW[3]}
|
|
723
|
-
radius={shape.container}
|
|
724
|
-
className={cn(
|
|
725
|
-
// min-w tracks the trigger via the Positioner's --anchor-width
|
|
726
|
-
// var, matching the pre-migration minWidth: triggerRect.width.
|
|
727
|
-
`flex flex-col min-w-[var(--anchor-width)] max-h-[min(300px,var(--available-height))] overflow-hidden ${shape.container}`,
|
|
728
|
-
className,
|
|
729
|
-
)}
|
|
730
|
-
>
|
|
731
|
-
{/* The list scrolls inside a ScrollArea; this wrapper is the rows'
|
|
732
|
-
offsetParent, so the overlays scroll with them. */}
|
|
733
|
-
<ScrollArea
|
|
734
|
-
className={popupScrollAreaClass}
|
|
735
|
-
viewportClassName={cn(popupViewportClass, 'scroll-fade')}
|
|
736
|
-
>
|
|
737
|
-
<div ref={containerRef} className="relative flex flex-col p-1">
|
|
738
|
-
{/* The three overlays are torn down as the close begins rather
|
|
739
|
-
than exit-animated, because an overlay still mounted when the
|
|
740
|
-
popup reopens is one AnimatePresence re-adopts under its old
|
|
741
|
-
key: `initial` never runs again, so it keeps the position of the
|
|
742
|
-
row it had before and animates from there to the new one. The
|
|
743
|
-
popup's own fade covers their disappearance. */}
|
|
744
|
-
{/* Selected background */}
|
|
745
|
-
{open && (
|
|
746
|
-
<AnimatePresence>
|
|
747
|
-
{checkedRect && (
|
|
748
|
-
<motion.div
|
|
749
|
-
className={`absolute ${shape.bg} bg-active pointer-events-none`}
|
|
750
|
-
// Position lives in `animate` so an in-session value
|
|
751
|
-
// change springs the marker to the picked row (the
|
|
752
|
-
// selection acknowledgment). Safe against the reopen
|
|
753
|
-
// slide: the `open &&` teardown means no marker
|
|
754
|
-
// survives a close, and a fresh mount with
|
|
755
|
-
// initial={false} renders snapped at these values.
|
|
756
|
-
initial={false}
|
|
757
|
-
animate={{
|
|
758
|
-
top: checkedRect.top,
|
|
759
|
-
left: checkedRect.left,
|
|
760
|
-
width: checkedRect.width,
|
|
761
|
-
height: checkedRect.height,
|
|
762
|
-
opacity: 1,
|
|
763
|
-
}}
|
|
764
|
-
exit={{ opacity: 0, transition: spring.moderate.exit }}
|
|
765
|
-
transition={{
|
|
766
|
-
...spring.moderate,
|
|
767
|
-
opacity: { duration: spring.fast.duration },
|
|
768
|
-
}}
|
|
769
|
-
/>
|
|
770
|
-
)}
|
|
771
|
-
</AnimatePresence>
|
|
772
|
-
)}
|
|
773
|
-
|
|
774
|
-
{/* Hover background */}
|
|
775
|
-
<FluidHoverHighlight hover={hover} hidden={!open} className={shape.bg} />
|
|
776
|
-
|
|
777
|
-
{/* Focus ring */}
|
|
778
|
-
{open && <FocusRing rect={focusRect} shape="rounded" />}
|
|
779
|
-
|
|
780
|
-
<RowOrderContext.Provider value={rowOrder}>{children}</RowOrderContext.Provider>
|
|
781
|
-
</div>
|
|
782
|
-
</ScrollArea>
|
|
783
|
-
</MorphSurface>
|
|
784
|
-
</SurfaceProvider>
|
|
785
|
-
</SelectPrimitive.Popup>
|
|
786
|
-
</SelectContentContext.Provider>
|
|
787
|
-
</SelectPrimitive.Positioner>
|
|
788
|
-
</SelectPrimitive.Portal>
|
|
789
|
-
)
|
|
790
|
-
},
|
|
791
|
-
)
|
|
792
|
-
|
|
793
|
-
SelectContent.displayName = 'SelectContent'
|
|
794
|
-
|
|
795
|
-
// ---------------------------------------------------------------------------
|
|
796
|
-
// SelectItem
|
|
797
|
-
// ---------------------------------------------------------------------------
|
|
798
|
-
|
|
799
|
-
interface SelectItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
800
|
-
/** The element that becomes the row, e.g. a router link (it must forward its ref); it gets the row's look and behaviour. Its children fill the label unless `children` is given. */
|
|
801
|
-
render?: ReactElement
|
|
802
|
-
/** Make the single child element the row, its own children being the label. Both spellings work. Defaults to `false`. */
|
|
803
|
-
asChild?: boolean
|
|
804
|
-
/** Optional icon displayed before the label. */
|
|
805
|
-
icon?: IconComponent
|
|
806
|
-
/** Position among the menu's rows — what fluid hover measures rows by. Defaults to the row's place in document order. */
|
|
807
|
-
index?: number
|
|
808
|
-
/** Unique value for this option. */
|
|
809
|
-
value: string
|
|
810
|
-
/** The row's text for typeahead, when its children are not a plain string. */
|
|
811
|
-
textValue?: string
|
|
812
|
-
/** Disables this individual item. Defaults to `false`. */
|
|
813
|
-
disabled?: boolean
|
|
814
|
-
/** The row label. A string label is also what the trigger shows for this value. With `asChild`, the single child element that becomes the row. */
|
|
815
|
-
children?: ReactNode
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
|
|
819
|
-
(
|
|
820
|
-
{
|
|
821
|
-
className,
|
|
822
|
-
children: childrenProp,
|
|
823
|
-
render,
|
|
824
|
-
asChild,
|
|
825
|
-
icon: Icon,
|
|
826
|
-
value,
|
|
827
|
-
index: indexProp,
|
|
828
|
-
textValue,
|
|
829
|
-
disabled = false,
|
|
830
|
-
...props
|
|
831
|
-
},
|
|
832
|
-
ref,
|
|
833
|
-
) => {
|
|
834
|
-
const el = slotRender(render, asChild, asChild ? childrenProp : undefined)
|
|
835
|
-
const children = itemLabel(asChild, render, childrenProp)
|
|
836
|
-
const selectCtx = useSelectContext()
|
|
837
|
-
const contentCtx = useContext(SelectContentContext)
|
|
838
|
-
const internalRef = useRef<HTMLDivElement>(null)
|
|
839
|
-
const shape = popupShape
|
|
840
|
-
const sizeClasses = useSize()
|
|
841
|
-
const compact = sizeClasses.variant === 'compact'
|
|
842
|
-
const hasMounted = useRef(false)
|
|
843
|
-
|
|
844
|
-
useEffect(() => {
|
|
845
|
-
hasMounted.current = true
|
|
846
|
-
}, [])
|
|
847
|
-
|
|
848
|
-
// Register with fluid hover. Depends on the (stable) registerItem
|
|
849
|
-
// rather than the content context, which is rebuilt on every activeIndex
|
|
850
|
-
// change: keying the effect to the whole context re-ran it per mousemove,
|
|
851
|
-
// unregistering and re-registering every row and so keeping the hook's
|
|
852
|
-
// measurement permanently unsettled while the pointer moved.
|
|
853
|
-
const registerItem = contentCtx?.registerItem
|
|
854
|
-
const { index } = useRowSlot(internalRef, indexProp)
|
|
855
|
-
useRegisterFluidHoverItem(registerItem, index, internalRef)
|
|
856
|
-
|
|
857
|
-
const isActive = contentCtx?.activeIndex === index
|
|
858
|
-
const isChecked = selectCtx.value === value
|
|
859
|
-
const skipAnimation = !hasMounted.current
|
|
860
|
-
|
|
861
|
-
const rowProps = {
|
|
862
|
-
ref: (node: HTMLDivElement | null) => {
|
|
863
|
-
;(internalRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
864
|
-
for (const r of [ref, elementRef(el)]) {
|
|
865
|
-
if (typeof r === 'function') r(node)
|
|
866
|
-
else if (r) (r as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
867
|
-
}
|
|
868
|
-
},
|
|
869
|
-
'data-fluid-hover-index': index,
|
|
870
|
-
'data-value': value,
|
|
871
|
-
className: cn(
|
|
872
|
-
// Fixed height (was py-2 around a 19.5px line box ≈ 35.5px) so
|
|
873
|
-
// the text-box trim on the item text doesn't shrink the row.
|
|
874
|
-
// shrink-0: the popup is a max-height flex column, so without it
|
|
875
|
-
// a long list compresses rows to fit instead of scrolling.
|
|
876
|
-
`relative z-10 flex ${sizeClasses.control} shrink-0 items-center ${sizeClasses.gap} ${shape.item} ${sizeClasses.itemPx} ${sizeClasses.text} cursor-pointer outline-none select-none`,
|
|
877
|
-
'transition-[color] duration-fast',
|
|
878
|
-
isActive || isChecked ? 'text-foreground' : 'text-muted-foreground',
|
|
879
|
-
disabled && 'opacity-50 pointer-events-none',
|
|
880
|
-
className,
|
|
881
|
-
),
|
|
882
|
-
...props,
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
return (
|
|
886
|
-
<SelectPrimitive.Item
|
|
887
|
-
value={value}
|
|
888
|
-
disabled={disabled}
|
|
889
|
-
label={textValue ?? (typeof children === 'string' ? children : undefined)}
|
|
890
|
-
render={
|
|
891
|
-
el ? (
|
|
892
|
-
cloneElement(el, {
|
|
893
|
-
...rowProps,
|
|
894
|
-
className: cn(rowProps.className, (el.props as { className?: string }).className),
|
|
895
|
-
})
|
|
896
|
-
) : (
|
|
897
|
-
<div {...rowProps} />
|
|
898
|
-
)
|
|
899
|
-
}
|
|
900
|
-
>
|
|
901
|
-
{Icon && (
|
|
902
|
-
<Icon
|
|
903
|
-
size={sizeClasses.icon}
|
|
904
|
-
strokeWidth={isActive || isChecked ? 2 : 1.5}
|
|
905
|
-
className="shrink-0 transition-[color,stroke-width] duration-fast"
|
|
906
|
-
/>
|
|
907
|
-
)}
|
|
908
|
-
|
|
909
|
-
<SelectPrimitive.ItemText
|
|
910
|
-
// py-1/-my-1 keeps truncate's overflow:hidden from clipping
|
|
911
|
-
// ascenders/descenders outside the trimmed box.
|
|
912
|
-
render={
|
|
913
|
-
<span
|
|
914
|
-
className={cn(
|
|
915
|
-
'flex-1 min-w-0',
|
|
916
|
-
// Nodes keep their own box; only text is trimmed and truncated.
|
|
917
|
-
typeof children === 'string' || typeof children === 'number'
|
|
918
|
-
? 'truncate [text-box:trim-both_cap_alphabetic] py-1 -my-1'
|
|
919
|
-
: 'flex items-center',
|
|
920
|
-
)}
|
|
921
|
-
/>
|
|
922
|
-
}
|
|
923
|
-
>
|
|
924
|
-
{children}
|
|
925
|
-
</SelectPrimitive.ItemText>
|
|
926
|
-
|
|
927
|
-
{/* Always-rendered fixed slot so the check appearing/disappearing
|
|
928
|
-
never changes the row's intrinsic width — without it the whole
|
|
929
|
-
popup resizes when a selection lands. */}
|
|
930
|
-
<span aria-hidden className={cn('shrink-0', compact ? 'w-3.5 h-3.5' : 'w-4 h-4')}>
|
|
931
|
-
<AnimatePresence>
|
|
932
|
-
{isChecked && (
|
|
933
|
-
<motion.svg
|
|
934
|
-
key="check"
|
|
935
|
-
width={sizeClasses.icon}
|
|
936
|
-
height={sizeClasses.icon}
|
|
937
|
-
viewBox="0 0 24 24"
|
|
938
|
-
fill="none"
|
|
939
|
-
stroke="currentColor"
|
|
940
|
-
strokeWidth={2}
|
|
941
|
-
strokeLinecap="round"
|
|
942
|
-
strokeLinejoin="round"
|
|
943
|
-
className="text-foreground"
|
|
944
|
-
initial={{ opacity: 1 }}
|
|
945
|
-
animate={{ opacity: 1 }}
|
|
946
|
-
exit={{ opacity: 1 }}
|
|
947
|
-
>
|
|
948
|
-
<motion.path
|
|
949
|
-
d="M4 12L9 17L20 6"
|
|
950
|
-
initial={{ pathLength: skipAnimation ? 1 : 0 }}
|
|
951
|
-
animate={{
|
|
952
|
-
pathLength: 1,
|
|
953
|
-
transition: { duration: spring.fast.duration, ease: 'easeOut' },
|
|
954
|
-
}}
|
|
955
|
-
exit={{
|
|
956
|
-
pathLength: 0,
|
|
957
|
-
transition: { duration: 0.04, ease: 'easeIn' },
|
|
958
|
-
}}
|
|
959
|
-
/>
|
|
960
|
-
</motion.svg>
|
|
961
|
-
)}
|
|
962
|
-
</AnimatePresence>
|
|
963
|
-
</span>
|
|
964
|
-
</SelectPrimitive.Item>
|
|
965
|
-
)
|
|
966
|
-
},
|
|
967
|
-
)
|
|
968
|
-
|
|
969
|
-
SelectItem.displayName = 'SelectItem'
|
|
970
|
-
|
|
971
|
-
// ---------------------------------------------------------------------------
|
|
972
|
-
// SelectGroup + SelectLabel + SelectSeparator
|
|
973
|
-
// ---------------------------------------------------------------------------
|
|
974
|
-
|
|
975
|
-
const SelectGroup = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
976
|
-
({ children, className, ...props }, ref) => (
|
|
977
|
-
<div ref={ref} role="group" className={className} {...props}>
|
|
978
|
-
{children}
|
|
979
|
-
</div>
|
|
980
|
-
),
|
|
981
|
-
)
|
|
982
|
-
|
|
983
|
-
SelectGroup.displayName = 'SelectGroup'
|
|
984
|
-
|
|
985
|
-
const SelectLabel = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => {
|
|
986
|
-
// Group labels are the caption role of the type scale — see /docs/sizes.
|
|
987
|
-
const compact = useSize().variant === 'compact'
|
|
988
|
-
return (
|
|
989
|
-
<div
|
|
990
|
-
ref={ref}
|
|
991
|
-
className={cn(
|
|
992
|
-
'px-2 py-1.5 shrink-0 text-muted-foreground',
|
|
993
|
-
compact ? 'text-caption-compact' : 'text-caption',
|
|
994
|
-
className,
|
|
995
|
-
)}
|
|
996
|
-
{...props}
|
|
997
|
-
/>
|
|
998
|
-
)
|
|
999
|
-
})
|
|
1000
|
-
|
|
1001
|
-
SelectLabel.displayName = 'SelectLabel'
|
|
1002
|
-
|
|
1003
|
-
const SelectSeparator = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
|
|
1004
|
-
<div ref={ref} role="separator" className={cn('my-1 -mx-1 h-px shrink-0 bg-border/60', className)} {...props} />
|
|
1005
|
-
))
|
|
1006
|
-
|
|
1007
|
-
SelectSeparator.displayName = 'SelectSeparator'
|
|
1008
|
-
|
|
1009
|
-
// Compound statics: `<Select.Trigger>`, `<Select.Item>`, … Assigned directly
|
|
1010
|
-
// (not through Object.assign) so TypeScript's expando inference types them
|
|
1011
|
-
// onto the root function declaration.
|
|
1012
|
-
Select.Trigger = SelectTrigger
|
|
1013
|
-
Select.Value = SelectValue
|
|
1014
|
-
Select.Content = SelectContent
|
|
1015
|
-
Select.Item = SelectItem
|
|
1016
|
-
Select.Group = SelectGroup
|
|
1017
|
-
Select.Label = SelectLabel
|
|
1018
|
-
Select.Separator = SelectSeparator
|
|
1019
|
-
|
|
1020
|
-
// ---------------------------------------------------------------------------
|
|
1021
|
-
// Exports
|
|
1022
|
-
// ---------------------------------------------------------------------------
|
|
1023
|
-
|
|
1024
|
-
export type {
|
|
1025
|
-
SelectContentProps,
|
|
1026
|
-
SelectItemProps,
|
|
1027
|
-
SelectOpenChangeDetails,
|
|
1028
|
-
SelectProps,
|
|
1029
|
-
SelectTriggerProps,
|
|
1030
|
-
SelectValueProps,
|
|
1031
|
-
}
|
|
1032
|
-
export {
|
|
1033
|
-
Select,
|
|
1034
|
-
SelectContent,
|
|
1035
|
-
SelectGroup,
|
|
1036
|
-
SelectItem,
|
|
1037
|
-
SelectLabel,
|
|
1038
|
-
SelectSeparator,
|
|
1039
|
-
SelectTrigger,
|
|
1040
|
-
SelectValue,
|
|
1041
|
-
triggerVariants,
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
export default Select
|