@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,494 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
|
|
3
|
+
* `registry/base/tabs.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
|
|
4
|
+
* MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
|
|
5
|
+
* Local modifications: `"use client"` dropped; `@/lib/*` and `@/hooks/*`
|
|
6
|
+
* imports rewritten to `../../../lib/*` (`SizeProvider` to
|
|
7
|
+
* `../../../primitives/sizes`), `framer-motion` to `motion/react`; `defaultSelectedIndex` added as the index-mode twin of the
|
|
8
|
+
* existing value-mode `defaultValue` (it only seeds the first render, like
|
|
9
|
+
* `defaultValue`); modo docs — TSDoc with FF's docs/API text, `Tabs.List` /
|
|
10
|
+
* `Tabs.Item` / `Tabs.Panel` statics (typed via a cast on the root), default
|
|
11
|
+
* export (upstream has none).
|
|
12
|
+
* Styling reads DS tokens (AGENTS.md styling): inline `fontVariationSettings`
|
|
13
|
+
* → `weight-*`; the hex focus-ring fallback → `ring-focus-ring` /
|
|
14
|
+
* `border-focus-ring`; `duration-80|120|160` and tier-length JS durations →
|
|
15
|
+
* `duration-<tier>` / `spring.*`.
|
|
16
|
+
* The active indicator and the hover pill are `GooIndicator`s
|
|
17
|
+
* (`lib/goo-indicator.tsx`, local): they melt from tab to tab instead of
|
|
18
|
+
* sliding; the hover pill now always returns to the active tab as it fades.
|
|
19
|
+
* - `Tabs.Item` `label` takes any node (a counter, a badge) besides a string;
|
|
20
|
+
* the weight ghost-span keeps holding the width, its hidden copy `inert`
|
|
21
|
+
* (`markInert` from `lib/inert.ts`).
|
|
22
|
+
* `href` renders the tab as an anchor (link tabs) through Base UI's `render`.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { Tabs as TabsPrimitive } from '@base-ui/react/tabs'
|
|
26
|
+
import { AnimatePresence, motion } from 'motion/react'
|
|
27
|
+
import {
|
|
28
|
+
Children,
|
|
29
|
+
type ComponentPropsWithoutRef,
|
|
30
|
+
cloneElement,
|
|
31
|
+
createContext,
|
|
32
|
+
type ForwardRefExoticComponent,
|
|
33
|
+
forwardRef,
|
|
34
|
+
isValidElement,
|
|
35
|
+
type ReactNode,
|
|
36
|
+
type RefAttributes,
|
|
37
|
+
useCallback,
|
|
38
|
+
useContext,
|
|
39
|
+
useEffect,
|
|
40
|
+
useLayoutEffect,
|
|
41
|
+
useRef,
|
|
42
|
+
useState,
|
|
43
|
+
} from 'react'
|
|
44
|
+
import { GooIndicator } from '../../../lib/goo-indicator'
|
|
45
|
+
import type { IconComponent } from '../../../lib/icon-context'
|
|
46
|
+
import { markInert } from '../../../lib/inert'
|
|
47
|
+
import { useShape } from '../../../lib/shape-context'
|
|
48
|
+
import { type SizeVariant, useSize } from '../../../lib/size-context'
|
|
49
|
+
import { spring } from '../../../lib/springs'
|
|
50
|
+
import { SURFACE_BG, SURFACE_SHADOW } from '../../../lib/surface-classes'
|
|
51
|
+
import { useSurface } from '../../../lib/surface-context'
|
|
52
|
+
import { useFluidHover } from '../../../lib/use-fluid-hover'
|
|
53
|
+
import { cn } from '../../../lib/utils'
|
|
54
|
+
import { SizeProvider } from '../../../primitives/sizes'
|
|
55
|
+
|
|
56
|
+
/* ─────────────────────── Contexts ─────────────────────── */
|
|
57
|
+
|
|
58
|
+
interface TabsValueOrderContextValue {
|
|
59
|
+
valueOrder: string[]
|
|
60
|
+
setValueOrder: (order: string[]) => void
|
|
61
|
+
selectedValue: string | undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const TabsValueOrderContext = createContext<TabsValueOrderContextValue | null>(null)
|
|
65
|
+
|
|
66
|
+
interface TabsListContextValue {
|
|
67
|
+
registerTab: (index: number, value: string, el: HTMLElement | null) => void
|
|
68
|
+
hoveredIndex: number | null
|
|
69
|
+
selectedValue: string | undefined
|
|
70
|
+
setOptimisticIdx: (index: number) => void
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const TabsListContext = createContext<TabsListContextValue | null>(null)
|
|
74
|
+
|
|
75
|
+
function useTabsList() {
|
|
76
|
+
const ctx = useContext(TabsListContext)
|
|
77
|
+
if (!ctx) throw new Error('TabItem must be used within a TabsList')
|
|
78
|
+
return ctx
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ─────────────────────── Tabs (Root) ─────────────────────── */
|
|
82
|
+
|
|
83
|
+
interface TabsProps
|
|
84
|
+
extends Omit<
|
|
85
|
+
ComponentPropsWithoutRef<typeof TabsPrimitive.Root>,
|
|
86
|
+
'onValueChange' | 'value' | 'defaultValue' | 'onSelect'
|
|
87
|
+
> {
|
|
88
|
+
/** Controlled active tab value. Takes precedence over selectedIndex. */
|
|
89
|
+
value?: string
|
|
90
|
+
/** Called when the active tab changes. */
|
|
91
|
+
onValueChange?: (value: string) => void
|
|
92
|
+
/** Index-based controlled alternative. */
|
|
93
|
+
selectedIndex?: number
|
|
94
|
+
/** Called with the new index when the active tab changes. */
|
|
95
|
+
onSelect?: (index: number) => void
|
|
96
|
+
/** Default active tab for uncontrolled usage. Defaults to the first tab. */
|
|
97
|
+
defaultValue?: string
|
|
98
|
+
/** Index-based uncontrolled alternative: the tab active on first render. Ignored when `defaultValue` is set. */
|
|
99
|
+
defaultSelectedIndex?: number
|
|
100
|
+
/** Pins the segmented control to one step of the size ladder (default 36px outer, compact 28px). Defaults to the surrounding SizeProvider. */
|
|
101
|
+
size?: SizeVariant
|
|
102
|
+
/** Tabs.List and Tabs.Panel children. */
|
|
103
|
+
children?: ReactNode
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type TabsComponent = ForwardRefExoticComponent<TabsProps & RefAttributes<HTMLDivElement>> & {
|
|
107
|
+
List: typeof TabsList
|
|
108
|
+
Item: typeof TabItem
|
|
109
|
+
Panel: typeof TabPanel
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Segmented control tabs with sliding active indicator, fluid hover, and
|
|
114
|
+
* spring animations.
|
|
115
|
+
*
|
|
116
|
+
* Built on Base UI Tabs: `Tabs` owns the selection, `Tabs.List` draws the
|
|
117
|
+
* segmented track with an elevated indicator that melts toward the active tab
|
|
118
|
+
* like a drop (lifted three surface levels above whatever it sits on; see the
|
|
119
|
+
* liquid indicators in Morph) and a translucent hover pill that drips out of
|
|
120
|
+
* it, and `Tabs.Panel` shows the content of the active tab. Arrow keys
|
|
121
|
+
* move and activate. Select by value — `defaultValue` uncontrolled, `value` +
|
|
122
|
+
* `onValueChange` controlled — or by index with `defaultSelectedIndex` /
|
|
123
|
+
* `selectedIndex` + `onSelect`.
|
|
124
|
+
*
|
|
125
|
+
* Statics:
|
|
126
|
+
* - `Tabs.List` — the segmented track holding the tabs.
|
|
127
|
+
* - `Tabs.Item` — one tab: `value`, `label` (text, or a node such as a
|
|
128
|
+
* label with a counter), optional `icon`, and `href` to render it as a link.
|
|
129
|
+
* - `Tabs.Panel` — content shown while the tab with the same `value` is active.
|
|
130
|
+
*
|
|
131
|
+
* @example {@include ./examples.mdx}
|
|
132
|
+
*/
|
|
133
|
+
const Tabs = forwardRef<HTMLDivElement, TabsProps>(
|
|
134
|
+
(
|
|
135
|
+
{ value, onValueChange, selectedIndex, onSelect, defaultValue, defaultSelectedIndex, size, children, ...props },
|
|
136
|
+
ref,
|
|
137
|
+
) => {
|
|
138
|
+
const [valueOrder, setValueOrder] = useState<string[]>([])
|
|
139
|
+
const [uncontrolledValue, setUncontrolledValue] = useState<string | undefined>(defaultValue)
|
|
140
|
+
const updateValueOrder = useCallback((order: string[]) => {
|
|
141
|
+
setValueOrder(current => {
|
|
142
|
+
if (current.length === order.length && current.every((v, i) => v === order[i])) {
|
|
143
|
+
return current
|
|
144
|
+
}
|
|
145
|
+
return order
|
|
146
|
+
})
|
|
147
|
+
}, [])
|
|
148
|
+
|
|
149
|
+
// Resolve value: explicit value > selectedIndex lookup > uncontrolled state.
|
|
150
|
+
// Uncontrolled with no defaultValue falls back to the first tab so the
|
|
151
|
+
// FF layer's selectedValue matches what the primitive shows.
|
|
152
|
+
// Local: ...or to the `defaultSelectedIndex` tab, when given.
|
|
153
|
+
const resolvedValue =
|
|
154
|
+
value ??
|
|
155
|
+
(selectedIndex != null ? valueOrder[selectedIndex] : (uncontrolledValue ?? valueOrder[defaultSelectedIndex ?? 0]))
|
|
156
|
+
|
|
157
|
+
// Base UI passes (value, eventDetails); we only need value.
|
|
158
|
+
const handleValueChange = useCallback(
|
|
159
|
+
(newValue: unknown) => {
|
|
160
|
+
const v = newValue as string
|
|
161
|
+
if (value === undefined && selectedIndex == null) {
|
|
162
|
+
setUncontrolledValue(v)
|
|
163
|
+
}
|
|
164
|
+
onValueChange?.(v)
|
|
165
|
+
if (onSelect) {
|
|
166
|
+
const idx = valueOrder.indexOf(v)
|
|
167
|
+
if (idx !== -1) onSelect(idx)
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
[onValueChange, onSelect, valueOrder, value, selectedIndex],
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
const root = (
|
|
174
|
+
<TabsValueOrderContext.Provider
|
|
175
|
+
value={{
|
|
176
|
+
valueOrder,
|
|
177
|
+
setValueOrder: updateValueOrder,
|
|
178
|
+
selectedValue: resolvedValue,
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
{/*
|
|
182
|
+
Always controlled: Base UI's useControlled logs a dev warning when
|
|
183
|
+
value flips undefined → defined. valueOrder is empty on the first
|
|
184
|
+
commit, so fall back to an empty-string sentinel — TabsList's
|
|
185
|
+
layout effect populates valueOrder pre-paint, so the corrected
|
|
186
|
+
value lands before anything is visible.
|
|
187
|
+
*/}
|
|
188
|
+
<TabsPrimitive.Root ref={ref} value={resolvedValue ?? ''} onValueChange={handleValueChange} {...props}>
|
|
189
|
+
{children}
|
|
190
|
+
</TabsPrimitive.Root>
|
|
191
|
+
</TabsValueOrderContext.Provider>
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
// A size prop pins the whole compound (list + items) to one ladder step.
|
|
195
|
+
return size ? <SizeProvider size={size}>{root}</SizeProvider> : root
|
|
196
|
+
},
|
|
197
|
+
) as TabsComponent
|
|
198
|
+
|
|
199
|
+
Tabs.displayName = 'Tabs'
|
|
200
|
+
|
|
201
|
+
/* ─────────────────────── TabsList ─────────────────────── */
|
|
202
|
+
|
|
203
|
+
type TabsListProps = ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
204
|
+
|
|
205
|
+
const TabsList = forwardRef<HTMLDivElement, TabsListProps>(({ children, className, ...props }, ref) => {
|
|
206
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
207
|
+
const isMouseInside = useRef(false)
|
|
208
|
+
const shape = useShape()
|
|
209
|
+
const sizeClasses = useSize()
|
|
210
|
+
const substrate = useSurface()
|
|
211
|
+
const indicatorLevel = Math.min(substrate + 3, 8)
|
|
212
|
+
const valueOrderCtx = useContext(TabsValueOrderContext)
|
|
213
|
+
const [optimisticIdx, setOptimisticIdx] = useState<number | null>(null)
|
|
214
|
+
|
|
215
|
+
const values = Children.toArray(children)
|
|
216
|
+
.filter(isValidElement)
|
|
217
|
+
.map(child => (child.props as { value?: string }).value)
|
|
218
|
+
.filter((v): v is string => typeof v === 'string')
|
|
219
|
+
const valueOrderKey = values.join(',')
|
|
220
|
+
const setValueOrder = valueOrderCtx?.setValueOrder
|
|
221
|
+
|
|
222
|
+
useLayoutEffect(() => {
|
|
223
|
+
setValueOrder?.(values)
|
|
224
|
+
}, [setValueOrder, valueOrderKey])
|
|
225
|
+
|
|
226
|
+
const {
|
|
227
|
+
activeIndex: hoveredIndex,
|
|
228
|
+
setActiveIndex: setHoveredIndex,
|
|
229
|
+
itemRects,
|
|
230
|
+
handlers,
|
|
231
|
+
registerItem,
|
|
232
|
+
measureItems,
|
|
233
|
+
} = useFluidHover(containerRef, { axis: 'x' })
|
|
234
|
+
|
|
235
|
+
const registerTab = useCallback(
|
|
236
|
+
(index: number, _value: string, el: HTMLElement | null) => {
|
|
237
|
+
registerItem(index, el)
|
|
238
|
+
},
|
|
239
|
+
[registerItem],
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
measureItems()
|
|
244
|
+
}, [measureItems, children])
|
|
245
|
+
|
|
246
|
+
const handleMouseMove = useCallback(
|
|
247
|
+
(e: React.MouseEvent) => {
|
|
248
|
+
isMouseInside.current = true
|
|
249
|
+
handlers.onMouseMove(e)
|
|
250
|
+
},
|
|
251
|
+
[handlers],
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
const handleMouseLeave = useCallback(() => {
|
|
255
|
+
isMouseInside.current = false
|
|
256
|
+
handlers.onMouseLeave()
|
|
257
|
+
}, [handlers])
|
|
258
|
+
|
|
259
|
+
const [focusedIndex, setFocusedIndex] = useState<number | null>(null)
|
|
260
|
+
const selectedValue = valueOrderCtx?.selectedValue
|
|
261
|
+
const selectedIdx = selectedValue !== undefined ? values.indexOf(selectedValue) : -1
|
|
262
|
+
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
setOptimisticIdx(selectedIdx >= 0 ? selectedIdx : null)
|
|
265
|
+
}, [selectedIdx])
|
|
266
|
+
|
|
267
|
+
const activeSelectedIdx = optimisticIdx
|
|
268
|
+
const selectedRect = activeSelectedIdx !== null ? itemRects[activeSelectedIdx] : null
|
|
269
|
+
const hoverRect = hoveredIndex !== null ? itemRects[hoveredIndex] : null
|
|
270
|
+
const focusRect = focusedIndex !== null ? itemRects[focusedIndex] : null
|
|
271
|
+
const isHoveringSelected = hoveredIndex === activeSelectedIdx
|
|
272
|
+
const isHovering = hoveredIndex !== null && !isHoveringSelected
|
|
273
|
+
|
|
274
|
+
const indexedChildren = Children.map(children, (child, i) => {
|
|
275
|
+
// Skip plain DOM elements — injecting _index into e.g. a <div>
|
|
276
|
+
// triggers React's unknown-prop warning.
|
|
277
|
+
if (isValidElement(child) && typeof child.type !== 'string') {
|
|
278
|
+
return cloneElement(child, { _index: i } as Record<string, unknown>)
|
|
279
|
+
}
|
|
280
|
+
return child
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
return (
|
|
284
|
+
<TabsListContext.Provider
|
|
285
|
+
value={{
|
|
286
|
+
registerTab,
|
|
287
|
+
hoveredIndex,
|
|
288
|
+
selectedValue,
|
|
289
|
+
setOptimisticIdx,
|
|
290
|
+
}}
|
|
291
|
+
>
|
|
292
|
+
<TabsPrimitive.List
|
|
293
|
+
// Match Radix's `activationMode="automatic"` — arrow keys move + activate.
|
|
294
|
+
activateOnFocus
|
|
295
|
+
ref={node => {
|
|
296
|
+
;(containerRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
297
|
+
if (typeof ref === 'function') ref(node)
|
|
298
|
+
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
299
|
+
}}
|
|
300
|
+
onMouseMove={handleMouseMove}
|
|
301
|
+
onMouseLeave={handleMouseLeave}
|
|
302
|
+
onFocus={e => {
|
|
303
|
+
const trigger = (e.target as HTMLElement).closest('[role="tab"]')
|
|
304
|
+
if (!trigger) return
|
|
305
|
+
const indexAttr = trigger.getAttribute('data-fluid-hover-index')
|
|
306
|
+
if (indexAttr != null) {
|
|
307
|
+
const idx = Number(indexAttr)
|
|
308
|
+
setHoveredIndex(idx)
|
|
309
|
+
setFocusedIndex((e.target as HTMLElement).matches(':focus-visible') ? idx : null)
|
|
310
|
+
}
|
|
311
|
+
}}
|
|
312
|
+
onBlur={e => {
|
|
313
|
+
if (containerRef.current?.contains(e.relatedTarget as Node)) return
|
|
314
|
+
setFocusedIndex(null)
|
|
315
|
+
if (isMouseInside.current) return
|
|
316
|
+
setHoveredIndex(null)
|
|
317
|
+
}}
|
|
318
|
+
className={cn(
|
|
319
|
+
// segmentPad + segmentItem add up to the ladder's control height
|
|
320
|
+
// (36px default, 28px compact) so the segmented control's outer
|
|
321
|
+
// box lines up with buttons, selects, and inputs beside it.
|
|
322
|
+
'relative inline-flex items-center select-none bg-muted',
|
|
323
|
+
sizeClasses.segmentPad,
|
|
324
|
+
shape.container,
|
|
325
|
+
className,
|
|
326
|
+
)}
|
|
327
|
+
{...props}
|
|
328
|
+
>
|
|
329
|
+
{selectedRect && (
|
|
330
|
+
<GooIndicator
|
|
331
|
+
rect={selectedRect}
|
|
332
|
+
className={cn(SURFACE_BG[indicatorLevel], shape.bg)}
|
|
333
|
+
shadow={SURFACE_SHADOW[indicatorLevel]}
|
|
334
|
+
opacity={isHovering ? 0.85 : 1}
|
|
335
|
+
/>
|
|
336
|
+
)}
|
|
337
|
+
|
|
338
|
+
{/* the hover pill rests on the active tab, so it drips back into it as it fades */}
|
|
339
|
+
{selectedRect && (
|
|
340
|
+
<GooIndicator
|
|
341
|
+
rect={isHovering && hoverRect ? hoverRect : selectedRect}
|
|
342
|
+
className={cn('bg-hover', shape.bg)}
|
|
343
|
+
transition={spring.fast}
|
|
344
|
+
opacity={isHovering ? 0.4 : 0}
|
|
345
|
+
/>
|
|
346
|
+
)}
|
|
347
|
+
|
|
348
|
+
{/* Focus ring */}
|
|
349
|
+
<AnimatePresence>
|
|
350
|
+
{focusRect && (
|
|
351
|
+
<motion.div
|
|
352
|
+
className={cn('absolute pointer-events-none z-20 border border-focus-ring', shape.focusRing)}
|
|
353
|
+
initial={false}
|
|
354
|
+
animate={{
|
|
355
|
+
left: focusRect.left - 2,
|
|
356
|
+
top: focusRect.top - 2,
|
|
357
|
+
width: focusRect.width + 4,
|
|
358
|
+
height: focusRect.height + 4,
|
|
359
|
+
}}
|
|
360
|
+
exit={{ opacity: 0, transition: spring.fast.exit }}
|
|
361
|
+
transition={{
|
|
362
|
+
...spring.fast,
|
|
363
|
+
opacity: { duration: spring.fast.duration },
|
|
364
|
+
}}
|
|
365
|
+
/>
|
|
366
|
+
)}
|
|
367
|
+
</AnimatePresence>
|
|
368
|
+
|
|
369
|
+
{indexedChildren}
|
|
370
|
+
</TabsPrimitive.List>
|
|
371
|
+
</TabsListContext.Provider>
|
|
372
|
+
)
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
TabsList.displayName = 'TabsList'
|
|
376
|
+
|
|
377
|
+
/* ─────────────────────── TabItem ─────────────────────── */
|
|
378
|
+
|
|
379
|
+
interface TabItemProps extends ComponentPropsWithoutRef<typeof TabsPrimitive.Tab> {
|
|
380
|
+
/** The tab's value, matched by `Tabs.Panel` and the root's `value`. */
|
|
381
|
+
value: string
|
|
382
|
+
/** Leading icon; its stroke thickens while the tab is hovered or active. */
|
|
383
|
+
icon?: IconComponent
|
|
384
|
+
/** The label: text, or a node such as text with a counter or a badge. */
|
|
385
|
+
label: ReactNode
|
|
386
|
+
/** Render the tab as an anchor to this URL (link tabs): pressing it follows the link and selects the tab. */
|
|
387
|
+
href?: string
|
|
388
|
+
/** @internal Auto-assigned by TabsList. */
|
|
389
|
+
_index?: number
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const TabItem = forwardRef<HTMLButtonElement, TabItemProps>(
|
|
393
|
+
({ value, icon: Icon, label, href, _index = 0, className, onClick, ...props }, ref) => {
|
|
394
|
+
const internalRef = useRef<HTMLButtonElement>(null)
|
|
395
|
+
const sizeClasses = useSize()
|
|
396
|
+
const { registerTab, hoveredIndex, selectedValue, setOptimisticIdx } = useTabsList()
|
|
397
|
+
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
registerTab(_index, value, internalRef.current)
|
|
400
|
+
return () => registerTab(_index, value, null)
|
|
401
|
+
}, [_index, value, registerTab])
|
|
402
|
+
|
|
403
|
+
const isSelected = selectedValue === value
|
|
404
|
+
const isActive = hoveredIndex === _index || isSelected
|
|
405
|
+
// A text label trims its box to the cap height; a node (a counter, a badge) lays out as a row.
|
|
406
|
+
const text = typeof label === 'string' || typeof label === 'number'
|
|
407
|
+
const labelLayout = text ? '[text-box:trim-both_cap_alphabetic]' : 'inline-flex items-center gap-1.5'
|
|
408
|
+
|
|
409
|
+
return (
|
|
410
|
+
<TabsPrimitive.Tab
|
|
411
|
+
// Composed (not spread-overridable): a consumer onClick must not
|
|
412
|
+
// replace the optimistic indicator jump.
|
|
413
|
+
onClick={e => {
|
|
414
|
+
setOptimisticIdx(_index)
|
|
415
|
+
onClick?.(e)
|
|
416
|
+
}}
|
|
417
|
+
ref={node => {
|
|
418
|
+
;(internalRef as React.MutableRefObject<HTMLElement | null>).current = node as HTMLButtonElement | null
|
|
419
|
+
if (typeof ref === 'function') ref(node as HTMLButtonElement)
|
|
420
|
+
else if (ref)
|
|
421
|
+
(ref as React.MutableRefObject<HTMLButtonElement | null>).current = node as HTMLButtonElement | null
|
|
422
|
+
}}
|
|
423
|
+
value={value}
|
|
424
|
+
// Local: link tabs render an anchor (Base UI keeps the tab semantics on it).
|
|
425
|
+
// biome-ignore lint/a11y/useAnchorContent: Base UI renders the tab's label into the anchor
|
|
426
|
+
{...(href !== undefined && { render: <a href={href} />, nativeButton: false })}
|
|
427
|
+
data-fluid-hover-index={_index}
|
|
428
|
+
className={cn(
|
|
429
|
+
// Fixed height (not py) so the text-box trim below doesn't shrink
|
|
430
|
+
// the tab — browsers without text-box support render identically.
|
|
431
|
+
'relative z-10 flex items-center px-3 cursor-pointer bg-transparent border-none outline-none no-underline',
|
|
432
|
+
sizeClasses.segmentItem,
|
|
433
|
+
sizeClasses.gap,
|
|
434
|
+
className,
|
|
435
|
+
)}
|
|
436
|
+
{...props}
|
|
437
|
+
>
|
|
438
|
+
{Icon && (
|
|
439
|
+
<Icon
|
|
440
|
+
size={sizeClasses.icon}
|
|
441
|
+
strokeWidth={isActive ? 2 : 1.5}
|
|
442
|
+
className={cn(
|
|
443
|
+
'transition-[color,stroke-width] duration-fast',
|
|
444
|
+
isActive ? 'text-foreground' : 'text-muted-foreground',
|
|
445
|
+
)}
|
|
446
|
+
/>
|
|
447
|
+
)}
|
|
448
|
+
{/* Both stacked spans carry the same layout so the invisible bold
|
|
449
|
+
sizer and the visible label keep identical boxes. A node label
|
|
450
|
+
renders twice too; its hidden copy is inert, so keep it
|
|
451
|
+
presentational (text, counters, badges). */}
|
|
452
|
+
<span className={cn('inline-grid whitespace-nowrap', sizeClasses.text)}>
|
|
453
|
+
<span
|
|
454
|
+
className={cn('col-start-1 row-start-1 invisible weight-semibold', labelLayout)}
|
|
455
|
+
aria-hidden="true"
|
|
456
|
+
ref={text ? undefined : markInert}
|
|
457
|
+
>
|
|
458
|
+
{label}
|
|
459
|
+
</span>
|
|
460
|
+
<span
|
|
461
|
+
className={cn(
|
|
462
|
+
'col-start-1 row-start-1 transition-[color,font-variation-settings] duration-fast',
|
|
463
|
+
labelLayout,
|
|
464
|
+
isActive ? 'text-foreground' : 'text-muted-foreground',
|
|
465
|
+
isSelected ? 'weight-semibold' : 'weight-normal',
|
|
466
|
+
)}
|
|
467
|
+
>
|
|
468
|
+
{label}
|
|
469
|
+
</span>
|
|
470
|
+
</span>
|
|
471
|
+
</TabsPrimitive.Tab>
|
|
472
|
+
)
|
|
473
|
+
},
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
TabItem.displayName = 'TabItem'
|
|
477
|
+
|
|
478
|
+
/* ─────────────────────── TabPanel ─────────────────────── */
|
|
479
|
+
|
|
480
|
+
interface TabPanelProps extends ComponentPropsWithoutRef<typeof TabsPrimitive.Panel> {
|
|
481
|
+
value: string
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const TabPanel = forwardRef<HTMLDivElement, TabPanelProps>(({ className, ...props }, ref) => {
|
|
485
|
+
return <TabsPrimitive.Panel ref={ref} className={cn('outline-none', className)} {...props} />
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
TabPanel.displayName = 'TabPanel'
|
|
489
|
+
|
|
490
|
+
Object.assign(Tabs, { List: TabsList, Item: TabItem, Panel: TabPanel })
|
|
491
|
+
|
|
492
|
+
export type { TabItemProps, TabPanelProps, TabsListProps, TabsProps }
|
|
493
|
+
export { TabItem, TabPanel, Tabs, TabsList }
|
|
494
|
+
export default Tabs
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import TabsSubtle from './index'
|
|
2
|
+
import { Clock, SquareLibrary, Star, Users } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
# Basic
|
|
5
|
+
|
|
6
|
+
The pill follows the selected tab; panels swap with it.
|
|
7
|
+
|
|
8
|
+
<div className="flex flex-col gap-4 w-full">
|
|
9
|
+
<TabsSubtle defaultSelectedIndex={0}>
|
|
10
|
+
{['Teamspaces', 'Recents', 'Favorites', 'Shared'].map((label, i) => (
|
|
11
|
+
<TabsSubtle.Item key={label} index={i} label={label} />
|
|
12
|
+
))}
|
|
13
|
+
{['Teamspaces', 'Recents', 'Favorites', 'Shared'].map((label, i) => (
|
|
14
|
+
<TabsSubtle.Panel key={label} index={i}>
|
|
15
|
+
<p className="text-body text-muted-foreground px-3">{label} content goes here.</p>
|
|
16
|
+
</TabsSubtle.Panel>
|
|
17
|
+
))}
|
|
18
|
+
</TabsSubtle>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
# With Icons
|
|
22
|
+
|
|
23
|
+
An `icon` sits ahead of the label and thickens its stroke while its tab is
|
|
24
|
+
hovered or selected.
|
|
25
|
+
|
|
26
|
+
<div className="flex flex-col gap-4 w-full">
|
|
27
|
+
<TabsSubtle defaultSelectedIndex={0}>
|
|
28
|
+
<TabsSubtle.Item index={0} icon={SquareLibrary} label="Teamspaces" />
|
|
29
|
+
<TabsSubtle.Item index={1} icon={Clock} label="Recents" />
|
|
30
|
+
<TabsSubtle.Item index={2} icon={Star} label="Favorites" />
|
|
31
|
+
<TabsSubtle.Item index={3} icon={Users} label="Shared" />
|
|
32
|
+
<TabsSubtle.Panel index={0}><p className="text-body text-muted-foreground px-3">Teamspaces content goes here.</p></TabsSubtle.Panel>
|
|
33
|
+
<TabsSubtle.Panel index={1}><p className="text-body text-muted-foreground px-3">Recents content goes here.</p></TabsSubtle.Panel>
|
|
34
|
+
<TabsSubtle.Panel index={2}><p className="text-body text-muted-foreground px-3">Favorites content goes here.</p></TabsSubtle.Panel>
|
|
35
|
+
<TabsSubtle.Panel index={3}><p className="text-body text-muted-foreground px-3">Shared content goes here.</p></TabsSubtle.Panel>
|
|
36
|
+
</TabsSubtle>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
# Active Label
|
|
40
|
+
|
|
41
|
+
With `activeLabel`, every tab shrinks to its icon and only the selected one
|
|
42
|
+
spells itself out.
|
|
43
|
+
|
|
44
|
+
<TabsSubtle activeLabel defaultSelectedIndex={0}>
|
|
45
|
+
<TabsSubtle.Item index={0} icon={SquareLibrary} label="Teamspaces" />
|
|
46
|
+
<TabsSubtle.Item index={1} icon={Clock} label="Recents" />
|
|
47
|
+
<TabsSubtle.Item index={2} icon={Star} label="Favorites" />
|
|
48
|
+
<TabsSubtle.Item index={3} icon={Users} label="Shared" />
|
|
49
|
+
</TabsSubtle>
|