@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,48 @@
|
|
|
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/blocks/sidebar-app/search-field.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
|
|
5
|
+
* MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
|
|
6
|
+
* Local modifications:
|
|
7
|
+
* - `"use client"` dropped; `@/components/ui/{sidebar,dropdown}` → `../../components/*`,
|
|
8
|
+
* `@/lib/sidebar-menu-grid` → `./menu-grid`, `@/lib/*` → `../../lib/*`.
|
|
9
|
+
* - Styling reads DS tokens (AGENTS.md styling): `text-[11px]` → `text-micro`;
|
|
10
|
+
* `duration-80` → `duration-fast`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { type ComponentProps } from 'react'
|
|
14
|
+
import { SidebarInput } from '../../components/navigation/sidebar'
|
|
15
|
+
import { useIcon } from '../../lib/icon-context'
|
|
16
|
+
import { useSize } from '../../lib/size-context'
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// The header's search field, on the menu rows' own rhythm. The leading icon
|
|
20
|
+
// sits on the rows' 16px leading axis; pl-8 starts the text on the rows'
|
|
21
|
+
// 32px text axis; and the shortcut chip waits at the trailing edge, revealed
|
|
22
|
+
// on hover/focus — the placeholder owns the field at rest.
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export interface SidebarSearchFieldProps extends Omit<ComponentProps<typeof SidebarInput>, 'className'> {
|
|
26
|
+
/** Keystroke shown in the trailing chip. Pass null to drop the chip. */
|
|
27
|
+
shortcut?: string | null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function SidebarSearchField({ placeholder = 'Search…', shortcut = '⌘K', ...props }: SidebarSearchFieldProps) {
|
|
31
|
+
const iconSize = useSize().icon
|
|
32
|
+
const SearchIcon = useIcon('search')
|
|
33
|
+
return (
|
|
34
|
+
<div className="group/search relative">
|
|
35
|
+
<SearchIcon
|
|
36
|
+
size={iconSize}
|
|
37
|
+
strokeWidth={1.5}
|
|
38
|
+
className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground"
|
|
39
|
+
/>
|
|
40
|
+
<SidebarInput placeholder={placeholder} aria-label="Search" className="pl-8 pr-12" {...props} />
|
|
41
|
+
{shortcut && (
|
|
42
|
+
<kbd className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 font-sans text-micro text-muted-foreground opacity-0 transition-opacity duration-fast group-hover/search:opacity-100 group-focus-within/search:opacity-100">
|
|
43
|
+
{shortcut}
|
|
44
|
+
</kbd>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
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/blocks/sidebar-user-footer.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
|
|
5
|
+
* MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
|
|
6
|
+
* Local modifications:
|
|
7
|
+
* - `"use client"` dropped; `@/components/ui/{sidebar,dropdown}` → `../../components/*`,
|
|
8
|
+
* `@/lib/sidebar-menu-grid` → `./menu-grid`, `@/lib/*` → `../../lib/*`.
|
|
9
|
+
* - Styling reads DS tokens (AGENTS.md styling): `text-[13px]` → `text-body`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { type ReactNode } from 'react'
|
|
13
|
+
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '../../components/navigation/sidebar'
|
|
14
|
+
import { DropdownContent, DropdownMenu, DropdownTrigger } from '../../components/overlays/dropdown'
|
|
15
|
+
import { useIcon } from '../../lib/icon-context'
|
|
16
|
+
import { useSize } from '../../lib/size-context'
|
|
17
|
+
import { cn } from '../../lib/utils'
|
|
18
|
+
import { SIDEBAR_MENU_POPUP } from './menu-grid'
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Footer user row: identity anchors the sidebar's outer edge. The 20px
|
|
22
|
+
// avatar pulls -ml-0.5 to centre on the rows' leading icon axis; the
|
|
23
|
+
// chevron rides a 24px slot pulled -mr-0.5 onto the trailing action axis —
|
|
24
|
+
// the same axes every other sidebar row uses. Its menu opens upward on the
|
|
25
|
+
// shared sidebar menu grid, so labels and trailing glyphs line up with the
|
|
26
|
+
// trigger row exactly.
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
export interface SidebarUserFooterProps {
|
|
30
|
+
/** Display name shown in the row. */
|
|
31
|
+
name: ReactNode
|
|
32
|
+
/** 20px avatar — e.g. <img className="size-5 rounded-full" …/>. The row
|
|
33
|
+
* positions it on the leading icon axis; the element owns its look. */
|
|
34
|
+
avatar: ReactNode
|
|
35
|
+
/** Dropdown content (MenuItem rows). */
|
|
36
|
+
menu: ReactNode
|
|
37
|
+
/** Extra classes for the wrapping SidebarMenu — pass "min-w-0 flex-1" when
|
|
38
|
+
* the row shares a horizontal footer line with icon buttons. */
|
|
39
|
+
className?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function SidebarUserFooter({ name, avatar, menu, className }: SidebarUserFooterProps) {
|
|
43
|
+
const iconSize = useSize().icon
|
|
44
|
+
const ChevronsUpDown = useIcon('chevrons-up-down')
|
|
45
|
+
return (
|
|
46
|
+
<SidebarMenu aria-label="User" className={cn(className)}>
|
|
47
|
+
<SidebarMenuItem>
|
|
48
|
+
<DropdownMenu>
|
|
49
|
+
<DropdownTrigger
|
|
50
|
+
render={
|
|
51
|
+
<SidebarMenuButton aria-label="Open user menu">
|
|
52
|
+
<span className="-ml-0.5 -mr-0.5 flex size-5 shrink-0 items-center justify-center">{avatar}</span>
|
|
53
|
+
<span className="min-w-0 truncate text-body text-foreground">{name}</span>
|
|
54
|
+
<span className="ml-auto -mr-0.5 flex size-6 shrink-0 items-center justify-center">
|
|
55
|
+
<ChevronsUpDown size={iconSize} strokeWidth={1.5} className="text-muted-foreground" />
|
|
56
|
+
</span>
|
|
57
|
+
</SidebarMenuButton>
|
|
58
|
+
}
|
|
59
|
+
/>
|
|
60
|
+
<DropdownContent className={SIDEBAR_MENU_POPUP} side="top" align="start" sideOffset={6}>
|
|
61
|
+
{menu}
|
|
62
|
+
</DropdownContent>
|
|
63
|
+
</DropdownMenu>
|
|
64
|
+
</SidebarMenuItem>
|
|
65
|
+
</SidebarMenu>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
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/blocks/sidebar-workspace-header.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
|
|
5
|
+
* MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
|
|
6
|
+
* Local modifications:
|
|
7
|
+
* - `"use client"` dropped; `@/components/ui/{sidebar,dropdown}` → `../../components/*`,
|
|
8
|
+
* `@/lib/sidebar-menu-grid` → `./menu-grid`, `@/lib/*` → `../../lib/*`.
|
|
9
|
+
* - Styling reads DS tokens (AGENTS.md styling): inline
|
|
10
|
+
* `fontVariationSettings` → `weight-semibold`; `text-[13px]` / `text-[10px]`
|
|
11
|
+
* → `text-body` / `text-micro-compact`; `duration-80` → `duration-fast`.
|
|
12
|
+
* - `defaultCheckedIndex` passed through to the menu, so a switcher keeps its
|
|
13
|
+
* own pick (the Dropdown's uncontrolled selection).
|
|
14
|
+
* - No peek trigger: the sidebar opens on hover in flow (the provider's
|
|
15
|
+
* `openOnHover`), the topbar's trigger stays beside it, so the tile no
|
|
16
|
+
* longer cross-fades with a trigger over the row.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { type ReactNode } from 'react'
|
|
20
|
+
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '../../components/navigation/sidebar'
|
|
21
|
+
import { DropdownContent, DropdownMenu, DropdownTrigger } from '../../components/overlays/dropdown'
|
|
22
|
+
import { useIcon } from '../../lib/icon-context'
|
|
23
|
+
import { useShape } from '../../lib/shape-context'
|
|
24
|
+
import { useSize } from '../../lib/size-context'
|
|
25
|
+
import { cn } from '../../lib/utils'
|
|
26
|
+
import { SIDEBAR_MENU_POPUP } from './menu-grid'
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Workspace brand row for a sidebar header. The row's pl-8 keeps the name on
|
|
30
|
+
// the rows' 32px text axis, the tile in the leading slot.
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
export interface SidebarWorkspaceHeaderProps {
|
|
34
|
+
/** Workspace or product name shown in the row. */
|
|
35
|
+
name: ReactNode
|
|
36
|
+
/** 20px mark for the leading slot — a letter tile (see WorkspaceTile), a
|
|
37
|
+
* logo, an avatar. The header positions and cross-fades it; the mark owns
|
|
38
|
+
* its own colors and rounding. */
|
|
39
|
+
tile: ReactNode
|
|
40
|
+
/** Dropdown content (MenuItem rows). Omit to render a non-interactive logo
|
|
41
|
+
* lockup instead of a workspace switcher. */
|
|
42
|
+
menu?: ReactNode
|
|
43
|
+
/** Index of the checked menu row, forwarded to DropdownContent. */
|
|
44
|
+
checkedIndex?: number
|
|
45
|
+
/** Initially checked menu row, for a switcher that keeps its own pick. */
|
|
46
|
+
defaultCheckedIndex?: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function SidebarWorkspaceHeader({
|
|
50
|
+
name,
|
|
51
|
+
tile,
|
|
52
|
+
menu,
|
|
53
|
+
checkedIndex,
|
|
54
|
+
defaultCheckedIndex,
|
|
55
|
+
}: SidebarWorkspaceHeaderProps) {
|
|
56
|
+
const iconSize = useSize().icon
|
|
57
|
+
const ChevronDown = useIcon('chevron-down')
|
|
58
|
+
|
|
59
|
+
// The tile sits absolutely in the row's leading slot — 20px at left-1.5
|
|
60
|
+
// centres it on the rows' 16px leading icon axis.
|
|
61
|
+
const tileSlot = (
|
|
62
|
+
<span aria-hidden className="pointer-events-none absolute left-1.5 top-1/2 -translate-y-1/2">
|
|
63
|
+
{tile}
|
|
64
|
+
</span>
|
|
65
|
+
)
|
|
66
|
+
const nameSpan = <span className="min-w-0 truncate text-body weight-semibold text-foreground">{name}</span>
|
|
67
|
+
|
|
68
|
+
if (!menu) {
|
|
69
|
+
// Not interactive, so it renders outside SidebarMenu — a menu row would
|
|
70
|
+
// track the traveling hover background.
|
|
71
|
+
return (
|
|
72
|
+
<div className="relative flex h-8 items-center pl-8 pr-2">
|
|
73
|
+
{tileSlot}
|
|
74
|
+
{nameSpan}
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
return (
|
|
79
|
+
// @container: the row hides its dropdown chevron once it gets too narrow
|
|
80
|
+
// to show a useful slice of the name (squeezed by trailing header actions
|
|
81
|
+
// or a mid-drag width) — the text keeps whatever room is left.
|
|
82
|
+
<SidebarMenu aria-label="Workspace" className="@container">
|
|
83
|
+
<SidebarMenuItem>
|
|
84
|
+
<DropdownMenu>
|
|
85
|
+
<DropdownTrigger
|
|
86
|
+
render={
|
|
87
|
+
<SidebarMenuButton aria-label="Switch workspace" className="pl-8">
|
|
88
|
+
{tileSlot}
|
|
89
|
+
{nameSpan}
|
|
90
|
+
<span className="ml-auto inline-flex @max-[7rem]:hidden">
|
|
91
|
+
<ChevronDown size={iconSize} strokeWidth={1.5} className="text-muted-foreground" />
|
|
92
|
+
</span>
|
|
93
|
+
</SidebarMenuButton>
|
|
94
|
+
}
|
|
95
|
+
/>
|
|
96
|
+
{/* Trigger-width popup on the shared sidebar menu grid: items start
|
|
97
|
+
at the row's edge, icon slots land on the leading axis, and the
|
|
98
|
+
check sits on the chevron's vertical axis. */}
|
|
99
|
+
<DropdownContent
|
|
100
|
+
className={SIDEBAR_MENU_POPUP}
|
|
101
|
+
align="start"
|
|
102
|
+
sideOffset={4}
|
|
103
|
+
checkedIndex={checkedIndex}
|
|
104
|
+
defaultCheckedIndex={defaultCheckedIndex}
|
|
105
|
+
>
|
|
106
|
+
{menu}
|
|
107
|
+
</DropdownContent>
|
|
108
|
+
</DropdownMenu>
|
|
109
|
+
</SidebarMenuItem>
|
|
110
|
+
</SidebarMenu>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The 20px letter-tile treatment the switcher's trigger and its menu rows
|
|
115
|
+
* share — squared to the shape system, semibold 10px glyph. */
|
|
116
|
+
export function WorkspaceTile({ children, className }: { children: ReactNode; className?: string }) {
|
|
117
|
+
const shape = useShape()
|
|
118
|
+
return (
|
|
119
|
+
<span
|
|
120
|
+
className={cn(
|
|
121
|
+
'flex size-5 shrink-0 items-center justify-center bg-foreground text-micro-compact weight-semibold text-background',
|
|
122
|
+
shape.bgRadius >= 20 ? 'rounded-full' : 'rounded-md',
|
|
123
|
+
className,
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
{children}
|
|
127
|
+
</span>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import InputMessage from './index'
|
|
2
|
+
import Button from '../../button'
|
|
3
|
+
import { ChevronDown, Plus } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
# Basic
|
|
6
|
+
|
|
7
|
+
No props at all: the composer keeps its own draft, grows as you type and
|
|
8
|
+
clears itself on Enter.
|
|
9
|
+
|
|
10
|
+
<div className="w-full max-w-xl">
|
|
11
|
+
<InputMessage />
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
# Suggestions
|
|
15
|
+
|
|
16
|
+
The ghost placeholder renders with a Tab keycap — Tab fills it into the
|
|
17
|
+
composer. ArrowDown/ArrowUp walk the list below (focus stays in the
|
|
18
|
+
textarea) and Enter or click fills the highlighted prompt; the first row
|
|
19
|
+
shows a ↓ hint until a row is highlighted. Typing collapses the list.
|
|
20
|
+
|
|
21
|
+
<div className="w-full max-w-xl">
|
|
22
|
+
<InputMessage
|
|
23
|
+
placeholderSuggestion="Why is every other input box so stiff?"
|
|
24
|
+
suggestions={[
|
|
25
|
+
'What is Fluid Functionalism about?',
|
|
26
|
+
'How does Micka tune the springs behind these animations?',
|
|
27
|
+
'Install the InputMessage component in my project',
|
|
28
|
+
'Draft a short thank-you note to Micka for the library',
|
|
29
|
+
]}
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
# Attachments
|
|
34
|
+
|
|
35
|
+
`defaultFiles` seeds the composer and turns attachment behavior on: drop a
|
|
36
|
+
PNG, JPEG or PDF anywhere on the panel, or use the render-fn slot's
|
|
37
|
+
`openFilePicker`. Images preview with object-cover, PDFs render their
|
|
38
|
+
first page, anything else falls back to a document glyph — hover a tile
|
|
39
|
+
for its × button.
|
|
40
|
+
|
|
41
|
+
<div className="w-full max-w-xl">
|
|
42
|
+
<InputMessage
|
|
43
|
+
defaultFiles={[
|
|
44
|
+
new File(['<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><rect width="80" height="80" fill="#6B97FF" /></svg>'], 'swatch.svg', { type: 'image/svg+xml' }),
|
|
45
|
+
new File(['Quarterly notes'], 'notes.txt', { type: 'text/plain' }),
|
|
46
|
+
]}
|
|
47
|
+
leftSlot={({ openFilePicker }) => (
|
|
48
|
+
<Button variant="ghost" size="icon-compact" aria-label="Attach" onClick={() => openFilePicker()}>
|
|
49
|
+
<Plus />
|
|
50
|
+
</Button>
|
|
51
|
+
)}
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
# Queued messages
|
|
56
|
+
|
|
57
|
+
`defaultStatus="streaming"` plus `defaultQueue` runs the composer as if a
|
|
58
|
+
response were in flight: the send button is a Queue action, so a typed
|
|
59
|
+
message joins the rows above instead of sending. Drag a row (or Alt+↑/↓)
|
|
60
|
+
to reorder, double-click to pull it back into the composer, × to drop it.
|
|
61
|
+
Clear the draft and the button becomes Stop — pressing it ends the
|
|
62
|
+
response, and that streaming→idle edge dispatches the row at the top.
|
|
63
|
+
|
|
64
|
+
<div className="w-full max-w-xl">
|
|
65
|
+
<InputMessage
|
|
66
|
+
defaultStatus="streaming"
|
|
67
|
+
defaultQueue={[
|
|
68
|
+
{ id: 'q1', text: 'Also add a dark-mode screenshot', files: [] },
|
|
69
|
+
{ id: 'q2', text: 'And link the changelog entry', files: [] },
|
|
70
|
+
]}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
# Left Slot Only
|
|
75
|
+
|
|
76
|
+
`leftSlot` takes the bottom-left action area — an attach button, a model
|
|
77
|
+
switcher, a mode toggle.
|
|
78
|
+
|
|
79
|
+
<div className="w-full max-w-xl">
|
|
80
|
+
<InputMessage
|
|
81
|
+
leftSlot={
|
|
82
|
+
<Button variant="ghost" size="icon-compact" aria-label="Attach">
|
|
83
|
+
<Plus />
|
|
84
|
+
</Button>
|
|
85
|
+
}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
# Right Slot Only
|
|
90
|
+
|
|
91
|
+
`rightSlot` sits in the bottom-right, just before the built-in send
|
|
92
|
+
button.
|
|
93
|
+
|
|
94
|
+
<div className="w-full max-w-xl">
|
|
95
|
+
<InputMessage
|
|
96
|
+
rightSlot={
|
|
97
|
+
<Button variant="ghost" size="compact" trailingIcon={ChevronDown}>
|
|
98
|
+
Sonnet 4.6
|
|
99
|
+
</Button>
|
|
100
|
+
}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
# Send Handler
|
|
105
|
+
|
|
106
|
+
`onSend` receives the trimmed text and the attached files; an uncontrolled
|
|
107
|
+
composer clears its own draft afterwards. The handler here is an empty
|
|
108
|
+
arrow — in an app it is where the message joins the transcript.
|
|
109
|
+
|
|
110
|
+
<div className="w-full max-w-xl">
|
|
111
|
+
<InputMessage
|
|
112
|
+
onSend={() => {}}
|
|
113
|
+
placeholder="Press Enter to send. Shift+Enter for newline."
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
# Disabled
|
|
118
|
+
|
|
119
|
+
Dims the panel to 50% and drops pointer events — textarea, send button and
|
|
120
|
+
drag-and-drop all go inert.
|
|
121
|
+
|
|
122
|
+
<div className="w-full max-w-xl">
|
|
123
|
+
<InputMessage defaultValue="This composer is disabled" disabled />
|
|
124
|
+
</div>
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Ported from Fluid Functionalism — github.com/mickadesign/fluid-functionalism
|
|
3
|
+
* `registry/default/file-thumbnail.tsx` @ b3587bdbd83fc66c2a6aae3817ffb856cb09260b
|
|
4
|
+
* MIT License © 2026 Micka Touillaud — fluidfunctionalism.com (notice: LICENSE.fluid-functionalism)
|
|
5
|
+
* Local modifications:
|
|
6
|
+
* - `"use client"` directive dropped (no RSC here); the `@next/next/no-img-element`
|
|
7
|
+
* eslint-disable comment dropped (no Next.js).
|
|
8
|
+
* - `@/lib/{utils,shape-context}` rewritten to `../../../lib/*`.
|
|
9
|
+
* - The lazy `import("pdfjs-dist")` is kept as-is: verified against modo's own
|
|
10
|
+
* esbuild settings (bundle, `platform: "browser"`, `target: "es2022"`, both
|
|
11
|
+
* with and without `splitting: true`) — it resolves cleanly and lands in a
|
|
12
|
+
* ~790kB chunk that is only fetched when a PDF is actually attached. No CDN
|
|
13
|
+
* fallback was needed. The worker still comes from jsDelivr, so no
|
|
14
|
+
* bundler-side worker config is required.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useEffect, useState } from 'react'
|
|
18
|
+
import { useShape } from '../../../lib/shape-context'
|
|
19
|
+
import { cn } from '../../../lib/utils'
|
|
20
|
+
|
|
21
|
+
// ─── Lazy pdfjs loader ────────────────────────────────────────────────────
|
|
22
|
+
// Imports pdfjs-dist on first PDF, caches the module, and points the worker
|
|
23
|
+
// at the matching CDN build. Consumers don't need bundler-side worker config.
|
|
24
|
+
type PdfjsModule = typeof import('pdfjs-dist')
|
|
25
|
+
let pdfjsPromise: Promise<PdfjsModule> | null = null
|
|
26
|
+
|
|
27
|
+
async function loadPdfjs(): Promise<PdfjsModule> {
|
|
28
|
+
if (!pdfjsPromise) {
|
|
29
|
+
pdfjsPromise = import('pdfjs-dist').then(mod => {
|
|
30
|
+
if (!mod.GlobalWorkerOptions.workerSrc) {
|
|
31
|
+
mod.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${mod.version}/build/pdf.worker.min.mjs`
|
|
32
|
+
}
|
|
33
|
+
return mod
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
return pdfjsPromise
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function renderPdfFirstPage(file: File, targetWidth: number): Promise<string> {
|
|
40
|
+
const pdfjs = await loadPdfjs()
|
|
41
|
+
const buffer = await file.arrayBuffer()
|
|
42
|
+
const pdf = await pdfjs.getDocument({ data: buffer }).promise
|
|
43
|
+
const page = await pdf.getPage(1)
|
|
44
|
+
const baseViewport = page.getViewport({ scale: 1 })
|
|
45
|
+
const scale = (targetWidth * 2) / baseViewport.width // 2× for retina
|
|
46
|
+
const viewport = page.getViewport({ scale })
|
|
47
|
+
const canvas = document.createElement('canvas')
|
|
48
|
+
canvas.width = viewport.width
|
|
49
|
+
canvas.height = viewport.height
|
|
50
|
+
await page.render({ canvas, viewport }).promise
|
|
51
|
+
return canvas.toDataURL('image/png')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── File thumbnail ───────────────────────────────────────────────────────
|
|
55
|
+
// Read-only square preview of a File. Images use object-cover via
|
|
56
|
+
// `URL.createObjectURL`; PDFs render the first page via pdfjs; while either is
|
|
57
|
+
// resolving a spinner is shown. Self-contained (border + surface + sizing) so
|
|
58
|
+
// it can be reused both inside the composer's preview row and to render
|
|
59
|
+
// already-sent attachments in a chat transcript.
|
|
60
|
+
interface FileThumbnailProps {
|
|
61
|
+
file: File
|
|
62
|
+
/** Side length of the square thumbnail in pixels. */
|
|
63
|
+
size: number
|
|
64
|
+
className?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function FileThumbnail({ file, size, className }: FileThumbnailProps) {
|
|
68
|
+
const shape = useShape()
|
|
69
|
+
const isImage = file.type.startsWith('image/')
|
|
70
|
+
const isPdf = file.type === 'application/pdf'
|
|
71
|
+
|
|
72
|
+
// Create blob URL inside an effect (NOT useMemo) so the cleanup-revoke
|
|
73
|
+
// and the URL-creation stay in sync. In React 18 StrictMode dev, a
|
|
74
|
+
// useMemo-created URL gets revoked by the simulated effect-cleanup but
|
|
75
|
+
// useMemo doesn't re-run on the simulated re-mount (no re-render happens),
|
|
76
|
+
// leaving the DOM with a stale, revoked `blob:` URL — broken image.
|
|
77
|
+
// Putting both in the same effect means the simulated re-mount creates a
|
|
78
|
+
// fresh URL and updates state. The one-frame "before URL" state is
|
|
79
|
+
// covered by the bg-accent (no fallback icon shown for images), so the
|
|
80
|
+
// transition is visually clean.
|
|
81
|
+
const [imageUrl, setImageUrl] = useState<string | null>(null)
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!isImage) {
|
|
84
|
+
// Clear stale state if the `file` prop swaps type on the same mount —
|
|
85
|
+
// otherwise a revoked blob URL would keep winning over the new preview.
|
|
86
|
+
setImageUrl(null)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
const url = URL.createObjectURL(file)
|
|
90
|
+
setImageUrl(url)
|
|
91
|
+
return () => URL.revokeObjectURL(url)
|
|
92
|
+
}, [isImage, file])
|
|
93
|
+
|
|
94
|
+
// PDFs need async rendering — loading flash is unavoidable for the first
|
|
95
|
+
// ~100–300ms while pdfjs loads. Falls back to the generic icon on error
|
|
96
|
+
// (corrupt/password-protected file, CDN worker blocked).
|
|
97
|
+
const [pdfUrl, setPdfUrl] = useState<string | null>(null)
|
|
98
|
+
const [pdfError, setPdfError] = useState(false)
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
setPdfError(false)
|
|
101
|
+
if (!isPdf) {
|
|
102
|
+
setPdfUrl(null)
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
let cancelled = false
|
|
106
|
+
renderPdfFirstPage(file, size)
|
|
107
|
+
.then(url => {
|
|
108
|
+
if (!cancelled) setPdfUrl(url)
|
|
109
|
+
})
|
|
110
|
+
.catch(() => {
|
|
111
|
+
if (!cancelled) setPdfError(true)
|
|
112
|
+
})
|
|
113
|
+
return () => {
|
|
114
|
+
cancelled = true
|
|
115
|
+
}
|
|
116
|
+
}, [file, isPdf, size])
|
|
117
|
+
|
|
118
|
+
const previewUrl = imageUrl ?? pdfUrl
|
|
119
|
+
// Spinner only while a preview is genuinely pending; anything that can't
|
|
120
|
+
// produce one (failed PDF, unsupported type) gets the generic icon instead.
|
|
121
|
+
const isPending = (isImage && !imageUrl) || (isPdf && !pdfUrl && !pdfError)
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
className={cn('relative shrink-0 overflow-hidden bg-accent border border-border', shape.bg, className)}
|
|
126
|
+
style={{ width: size, height: size }}
|
|
127
|
+
>
|
|
128
|
+
{previewUrl ? (
|
|
129
|
+
<img src={previewUrl} alt={file.name} className="absolute inset-0 w-full h-full object-cover" />
|
|
130
|
+
) : isPending ? (
|
|
131
|
+
// Circular spinner while we wait for the preview to be ready.
|
|
132
|
+
// Used for both images (brief URL-creation gap) and PDFs (longer
|
|
133
|
+
// pdfjs render). The thin ring is mostly subtle (border-border)
|
|
134
|
+
// with one quadrant accented (border-t-muted-foreground) so the
|
|
135
|
+
// `animate-spin` rotation reads as a moving arc.
|
|
136
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
137
|
+
<div
|
|
138
|
+
className="w-6 h-6 rounded-full border-2 border-border border-t-muted-foreground animate-spin"
|
|
139
|
+
aria-label="Loading preview"
|
|
140
|
+
role="status"
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
) : (
|
|
144
|
+
// Generic document glyph for files with no renderable preview.
|
|
145
|
+
// Inline SVG (not the icon system) so the thumbnail stays
|
|
146
|
+
// self-contained for registry consumers.
|
|
147
|
+
<div
|
|
148
|
+
className="absolute inset-0 flex items-center justify-center text-muted-foreground"
|
|
149
|
+
role="img"
|
|
150
|
+
aria-label={file.name}
|
|
151
|
+
>
|
|
152
|
+
<svg
|
|
153
|
+
width={Math.max(16, size * 0.35)}
|
|
154
|
+
height={Math.max(16, size * 0.35)}
|
|
155
|
+
viewBox="0 0 24 24"
|
|
156
|
+
fill="none"
|
|
157
|
+
stroke="currentColor"
|
|
158
|
+
strokeWidth="1.5"
|
|
159
|
+
strokeLinecap="round"
|
|
160
|
+
strokeLinejoin="round"
|
|
161
|
+
aria-hidden="true"
|
|
162
|
+
>
|
|
163
|
+
<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />
|
|
164
|
+
<path d="M14 3v5h5" />
|
|
165
|
+
</svg>
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export type { FileThumbnailProps }
|
|
173
|
+
export { FileThumbnail, loadPdfjs, renderPdfFirstPage }
|