@justanarthur/modo-ui 0.5.0 → 0.6.0-canary.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/DESIGN.md +608 -0
- package/README.md +61 -39
- package/blocks/sidebar-app/examples.mdx +33 -0
- package/blocks/sidebar-app/index.tsx +218 -0
- package/blocks/sidebar-app/inset-topbar.tsx +25 -0
- package/blocks/sidebar-app/menu-grid.ts +26 -0
- package/blocks/sidebar-app/nav-data.ts +36 -0
- package/blocks/sidebar-app/search-field.tsx +48 -0
- package/blocks/sidebar-app/user-footer.tsx +67 -0
- package/blocks/sidebar-app/workspace-header.tsx +129 -0
- package/components/AI/input-message/examples.mdx +124 -0
- package/components/AI/input-message/file-thumbnail.tsx +173 -0
- package/components/AI/input-message/index.tsx +1383 -0
- package/components/AI/thinking-indicator/examples.mdx +27 -0
- package/components/AI/thinking-indicator/index.tsx +210 -0
- package/components/accordion/examples.mdx +148 -0
- package/components/accordion/index.tsx +1034 -0
- package/components/avatar/examples.mdx +95 -0
- package/components/avatar/index.tsx +129 -0
- package/components/badge/change-demo.tsx +29 -0
- package/components/badge/examples.mdx +120 -0
- package/components/badge/index.tsx +225 -0
- package/components/badge/variants.ts +73 -0
- package/components/button/examples.mdx +164 -0
- package/components/button/index.tsx +416 -0
- package/components/button/loading-demo.tsx +25 -0
- package/components/button/variants.ts +166 -0
- package/components/card/examples.mdx +212 -0
- package/components/card/index.tsx +1037 -0
- package/components/copy/examples.mdx +87 -0
- package/components/copy/index.tsx +482 -0
- package/components/inputs/checkbox/examples.mdx +83 -0
- package/components/inputs/checkbox/index.tsx +611 -0
- package/components/inputs/checkbox/select-all-demo.tsx +48 -0
- package/components/inputs/color-picker/examples.mdx +72 -0
- package/components/inputs/color-picker/index.tsx +1955 -0
- package/components/inputs/color-picker/variants.ts +401 -0
- package/components/inputs/combobox/examples.mdx +273 -0
- package/components/inputs/combobox/index.tsx +1436 -0
- package/components/inputs/combobox/remote-demo.tsx +101 -0
- package/components/inputs/date-time/calendar/examples.mdx +34 -0
- package/components/inputs/date-time/calendar/index.tsx +351 -0
- package/components/inputs/date-time/calendar/use-month-window.ts +163 -0
- package/components/inputs/date-time/date-picker/examples.mdx +166 -0
- package/components/inputs/date-time/date-picker/index.tsx +385 -0
- package/components/inputs/date-time/date-picker/masked-date-input.tsx +336 -0
- package/components/inputs/date-time/date-range-picker/examples.mdx +29 -0
- package/components/inputs/date-time/date-range-picker/index.tsx +217 -0
- package/components/inputs/date-time/duration-picker/examples.mdx +16 -0
- package/components/inputs/date-time/duration-picker/index.tsx +189 -0
- package/components/inputs/date-time/month-picker/examples.mdx +16 -0
- package/components/inputs/date-time/month-picker/index.tsx +190 -0
- package/components/inputs/date-time/picker-shell.tsx +395 -0
- package/components/inputs/date-time/time-columns.tsx +91 -0
- package/components/inputs/date-time/time-picker/examples.mdx +26 -0
- package/components/inputs/date-time/time-picker/index.tsx +166 -0
- package/components/inputs/field/examples.mdx +113 -0
- package/components/inputs/field/index.tsx +267 -0
- package/components/inputs/file-dropzone/examples.mdx +73 -0
- package/components/inputs/file-dropzone/file-row.tsx +201 -0
- package/components/inputs/file-dropzone/index.tsx +296 -0
- package/components/inputs/file-dropzone/upload-demo.tsx +67 -0
- package/components/inputs/file-dropzone/variants.ts +94 -0
- package/components/inputs/input/examples.mdx +135 -0
- package/components/inputs/input/index.tsx +494 -0
- package/components/inputs/input/password-demo.tsx +30 -0
- package/components/inputs/label/examples.mdx +67 -0
- package/components/inputs/label/index.tsx +85 -0
- package/components/inputs/number-input/examples.mdx +56 -0
- package/components/inputs/number-input/index.tsx +214 -0
- package/components/inputs/radio/examples.mdx +77 -0
- package/components/inputs/radio/index.tsx +636 -0
- package/components/inputs/search/examples.mdx +74 -0
- package/components/inputs/search/index.tsx +333 -0
- package/components/inputs/select/examples.mdx +218 -0
- package/components/inputs/select/index.tsx +862 -0
- package/components/inputs/select/variants.ts +32 -0
- package/components/inputs/slider/examples.mdx +127 -0
- package/components/inputs/slider/index.tsx +1725 -0
- package/components/inputs/switch/examples.mdx +94 -0
- package/components/inputs/switch/index.tsx +504 -0
- package/components/inputs/textarea/examples.mdx +88 -0
- package/components/inputs/textarea/index.tsx +161 -0
- package/components/navigation/navigation-menu/examples.mdx +109 -0
- package/components/navigation/navigation-menu/index.tsx +613 -0
- package/components/navigation/pagination/examples.mdx +64 -0
- package/components/navigation/pagination/index.tsx +395 -0
- package/components/navigation/sidebar/examples.mdx +319 -0
- package/components/navigation/sidebar/index.tsx +453 -0
- package/components/navigation/sidebar/sidebar-core.tsx +1408 -0
- package/components/navigation/sidebar/sidebar-menu.tsx +1495 -0
- package/components/navigation/sidebar/sidebar.css +74 -0
- package/components/navigation/sidebar/variants.ts +55 -0
- package/components/navigation/tabs/examples.mdx +89 -0
- package/components/navigation/tabs/index.tsx +494 -0
- package/components/navigation/tabs-subtle/examples.mdx +49 -0
- package/components/navigation/tabs-subtle/index.tsx +512 -0
- package/components/overlays/alert-dialog/examples.mdx +123 -0
- package/components/overlays/alert-dialog/index.tsx +258 -0
- package/components/overlays/alert-dialog/pending-demo.tsx +36 -0
- package/components/overlays/command/examples.mdx +113 -0
- package/components/overlays/command/index.tsx +416 -0
- package/components/overlays/context-menu/examples.mdx +56 -0
- package/components/overlays/context-menu/index.tsx +279 -0
- package/components/overlays/dialog/examples.mdx +144 -0
- package/components/overlays/dialog/index.tsx +366 -0
- package/components/overlays/dropdown/dropdown-search.tsx +395 -0
- package/components/overlays/dropdown/examples.mdx +294 -0
- package/components/overlays/dropdown/index.tsx +1168 -0
- package/components/overlays/dropdown/menu-item.tsx +464 -0
- package/components/overlays/dropdown/menu-parts.tsx +660 -0
- package/components/overlays/popover/anchored-demo.tsx +59 -0
- package/components/overlays/popover/examples.mdx +180 -0
- package/components/overlays/popover/index.tsx +315 -0
- package/components/overlays/preview-card/examples.mdx +61 -0
- package/components/overlays/preview-card/index.tsx +204 -0
- package/components/overlays/sheet/examples.mdx +89 -0
- package/components/overlays/sheet/index.tsx +247 -0
- package/components/overlays/toast/examples.mdx +68 -0
- package/components/overlays/toast/global-demo.tsx +72 -0
- package/components/overlays/toast/index.tsx +474 -0
- package/components/overlays/toast/manager.ts +193 -0
- package/components/overlays/tooltip/examples.mdx +106 -0
- package/components/overlays/tooltip/index.tsx +519 -0
- package/components/progress/examples.mdx +59 -0
- package/components/progress/index.tsx +174 -0
- package/components/progress/progress.css +49 -0
- package/components/progress/step-demo.tsx +27 -0
- package/components/table/examples.mdx +184 -0
- package/components/table/index.tsx +510 -0
- package/components/table/sort-demo.tsx +61 -0
- package/dist/base.css +100 -0
- package/dist/blocks/sidebar-app/index.js +39 -26
- package/dist/components/AI/input-message/index.js +22 -16
- package/dist/components/AI/thinking-indicator/index.js +9 -6
- package/dist/components/accordion/index.js +16 -13
- package/dist/components/avatar/index.d.ts +46 -0
- package/dist/components/avatar/index.js +18 -0
- package/dist/components/badge/index.d.ts +18 -11
- package/dist/components/badge/index.js +11 -7
- package/dist/components/badge/variants.d.ts +33 -0
- package/dist/components/badge/variants.js +10 -0
- package/dist/components/button/index.d.ts +53 -17
- package/dist/components/button/index.js +10 -6
- package/dist/components/button/variants.d.ts +39 -0
- package/dist/components/button/variants.js +8 -0
- package/dist/components/card/index.d.ts +16 -3
- package/dist/components/card/index.js +16 -11
- package/dist/components/copy/index.js +13 -9
- package/dist/components/inputs/checkbox/index.d.ts +28 -9
- package/dist/components/inputs/checkbox/index.js +17 -13
- package/dist/components/inputs/color-picker/index.d.ts +13 -13
- package/dist/components/inputs/color-picker/index.js +25 -18
- package/dist/components/inputs/color-picker/variants.d.ts +61 -0
- package/dist/components/inputs/color-picker/variants.js +28 -0
- package/dist/components/inputs/combobox/index.d.ts +60 -17
- package/dist/components/inputs/combobox/index.js +26 -18
- package/dist/components/inputs/date-time/calendar/index.js +14 -11
- package/dist/components/inputs/date-time/date-picker/index.d.ts +53 -11
- package/dist/components/inputs/date-time/date-picker/index.js +33 -25
- package/dist/components/inputs/date-time/date-range-picker/index.d.ts +3 -3
- package/dist/components/inputs/date-time/date-range-picker/index.js +32 -24
- package/dist/components/inputs/date-time/duration-picker/index.d.ts +3 -3
- package/dist/components/inputs/date-time/duration-picker/index.js +32 -24
- package/dist/components/inputs/date-time/month-picker/index.d.ts +3 -3
- package/dist/components/inputs/date-time/month-picker/index.js +31 -23
- package/dist/components/inputs/date-time/time-picker/index.d.ts +3 -3
- package/dist/components/inputs/date-time/time-picker/index.js +32 -24
- package/dist/components/inputs/field/index.d.ts +116 -0
- package/dist/components/inputs/field/index.js +28 -0
- package/dist/components/inputs/file-dropzone/index.d.ts +165 -0
- package/dist/components/inputs/file-dropzone/index.js +32 -0
- package/dist/components/inputs/file-dropzone/variants.d.ts +35 -0
- package/dist/components/inputs/file-dropzone/variants.js +12 -0
- package/dist/components/inputs/input/index.d.ts +95 -13
- package/dist/components/inputs/input/index.js +17 -9
- package/dist/components/inputs/label/index.d.ts +32 -0
- package/dist/components/inputs/label/index.js +11 -0
- package/dist/components/inputs/number-input/index.d.ts +84 -0
- package/dist/components/inputs/number-input/index.js +20 -0
- package/dist/components/inputs/radio/index.d.ts +20 -5
- package/dist/components/inputs/radio/index.js +15 -12
- package/dist/components/inputs/search/index.js +25 -18
- package/dist/components/inputs/select/index.d.ts +41 -11
- package/dist/components/inputs/select/index.js +27 -14
- package/dist/components/inputs/select/variants.d.ts +5 -0
- package/dist/components/inputs/select/variants.js +6 -0
- package/dist/components/inputs/slider/index.js +7 -5
- package/dist/components/inputs/switch/index.d.ts +8 -2
- package/dist/components/inputs/switch/index.js +14 -12
- package/dist/components/inputs/textarea/index.d.ts +4 -0
- package/dist/components/inputs/textarea/index.js +8 -6
- package/dist/components/navigation/navigation-menu/index.d.ts +114 -0
- package/dist/components/navigation/navigation-menu/index.js +40 -0
- package/dist/components/navigation/pagination/index.d.ts +73 -0
- package/dist/components/navigation/pagination/index.js +39 -0
- package/dist/components/navigation/sidebar/index.d.ts +44 -21
- package/dist/components/navigation/sidebar/index.js +41 -31
- package/dist/components/navigation/sidebar/variants.d.ts +25 -0
- package/dist/components/navigation/sidebar/variants.js +28 -0
- package/dist/components/navigation/tabs/index.d.ts +8 -2
- package/dist/components/navigation/tabs/index.js +14 -10
- package/dist/components/navigation/tabs-subtle/index.js +12 -10
- package/dist/components/overlays/alert-dialog/index.d.ts +121 -15
- package/dist/components/overlays/alert-dialog/index.js +22 -13
- package/dist/components/overlays/command/index.js +24 -17
- package/dist/components/overlays/context-menu/index.d.ts +33 -17
- package/dist/components/overlays/context-menu/index.js +27 -18
- package/dist/components/overlays/dialog/index.js +17 -12
- package/dist/components/overlays/dropdown/index.d.ts +257 -31
- package/dist/components/overlays/dropdown/index.js +42 -17
- package/dist/components/overlays/popover/index.d.ts +31 -9
- package/dist/components/overlays/popover/index.js +12 -9
- package/dist/components/overlays/preview-card/index.js +11 -8
- package/dist/components/overlays/sheet/index.js +18 -13
- package/dist/components/overlays/toast/index.d.ts +123 -47
- package/dist/components/overlays/toast/index.js +18 -10
- package/dist/components/overlays/tooltip/index.d.ts +113 -21
- package/dist/components/overlays/tooltip/index.js +15 -6
- package/dist/components/progress/index.d.ts +56 -0
- package/dist/components/progress/index.js +15 -0
- package/dist/components/table/index.d.ts +63 -8
- package/dist/components/table/index.js +19 -9
- package/dist/index.d.ts +2201 -736
- package/dist/index.js +223 -82
- package/dist/lib/date-locale.d.ts +14 -0
- package/dist/lib/date-locale.js +16 -0
- package/dist/lib/field-classes.d.ts +144 -0
- package/dist/lib/field-classes.js +40 -0
- package/dist/lib/field-variants.d.ts +47 -0
- package/dist/lib/field-variants.js +26 -0
- package/dist/lib/fluid-hover-highlight.d.ts +112 -0
- package/dist/lib/fluid-hover-highlight.js +17 -0
- package/dist/lib/focus-ring.d.ts +18 -0
- package/dist/lib/focus-ring.js +10 -0
- package/dist/lib/goo-indicator.d.ts +46 -0
- package/dist/lib/goo-indicator.js +14 -0
- package/dist/lib/icon-context.d.ts +30 -0
- package/dist/lib/icon-context.js +12 -0
- package/dist/lib/inert.d.ts +9 -0
- package/dist/lib/inert.js +8 -0
- package/dist/lib/morph-layers.d.ts +93 -0
- package/dist/lib/morph-layers.js +13 -0
- package/dist/lib/morph-part.d.ts +21 -0
- package/dist/lib/morph-part.js +12 -0
- package/dist/lib/popup.d.ts +20 -0
- package/dist/lib/popup.js +12 -0
- package/dist/lib/reduced-motion.d.ts +9 -0
- package/dist/lib/reduced-motion.js +6 -0
- package/dist/lib/row-index.d.ts +10 -0
- package/dist/lib/row-index.js +8 -0
- package/dist/lib/row-order.d.ts +28 -0
- package/dist/lib/row-order.js +10 -0
- package/dist/lib/scroll-column.d.ts +27 -0
- package/dist/lib/scroll-column.js +18 -0
- package/dist/lib/shape-context.d.ts +30 -0
- package/dist/lib/shape-context.js +12 -0
- package/dist/lib/size-context.d.ts +94 -0
- package/dist/lib/size-context.js +20 -0
- package/dist/lib/size-scale.d.ts +79 -0
- package/dist/lib/size-scale.js +8 -0
- package/dist/lib/slot.d.ts +12 -0
- package/dist/lib/slot.js +6 -0
- package/dist/lib/springs.d.ts +44 -0
- package/dist/lib/springs.js +8 -0
- package/dist/lib/surface-classes.d.ts +19 -0
- package/dist/lib/surface-classes.js +10 -0
- package/dist/lib/surface-context.d.ts +9 -0
- package/dist/lib/surface-context.js +8 -0
- package/dist/lib/use-controllable-state.d.ts +2 -0
- package/dist/lib/use-controllable-state.js +6 -0
- package/dist/lib/use-fluid-hover.d.ts +138 -0
- package/dist/lib/use-fluid-hover.js +14 -0
- package/dist/lib/use-is-mobile.d.ts +2 -0
- package/dist/lib/use-is-mobile.js +6 -0
- package/dist/lib/use-keyboard-nav-gate.d.ts +20 -0
- package/dist/lib/use-keyboard-nav-gate.js +7 -0
- package/dist/lib/use-merge-split.d.ts +47 -0
- package/dist/lib/use-merge-split.js +15 -0
- package/dist/lib/use-morph.d.ts +101 -0
- package/dist/lib/use-morph.js +20 -0
- package/dist/lib/use-touch-primary.d.ts +2 -0
- package/dist/lib/use-touch-primary.js +6 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.js +6 -0
- package/dist/preset.d.ts +17 -0
- package/dist/preset.js +22 -2
- package/dist/primitives/code/index.js +7 -5
- package/dist/primitives/craft/index.js +9 -6
- package/dist/primitives/fluid-hover/index.js +9 -8
- package/dist/primitives/morph/index.js +15 -10
- package/dist/primitives/motion/index.js +10 -8
- package/dist/primitives/scroll-area/index.js +4 -3
- package/dist/primitives/separator/index.d.ts +25 -0
- package/dist/primitives/separator/index.js +9 -0
- package/dist/primitives/sizes/index.js +6 -4
- package/dist/primitives/skeleton/index.d.ts +35 -0
- package/dist/primitives/skeleton/index.js +12 -0
- package/dist/primitives/surface/index.js +6 -4
- package/dist/shared/{components-3mkepka5.js → components-0jmqhx2k.js} +28 -25
- package/dist/shared/components-124epb7f.js +53 -0
- package/dist/shared/{components-gz8zf3ec.js → components-1k0wv53d.js} +15 -43
- package/dist/shared/components-26snnbs9.js +322 -0
- package/dist/shared/{components-2dxkv6zm.js → components-2c0qcfx4.js} +8 -7
- package/dist/shared/{components-473g814w.js → components-2mt9te6d.js} +243 -137
- package/dist/shared/{components-hawvy2qz.js → components-2z48g4fp.js} +9 -30
- package/dist/shared/components-34txk9rj.js +35 -0
- package/dist/shared/components-3h92d94m.js +23 -0
- package/dist/shared/{components-zfnmn5d4.js → components-3sp623ae.js} +36 -31
- package/dist/shared/{components-jwst3vtj.js → components-3wxkjgqn.js} +186 -76
- package/dist/shared/{components-s2bz1xpk.js → components-49bsynbw.js} +8 -5
- package/dist/shared/components-4bwsd46w.js +71 -0
- package/dist/shared/components-4n4f5m3e.js +297 -0
- package/dist/shared/{components-0q0sqfh4.js → components-4y816hwp.js} +27 -49
- package/dist/shared/components-52ngsgca.js +399 -0
- package/dist/shared/{components-y9y0g8er.js → components-52r5763g.js} +22 -19
- package/dist/shared/components-559tf7yb.js +28 -0
- package/dist/shared/components-5mkga9hj.js +440 -0
- package/dist/shared/{components-kwa6mrf9.js → components-5s0nfqvw.js} +14 -9
- package/dist/shared/{components-dk0gjr5a.js → components-60atskhp.js} +19 -18
- package/dist/shared/{components-rr7a6ja9.js → components-658b26nb.js} +21 -18
- package/dist/shared/{components-xxvdw7k1.js → components-65vwfjs6.js} +37 -34
- package/dist/shared/{components-frw34b65.js → components-6j2fh74k.js} +114 -123
- package/dist/shared/components-6zg1yft7.js +167 -0
- package/dist/shared/components-6zng858q.js +53 -0
- package/dist/shared/components-72w4ge68.js +64 -0
- package/dist/shared/{components-cv16mryv.js → components-78x40gee.js} +13 -7
- package/dist/shared/{components-b2wyq2cq.js → components-7d4r7hh1.js} +39 -57
- package/dist/shared/components-7ntvvegr.js +316 -0
- package/dist/shared/components-8nwsqjn8.js +34 -0
- package/dist/shared/{components-5xc2j8jz.js → components-8tt3xbb5.js} +19 -18
- package/dist/shared/{components-vc7vq5nz.js → components-9as3rf68.js} +6 -3
- package/dist/shared/components-9mys9jna.js +255 -0
- package/dist/shared/components-adkarj8n.js +35 -0
- package/dist/shared/{components-b3c5et5b.js → components-ae9fncz4.js} +32 -31
- package/dist/shared/{components-hqeq3w9q.js → components-awra5sff.js} +24 -16
- package/dist/shared/components-b1e239g3.js +28 -0
- package/dist/shared/{components-q5xj5c16.js → components-b1f2bjf1.js} +9 -8
- package/dist/shared/{components-qmmjx1qw.js → components-bn42vq6d.js} +71 -72
- package/dist/shared/components-c96g82th.js +1444 -0
- package/dist/shared/components-c9yr4ed1.js +44 -0
- package/dist/shared/components-d3shbwqq.js +95 -0
- package/dist/shared/{components-jjw69c4c.js → components-dcc02hs2.js} +7 -4
- package/dist/shared/{components-men63rwj.js → components-de99ykt7.js} +3 -2
- package/dist/shared/components-dhgk6mbt.js +16 -0
- package/dist/shared/components-drxrtnp2.js +25 -0
- package/dist/shared/{components-6p8tfcd6.js → components-ds9fqat9.js} +21 -18
- package/dist/shared/{components-mjthm1kf.js → components-egy4tyjt.js} +11 -8
- package/dist/shared/components-f0xfpkhh.js +82 -0
- package/dist/shared/components-f7h57m6t.js +59 -0
- package/dist/shared/{components-d6xtjht3.js → components-fjkpbg6n.js} +33 -30
- package/dist/shared/{components-gc5vtq2f.js → components-gh0qeey3.js} +63 -29
- package/dist/shared/{components-39jq9va1.js → components-gvbapfen.js} +29 -24
- package/dist/shared/{components-5vgpnxgy.js → components-gzesg7hy.js} +19 -16
- package/dist/shared/components-h6p86be3.js +19 -0
- package/dist/shared/components-h83az0bz.js +33 -0
- package/dist/shared/components-jjrj6ymn.js +104 -0
- package/dist/shared/{components-wvp1m52g.js → components-jp5v0m0f.js} +15 -14
- package/dist/shared/{components-zrkzcayz.js → components-jqpefjsn.js} +54 -6
- package/dist/shared/{components-1z6tdadk.js → components-k74j4hwh.js} +20 -17
- package/dist/shared/components-kswxnm3h.js +24 -0
- package/dist/shared/{components-jbbmdr74.js → components-mkkbbgb7.js} +36 -17
- package/dist/shared/{components-pbmqctfa.js → components-mpw667rb.js} +169 -65
- package/dist/shared/{components-s5ngg32c.js → components-paa08427.js} +121 -47
- package/dist/shared/{components-vfbhxf5r.js → components-pd42emw3.js} +6 -5
- package/dist/shared/{components-hw9yk32z.js → components-pmfszv7a.js} +35 -23
- package/dist/shared/{components-chacnk61.js → components-pw06rbxq.js} +19 -19
- package/dist/shared/components-q03cktx8.js +246 -0
- package/dist/shared/{components-kvr7f9hy.js → components-qdm3yh26.js} +19 -10
- package/dist/shared/{components-27nnvrwe.js → components-qs4rkqsj.js} +15 -14
- package/dist/shared/{components-s083f38z.js → components-r6gbmkew.js} +22 -21
- package/dist/shared/{components-ta7shw28.js → components-r8eqs7gz.js} +4 -4
- package/dist/shared/{components-sn1rxx1d.js → components-rm02h9nt.js} +12 -11
- package/dist/shared/{components-c6s6b43g.js → components-rrbpmfx4.js} +3 -2
- package/dist/shared/{components-n7mjr357.js → components-tz1fv35x.js} +3 -2
- package/dist/shared/components-tz4gaf1v.js +23 -0
- package/dist/shared/{components-zx6b6yby.js → components-vf45j12c.js} +4 -18
- package/dist/shared/{components-3ky44908.js → components-vfjc9jzz.js} +2 -3
- package/dist/shared/{components-39vmfq9s.js → components-vsa625vk.js} +21 -20
- package/dist/shared/components-wawnmkph.js +500 -0
- package/dist/shared/components-wktrrb4m.js +38 -0
- package/dist/shared/{components-8snj490h.js → components-wwd363me.js} +78 -32
- package/dist/shared/{components-5pnw8183.js → components-xcwvgcrd.js} +23 -22
- package/dist/shared/components-xdqmt6ed.js +20 -0
- package/dist/shared/{components-h2ajmhwn.js → components-y1w3a6wx.js} +107 -35
- package/dist/shared/{components-ar66gnt0.js → components-y6578b8h.js} +37 -34
- package/dist/shared/{components-59efxjd9.js → components-yhgq0qxx.js} +13 -12
- package/dist/shared/{components-jthe1pwb.js → components-yz84gpkq.js} +101 -404
- package/dist/shared/components-z3v0n0de.js +11 -0
- package/dist/shared/{components-7r3etddy.js → components-z9n19fyk.js} +19 -54
- package/dist/shared/components-zbvjzh1p.js +141 -0
- package/dist/styles.css +241 -16
- package/dist/tailwind.css +13 -0
- package/dist/tokens.css +34 -0
- package/index.ts +60 -0
- package/lib/date-locale.ts +50 -0
- package/lib/field-classes.ts +115 -0
- package/lib/field-variants.ts +117 -0
- package/lib/fluid-hover-highlight.tsx +139 -0
- package/lib/focus-ring.tsx +46 -0
- package/lib/goo-indicator.tsx +261 -0
- package/lib/icon-context.tsx +310 -0
- package/lib/inert.ts +22 -0
- package/lib/morph-layers.tsx +102 -0
- package/lib/morph-part.tsx +49 -0
- package/lib/popup.ts +52 -0
- package/lib/reduced-motion.ts +19 -0
- package/lib/row-index.tsx +29 -0
- package/lib/row-order.tsx +115 -0
- package/lib/scroll-column.tsx +244 -0
- package/lib/shape-context.tsx +124 -0
- package/lib/size-context.tsx +66 -0
- package/lib/size-scale.ts +115 -0
- package/lib/slot.ts +25 -0
- package/lib/springs.ts +62 -0
- package/lib/surface-classes.ts +58 -0
- package/lib/surface-context.tsx +21 -0
- package/lib/use-controllable-state.ts +44 -0
- package/lib/use-fluid-hover.ts +541 -0
- package/lib/use-is-mobile.ts +25 -0
- package/lib/use-keyboard-nav-gate.ts +45 -0
- package/lib/use-merge-split.tsx +209 -0
- package/lib/use-morph.ts +557 -0
- package/lib/use-touch-primary.tsx +43 -0
- package/lib/utils.ts +37 -0
- package/package.json +177 -4
- package/preset.ts +196 -0
- package/primitives/code/examples.mdx +27 -0
- package/primitives/code/index.tsx +80 -0
- package/primitives/craft/demos.tsx +173 -0
- package/primitives/craft/examples.mdx +138 -0
- package/primitives/craft/index.tsx +76 -0
- package/primitives/fluid-hover/examples.mdx +84 -0
- package/primitives/fluid-hover/index.tsx +316 -0
- package/primitives/morph/examples.mdx +135 -0
- package/primitives/morph/index.tsx +192 -0
- package/primitives/motion/examples.mdx +100 -0
- package/primitives/motion/index.tsx +160 -0
- package/primitives/scroll-area/examples.mdx +58 -0
- package/primitives/scroll-area/index.tsx +200 -0
- package/primitives/separator/examples.mdx +30 -0
- package/primitives/separator/index.tsx +53 -0
- package/primitives/sizes/examples.mdx +167 -0
- package/primitives/sizes/index.tsx +89 -0
- package/primitives/skeleton/examples.mdx +51 -0
- package/primitives/skeleton/index.tsx +72 -0
- package/primitives/skeleton/skeleton.css +15 -0
- package/primitives/surface/examples.mdx +173 -0
- package/primitives/surface/index.tsx +109 -0
- package/primitives/surface/surface.css +176 -0
- package/dist/shared/components-0sn7ypjw.js +0 -61
- package/dist/shared/components-1434p1mh.js +0 -123
- package/dist/shared/components-a86dytt0.js +0 -986
- package/dist/shared/components-gf5s6ap9.js +0 -42
- package/dist/shared/components-hyq23phv.js +0 -310
- package/dist/shared/components-ng72k97e.js +0 -128
- package/dist/shared/components-raknhppb.js +0 -64
- package/dist/shared/components-vynjtrk6.js +0 -19
- package/dist/shared/components-wy9jpqny.js +0 -146
- /package/dist/shared/{components-1v7rxytx.js → components-3ey0hzpb.js} +0 -0
- /package/dist/shared/{components-ezxhrmer.js → components-jcnjay5g.js} +0 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Local addition (not part of Fluid Functionalism): a site navigation bar
|
|
3
|
+
* whose flyout panels grow out of their trigger through the morph engine
|
|
4
|
+
* (`lib/use-morph.ts`), on Base UI's NavigationMenu. One panel is shared by
|
|
5
|
+
* every item: moving to another trigger springs its size to the new content
|
|
6
|
+
* while the content crossfades in from the side it came from. The strip has
|
|
7
|
+
* one fluid hover highlight, and `NavigationMenu.Indicator` is a liquid
|
|
8
|
+
* marker (`lib/goo-indicator.tsx`) under the open or current item. A panel
|
|
9
|
+
* draws one keyboard ring (`lib/focus-ring.tsx`, on the surrounding shape).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { NavigationMenu as NavigationMenuPrimitive } from '@base-ui/react/navigation-menu'
|
|
13
|
+
import { motion } from 'motion/react'
|
|
14
|
+
import {
|
|
15
|
+
type ComponentPropsWithoutRef,
|
|
16
|
+
createContext,
|
|
17
|
+
forwardRef,
|
|
18
|
+
type MutableRefObject,
|
|
19
|
+
type ReactNode,
|
|
20
|
+
type RefObject,
|
|
21
|
+
useCallback,
|
|
22
|
+
useContext,
|
|
23
|
+
useId,
|
|
24
|
+
useLayoutEffect,
|
|
25
|
+
useMemo,
|
|
26
|
+
useRef,
|
|
27
|
+
useState,
|
|
28
|
+
} from 'react'
|
|
29
|
+
import { FluidHoverHighlight } from '../../../lib/fluid-hover-highlight'
|
|
30
|
+
import { FocusRing } from '../../../lib/focus-ring'
|
|
31
|
+
import { GooIndicator } from '../../../lib/goo-indicator'
|
|
32
|
+
import { type IconComponent, useIcon } from '../../../lib/icon-context'
|
|
33
|
+
import { MorphSurface } from '../../../lib/morph-layers'
|
|
34
|
+
import { useReduceMotion } from '../../../lib/reduced-motion'
|
|
35
|
+
import { useShape } from '../../../lib/shape-context'
|
|
36
|
+
import { type SizeVariant, useSize } from '../../../lib/size-context'
|
|
37
|
+
import { spring } from '../../../lib/springs'
|
|
38
|
+
import { SURFACE_BG, SURFACE_SHADOW } from '../../../lib/surface-classes'
|
|
39
|
+
import { SurfaceProvider, useSurface } from '../../../lib/surface-context'
|
|
40
|
+
import { useControllableState } from '../../../lib/use-controllable-state'
|
|
41
|
+
import { type ItemRect, type UseFluidHoverReturn, useFluidHover } from '../../../lib/use-fluid-hover'
|
|
42
|
+
import { useKeyboardNavGate } from '../../../lib/use-keyboard-nav-gate'
|
|
43
|
+
import { useMorph, useMorphOrigin } from '../../../lib/use-morph'
|
|
44
|
+
import { cn } from '../../../lib/utils'
|
|
45
|
+
import { SizeProvider } from '../../../primitives/sizes'
|
|
46
|
+
|
|
47
|
+
/* ─────────────────────── Shared ─────────────────────── */
|
|
48
|
+
|
|
49
|
+
const TARGET = 'data-navigation-menu-target'
|
|
50
|
+
const ROW = 'data-navigation-menu-row'
|
|
51
|
+
|
|
52
|
+
interface PanelSize {
|
|
53
|
+
width: number
|
|
54
|
+
height: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface RootContextValue {
|
|
58
|
+
value: string | null
|
|
59
|
+
triggers: MutableRefObject<Map<string, HTMLElement>>
|
|
60
|
+
setList: (el: HTMLElement | null) => void
|
|
61
|
+
setPanelSize: (size: PanelSize) => void
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const RootContext = createContext<RootContextValue | null>(null)
|
|
65
|
+
|
|
66
|
+
function useRoot() {
|
|
67
|
+
const ctx = useContext(RootContext)
|
|
68
|
+
if (!ctx) throw new Error('NavigationMenu parts must be used within a NavigationMenu')
|
|
69
|
+
return ctx
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface ListContextValue {
|
|
73
|
+
hover: UseFluidHoverReturn
|
|
74
|
+
indicatorRect: ItemRect | undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const ListContext = createContext<ListContextValue | null>(null)
|
|
78
|
+
|
|
79
|
+
const ItemContext = createContext<string>('')
|
|
80
|
+
|
|
81
|
+
// True inside a panel: links there are rows, not strip items.
|
|
82
|
+
const InContentContext = createContext(false)
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Registers every element matching `selector` inside the container with the
|
|
86
|
+
* fluid hover, in document order, so parts need no `index`. Runs after every
|
|
87
|
+
* render and only re-registers when the set changed.
|
|
88
|
+
*/
|
|
89
|
+
function useRegisterBySelector(
|
|
90
|
+
container: RefObject<HTMLElement | null>,
|
|
91
|
+
selector: string,
|
|
92
|
+
register: UseFluidHoverReturn['registerItem'],
|
|
93
|
+
) {
|
|
94
|
+
const registered = useRef<HTMLElement[]>([])
|
|
95
|
+
const [items, setItems] = useState<HTMLElement[]>([])
|
|
96
|
+
|
|
97
|
+
useLayoutEffect(() => {
|
|
98
|
+
const root = container.current
|
|
99
|
+
if (!root) return
|
|
100
|
+
const next = [...root.querySelectorAll<HTMLElement>(selector)]
|
|
101
|
+
const previous = registered.current
|
|
102
|
+
if (next.length === previous.length && next.every((el, i) => el === previous[i])) return
|
|
103
|
+
for (let i = next.length; i < previous.length; i++) register(i, null)
|
|
104
|
+
next.forEach((el, i) => {
|
|
105
|
+
if (previous[i] !== el) register(i, el)
|
|
106
|
+
})
|
|
107
|
+
registered.current = next
|
|
108
|
+
setItems(next)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
return items
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Writes a node into a forwarded ref as well as a local one. */
|
|
115
|
+
function assignRef<T>(ref: React.ForwardedRef<T>, node: T | null) {
|
|
116
|
+
if (typeof ref === 'function') ref(node)
|
|
117
|
+
else if (ref) ref.current = node
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ─────────────────────── List ─────────────────────── */
|
|
121
|
+
|
|
122
|
+
interface NavigationMenuListProps extends ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List> {
|
|
123
|
+
/** `NavigationMenu.Item`s, and optionally a `NavigationMenu.Indicator`. */
|
|
124
|
+
children?: ReactNode
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const NavigationMenuList = forwardRef<HTMLUListElement, NavigationMenuListProps>(
|
|
128
|
+
({ className, children, ...props }, ref) => {
|
|
129
|
+
const { value, setList } = useRoot()
|
|
130
|
+
const containerRef = useRef<HTMLUListElement | null>(null)
|
|
131
|
+
const hover = useFluidHover(containerRef, { axis: 'x' })
|
|
132
|
+
const shape = useShape()
|
|
133
|
+
const items = useRegisterBySelector(containerRef, `[${TARGET}]`, hover.registerItem)
|
|
134
|
+
|
|
135
|
+
// The marker sits under the open item, else under the current page's item.
|
|
136
|
+
const [marked, setMarked] = useState(-1)
|
|
137
|
+
useLayoutEffect(() => {
|
|
138
|
+
const open = value === null ? -1 : items.findIndex(el => el.dataset.navigationMenuValue === value)
|
|
139
|
+
const next = open >= 0 ? open : items.findIndex(el => el.hasAttribute('data-active'))
|
|
140
|
+
setMarked(next)
|
|
141
|
+
})
|
|
142
|
+
const indicatorRect = hover.isMeasured && marked >= 0 ? hover.itemRects[marked] : undefined
|
|
143
|
+
|
|
144
|
+
const ctx = useMemo(() => ({ hover, indicatorRect }), [hover, indicatorRect])
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<ListContext.Provider value={ctx}>
|
|
148
|
+
<NavigationMenuPrimitive.List
|
|
149
|
+
ref={node => {
|
|
150
|
+
containerRef.current = node as HTMLUListElement | null
|
|
151
|
+
setList(node as HTMLElement | null)
|
|
152
|
+
assignRef(ref, node as HTMLUListElement | null)
|
|
153
|
+
}}
|
|
154
|
+
{...hover.handlers}
|
|
155
|
+
onFocus={event => {
|
|
156
|
+
const index = items.indexOf(
|
|
157
|
+
(event.target as HTMLElement).closest<HTMLElement>(`[${TARGET}]`) as HTMLElement,
|
|
158
|
+
)
|
|
159
|
+
if (index >= 0) hover.setActiveIndex(index)
|
|
160
|
+
}}
|
|
161
|
+
className={cn('relative m-0 flex list-none items-center p-0', className)}
|
|
162
|
+
{...props}
|
|
163
|
+
>
|
|
164
|
+
<FluidHoverHighlight hover={hover} className={shape.bg} />
|
|
165
|
+
{children}
|
|
166
|
+
</NavigationMenuPrimitive.List>
|
|
167
|
+
</ListContext.Provider>
|
|
168
|
+
)
|
|
169
|
+
},
|
|
170
|
+
)
|
|
171
|
+
NavigationMenuList.displayName = 'NavigationMenuList'
|
|
172
|
+
|
|
173
|
+
/* ─────────────────────── Indicator ─────────────────────── */
|
|
174
|
+
|
|
175
|
+
interface NavigationMenuIndicatorProps {
|
|
176
|
+
/** Classes for the marker: its fill. Defaults to the `bg-active` overlay. */
|
|
177
|
+
className?: string
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** A liquid marker under the open item, or the current page's item when nothing is open. */
|
|
181
|
+
function NavigationMenuIndicator({ className }: NavigationMenuIndicatorProps) {
|
|
182
|
+
const ctx = useContext(ListContext)
|
|
183
|
+
const shape = useShape()
|
|
184
|
+
const rect = ctx?.indicatorRect
|
|
185
|
+
// Rendered as a list item so the list stays valid markup.
|
|
186
|
+
return (
|
|
187
|
+
<li aria-hidden="true" className="contents">
|
|
188
|
+
{rect && <GooIndicator rect={rect} className={cn('bg-active', shape.bg, className)} />}
|
|
189
|
+
</li>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* ─────────────────────── Item ─────────────────────── */
|
|
194
|
+
|
|
195
|
+
interface NavigationMenuItemProps extends ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Item> {
|
|
196
|
+
/** Identifies the item for a controlled `value`. Defaults to a generated id. */
|
|
197
|
+
value?: string
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const NavigationMenuItem = forwardRef<HTMLLIElement, NavigationMenuItemProps>(({ value, ...props }, ref) => {
|
|
201
|
+
const generated = useId()
|
|
202
|
+
const itemValue = value ?? generated
|
|
203
|
+
return (
|
|
204
|
+
<ItemContext.Provider value={itemValue}>
|
|
205
|
+
<NavigationMenuPrimitive.Item ref={ref} value={itemValue} {...props} />
|
|
206
|
+
</ItemContext.Provider>
|
|
207
|
+
)
|
|
208
|
+
})
|
|
209
|
+
NavigationMenuItem.displayName = 'NavigationMenuItem'
|
|
210
|
+
|
|
211
|
+
/* ─────────────────────── Strip label ─────────────────────── */
|
|
212
|
+
|
|
213
|
+
/** The strip's label: semibold while open or current, with an invisible semibold copy holding its width. */
|
|
214
|
+
function StripLabel({ emphasized, children }: { emphasized: boolean; children: ReactNode }) {
|
|
215
|
+
const size = useSize()
|
|
216
|
+
return (
|
|
217
|
+
<span className={cn('inline-grid whitespace-nowrap', size.text)}>
|
|
218
|
+
<span
|
|
219
|
+
aria-hidden="true"
|
|
220
|
+
className="invisible col-start-1 row-start-1 weight-semibold [text-box:trim-both_cap_alphabetic]"
|
|
221
|
+
>
|
|
222
|
+
{children}
|
|
223
|
+
</span>
|
|
224
|
+
<span
|
|
225
|
+
className={cn(
|
|
226
|
+
'col-start-1 row-start-1 transition-[font-variation-settings] duration-fast [text-box:trim-both_cap_alphabetic]',
|
|
227
|
+
emphasized ? 'weight-semibold' : 'weight-normal',
|
|
228
|
+
)}
|
|
229
|
+
>
|
|
230
|
+
{children}
|
|
231
|
+
</span>
|
|
232
|
+
</span>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const STRIP_ITEM =
|
|
237
|
+
'group/item relative z-10 flex cursor-pointer select-none items-center bg-transparent text-muted-foreground no-underline outline-none transition-[color] duration-fast focus-visible:ring-1 focus-visible:ring-focus-ring data-[fluid-hover-active]:text-foreground'
|
|
238
|
+
|
|
239
|
+
/* ─────────────────────── Trigger ─────────────────────── */
|
|
240
|
+
|
|
241
|
+
interface NavigationMenuTriggerProps extends ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger> {
|
|
242
|
+
/** The trigger's label. */
|
|
243
|
+
children?: ReactNode
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const NavigationMenuTrigger = forwardRef<HTMLButtonElement, NavigationMenuTriggerProps>(
|
|
247
|
+
({ className, children, ...props }, ref) => {
|
|
248
|
+
const { value, triggers } = useRoot()
|
|
249
|
+
const itemValue = useContext(ItemContext)
|
|
250
|
+
const size = useSize()
|
|
251
|
+
const shape = useShape()
|
|
252
|
+
const reduced = useReduceMotion()
|
|
253
|
+
const Chevron = useIcon('chevron-down')
|
|
254
|
+
const open = value === itemValue
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<NavigationMenuPrimitive.Trigger
|
|
258
|
+
ref={node => {
|
|
259
|
+
if (node) triggers.current.set(itemValue, node)
|
|
260
|
+
else triggers.current.delete(itemValue)
|
|
261
|
+
assignRef(ref, node)
|
|
262
|
+
}}
|
|
263
|
+
{...{ [TARGET]: '', 'data-navigation-menu-value': itemValue }}
|
|
264
|
+
className={cn(STRIP_ITEM, size.control, size.px, size.gap, shape.button, open && 'text-foreground', className)}
|
|
265
|
+
{...props}
|
|
266
|
+
>
|
|
267
|
+
<StripLabel emphasized={open}>{children}</StripLabel>
|
|
268
|
+
<motion.span
|
|
269
|
+
aria-hidden="true"
|
|
270
|
+
className="flex"
|
|
271
|
+
initial={false}
|
|
272
|
+
animate={{ rotate: open ? 180 : 0 }}
|
|
273
|
+
transition={reduced ? { duration: 0 } : spring.moderate}
|
|
274
|
+
>
|
|
275
|
+
<Chevron
|
|
276
|
+
size={size.icon}
|
|
277
|
+
strokeWidth={1.5}
|
|
278
|
+
className={cn(
|
|
279
|
+
'transition-[stroke-width] duration-fast group-[[data-fluid-hover-active]]/item:stroke-2',
|
|
280
|
+
open && 'stroke-2',
|
|
281
|
+
)}
|
|
282
|
+
/>
|
|
283
|
+
</motion.span>
|
|
284
|
+
</NavigationMenuPrimitive.Trigger>
|
|
285
|
+
)
|
|
286
|
+
},
|
|
287
|
+
)
|
|
288
|
+
NavigationMenuTrigger.displayName = 'NavigationMenuTrigger'
|
|
289
|
+
|
|
290
|
+
/* ─────────────────────── Content ─────────────────────── */
|
|
291
|
+
|
|
292
|
+
interface NavigationMenuContentProps extends ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content> {
|
|
293
|
+
/** Classes for the panel's content: its width and layout (a grid of links, a feature column). */
|
|
294
|
+
className?: string
|
|
295
|
+
/** The panel: `NavigationMenu.Link`s and anything else. */
|
|
296
|
+
children?: ReactNode
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const NavigationMenuContent = forwardRef<HTMLDivElement, NavigationMenuContentProps>(
|
|
300
|
+
({ className, children, ...props }, ref) => {
|
|
301
|
+
const { value, setPanelSize } = useRoot()
|
|
302
|
+
const itemValue = useContext(ItemContext)
|
|
303
|
+
const open = value === itemValue
|
|
304
|
+
const reduced = useReduceMotion()
|
|
305
|
+
const shape = useShape()
|
|
306
|
+
const containerRef = useRef<HTMLDivElement | null>(null)
|
|
307
|
+
const [element, setElement] = useState<HTMLDivElement | null>(null)
|
|
308
|
+
const hover = useFluidHover(containerRef, { axis: 'xy' })
|
|
309
|
+
const rows = useRegisterBySelector(containerRef, `[${ROW}]`, hover.registerItem)
|
|
310
|
+
// Only keyboard focus earns the ring: Tab or an arrow key, or focus that arrived as :focus-visible.
|
|
311
|
+
const { keyboardNavRef, trackKeyboardNav } = useKeyboardNavGate(open)
|
|
312
|
+
const [focused, setFocused] = useState<number | null>(null)
|
|
313
|
+
|
|
314
|
+
// The open panel tells the shared frame its natural size, and keeps telling it as it changes.
|
|
315
|
+
useLayoutEffect(() => {
|
|
316
|
+
if (!open || !element) return
|
|
317
|
+
const report = () => setPanelSize({ width: element.offsetWidth, height: element.offsetHeight })
|
|
318
|
+
report()
|
|
319
|
+
const observer = new ResizeObserver(report)
|
|
320
|
+
observer.observe(element)
|
|
321
|
+
return () => observer.disconnect()
|
|
322
|
+
}, [open, element, setPanelSize])
|
|
323
|
+
|
|
324
|
+
const setRefs = useCallback(
|
|
325
|
+
(node: HTMLDivElement | null) => {
|
|
326
|
+
containerRef.current = node
|
|
327
|
+
setElement(node)
|
|
328
|
+
assignRef(ref, node)
|
|
329
|
+
},
|
|
330
|
+
[ref],
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
return (
|
|
334
|
+
<InContentContext.Provider value={true}>
|
|
335
|
+
<NavigationMenuPrimitive.Content
|
|
336
|
+
ref={setRefs}
|
|
337
|
+
{...hover.handlers}
|
|
338
|
+
onKeyDown={event => {
|
|
339
|
+
if (event.key === 'Tab') keyboardNavRef.current = true
|
|
340
|
+
trackKeyboardNav(event)
|
|
341
|
+
}}
|
|
342
|
+
onFocus={event => {
|
|
343
|
+
const target = event.target as HTMLElement
|
|
344
|
+
const index = rows.indexOf(target.closest<HTMLElement>(`[${ROW}]`) as HTMLElement)
|
|
345
|
+
if (index < 0) return
|
|
346
|
+
hover.setActiveIndex(index)
|
|
347
|
+
setFocused(keyboardNavRef.current || target.matches(':focus-visible') ? index : null)
|
|
348
|
+
}}
|
|
349
|
+
onBlur={event => {
|
|
350
|
+
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setFocused(null)
|
|
351
|
+
}}
|
|
352
|
+
className={cn(
|
|
353
|
+
'relative w-max p-1 transition-[opacity,transform] duration-moderate data-[ending-style]:duration-moderate-exit data-[starting-style]:opacity-0 data-[ending-style]:opacity-0',
|
|
354
|
+
!reduced &&
|
|
355
|
+
'data-[starting-style]:data-[activation-direction=right]:translate-x-4 data-[starting-style]:data-[activation-direction=left]:-translate-x-4 data-[ending-style]:data-[activation-direction=right]:-translate-x-4 data-[ending-style]:data-[activation-direction=left]:translate-x-4',
|
|
356
|
+
className,
|
|
357
|
+
)}
|
|
358
|
+
{...props}
|
|
359
|
+
>
|
|
360
|
+
<FluidHoverHighlight hover={hover} className={shape.bg} />
|
|
361
|
+
<FocusRing rect={open && focused !== null ? hover.itemRects[focused] : undefined} />
|
|
362
|
+
{children}
|
|
363
|
+
</NavigationMenuPrimitive.Content>
|
|
364
|
+
</InContentContext.Provider>
|
|
365
|
+
)
|
|
366
|
+
},
|
|
367
|
+
)
|
|
368
|
+
NavigationMenuContent.displayName = 'NavigationMenuContent'
|
|
369
|
+
|
|
370
|
+
/* ─────────────────────── Link ─────────────────────── */
|
|
371
|
+
|
|
372
|
+
interface NavigationMenuLinkProps extends ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Link> {
|
|
373
|
+
/** Marks the link as the current page (`aria-current`); in the strip, the indicator rests under it. Defaults to `false`. */
|
|
374
|
+
active?: boolean
|
|
375
|
+
/** Leading icon of a link inside a panel. */
|
|
376
|
+
icon?: IconComponent
|
|
377
|
+
/** A muted line under the title of a link inside a panel. */
|
|
378
|
+
description?: ReactNode
|
|
379
|
+
/** Close the panel when the link is pressed. Defaults to `false`. */
|
|
380
|
+
closeOnClick?: boolean
|
|
381
|
+
/** The link's title. */
|
|
382
|
+
children?: ReactNode
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const NavigationMenuLink = forwardRef<HTMLAnchorElement, NavigationMenuLinkProps>(
|
|
386
|
+
({ active = false, icon: Icon, description, className, children, ...props }, ref) => {
|
|
387
|
+
const inContent = useContext(InContentContext)
|
|
388
|
+
const size = useSize()
|
|
389
|
+
const shape = useShape()
|
|
390
|
+
|
|
391
|
+
if (!inContent) {
|
|
392
|
+
return (
|
|
393
|
+
<NavigationMenuPrimitive.Link
|
|
394
|
+
ref={ref}
|
|
395
|
+
active={active}
|
|
396
|
+
{...{ [TARGET]: '' }}
|
|
397
|
+
className={cn(STRIP_ITEM, size.control, size.px, shape.button, active && 'text-foreground', className)}
|
|
398
|
+
{...props}
|
|
399
|
+
>
|
|
400
|
+
<StripLabel emphasized={active}>{children}</StripLabel>
|
|
401
|
+
</NavigationMenuPrimitive.Link>
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const compact = size.variant === 'compact'
|
|
406
|
+
return (
|
|
407
|
+
<NavigationMenuPrimitive.Link
|
|
408
|
+
ref={ref}
|
|
409
|
+
active={active}
|
|
410
|
+
{...{ [ROW]: '' }}
|
|
411
|
+
className={cn(
|
|
412
|
+
'group/row relative z-10 flex min-w-0 cursor-pointer items-start no-underline outline-none',
|
|
413
|
+
description ? 'py-2' : cn(size.control, 'items-center'),
|
|
414
|
+
size.itemPx,
|
|
415
|
+
size.gap,
|
|
416
|
+
shape.item,
|
|
417
|
+
className,
|
|
418
|
+
)}
|
|
419
|
+
{...props}
|
|
420
|
+
>
|
|
421
|
+
{Icon && (
|
|
422
|
+
<Icon
|
|
423
|
+
size={size.icon}
|
|
424
|
+
strokeWidth={1.5}
|
|
425
|
+
className={cn(
|
|
426
|
+
'shrink-0 text-muted-foreground transition-[color,stroke-width] duration-fast group-[[data-fluid-hover-active]]/row:stroke-2 group-[[data-fluid-hover-active]]/row:text-foreground',
|
|
427
|
+
description && 'mt-0.5',
|
|
428
|
+
)}
|
|
429
|
+
/>
|
|
430
|
+
)}
|
|
431
|
+
<span className="flex min-w-0 flex-col gap-0.5">
|
|
432
|
+
<span className={cn('truncate text-foreground', active ? 'weight-semibold' : 'weight-normal', size.text)}>
|
|
433
|
+
{children}
|
|
434
|
+
</span>
|
|
435
|
+
{description && (
|
|
436
|
+
<span className={cn('text-muted-foreground', compact ? 'text-caption-compact' : 'text-caption')}>
|
|
437
|
+
{description}
|
|
438
|
+
</span>
|
|
439
|
+
)}
|
|
440
|
+
</span>
|
|
441
|
+
</NavigationMenuPrimitive.Link>
|
|
442
|
+
)
|
|
443
|
+
},
|
|
444
|
+
)
|
|
445
|
+
NavigationMenuLink.displayName = 'NavigationMenuLink'
|
|
446
|
+
|
|
447
|
+
/* ─────────────────────── Root ─────────────────────── */
|
|
448
|
+
|
|
449
|
+
interface NavigationMenuProps {
|
|
450
|
+
/** Controlled open item (its `value`), or `null` when closed. Pair with `onValueChange`. */
|
|
451
|
+
value?: string | null
|
|
452
|
+
/** Initially open item, for an uncontrolled menu. Defaults to `null`. */
|
|
453
|
+
defaultValue?: string | null
|
|
454
|
+
/** Called with the open item's value, or `null` as the menu closes. */
|
|
455
|
+
onValueChange?: (value: string | null) => void
|
|
456
|
+
/** Hover delay before a panel opens, in ms. Defaults to `50`. */
|
|
457
|
+
delay?: number
|
|
458
|
+
/** Delay before the panel closes once the pointer leaves, in ms. Defaults to `50`. */
|
|
459
|
+
closeDelay?: number
|
|
460
|
+
/** Alignment of the panel along the strip. Defaults to `'start'`. */
|
|
461
|
+
align?: 'start' | 'center' | 'end'
|
|
462
|
+
/** Gap between strip and panel in px — the length of the goo neck. Defaults to `12`. */
|
|
463
|
+
sideOffset?: number
|
|
464
|
+
/** Where the panel grows from (see Morph): the pressed trigger, the press point, its own center, a viewport edge, or a ref to any element. Defaults to `'trigger'`. */
|
|
465
|
+
from?: 'trigger' | 'pointer' | 'center' | 'top' | 'right' | 'bottom' | 'left' | RefObject<HTMLElement | null>
|
|
466
|
+
/** How it grows (see Morph): with the liquid goo neck, a plain morph, a slide or a fade. Defaults to `'goo'`. */
|
|
467
|
+
effect?: 'goo' | 'morph' | 'slide' | 'fade'
|
|
468
|
+
/** Hide the trigger while open, so it reads as turning into the panel. Defaults to `false`. */
|
|
469
|
+
hideSource?: boolean
|
|
470
|
+
/** Spring tier of a plain morph, slide or fade; goo runs on its own `spring.goo`. Defaults to `'slow'`. */
|
|
471
|
+
tier?: 'moderate' | 'slow'
|
|
472
|
+
/** Pins the strip and its panels to one step of the size ladder (36px default, 28px compact). Defaults to the surrounding SizeProvider. */
|
|
473
|
+
size?: SizeVariant
|
|
474
|
+
/** Classes for the `nav` root. */
|
|
475
|
+
className?: string
|
|
476
|
+
/** A `NavigationMenu.List`. */
|
|
477
|
+
children?: ReactNode
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* A site navigation bar whose items open flyout panels of links.
|
|
482
|
+
*
|
|
483
|
+
* The panel grows out of the trigger that opened it (see Morph) and closes
|
|
484
|
+
* back into it. Moving to another trigger keeps the one panel open: it
|
|
485
|
+
* springs to the new content's size while the content slides in from the
|
|
486
|
+
* side the pointer came from. One fluid hover highlight runs along the
|
|
487
|
+
* strip and another inside each panel, and `NavigationMenu.Indicator` melts
|
|
488
|
+
* from item to item under the open one, or under the current page's link.
|
|
489
|
+
* The panel lifts 2 surface levels off its substrate. Built on Base UI's
|
|
490
|
+
* NavigationMenu: panels open on hover or press, arrow keys move between
|
|
491
|
+
* items, Escape closes; controlled with `value` + `onValueChange`.
|
|
492
|
+
*
|
|
493
|
+
* Statics:
|
|
494
|
+
* - `NavigationMenu.List` — the strip.
|
|
495
|
+
* - `NavigationMenu.Item` — one entry: a Trigger + Content pair, or a Link;
|
|
496
|
+
* `value`.
|
|
497
|
+
* - `NavigationMenu.Trigger` — opens its item's panel; the chevron turns and
|
|
498
|
+
* the label turns semibold while open.
|
|
499
|
+
* - `NavigationMenu.Content` — the item's panel; size it with `className`.
|
|
500
|
+
* - `NavigationMenu.Link` — a link: in the strip, a top-level item; in a
|
|
501
|
+
* panel, a row with `icon` and `description`. `active` marks the current
|
|
502
|
+
* page; `closeOnClick`.
|
|
503
|
+
* - `NavigationMenu.Indicator` — the liquid marker; put it in the List.
|
|
504
|
+
*
|
|
505
|
+
* @example {@include ./examples.mdx}
|
|
506
|
+
*/
|
|
507
|
+
function NavigationMenu({
|
|
508
|
+
value,
|
|
509
|
+
defaultValue = null,
|
|
510
|
+
onValueChange,
|
|
511
|
+
delay,
|
|
512
|
+
closeDelay,
|
|
513
|
+
align = 'start',
|
|
514
|
+
sideOffset = 12,
|
|
515
|
+
from,
|
|
516
|
+
effect,
|
|
517
|
+
hideSource,
|
|
518
|
+
tier,
|
|
519
|
+
size,
|
|
520
|
+
className,
|
|
521
|
+
children,
|
|
522
|
+
}: NavigationMenuProps) {
|
|
523
|
+
const [current, setCurrent] = useControllableState<string | null>(value, defaultValue, onValueChange)
|
|
524
|
+
const { origin, capture } = useMorphOrigin()
|
|
525
|
+
const triggers = useRef(new Map<string, HTMLElement>())
|
|
526
|
+
const [list, setList] = useState<HTMLElement | null>(null)
|
|
527
|
+
const [panelSize, setPanelSize] = useState<PanelSize | null>(null)
|
|
528
|
+
const open = current !== null
|
|
529
|
+
const morph = useMorph(open, origin, { from, effect, hideSource, tier })
|
|
530
|
+
const reduced = useReduceMotion()
|
|
531
|
+
const shape = useShape()
|
|
532
|
+
const level = Math.min(useSurface() + 2, 8)
|
|
533
|
+
|
|
534
|
+
// A fresh open measures the panel at its natural size.
|
|
535
|
+
if (!open && panelSize) setPanelSize(null)
|
|
536
|
+
|
|
537
|
+
const ctx = useMemo(() => ({ value: current, triggers, setList, setPanelSize }), [current])
|
|
538
|
+
|
|
539
|
+
const root = (
|
|
540
|
+
<RootContext.Provider value={ctx}>
|
|
541
|
+
<NavigationMenuPrimitive.Root
|
|
542
|
+
value={current}
|
|
543
|
+
onValueChange={(next, details) => {
|
|
544
|
+
const nextValue = (next as string | null) ?? null
|
|
545
|
+
// Every switch re-points the origin, so the panel closes into the trigger that is open.
|
|
546
|
+
if (nextValue !== null) capture({ trigger: triggers.current.get(nextValue), event: details.event })
|
|
547
|
+
setCurrent(nextValue)
|
|
548
|
+
}}
|
|
549
|
+
delay={delay}
|
|
550
|
+
closeDelay={closeDelay}
|
|
551
|
+
className={cn('relative', className)}
|
|
552
|
+
>
|
|
553
|
+
{children}
|
|
554
|
+
<NavigationMenuPrimitive.Portal>
|
|
555
|
+
<NavigationMenuPrimitive.Positioner
|
|
556
|
+
anchor={list}
|
|
557
|
+
side="bottom"
|
|
558
|
+
align={align}
|
|
559
|
+
sideOffset={sideOffset}
|
|
560
|
+
collisionPadding={8}
|
|
561
|
+
className="z-50 outline-none"
|
|
562
|
+
>
|
|
563
|
+
<NavigationMenuPrimitive.Popup ref={morph.popupRef} className="relative outline-none">
|
|
564
|
+
<SurfaceProvider value={level}>
|
|
565
|
+
<MorphSurface morph={morph} bg={SURFACE_BG[level]} shadow={SURFACE_SHADOW[3]} radius={shape.container}>
|
|
566
|
+
<motion.div
|
|
567
|
+
className="relative overflow-hidden"
|
|
568
|
+
initial={false}
|
|
569
|
+
animate={panelSize ? { width: panelSize.width, height: panelSize.height } : undefined}
|
|
570
|
+
transition={reduced ? { duration: 0 } : spring.moderate}
|
|
571
|
+
>
|
|
572
|
+
<NavigationMenuPrimitive.Viewport className="relative" />
|
|
573
|
+
</motion.div>
|
|
574
|
+
</MorphSurface>
|
|
575
|
+
</SurfaceProvider>
|
|
576
|
+
</NavigationMenuPrimitive.Popup>
|
|
577
|
+
</NavigationMenuPrimitive.Positioner>
|
|
578
|
+
</NavigationMenuPrimitive.Portal>
|
|
579
|
+
</NavigationMenuPrimitive.Root>
|
|
580
|
+
</RootContext.Provider>
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
// A size prop pins the strip and its panels to one ladder step.
|
|
584
|
+
return size ? <SizeProvider size={size}>{root}</SizeProvider> : root
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
NavigationMenu.List = NavigationMenuList
|
|
588
|
+
NavigationMenu.Item = NavigationMenuItem
|
|
589
|
+
NavigationMenu.Trigger = NavigationMenuTrigger
|
|
590
|
+
NavigationMenu.Content = NavigationMenuContent
|
|
591
|
+
NavigationMenu.Link = NavigationMenuLink
|
|
592
|
+
NavigationMenu.Indicator = NavigationMenuIndicator
|
|
593
|
+
|
|
594
|
+
export type {
|
|
595
|
+
NavigationMenuContentProps,
|
|
596
|
+
NavigationMenuIndicatorProps,
|
|
597
|
+
NavigationMenuItemProps,
|
|
598
|
+
NavigationMenuLinkProps,
|
|
599
|
+
NavigationMenuListProps,
|
|
600
|
+
NavigationMenuProps,
|
|
601
|
+
NavigationMenuTriggerProps,
|
|
602
|
+
}
|
|
603
|
+
export {
|
|
604
|
+
NavigationMenu,
|
|
605
|
+
NavigationMenuContent,
|
|
606
|
+
NavigationMenuIndicator,
|
|
607
|
+
NavigationMenuItem,
|
|
608
|
+
NavigationMenuLink,
|
|
609
|
+
NavigationMenuList,
|
|
610
|
+
NavigationMenuTrigger,
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export default NavigationMenu
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import Pagination from './index'
|
|
2
|
+
|
|
3
|
+
# Pages
|
|
4
|
+
|
|
5
|
+
The current page melts from page to page, and the hover glides along the strip. A click between two pages lands on the lit one.
|
|
6
|
+
|
|
7
|
+
<Pagination pageCount={12} defaultPage={5} />
|
|
8
|
+
|
|
9
|
+
# Few pages
|
|
10
|
+
|
|
11
|
+
With nothing to skip, every page shows.
|
|
12
|
+
|
|
13
|
+
<Pagination pageCount={5} defaultPage={2} />
|
|
14
|
+
|
|
15
|
+
# First and last
|
|
16
|
+
|
|
17
|
+
`edges` adds buttons to the ends, for long runs of pages.
|
|
18
|
+
|
|
19
|
+
<Pagination pageCount={40} defaultPage={18} edges />
|
|
20
|
+
|
|
21
|
+
# Jump to a page
|
|
22
|
+
|
|
23
|
+
`variant="input"` swaps the strip for a field: type a page and press Enter. First and last come with it.
|
|
24
|
+
|
|
25
|
+
<Pagination pageCount={24} defaultPage={3} variant="input" />
|
|
26
|
+
|
|
27
|
+
# Page size
|
|
28
|
+
|
|
29
|
+
Setting `defaultPageSize` (or `pageSize`) adds a Select of `pageSizes`, the layout a data table footer uses.
|
|
30
|
+
|
|
31
|
+
<Pagination pageCount={8} variant="input" defaultPageSize={20} />
|
|
32
|
+
|
|
33
|
+
# Translated labels
|
|
34
|
+
|
|
35
|
+
Every label is a prop: the buttons' names, the field's name and the page-size caption.
|
|
36
|
+
|
|
37
|
+
<Pagination
|
|
38
|
+
pageCount={8}
|
|
39
|
+
defaultPage={2}
|
|
40
|
+
variant="input"
|
|
41
|
+
defaultPageSize={10}
|
|
42
|
+
label="Stránkovanie"
|
|
43
|
+
firstLabel="Prvá strana"
|
|
44
|
+
previousLabel="Predchádzajúca strana"
|
|
45
|
+
nextLabel="Ďalšia strana"
|
|
46
|
+
lastLabel="Posledná strana"
|
|
47
|
+
pageLabel="Strana"
|
|
48
|
+
pageSizeLabel="Riadkov na stranu"
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
# Compact
|
|
52
|
+
|
|
53
|
+
The strip steps down the size ladder with everything around it, the page-size menu included.
|
|
54
|
+
|
|
55
|
+
<div className="flex flex-col gap-4">
|
|
56
|
+
<Pagination pageCount={20} defaultPage={10} size="compact" />
|
|
57
|
+
<Pagination pageCount={20} defaultPage={10} variant="input" defaultPageSize={10} size="compact" />
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
# Disabled
|
|
61
|
+
|
|
62
|
+
`disabled` holds every control, e.g. while the next page loads.
|
|
63
|
+
|
|
64
|
+
<Pagination pageCount={12} defaultPage={4} disabled />
|