@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,512 @@
|
|
|
1
|
+
// biome-ignore-all lint: vendored upstream code keeps its own patterns (see the header)
|
|
2
|
+
/*
|
|
3
|
+
* Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
|
|
4
|
+
* `registry/base/tabs-subtle.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
|
|
5
|
+
* MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
|
|
6
|
+
* Local modifications: `"use client"` dropped; `@/lib/*` and `@/hooks/*`
|
|
7
|
+
* imports rewritten to `../../../lib/*` (`SizeProvider` to
|
|
8
|
+
* `../../../primitives/sizes`), `framer-motion` to `motion/react`; uncontrolled mode added — `selectedIndex` and `onSelect` are
|
|
9
|
+
* optional, with a `defaultSelectedIndex` twin backed by
|
|
10
|
+
* `useControllableState`; panels may now be written inside `<TabsSubtle>` —
|
|
11
|
+
* the root partitions its children, renders only the tabs inside the tab list
|
|
12
|
+
* and the `TabsSubtlePanel`s as its siblings right after it (the root element
|
|
13
|
+
* IS the `role="tablist"`, so panels cannot nest in it), and a panel with no
|
|
14
|
+
* `selectedIndex` / `idPrefix` of its own reads both from the TabsSubtle
|
|
15
|
+
* context, with the prefix falling back to a `useId()` one; a panel rendered
|
|
16
|
+
* outside `<TabsSubtle>` keeps taking them as props, exactly as upstream;
|
|
17
|
+
* React 18 types: the tab list's ref write goes through `MutableRefObject`; modo
|
|
18
|
+
* docs — TSDoc with FF's docs/API text, `TabsSubtle.Item` / `TabsSubtle.Panel`
|
|
19
|
+
* statics (typed via a cast on the root), default export.
|
|
20
|
+
* Styling reads DS tokens (AGENTS.md styling): inline `fontVariationSettings`
|
|
21
|
+
* → `weight-*`; the hex focus-ring fallback → `ring-focus-ring` /
|
|
22
|
+
* `border-focus-ring`; `duration-80|120|160` and tier-length JS durations →
|
|
23
|
+
* `duration-<tier>` / `spring.*`.
|
|
24
|
+
* The selected and hover pills are `GooIndicator`s (`lib/goo-indicator.tsx`,
|
|
25
|
+
* local): they melt from tab to tab instead of sliding; the hover pill now
|
|
26
|
+
* always returns to the selected tab as it fades.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { Tabs } from '@base-ui/react/tabs'
|
|
30
|
+
import { AnimatePresence, motion } from 'motion/react'
|
|
31
|
+
import {
|
|
32
|
+
Children,
|
|
33
|
+
createContext,
|
|
34
|
+
type ForwardRefExoticComponent,
|
|
35
|
+
forwardRef,
|
|
36
|
+
type HTMLAttributes,
|
|
37
|
+
isValidElement,
|
|
38
|
+
type ReactNode,
|
|
39
|
+
type RefAttributes,
|
|
40
|
+
useCallback,
|
|
41
|
+
useContext,
|
|
42
|
+
useEffect,
|
|
43
|
+
useId,
|
|
44
|
+
useRef,
|
|
45
|
+
useState,
|
|
46
|
+
} from 'react'
|
|
47
|
+
import { GooIndicator } from '../../../lib/goo-indicator'
|
|
48
|
+
import type { IconComponent } from '../../../lib/icon-context'
|
|
49
|
+
import { useShape } from '../../../lib/shape-context'
|
|
50
|
+
import { type SizeVariant, useSize } from '../../../lib/size-context'
|
|
51
|
+
import { spring } from '../../../lib/springs'
|
|
52
|
+
import { useControllableState } from '../../../lib/use-controllable-state'
|
|
53
|
+
import { useFluidHover } from '../../../lib/use-fluid-hover'
|
|
54
|
+
import { cn } from '../../../lib/utils'
|
|
55
|
+
import { SizeProvider } from '../../../primitives/sizes'
|
|
56
|
+
|
|
57
|
+
interface TabsSubtleContextValue {
|
|
58
|
+
registerTab: (index: number, element: HTMLElement | null) => void
|
|
59
|
+
hoveredIndex: number | null
|
|
60
|
+
selectedIndex: number
|
|
61
|
+
idPrefix: string | undefined
|
|
62
|
+
activeLabel: boolean
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const TabsSubtleContext = createContext<TabsSubtleContextValue | null>(null)
|
|
66
|
+
|
|
67
|
+
const isPanel = (child: ReactNode) => isValidElement(child) && child.type === TabsSubtlePanel
|
|
68
|
+
|
|
69
|
+
function useTabsSubtle() {
|
|
70
|
+
const ctx = useContext(TabsSubtleContext)
|
|
71
|
+
if (!ctx) throw new Error('useTabsSubtle must be used within a TabsSubtle')
|
|
72
|
+
return ctx
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface TabsSubtleProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect'> {
|
|
76
|
+
/** TabsSubtle.Item children, and optionally the TabsSubtle.Panel children they select. */
|
|
77
|
+
children: ReactNode
|
|
78
|
+
/** Index of the currently selected tab. Controlled — pair it with `onSelect`. */
|
|
79
|
+
selectedIndex?: number
|
|
80
|
+
/** Selected tab on first render when uncontrolled. Defaults to `0`. */
|
|
81
|
+
defaultSelectedIndex?: number
|
|
82
|
+
/** Called when a tab is selected. */
|
|
83
|
+
onSelect?: (index: number) => void
|
|
84
|
+
/** Prefix for ARIA IDs linking tabs to panels. Defaults to a generated one when panels are nested inside. */
|
|
85
|
+
idPrefix?: string
|
|
86
|
+
/** When true, only the selected tab shows its text label. Requires icons on tabs. Defaults to `false`. */
|
|
87
|
+
activeLabel?: boolean
|
|
88
|
+
/** Pins the tabs to one step of the size ladder (default 36px, compact 28px). Defaults to the surrounding SizeProvider. */
|
|
89
|
+
size?: SizeVariant
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
type TabsSubtleComponent = ForwardRefExoticComponent<TabsSubtleProps & RefAttributes<HTMLDivElement>> & {
|
|
93
|
+
Item: typeof TabsSubtleItem
|
|
94
|
+
Panel: typeof TabsSubtlePanel
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Tab navigation with smooth pill animations.
|
|
99
|
+
*
|
|
100
|
+
* A borderless tab strip: the selected pill melts from tab to tab like a drop
|
|
101
|
+
* (the liquid indicators in Morph), a lighter hover pill previews the next one, and the active label animates to semibold
|
|
102
|
+
* without shifting. With `activeLabel`, tabs collapse to their icon and the
|
|
103
|
+
* selected one expands its label to a measured width. Base UI owns
|
|
104
|
+
* `role="tablist"`, the roving tabindex and Arrow/Home/End navigation
|
|
105
|
+
* (manual activation — arrows move, Enter or Space selects). Uncontrolled with
|
|
106
|
+
* `defaultSelectedIndex`, or controlled with `selectedIndex` + `onSelect`.
|
|
107
|
+
*
|
|
108
|
+
* The root element is the tab list itself, so nested `TabsSubtle.Panel`
|
|
109
|
+
* children are lifted out and rendered as its siblings, wired to the tabs
|
|
110
|
+
* through a generated id prefix; panels rendered outside still take
|
|
111
|
+
* `selectedIndex` and `idPrefix` as props.
|
|
112
|
+
*
|
|
113
|
+
* Statics:
|
|
114
|
+
* - `TabsSubtle.Item` — one tab: `index`, `label`, optional `icon`.
|
|
115
|
+
* - `TabsSubtle.Panel` — content for the tab with the same `index`.
|
|
116
|
+
*
|
|
117
|
+
* @example {@include ./examples.mdx}
|
|
118
|
+
*/
|
|
119
|
+
const TabsSubtle = forwardRef<HTMLDivElement, TabsSubtleProps>(
|
|
120
|
+
(
|
|
121
|
+
{
|
|
122
|
+
children,
|
|
123
|
+
selectedIndex: selectedIndexProp,
|
|
124
|
+
defaultSelectedIndex = 0,
|
|
125
|
+
onSelect: onSelectProp,
|
|
126
|
+
idPrefix,
|
|
127
|
+
activeLabel = false,
|
|
128
|
+
size,
|
|
129
|
+
className,
|
|
130
|
+
...props
|
|
131
|
+
},
|
|
132
|
+
ref,
|
|
133
|
+
) => {
|
|
134
|
+
const [selectedIndex, onSelect] = useControllableState(selectedIndexProp, defaultSelectedIndex, onSelectProp)
|
|
135
|
+
|
|
136
|
+
// the root IS the tab list, so panels written inside it are rendered right after it
|
|
137
|
+
const childList = Children.toArray(children)
|
|
138
|
+
const panels = childList.filter(isPanel)
|
|
139
|
+
const tabs = childList.filter(child => !isPanel(child))
|
|
140
|
+
const generatedIdPrefix = useId()
|
|
141
|
+
const resolvedIdPrefix = idPrefix ?? (panels.length > 0 ? generatedIdPrefix : undefined)
|
|
142
|
+
|
|
143
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
144
|
+
const isMouseInside = useRef(false)
|
|
145
|
+
const shape = useShape()
|
|
146
|
+
|
|
147
|
+
const {
|
|
148
|
+
activeIndex: hoveredIndex,
|
|
149
|
+
setActiveIndex: setHoveredIndex,
|
|
150
|
+
itemRects: tabRects,
|
|
151
|
+
handlers,
|
|
152
|
+
registerItem,
|
|
153
|
+
measureItems: measureTabs,
|
|
154
|
+
} = useFluidHover(containerRef, { axis: 'x' })
|
|
155
|
+
|
|
156
|
+
// Track tab elements locally so we can observe their individual resizes
|
|
157
|
+
const tabElementsRef = useRef(new Map<number, HTMLElement>())
|
|
158
|
+
const registerTab = useCallback(
|
|
159
|
+
(index: number, element: HTMLElement | null) => {
|
|
160
|
+
registerItem(index, element)
|
|
161
|
+
if (element) {
|
|
162
|
+
tabElementsRef.current.set(index, element)
|
|
163
|
+
} else {
|
|
164
|
+
tabElementsRef.current.delete(index)
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
[registerItem],
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
measureTabs()
|
|
172
|
+
}, [measureTabs, children])
|
|
173
|
+
|
|
174
|
+
// Observe individual tab buttons for resize (label expand/collapse in activeLabel mode)
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
const elements = tabElementsRef.current
|
|
177
|
+
if (elements.size === 0) return
|
|
178
|
+
const ro = new ResizeObserver(() => measureTabs())
|
|
179
|
+
elements.forEach(el => ro.observe(el))
|
|
180
|
+
return () => ro.disconnect()
|
|
181
|
+
}, [measureTabs, children])
|
|
182
|
+
|
|
183
|
+
// Wrap handlers to track isMouseInside
|
|
184
|
+
const handleMouseMove = useCallback(
|
|
185
|
+
(e: React.MouseEvent) => {
|
|
186
|
+
isMouseInside.current = true
|
|
187
|
+
handlers.onMouseMove(e)
|
|
188
|
+
},
|
|
189
|
+
[handlers],
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
const handleMouseLeave = useCallback(() => {
|
|
193
|
+
isMouseInside.current = false
|
|
194
|
+
handlers.onMouseLeave()
|
|
195
|
+
}, [handlers])
|
|
196
|
+
|
|
197
|
+
const [focusedIndex, setFocusedIndex] = useState<number | null>(null)
|
|
198
|
+
|
|
199
|
+
const selectedRect = tabRects[selectedIndex]
|
|
200
|
+
const hoverRect = hoveredIndex !== null ? tabRects[hoveredIndex] : null
|
|
201
|
+
const focusRect = focusedIndex !== null ? tabRects[focusedIndex] : null
|
|
202
|
+
const isHoveringSelected = hoveredIndex === selectedIndex
|
|
203
|
+
const isHovering = hoveredIndex !== null && !isHoveringSelected
|
|
204
|
+
|
|
205
|
+
const root = (
|
|
206
|
+
<TabsSubtleContext.Provider
|
|
207
|
+
value={{
|
|
208
|
+
registerTab,
|
|
209
|
+
hoveredIndex,
|
|
210
|
+
selectedIndex,
|
|
211
|
+
idPrefix: resolvedIdPrefix,
|
|
212
|
+
activeLabel,
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
{/* Root is merged into List via `render` so a single <div> is emitted,
|
|
216
|
+
matching the previous DOM structure. Base UI owns role="tablist",
|
|
217
|
+
roving tabindex, and Arrow/Home/End keyboard navigation.
|
|
218
|
+
`activateOnFocus={false}` keeps manual activation: arrows move
|
|
219
|
+
focus, Enter/Space selects. */}
|
|
220
|
+
<Tabs.Root
|
|
221
|
+
value={selectedIndex}
|
|
222
|
+
onValueChange={value => {
|
|
223
|
+
if (typeof value === 'number') onSelect(value)
|
|
224
|
+
}}
|
|
225
|
+
render={
|
|
226
|
+
<Tabs.List
|
|
227
|
+
activateOnFocus={false}
|
|
228
|
+
ref={(node: HTMLDivElement | null) => {
|
|
229
|
+
// React 18 types: `useRef<T>(null)` is a read-only RefObject.
|
|
230
|
+
;(containerRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
231
|
+
if (typeof ref === 'function') ref(node)
|
|
232
|
+
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
233
|
+
}}
|
|
234
|
+
onMouseMove={handleMouseMove}
|
|
235
|
+
onMouseLeave={handleMouseLeave}
|
|
236
|
+
onFocus={(e: React.FocusEvent<HTMLDivElement>) => {
|
|
237
|
+
const indexAttr = (e.target as HTMLElement)
|
|
238
|
+
.closest('[data-fluid-hover-index]')
|
|
239
|
+
?.getAttribute('data-fluid-hover-index')
|
|
240
|
+
if (indexAttr != null) {
|
|
241
|
+
const idx = Number(indexAttr)
|
|
242
|
+
setHoveredIndex(idx)
|
|
243
|
+
setFocusedIndex((e.target as HTMLElement).matches(':focus-visible') ? idx : null)
|
|
244
|
+
}
|
|
245
|
+
}}
|
|
246
|
+
onBlur={(e: React.FocusEvent<HTMLDivElement>) => {
|
|
247
|
+
if (containerRef.current?.contains(e.relatedTarget as Node)) return
|
|
248
|
+
setFocusedIndex(null)
|
|
249
|
+
if (isMouseInside.current) return
|
|
250
|
+
setHoveredIndex(null)
|
|
251
|
+
}}
|
|
252
|
+
className={cn(
|
|
253
|
+
// -mx-1 px-1 / -my-1 py-1 give the 2px-outset focus ring room
|
|
254
|
+
// to draw without being clipped by overflow-x-auto. The
|
|
255
|
+
// max-width allows for the negative margins: fit-content
|
|
256
|
+
// parents size against the margin box (8px narrower than the
|
|
257
|
+
// border box), so a plain max-w-full would clamp the list 8px
|
|
258
|
+
// too small and clip the first/last tab's ring.
|
|
259
|
+
'relative flex items-center select-none overflow-x-auto max-w-[calc(100%_+_8px)] scrollbar-hide -mx-1 px-1 -my-1 py-1',
|
|
260
|
+
className,
|
|
261
|
+
)}
|
|
262
|
+
{...props}
|
|
263
|
+
>
|
|
264
|
+
{selectedRect && (
|
|
265
|
+
<GooIndicator
|
|
266
|
+
rect={selectedRect}
|
|
267
|
+
className={cn('bg-active', shape.bg)}
|
|
268
|
+
opacity={isHovering ? 0.8 : 1}
|
|
269
|
+
/>
|
|
270
|
+
)}
|
|
271
|
+
|
|
272
|
+
{/* the hover pill rests on the selected tab, so it drips back into it as it fades */}
|
|
273
|
+
{selectedRect && (
|
|
274
|
+
<GooIndicator
|
|
275
|
+
rect={isHovering && hoverRect ? hoverRect : selectedRect}
|
|
276
|
+
className={cn('bg-active', shape.bg)}
|
|
277
|
+
transition={spring.fast}
|
|
278
|
+
opacity={isHovering ? 0.4 : 0}
|
|
279
|
+
/>
|
|
280
|
+
)}
|
|
281
|
+
|
|
282
|
+
{/* Focus ring */}
|
|
283
|
+
<AnimatePresence>
|
|
284
|
+
{focusRect && (
|
|
285
|
+
<motion.div
|
|
286
|
+
className={cn('absolute pointer-events-none z-20 border border-focus-ring', shape.focusRing)}
|
|
287
|
+
initial={false}
|
|
288
|
+
animate={{
|
|
289
|
+
left: focusRect.left - 2,
|
|
290
|
+
top: focusRect.top - 2,
|
|
291
|
+
width: focusRect.width + 4,
|
|
292
|
+
height: focusRect.height + 4,
|
|
293
|
+
}}
|
|
294
|
+
exit={{ opacity: 0, transition: spring.fast.exit }}
|
|
295
|
+
transition={{
|
|
296
|
+
...spring.fast,
|
|
297
|
+
opacity: { duration: spring.fast.duration },
|
|
298
|
+
}}
|
|
299
|
+
/>
|
|
300
|
+
)}
|
|
301
|
+
</AnimatePresence>
|
|
302
|
+
|
|
303
|
+
{tabs}
|
|
304
|
+
</Tabs.List>
|
|
305
|
+
}
|
|
306
|
+
/>
|
|
307
|
+
{panels}
|
|
308
|
+
</TabsSubtleContext.Provider>
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
// A size prop pins every tab to one ladder step.
|
|
312
|
+
return size ? <SizeProvider size={size}>{root}</SizeProvider> : root
|
|
313
|
+
},
|
|
314
|
+
) as TabsSubtleComponent
|
|
315
|
+
|
|
316
|
+
TabsSubtle.displayName = 'TabsSubtle'
|
|
317
|
+
|
|
318
|
+
interface TabsSubtleItemProps extends HTMLAttributes<HTMLButtonElement> {
|
|
319
|
+
/** Icon displayed in the tab. */
|
|
320
|
+
icon?: IconComponent
|
|
321
|
+
/** Text label for the tab. */
|
|
322
|
+
label: string
|
|
323
|
+
/** Position index within the tab list. */
|
|
324
|
+
index: number
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const TabsSubtleItem = forwardRef<HTMLButtonElement, TabsSubtleItemProps>(
|
|
328
|
+
({ icon: Icon, label, index, className, ...props }, ref) => {
|
|
329
|
+
const internalRef = useRef<HTMLButtonElement | null>(null)
|
|
330
|
+
// The collapsing label animates to a MEASURED layout width, not "auto":
|
|
331
|
+
// framer resolves an "auto" target from the element's *visual*
|
|
332
|
+
// (transformed) size, so under a scaled ancestor (e.g. /demo's card) the
|
|
333
|
+
// spring overshoots to scale-x the real width and snaps when "auto"
|
|
334
|
+
// lands. offsetWidth and ResizeObserver are transform-immune — same
|
|
335
|
+
// setup as the accordions' height animation.
|
|
336
|
+
const [labelWidth, setLabelWidth] = useState<number | null>(null)
|
|
337
|
+
const labelRoRef = useRef<ResizeObserver | null>(null)
|
|
338
|
+
const measureLabel = useCallback((el: HTMLSpanElement | null) => {
|
|
339
|
+
labelRoRef.current?.disconnect()
|
|
340
|
+
labelRoRef.current = null
|
|
341
|
+
if (!el) return
|
|
342
|
+
const update = () => setLabelWidth(el.offsetWidth)
|
|
343
|
+
update()
|
|
344
|
+
labelRoRef.current = new ResizeObserver(update)
|
|
345
|
+
labelRoRef.current.observe(el)
|
|
346
|
+
}, [])
|
|
347
|
+
const shape = useShape()
|
|
348
|
+
const sizeClasses = useSize()
|
|
349
|
+
const { registerTab, hoveredIndex, selectedIndex, idPrefix, activeLabel } = useTabsSubtle()
|
|
350
|
+
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
registerTab(index, internalRef.current)
|
|
353
|
+
return () => registerTab(index, null)
|
|
354
|
+
}, [index, registerTab])
|
|
355
|
+
|
|
356
|
+
const isSelected = selectedIndex === index
|
|
357
|
+
const isActive = hoveredIndex === index || isSelected
|
|
358
|
+
const collapseLabel = activeLabel && !!Icon
|
|
359
|
+
const showLabel = !collapseLabel || isSelected
|
|
360
|
+
|
|
361
|
+
const labelContent = (
|
|
362
|
+
// Both stacked spans carry the text-box trim so the invisible bold
|
|
363
|
+
// sizer and the visible label keep identical boxes.
|
|
364
|
+
<span ref={measureLabel} className={cn('inline-grid whitespace-nowrap', sizeClasses.text)}>
|
|
365
|
+
<span
|
|
366
|
+
className="col-start-1 row-start-1 invisible [text-box:trim-both_cap_alphabetic] weight-semibold"
|
|
367
|
+
aria-hidden="true"
|
|
368
|
+
>
|
|
369
|
+
{label}
|
|
370
|
+
</span>
|
|
371
|
+
<span
|
|
372
|
+
className={cn(
|
|
373
|
+
'col-start-1 row-start-1 transition-[color,font-variation-settings] duration-fast [text-box:trim-both_cap_alphabetic]',
|
|
374
|
+
isActive ? 'text-foreground' : 'text-muted-foreground',
|
|
375
|
+
isSelected ? 'weight-semibold' : 'weight-normal',
|
|
376
|
+
)}
|
|
377
|
+
>
|
|
378
|
+
{label}
|
|
379
|
+
</span>
|
|
380
|
+
</span>
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
// Base UI Tab renders a native <button type="button"> and wires
|
|
385
|
+
// role="tab", aria-selected, roving tabindex, and activation for us.
|
|
386
|
+
// id/aria-controls are only overridden when an idPrefix is supplied so
|
|
387
|
+
// externally rendered TabsSubtlePanel elements stay linked.
|
|
388
|
+
<Tabs.Tab
|
|
389
|
+
ref={(node: HTMLElement | null) => {
|
|
390
|
+
const button = node as HTMLButtonElement | null
|
|
391
|
+
internalRef.current = button
|
|
392
|
+
if (typeof ref === 'function') ref(button)
|
|
393
|
+
else if (ref) (ref as React.MutableRefObject<HTMLButtonElement | null>).current = button
|
|
394
|
+
}}
|
|
395
|
+
value={index}
|
|
396
|
+
data-fluid-hover-index={index}
|
|
397
|
+
id={idPrefix ? `${idPrefix}-tab-${index}` : undefined}
|
|
398
|
+
aria-controls={idPrefix ? `${idPrefix}-panel-${index}` : undefined}
|
|
399
|
+
aria-label={collapseLabel && !showLabel ? label : undefined}
|
|
400
|
+
className={cn(
|
|
401
|
+
// Fixed heights (was py-2 around a 19.5px line box ≈ 35.5px) so the
|
|
402
|
+
// text-box trim on the label doesn't shrink the tab. Standalone
|
|
403
|
+
// pills sit directly on the ladder's control height.
|
|
404
|
+
'relative z-10 flex items-center cursor-pointer bg-transparent border-none outline-none',
|
|
405
|
+
sizeClasses.control,
|
|
406
|
+
sizeClasses.px,
|
|
407
|
+
!collapseLabel && sizeClasses.gap,
|
|
408
|
+
shape.bg,
|
|
409
|
+
className,
|
|
410
|
+
)}
|
|
411
|
+
{...props}
|
|
412
|
+
>
|
|
413
|
+
{Icon && (
|
|
414
|
+
<Icon
|
|
415
|
+
size={sizeClasses.icon}
|
|
416
|
+
strokeWidth={isActive ? 2 : 1.5}
|
|
417
|
+
className={cn(
|
|
418
|
+
'shrink-0 transition-[color,stroke-width] duration-fast',
|
|
419
|
+
isActive ? 'text-foreground' : 'text-muted-foreground',
|
|
420
|
+
)}
|
|
421
|
+
/>
|
|
422
|
+
)}
|
|
423
|
+
{collapseLabel ? (
|
|
424
|
+
<AnimatePresence initial={false}>
|
|
425
|
+
{showLabel && (
|
|
426
|
+
<motion.span
|
|
427
|
+
key="label"
|
|
428
|
+
className="overflow-hidden"
|
|
429
|
+
// Until the measurement lands, let CSS resolve the width
|
|
430
|
+
// instead of handing framer "auto": framer resolves an "auto"
|
|
431
|
+
// target from the element's *visual* size, so under a scaled
|
|
432
|
+
// ancestor (the /demo card, ~1.76x) it writes back a layout
|
|
433
|
+
// width that much too wide, then springs back down when the
|
|
434
|
+
// measured value arrives — the selected tab visibly pulses on
|
|
435
|
+
// arrival. Plain CSS auto is the true layout width, and the
|
|
436
|
+
// measured number that follows matches it exactly.
|
|
437
|
+
style={labelWidth == null ? { width: 'auto' } : undefined}
|
|
438
|
+
initial={{ width: 0, opacity: 0, marginLeft: 0 }}
|
|
439
|
+
animate={{
|
|
440
|
+
...(labelWidth != null ? { width: labelWidth } : null),
|
|
441
|
+
opacity: 1,
|
|
442
|
+
// Matches the ladder's icon-to-label gap (gap-2 / gap-1.5).
|
|
443
|
+
marginLeft: sizeClasses.variant === 'compact' ? 6 : 8,
|
|
444
|
+
}}
|
|
445
|
+
exit={{ width: 0, opacity: 0, marginLeft: 0 }}
|
|
446
|
+
transition={{
|
|
447
|
+
...spring.fast,
|
|
448
|
+
opacity: { duration: spring.fast.exit.duration },
|
|
449
|
+
}}
|
|
450
|
+
>
|
|
451
|
+
{labelContent}
|
|
452
|
+
</motion.span>
|
|
453
|
+
)}
|
|
454
|
+
</AnimatePresence>
|
|
455
|
+
) : (
|
|
456
|
+
labelContent
|
|
457
|
+
)}
|
|
458
|
+
</Tabs.Tab>
|
|
459
|
+
)
|
|
460
|
+
},
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
TabsSubtleItem.displayName = 'TabsSubtleItem'
|
|
464
|
+
|
|
465
|
+
interface TabsSubtlePanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
466
|
+
/** Index of this panel; rendered only when it matches the selected tab. */
|
|
467
|
+
index: number
|
|
468
|
+
/** Currently selected tab index. Defaults to the enclosing TabsSubtle's selection. */
|
|
469
|
+
selectedIndex?: number
|
|
470
|
+
/** Must match the TabsSubtle idPrefix. Defaults to the enclosing TabsSubtle's prefix. */
|
|
471
|
+
idPrefix?: string
|
|
472
|
+
/** Panel content, only rendered when selected. */
|
|
473
|
+
children: ReactNode
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Written either inside <TabsSubtle> (the root lifts it out of the tab list
|
|
477
|
+
// and it reads the selection from context) or outside it, next to the tabs
|
|
478
|
+
// (then it takes `selectedIndex` and `idPrefix` as props, as upstream). Either
|
|
479
|
+
// way it cannot use Base UI's Tabs.Panel — which needs the Tabs.Root context,
|
|
480
|
+
// and that root is merged into the tab list — so it stays a plain tabpanel
|
|
481
|
+
// linked to its tab through the shared idPrefix.
|
|
482
|
+
const TabsSubtlePanel = forwardRef<HTMLDivElement, TabsSubtlePanelProps>(
|
|
483
|
+
({ index, selectedIndex, idPrefix, children, className, ...props }, ref) => {
|
|
484
|
+
const ctx = useContext(TabsSubtleContext)
|
|
485
|
+
const resolvedSelectedIndex = selectedIndex ?? ctx?.selectedIndex ?? -1
|
|
486
|
+
const resolvedIdPrefix = idPrefix ?? ctx?.idPrefix
|
|
487
|
+
const isSelected = resolvedSelectedIndex === index
|
|
488
|
+
|
|
489
|
+
return (
|
|
490
|
+
<div
|
|
491
|
+
ref={ref}
|
|
492
|
+
id={resolvedIdPrefix ? `${resolvedIdPrefix}-panel-${index}` : undefined}
|
|
493
|
+
role="tabpanel"
|
|
494
|
+
aria-labelledby={resolvedIdPrefix ? `${resolvedIdPrefix}-tab-${index}` : undefined}
|
|
495
|
+
hidden={!isSelected}
|
|
496
|
+
tabIndex={-1}
|
|
497
|
+
className={cn('outline-none', className)}
|
|
498
|
+
{...props}
|
|
499
|
+
>
|
|
500
|
+
{isSelected && children}
|
|
501
|
+
</div>
|
|
502
|
+
)
|
|
503
|
+
},
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
TabsSubtlePanel.displayName = 'TabsSubtlePanel'
|
|
507
|
+
|
|
508
|
+
Object.assign(TabsSubtle, { Item: TabsSubtleItem, Panel: TabsSubtlePanel })
|
|
509
|
+
|
|
510
|
+
export type { TabsSubtleItemProps, TabsSubtlePanelProps, TabsSubtleProps }
|
|
511
|
+
export { TabsSubtle, TabsSubtleItem, TabsSubtlePanel }
|
|
512
|
+
export default TabsSubtle
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import AlertDialog from './index'
|
|
2
|
+
import Button from '../../button'
|
|
3
|
+
import Toast, { toast } from '../toast'
|
|
4
|
+
import Dialog from '../dialog'
|
|
5
|
+
import Tooltip from '../tooltip'
|
|
6
|
+
import PendingDemo from './pending-demo'
|
|
7
|
+
|
|
8
|
+
# Confirm a destructive step
|
|
9
|
+
|
|
10
|
+
A press outside does nothing; the answer is Cancel, Delete or Escape.
|
|
11
|
+
|
|
12
|
+
<AlertDialog>
|
|
13
|
+
<AlertDialog.Trigger render={<Button variant="secondary">Delete project</Button>} />
|
|
14
|
+
<AlertDialog.Content>
|
|
15
|
+
<AlertDialog.Header>
|
|
16
|
+
<AlertDialog.Title>Delete this project?</AlertDialog.Title>
|
|
17
|
+
<AlertDialog.Description>Its pages and history go with it. This can't be undone.</AlertDialog.Description>
|
|
18
|
+
</AlertDialog.Header>
|
|
19
|
+
<AlertDialog.Footer>
|
|
20
|
+
<AlertDialog.Close render={<Button variant="ghost">Cancel</Button>} />
|
|
21
|
+
<AlertDialog.Close render={<Button>Delete</Button>} />
|
|
22
|
+
</AlertDialog.Footer>
|
|
23
|
+
</AlertDialog.Content>
|
|
24
|
+
</AlertDialog>
|
|
25
|
+
|
|
26
|
+
# Wait for the confirm
|
|
27
|
+
|
|
28
|
+
`AlertDialog.Action`'s `onClick` returns a promise: the button folds into a spinner, Cancel greys out and Escape does nothing until it settles, then the dialog closes.
|
|
29
|
+
|
|
30
|
+
<AlertDialog>
|
|
31
|
+
<AlertDialog.Trigger render={<Button variant="secondary">Archive workspace</Button>} />
|
|
32
|
+
<AlertDialog.Content>
|
|
33
|
+
<AlertDialog.Header>
|
|
34
|
+
<AlertDialog.Title>Archive this workspace?</AlertDialog.Title>
|
|
35
|
+
<AlertDialog.Description>Members lose access until it is restored.</AlertDialog.Description>
|
|
36
|
+
</AlertDialog.Header>
|
|
37
|
+
<AlertDialog.Footer>
|
|
38
|
+
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
|
|
39
|
+
<AlertDialog.Action onClick={() => new Promise(resolve => setTimeout(resolve, 1600))}>Archive</AlertDialog.Action>
|
|
40
|
+
</AlertDialog.Footer>
|
|
41
|
+
</AlertDialog.Content>
|
|
42
|
+
</AlertDialog>
|
|
43
|
+
|
|
44
|
+
# With a mutation's pending
|
|
45
|
+
|
|
46
|
+
`pending` takes a mutation's `isPending`. A plain `onClick={() => mutate()}` then keeps the dialog open and locked while it runs, and closes it once `pending` falls back to false.
|
|
47
|
+
|
|
48
|
+
<PendingDemo />
|
|
49
|
+
|
|
50
|
+
# When it fails
|
|
51
|
+
|
|
52
|
+
A rejected promise keeps the dialog open and hands the reason to `onError` — here a toast — so the person can try again or cancel.
|
|
53
|
+
|
|
54
|
+
<Toast manager={toast} position="bottom-right">
|
|
55
|
+
<AlertDialog>
|
|
56
|
+
<AlertDialog.Trigger render={<Button variant="secondary">Delete account</Button>} />
|
|
57
|
+
<AlertDialog.Content>
|
|
58
|
+
<AlertDialog.Header>
|
|
59
|
+
<AlertDialog.Title>Delete your account?</AlertDialog.Title>
|
|
60
|
+
<AlertDialog.Description>Everything you made goes with it.</AlertDialog.Description>
|
|
61
|
+
</AlertDialog.Header>
|
|
62
|
+
<AlertDialog.Footer>
|
|
63
|
+
<AlertDialog.Cancel>Keep it</AlertDialog.Cancel>
|
|
64
|
+
<AlertDialog.Action
|
|
65
|
+
variant="destructive"
|
|
66
|
+
onClick={() => new Promise((resolve, reject) => setTimeout(() => reject(new Error('The server is busy')), 1400))}
|
|
67
|
+
onError={error => toast.error({ title: 'Could not delete', description: error.message })}
|
|
68
|
+
>
|
|
69
|
+
Delete
|
|
70
|
+
</AlertDialog.Action>
|
|
71
|
+
</AlertDialog.Footer>
|
|
72
|
+
</AlertDialog.Content>
|
|
73
|
+
</AlertDialog>
|
|
74
|
+
</Toast>
|
|
75
|
+
|
|
76
|
+
# From the pointer
|
|
77
|
+
|
|
78
|
+
The same morph options as Dialog: here a circle grows from where you pressed.
|
|
79
|
+
|
|
80
|
+
<AlertDialog>
|
|
81
|
+
<AlertDialog.Trigger render={<Button variant="secondary">Sign out</Button>} />
|
|
82
|
+
<AlertDialog.Content from="pointer" effect="morph">
|
|
83
|
+
<AlertDialog.Header>
|
|
84
|
+
<AlertDialog.Title>Sign out?</AlertDialog.Title>
|
|
85
|
+
<AlertDialog.Description>You'll need to sign in again on this device.</AlertDialog.Description>
|
|
86
|
+
</AlertDialog.Header>
|
|
87
|
+
<AlertDialog.Footer>
|
|
88
|
+
<AlertDialog.Close render={<Button variant="ghost">Stay</Button>} />
|
|
89
|
+
<AlertDialog.Close render={<Button>Sign out</Button>} />
|
|
90
|
+
</AlertDialog.Footer>
|
|
91
|
+
</AlertDialog.Content>
|
|
92
|
+
</AlertDialog>
|
|
93
|
+
|
|
94
|
+
# Inside a dialog
|
|
95
|
+
|
|
96
|
+
Opened from a dialog, the alert lifts above it, and so does the tooltip on the button that opens it.
|
|
97
|
+
|
|
98
|
+
<Dialog>
|
|
99
|
+
<Dialog.Trigger render={<Button variant="secondary">Project settings</Button>} />
|
|
100
|
+
<Dialog.Content>
|
|
101
|
+
<Dialog.Header>
|
|
102
|
+
<Dialog.Title>Project settings</Dialog.Title>
|
|
103
|
+
<Dialog.Description>Rename, transfer or delete this project.</Dialog.Description>
|
|
104
|
+
</Dialog.Header>
|
|
105
|
+
<Dialog.Footer>
|
|
106
|
+
<AlertDialog>
|
|
107
|
+
<Tooltip content="Removes it for everyone">
|
|
108
|
+
<AlertDialog.Trigger render={<Button variant="destructive">Delete project</Button>} />
|
|
109
|
+
</Tooltip>
|
|
110
|
+
<AlertDialog.Content>
|
|
111
|
+
<AlertDialog.Header>
|
|
112
|
+
<AlertDialog.Title>Delete this project?</AlertDialog.Title>
|
|
113
|
+
<AlertDialog.Description>Its pages and history go with it.</AlertDialog.Description>
|
|
114
|
+
</AlertDialog.Header>
|
|
115
|
+
<AlertDialog.Footer>
|
|
116
|
+
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
|
|
117
|
+
<AlertDialog.Action variant="destructive" onClick={() => new Promise(resolve => setTimeout(resolve, 1200))}>Delete</AlertDialog.Action>
|
|
118
|
+
</AlertDialog.Footer>
|
|
119
|
+
</AlertDialog.Content>
|
|
120
|
+
</AlertDialog>
|
|
121
|
+
</Dialog.Footer>
|
|
122
|
+
</Dialog.Content>
|
|
123
|
+
</Dialog>
|