@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,113 @@
|
|
|
1
|
+
import Field from './index'
|
|
2
|
+
import Input from '../input'
|
|
3
|
+
import Textarea from '../textarea'
|
|
4
|
+
import NumberInput from '../number-input'
|
|
5
|
+
import { Mail } from 'lucide-react'
|
|
6
|
+
|
|
7
|
+
# Field
|
|
8
|
+
|
|
9
|
+
A label above the control, help text below it. The label targets the control and the description is read with it.
|
|
10
|
+
|
|
11
|
+
<Field className="w-72 max-w-full">
|
|
12
|
+
<Field.Label>Email</Field.Label>
|
|
13
|
+
<Input icon={Mail} placeholder="you@example.com" />
|
|
14
|
+
<Field.Description>We only write about your account.</Field.Description>
|
|
15
|
+
</Field>
|
|
16
|
+
|
|
17
|
+
# Error
|
|
18
|
+
|
|
19
|
+
`invalid` turns the label and the box destructive; `Field.Error` shows its message while it has one. Both come from your validation.
|
|
20
|
+
|
|
21
|
+
<Field invalid className="w-72 max-w-full">
|
|
22
|
+
<Field.Label>Email</Field.Label>
|
|
23
|
+
<Input icon={Mail} defaultValue="ada@" />
|
|
24
|
+
<Field.Error>Enter a full address, like ada@example.com.</Field.Error>
|
|
25
|
+
</Field>
|
|
26
|
+
|
|
27
|
+
# Required
|
|
28
|
+
|
|
29
|
+
`required` marks the control required and the label with an asterisk.
|
|
30
|
+
|
|
31
|
+
<Field required name="company" className="w-72 max-w-full">
|
|
32
|
+
<Field.Label>Company</Field.Label>
|
|
33
|
+
<Input placeholder="Acme Inc." />
|
|
34
|
+
</Field>
|
|
35
|
+
|
|
36
|
+
# Native validation
|
|
37
|
+
|
|
38
|
+
With `validationMode`, Base UI checks the control's own rules (`required`, `type="email"`) and the field turns destructive on a failure; an empty `Field.Error` shows the browser's message. Type something that isn't an address and leave the field.
|
|
39
|
+
|
|
40
|
+
<Field required validationMode="onBlur" className="w-72 max-w-full">
|
|
41
|
+
<Field.Label>Work email</Field.Label>
|
|
42
|
+
<Input type="email" icon={Mail} placeholder="you@company.com" />
|
|
43
|
+
<Field.Error />
|
|
44
|
+
</Field>
|
|
45
|
+
|
|
46
|
+
# Disabled and read-only
|
|
47
|
+
|
|
48
|
+
The field hands its state to the control: `disabled` dims everything, `readOnly` keeps the value selectable.
|
|
49
|
+
|
|
50
|
+
<div className="flex flex-col gap-4 w-72 max-w-full">
|
|
51
|
+
<Field disabled>
|
|
52
|
+
<Field.Label>Workspace</Field.Label>
|
|
53
|
+
<Input defaultValue="Design" />
|
|
54
|
+
<Field.Description>Only owners can rename it.</Field.Description>
|
|
55
|
+
</Field>
|
|
56
|
+
<Field readOnly>
|
|
57
|
+
<Field.Label>Invoice number</Field.Label>
|
|
58
|
+
<Input defaultValue="INV-2026-0042" />
|
|
59
|
+
</Field>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
# Any field control
|
|
63
|
+
|
|
64
|
+
Textarea and NumberInput join the same way: the label, description and error wire to them.
|
|
65
|
+
|
|
66
|
+
<div className="flex flex-col gap-4 w-72 max-w-full">
|
|
67
|
+
<Field>
|
|
68
|
+
<Field.Label>Message</Field.Label>
|
|
69
|
+
<Textarea placeholder="Write your message" minRows={2} />
|
|
70
|
+
</Field>
|
|
71
|
+
<Field invalid>
|
|
72
|
+
<Field.Label>Seats</Field.Label>
|
|
73
|
+
<NumberInput defaultValue={0} min={0} />
|
|
74
|
+
<Field.Error>Book at least one seat.</Field.Error>
|
|
75
|
+
</Field>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
# Field.Control
|
|
79
|
+
|
|
80
|
+
A control that isn't a field control gets the field's id, `aria-describedby` and `aria-invalid` through `Field.Control`, here a native select.
|
|
81
|
+
|
|
82
|
+
<Field className="w-72 max-w-full">
|
|
83
|
+
<Field.Label>Country</Field.Label>
|
|
84
|
+
<Field.Control
|
|
85
|
+
render={
|
|
86
|
+
<select className="h-9 w-full bg-transparent px-1.5 text-body text-foreground">
|
|
87
|
+
<option>Slovakia</option>
|
|
88
|
+
<option>Czechia</option>
|
|
89
|
+
<option>Austria</option>
|
|
90
|
+
</select>
|
|
91
|
+
}
|
|
92
|
+
/>
|
|
93
|
+
<Field.Description>Where the invoice is sent.</Field.Description>
|
|
94
|
+
</Field>
|
|
95
|
+
|
|
96
|
+
# Compact
|
|
97
|
+
|
|
98
|
+
`size` pins the field and its control to the compact step.
|
|
99
|
+
|
|
100
|
+
<Field size="compact" className="w-64 max-w-full">
|
|
101
|
+
<Field.Label>Filter</Field.Label>
|
|
102
|
+
<Input placeholder="Name contains…" />
|
|
103
|
+
<Field.Description>Matches any part of the name.</Field.Description>
|
|
104
|
+
</Field>
|
|
105
|
+
|
|
106
|
+
# Hidden label
|
|
107
|
+
|
|
108
|
+
`visuallyHidden` keeps the label for assistive tech where the placeholder carries the meaning.
|
|
109
|
+
|
|
110
|
+
<Field className="w-72 max-w-full">
|
|
111
|
+
<Field.Label visuallyHidden>Search</Field.Label>
|
|
112
|
+
<Input variant="bordered" placeholder="Search people" />
|
|
113
|
+
</Field>
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Local addition (not part of Fluid Functionalism): a labelled form field on
|
|
3
|
+
* Base UI's Field, with no form library of its own. The label, description
|
|
4
|
+
* and error line up with the text inside the field box
|
|
5
|
+
* (`lib/field-variants.ts`), and the field's state reaches any control built
|
|
6
|
+
* on `useFieldBox` through `FieldStateContext` (`lib/field-classes.ts`), the
|
|
7
|
+
* way InputGroup.Field hands it to its control.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Field as BaseField } from '@base-ui/react/field'
|
|
11
|
+
import {
|
|
12
|
+
type ForwardRefExoticComponent,
|
|
13
|
+
forwardRef,
|
|
14
|
+
type HTMLAttributes,
|
|
15
|
+
type ReactElement,
|
|
16
|
+
type ReactNode,
|
|
17
|
+
type RefAttributes,
|
|
18
|
+
useMemo,
|
|
19
|
+
} from 'react'
|
|
20
|
+
import { FIELD_TEXT_INSET, FieldStateContext, useFieldState } from '../../../lib/field-classes'
|
|
21
|
+
import { type SizeVariant, useSize } from '../../../lib/size-context'
|
|
22
|
+
import { slotRender } from '../../../lib/slot'
|
|
23
|
+
import { cn } from '../../../lib/utils'
|
|
24
|
+
import { SizeProvider } from '../../../primitives/sizes'
|
|
25
|
+
import { Label } from '../input'
|
|
26
|
+
|
|
27
|
+
/** When native validity is checked. */
|
|
28
|
+
type FieldValidationMode = 'onSubmit' | 'onBlur' | 'onChange'
|
|
29
|
+
|
|
30
|
+
interface FieldProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
31
|
+
/** The field's parts: `Field.Label`, a control, `Field.Description`, `Field.Error`. */
|
|
32
|
+
children?: ReactNode
|
|
33
|
+
/** Marks the value invalid: the label and the control turn destructive and the control sets `aria-invalid`. Set it from your validation (a form library's field state). Defaults to `false`. */
|
|
34
|
+
invalid?: boolean
|
|
35
|
+
/** Disables the control and dims the field. Defaults to `false`. */
|
|
36
|
+
disabled?: boolean
|
|
37
|
+
/** Shows the value without letting it change (Input, NumberInput and FileDropzone read it). Defaults to `false`. */
|
|
38
|
+
readOnly?: boolean
|
|
39
|
+
/** Marks the control required and the label with an asterisk. Defaults to `false`. */
|
|
40
|
+
required?: boolean
|
|
41
|
+
/** Names the value when the form is submitted; wins over the control's own `name`. */
|
|
42
|
+
name?: string
|
|
43
|
+
/** Whether the value differs from its initial one, for a form library that tracks it (sets `data-dirty`). */
|
|
44
|
+
dirty?: boolean
|
|
45
|
+
/** Whether the control has been visited, for a form library that tracks it (sets `data-touched`). */
|
|
46
|
+
touched?: boolean
|
|
47
|
+
/** When Base UI checks the control's native validity (`required`, `pattern`, `type`): on form submit, on blur, or on every change. A failure turns the field destructive and an empty `Field.Error` shows the browser's message. Defaults to `"onSubmit"`. */
|
|
48
|
+
validationMode?: FieldValidationMode
|
|
49
|
+
/** Pins the field and its control to one step of the size ladder (36px default, 28px compact). Defaults to the surrounding SizeProvider. */
|
|
50
|
+
size?: SizeVariant
|
|
51
|
+
/** Classes for the root. */
|
|
52
|
+
className?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface FieldLabelProps {
|
|
56
|
+
/** Label text. */
|
|
57
|
+
children: ReactNode
|
|
58
|
+
/** Keep the label for assistive tech but don't render it, for a field whose placeholder or context carries the meaning. Defaults to `false`. */
|
|
59
|
+
visuallyHidden?: boolean
|
|
60
|
+
/** Classes for the label. */
|
|
61
|
+
className?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function FieldLabel({ children, visuallyHidden, className }: FieldLabelProps) {
|
|
65
|
+
const field = useFieldState()
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<BaseField.Label
|
|
69
|
+
render={
|
|
70
|
+
<Label invalid={field?.invalid} className={cn(visuallyHidden && 'sr-only', className)}>
|
|
71
|
+
{children}
|
|
72
|
+
{field?.required && <span aria-hidden="true"> *</span>}
|
|
73
|
+
</Label>
|
|
74
|
+
}
|
|
75
|
+
/>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface FieldDescriptionProps {
|
|
80
|
+
/** Help text under the control: a format, a limit, what the value is for. */
|
|
81
|
+
children: ReactNode
|
|
82
|
+
/** Classes for the description. */
|
|
83
|
+
className?: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function FieldDescription({ children, className }: FieldDescriptionProps) {
|
|
87
|
+
const size = useSize()
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<BaseField.Description
|
|
91
|
+
className={cn(
|
|
92
|
+
'text-muted-foreground weight-normal data-[disabled]:opacity-50',
|
|
93
|
+
size.variant === 'compact' ? 'text-caption-compact' : 'text-caption',
|
|
94
|
+
FIELD_TEXT_INSET[size.variant],
|
|
95
|
+
className,
|
|
96
|
+
)}
|
|
97
|
+
>
|
|
98
|
+
{children}
|
|
99
|
+
</BaseField.Description>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Which native validity state shows the message (`"valueMissing"`, `"typeMismatch"`, …). */
|
|
104
|
+
type FieldErrorMatch = boolean | keyof ValidityState
|
|
105
|
+
|
|
106
|
+
interface FieldErrorProps {
|
|
107
|
+
/** The message. While it has one, it shows; empty, it hides. */
|
|
108
|
+
children?: ReactNode
|
|
109
|
+
/** Overrides when it shows: `true` always, `false` never, or a native validity state such as `"valueMissing"`. Defaults to showing while it has a message. */
|
|
110
|
+
match?: FieldErrorMatch
|
|
111
|
+
/** Classes for the message. */
|
|
112
|
+
className?: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function FieldError({ children, match, className }: FieldErrorProps) {
|
|
116
|
+
const size = useSize()
|
|
117
|
+
const hasMessage = children != null && children !== false && children !== ''
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<BaseField.Error
|
|
121
|
+
// Without a message and a match, Base UI shows the native validation message.
|
|
122
|
+
match={match ?? (hasMessage || undefined)}
|
|
123
|
+
className={cn(
|
|
124
|
+
'text-destructive weight-medium',
|
|
125
|
+
size.variant === 'compact' ? 'text-caption-compact' : 'text-caption',
|
|
126
|
+
FIELD_TEXT_INSET[size.variant],
|
|
127
|
+
'transition-opacity duration-fast data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 data-[ending-style]:duration-fast-exit',
|
|
128
|
+
className,
|
|
129
|
+
)}
|
|
130
|
+
// An explicit `children: undefined` would hide Base UI's own message.
|
|
131
|
+
{...(hasMessage ? { children } : {})}
|
|
132
|
+
/>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface FieldControlProps {
|
|
137
|
+
/** The element that becomes the control, e.g. a native `<select>` or a third-party input. */
|
|
138
|
+
render?: ReactElement
|
|
139
|
+
/** Compose onto the single child instead. Defaults to `false`. */
|
|
140
|
+
asChild?: boolean
|
|
141
|
+
/** The control, with `asChild`. */
|
|
142
|
+
children?: ReactNode
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function FieldControl({ render, asChild, children }: FieldControlProps) {
|
|
146
|
+
return <BaseField.Control render={slotRender(render, asChild, children)} />
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface ProvideFieldStateProps {
|
|
150
|
+
invalid: boolean
|
|
151
|
+
disabled: boolean
|
|
152
|
+
readOnly: boolean
|
|
153
|
+
required: boolean
|
|
154
|
+
children?: ReactNode
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function ProvideFieldState({ invalid, disabled, readOnly, required, children }: ProvideFieldStateProps) {
|
|
158
|
+
const state = useMemo(
|
|
159
|
+
() => ({ active: false, invalid, disabled, readOnly, required }),
|
|
160
|
+
[invalid, disabled, readOnly, required],
|
|
161
|
+
)
|
|
162
|
+
return <FieldStateContext.Provider value={state}>{children}</FieldStateContext.Provider>
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
type FieldComponent = ForwardRefExoticComponent<FieldProps & RefAttributes<HTMLDivElement>> & {
|
|
166
|
+
Label: typeof FieldLabel
|
|
167
|
+
Description: typeof FieldDescription
|
|
168
|
+
Error: typeof FieldError
|
|
169
|
+
Control: typeof FieldControl
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* A labelled form field: a label above the control, help text and an error
|
|
174
|
+
* message below it, all linked for assistive tech.
|
|
175
|
+
*
|
|
176
|
+
* Put an Input, Textarea, NumberInput, FileDropzone, Select or any other
|
|
177
|
+
* field control inside and it joins on its own: the label targets it, the
|
|
178
|
+
* description and error are read with it, and the field hands it its
|
|
179
|
+
* `invalid`, `disabled`, `readOnly` and `required` state, so the box turns
|
|
180
|
+
* destructive with the label. The label and messages sit inset to line up
|
|
181
|
+
* with the text in the box. The field holds no validation of its own:
|
|
182
|
+
* `invalid` and the error's message come from yours (a form library's field
|
|
183
|
+
* state, a server response), and `Field.Error` shows while it has a message.
|
|
184
|
+
* A native failure (`required`, `pattern`, `type="email"`) caught by Base UI's
|
|
185
|
+
* validation turns it destructive too, and an empty `Field.Error` then shows
|
|
186
|
+
* the browser's message.
|
|
187
|
+
* Behavior comes from Base UI's Field.
|
|
188
|
+
*
|
|
189
|
+
* `size` pins the field and its control to one step of the size ladder;
|
|
190
|
+
* without it they follow the surrounding SizeProvider, which is how to set a
|
|
191
|
+
* form's density when a control's own props keep the native `<input size>`
|
|
192
|
+
* attribute (a character width, not a height). `Field.Control` passes a
|
|
193
|
+
* native `size` through to the element untouched.
|
|
194
|
+
*
|
|
195
|
+
* InputGroup.Field is the preset for a group of text fields: a label and an
|
|
196
|
+
* error string, and the group's hover gliding from field to field. Reach for
|
|
197
|
+
* Field when a field stands alone, needs a description, or holds a control
|
|
198
|
+
* other than a text input.
|
|
199
|
+
*
|
|
200
|
+
* Statics:
|
|
201
|
+
* - `Field.Label` — the label, weight-stable and muted; destructive while
|
|
202
|
+
* the field is invalid, with an asterisk while it is required:
|
|
203
|
+
* `visuallyHidden`.
|
|
204
|
+
* - `Field.Description` — muted help text under the control.
|
|
205
|
+
* - `Field.Error` — the destructive message; it fades in while it has
|
|
206
|
+
* children: `match` (`true`, `false`, or a native validity state).
|
|
207
|
+
* - `Field.Control` — wires a control that isn't a field control (a native
|
|
208
|
+
* element, a third-party input) with the field's id, `aria-describedby`
|
|
209
|
+
* and `aria-invalid`: `render={<select/>}` or `asChild`. Field controls
|
|
210
|
+
* (Input, NumberInput, …) need no wrapper.
|
|
211
|
+
*
|
|
212
|
+
* @example {@include ./examples.mdx}
|
|
213
|
+
*/
|
|
214
|
+
const Field = forwardRef<HTMLDivElement, FieldProps>(
|
|
215
|
+
(
|
|
216
|
+
{ invalid = false, disabled = false, readOnly = false, required = false, size, className, children, ...props },
|
|
217
|
+
ref,
|
|
218
|
+
) => {
|
|
219
|
+
const field = (
|
|
220
|
+
// Base UI wires the plumbing: Field.Label's htmlFor targets the
|
|
221
|
+
// control, Description and Error ids land in its aria-describedby, and
|
|
222
|
+
// `invalid` drives aria-invalid / data-invalid.
|
|
223
|
+
<BaseField.Root
|
|
224
|
+
ref={ref}
|
|
225
|
+
invalid={invalid}
|
|
226
|
+
disabled={disabled}
|
|
227
|
+
className={cn('flex min-w-0 flex-col gap-1', className)}
|
|
228
|
+
// The render function sees Base UI's validity, so a native failure
|
|
229
|
+
// (`required`, `pattern`, a type mismatch) turns the label and the box
|
|
230
|
+
// destructive too, not only the `invalid` prop.
|
|
231
|
+
render={(rootProps, rootState) => (
|
|
232
|
+
<div {...rootProps}>
|
|
233
|
+
<ProvideFieldState
|
|
234
|
+
invalid={invalid || rootState.valid === false}
|
|
235
|
+
disabled={disabled}
|
|
236
|
+
readOnly={readOnly}
|
|
237
|
+
required={required}
|
|
238
|
+
>
|
|
239
|
+
{rootProps.children}
|
|
240
|
+
</ProvideFieldState>
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
{...props}
|
|
244
|
+
>
|
|
245
|
+
{children}
|
|
246
|
+
</BaseField.Root>
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return size ? <SizeProvider size={size}>{field}</SizeProvider> : field
|
|
250
|
+
},
|
|
251
|
+
) as FieldComponent
|
|
252
|
+
|
|
253
|
+
Field.displayName = 'Field'
|
|
254
|
+
|
|
255
|
+
Object.assign(Field, { Label: FieldLabel, Description: FieldDescription, Error: FieldError, Control: FieldControl })
|
|
256
|
+
|
|
257
|
+
export type {
|
|
258
|
+
FieldControlProps,
|
|
259
|
+
FieldDescriptionProps,
|
|
260
|
+
FieldErrorMatch,
|
|
261
|
+
FieldErrorProps,
|
|
262
|
+
FieldLabelProps,
|
|
263
|
+
FieldProps,
|
|
264
|
+
FieldValidationMode,
|
|
265
|
+
}
|
|
266
|
+
export { Field, FieldControl, FieldDescription, FieldError, FieldLabel }
|
|
267
|
+
export default Field
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import FileDropzone from './index'
|
|
2
|
+
import UploadDemo from './upload-demo'
|
|
3
|
+
import Field from '../field'
|
|
4
|
+
|
|
5
|
+
# Dropzone
|
|
6
|
+
|
|
7
|
+
Drag files over the zone, or click it (or press Enter on it) to pick them. While files are in the air the line says whether all, some or none of them will be taken; files that pass go to the list, the rest to the error.
|
|
8
|
+
|
|
9
|
+
<UploadDemo multiple accept=".pdf,image/*" maxSize={5000000} description="PDF or images, up to 5 MB each, three at most" />
|
|
10
|
+
|
|
11
|
+
# Single file
|
|
12
|
+
|
|
13
|
+
Without `multiple` the zone takes one file: drag several and it says the rest will be skipped; drag only files of another type and it refuses the drop.
|
|
14
|
+
|
|
15
|
+
<div className="w-96 max-w-full">
|
|
16
|
+
<FileDropzone accept="image/*" description="PNG or JPG" />
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
# Compact
|
|
20
|
+
|
|
21
|
+
The compact step tightens the padding and steps the text down.
|
|
22
|
+
|
|
23
|
+
<div className="w-80 max-w-full">
|
|
24
|
+
<FileDropzone size="compact" label="Drop a CSV or click to browse" accept=".csv" />
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
# Translated
|
|
28
|
+
|
|
29
|
+
Every line is a prop.
|
|
30
|
+
|
|
31
|
+
<div className="w-96 max-w-full">
|
|
32
|
+
<FileDropzone
|
|
33
|
+
multiple
|
|
34
|
+
label="Presuňte súbory sem alebo kliknite"
|
|
35
|
+
activeLabel="Pustite a pridajte"
|
|
36
|
+
partialLabel="Niektoré súbory sa vynechajú"
|
|
37
|
+
rejectLabel="Tieto súbory nemožno pridať"
|
|
38
|
+
description="PDF, najviac 10 MB"
|
|
39
|
+
accept=".pdf"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
# States
|
|
44
|
+
|
|
45
|
+
Invalid takes the field's destructive ring; disabled dims the zone; read-only takes no files.
|
|
46
|
+
|
|
47
|
+
<div className="flex flex-col gap-3 w-96 max-w-full">
|
|
48
|
+
<Field invalid>
|
|
49
|
+
<Field.Label>Contract</Field.Label>
|
|
50
|
+
<FileDropzone accept=".pdf" />
|
|
51
|
+
<Field.Error>Attach the signed contract.</Field.Error>
|
|
52
|
+
</Field>
|
|
53
|
+
<FileDropzone disabled label="Uploads are paused" />
|
|
54
|
+
<FileDropzone readOnly label="No file attached" />
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
# File rows
|
|
58
|
+
|
|
59
|
+
A row shows one file: its glyph or thumbnail, the name, the size or a status line, and a remove button. `status` sets the glyph, and `progress` fills the bar while uploading.
|
|
60
|
+
|
|
61
|
+
<div className="flex flex-col w-96 max-w-full">
|
|
62
|
+
<FileDropzone.Row name="Quarterly report.pdf" fileSize={2480000} type="application/pdf" status="done" onRemove={() => {}} />
|
|
63
|
+
<FileDropzone.Row name="Site photo.jpg" fileSize={5320000} type="image/jpeg" status="uploading" progress={64} onRemove={() => {}} />
|
|
64
|
+
<FileDropzone.Row name="Budget.xlsx" fileSize={84000} status="error" error="Upload failed. Try again." onRemove={() => {}} />
|
|
65
|
+
<FileDropzone.Row name="Contract draft.docx" fileSize={1200000} description="Uploaded yesterday" />
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
# Compact rows
|
|
69
|
+
|
|
70
|
+
<div className="flex flex-col w-80 max-w-full">
|
|
71
|
+
<FileDropzone.Row size="compact" name="Invoice 0042.pdf" fileSize={182000} locale="sk-SK" onRemove={() => {}} />
|
|
72
|
+
<FileDropzone.Row size="compact" name="Receipt.png" fileSize={940000} type="image/png" status="uploading" onRemove={() => {}} />
|
|
73
|
+
</div>
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Local addition (not part of Fluid Functionalism): one file in a list of
|
|
3
|
+
* picked or uploaded files, shown by FileDropzone's page as `FileDropzone.Row`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AnimatePresence, motion } from 'motion/react'
|
|
7
|
+
import type { ReactNode } from 'react'
|
|
8
|
+
import { FIELD_ACTION_RADIUS } from '../../../lib/field-variants'
|
|
9
|
+
import { type IconComponent, useIcon } from '../../../lib/icon-context'
|
|
10
|
+
import { useReduceMotion } from '../../../lib/reduced-motion'
|
|
11
|
+
import { useShape } from '../../../lib/shape-context'
|
|
12
|
+
import { type SizeVariant, useSize } from '../../../lib/size-context'
|
|
13
|
+
import { spring } from '../../../lib/springs'
|
|
14
|
+
import { cn } from '../../../lib/utils'
|
|
15
|
+
import Button from '../../button'
|
|
16
|
+
import Progress, { Spinner } from '../../progress'
|
|
17
|
+
import { formatFileSize } from './variants'
|
|
18
|
+
|
|
19
|
+
/** Where a file is in its upload: `idle` (picked), `uploading`, `done` or `error`. */
|
|
20
|
+
type FileRowStatus = 'idle' | 'uploading' | 'done' | 'error'
|
|
21
|
+
|
|
22
|
+
interface FileRowProps {
|
|
23
|
+
/** The picked file: its name, size and type fill the row. */
|
|
24
|
+
file?: File
|
|
25
|
+
/** File name, for a file you only know by its metadata (one already uploaded). Wins over `file`. */
|
|
26
|
+
name?: string
|
|
27
|
+
/** File size in bytes. Wins over `file`. */
|
|
28
|
+
fileSize?: number
|
|
29
|
+
/** MIME type; an image shows the image glyph. Wins over `file`. */
|
|
30
|
+
type?: string
|
|
31
|
+
/** Image URL shown in place of the glyph, e.g. an object URL of the file or the server's preview. */
|
|
32
|
+
thumbnail?: string
|
|
33
|
+
/** Glyph in place of the file or image one. */
|
|
34
|
+
icon?: IconComponent
|
|
35
|
+
/** Where the file is in its upload; sets the status glyph and the progress bar. Defaults to `"idle"`. */
|
|
36
|
+
status?: FileRowStatus
|
|
37
|
+
/** Upload progress from 0 to 100, shown as a bar while `status` is `"uploading"` (see Progress). Omitted, the bar sweeps. */
|
|
38
|
+
progress?: number
|
|
39
|
+
/** Accessible name of the upload's bar. Defaults to `"Uploading"`. */
|
|
40
|
+
uploadingLabel?: string
|
|
41
|
+
/** The line under the name in place of the size, e.g. "Uploaded 2 min ago". */
|
|
42
|
+
description?: ReactNode
|
|
43
|
+
/** An error message in place of the size line; the row reads as failed. */
|
|
44
|
+
error?: ReactNode
|
|
45
|
+
/** Shows a remove button and is called when it is pressed. */
|
|
46
|
+
onRemove?: () => void
|
|
47
|
+
/** Accessible name of the remove button. Defaults to `"Remove file"`. */
|
|
48
|
+
removeLabel?: string
|
|
49
|
+
/** More controls before the remove button, e.g. a download link or a menu. */
|
|
50
|
+
actions?: ReactNode
|
|
51
|
+
/** Locale for the size (`1,5 MB` in `"sk-SK"`). Defaults to the browser's. */
|
|
52
|
+
locale?: Intl.LocalesArgument
|
|
53
|
+
/** Dims the row and its actions. Defaults to `false`. */
|
|
54
|
+
disabled?: boolean
|
|
55
|
+
/** Pins the row to one step of the size ladder. Defaults to the surrounding SizeProvider. */
|
|
56
|
+
size?: SizeVariant
|
|
57
|
+
/** Classes for the row. */
|
|
58
|
+
className?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Literal tables: UnoCSS only generates the classes it can read whole.
|
|
62
|
+
const THUMB = { default: 'size-8', compact: 'size-7' }
|
|
63
|
+
const ROW_PAD = { default: 'py-1.5', compact: 'py-1' }
|
|
64
|
+
|
|
65
|
+
// Literal table: the spinner at the ladder's glyph size.
|
|
66
|
+
const SPINNER = { default: 'size-4', compact: 'size-3.5' }
|
|
67
|
+
|
|
68
|
+
function StatusGlyph({ status, size }: { status: FileRowStatus; size: SizeVariant }) {
|
|
69
|
+
const Done = useIcon('circle-check')
|
|
70
|
+
const Failed = useIcon('circle-alert')
|
|
71
|
+
const reduced = useReduceMotion()
|
|
72
|
+
const icon = useSize(size).icon
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
// Decorative: the bar carries the progress, the meta line the error.
|
|
76
|
+
<span aria-hidden="true" className="inline-grid shrink-0 place-items-center">
|
|
77
|
+
<AnimatePresence initial={false} mode="popLayout">
|
|
78
|
+
{status !== 'idle' && (
|
|
79
|
+
<motion.span
|
|
80
|
+
key={status}
|
|
81
|
+
className={cn(
|
|
82
|
+
'col-start-1 row-start-1 flex',
|
|
83
|
+
status === 'done' && 'text-status-success',
|
|
84
|
+
status === 'error' && 'text-destructive',
|
|
85
|
+
status === 'uploading' && 'text-muted-foreground',
|
|
86
|
+
)}
|
|
87
|
+
// Under reduced motion the glyphs only fade.
|
|
88
|
+
initial={{ opacity: 0, scale: reduced ? 1 : 0.6 }}
|
|
89
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
90
|
+
exit={{ opacity: 0, scale: reduced ? 1 : 0.8, transition: spring.fast.exit }}
|
|
91
|
+
transition={spring.fast}
|
|
92
|
+
>
|
|
93
|
+
{status === 'done' && <Done size={icon} strokeWidth={2} />}
|
|
94
|
+
{status === 'error' && <Failed size={icon} strokeWidth={2} />}
|
|
95
|
+
{status === 'uploading' && <Spinner className={SPINNER[size]} />}
|
|
96
|
+
</motion.span>
|
|
97
|
+
)}
|
|
98
|
+
</AnimatePresence>
|
|
99
|
+
</span>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** One file in a list: its glyph or thumbnail, name, size or status, and a remove button. */
|
|
104
|
+
function FileRow({
|
|
105
|
+
file,
|
|
106
|
+
name: nameProp,
|
|
107
|
+
fileSize: fileSizeProp,
|
|
108
|
+
type: typeProp,
|
|
109
|
+
thumbnail,
|
|
110
|
+
icon,
|
|
111
|
+
status = 'idle',
|
|
112
|
+
progress,
|
|
113
|
+
description,
|
|
114
|
+
error,
|
|
115
|
+
onRemove,
|
|
116
|
+
removeLabel = 'Remove file',
|
|
117
|
+
uploadingLabel = 'Uploading',
|
|
118
|
+
actions,
|
|
119
|
+
locale,
|
|
120
|
+
disabled = false,
|
|
121
|
+
size: sizeProp,
|
|
122
|
+
className,
|
|
123
|
+
}: FileRowProps) {
|
|
124
|
+
const size = useSize(sizeProp)
|
|
125
|
+
const shape = useShape()
|
|
126
|
+
const compact = size.variant === 'compact'
|
|
127
|
+
const FileIcon = useIcon('file')
|
|
128
|
+
const ImageIcon = useIcon('image')
|
|
129
|
+
const Remove = useIcon('x')
|
|
130
|
+
|
|
131
|
+
const name = nameProp ?? file?.name ?? ''
|
|
132
|
+
const bytes = fileSizeProp ?? file?.size
|
|
133
|
+
const type = typeProp ?? file?.type ?? ''
|
|
134
|
+
const Glyph = icon ?? (type.startsWith('image/') ? ImageIcon : FileIcon)
|
|
135
|
+
const shown = error ? 'error' : status
|
|
136
|
+
const sizeText = bytes != null ? formatFileSize(bytes, locale) : undefined
|
|
137
|
+
const meta =
|
|
138
|
+
error ??
|
|
139
|
+
description ??
|
|
140
|
+
(status === 'uploading' && progress != null ? `${sizeText ?? ''} · ${Math.round(progress)}%` : sizeText)
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div
|
|
144
|
+
data-status={shown}
|
|
145
|
+
data-disabled={disabled || undefined}
|
|
146
|
+
className={cn(
|
|
147
|
+
'flex min-w-0 items-center transition-opacity duration-fast',
|
|
148
|
+
size.gap,
|
|
149
|
+
size.itemPx,
|
|
150
|
+
ROW_PAD[size.variant],
|
|
151
|
+
shape.item,
|
|
152
|
+
disabled && 'pointer-events-none opacity-50',
|
|
153
|
+
className,
|
|
154
|
+
)}
|
|
155
|
+
>
|
|
156
|
+
<span
|
|
157
|
+
className={cn(
|
|
158
|
+
'flex shrink-0 items-center justify-center overflow-hidden bg-muted text-muted-foreground',
|
|
159
|
+
THUMB[size.variant],
|
|
160
|
+
FIELD_ACTION_RADIUS[shape.variant],
|
|
161
|
+
)}
|
|
162
|
+
>
|
|
163
|
+
{thumbnail ? (
|
|
164
|
+
<img src={thumbnail} alt="" className="size-full object-cover" />
|
|
165
|
+
) : (
|
|
166
|
+
<Glyph size={size.icon} strokeWidth={1.5} />
|
|
167
|
+
)}
|
|
168
|
+
</span>
|
|
169
|
+
|
|
170
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
171
|
+
<span className={cn('truncate text-foreground weight-normal', size.text)} title={name}>
|
|
172
|
+
{name}
|
|
173
|
+
</span>
|
|
174
|
+
{meta != null && meta !== '' && (
|
|
175
|
+
<span
|
|
176
|
+
className={cn(
|
|
177
|
+
'truncate',
|
|
178
|
+
compact ? 'text-caption-compact' : 'text-caption',
|
|
179
|
+
error ? 'text-destructive weight-medium' : 'text-muted-foreground weight-normal',
|
|
180
|
+
)}
|
|
181
|
+
>
|
|
182
|
+
{meta}
|
|
183
|
+
</span>
|
|
184
|
+
)}
|
|
185
|
+
{shown === 'uploading' && <Progress value={progress ?? null} aria-label={uploadingLabel} className="mt-1" />}
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<StatusGlyph status={shown} size={size.variant} />
|
|
189
|
+
{actions}
|
|
190
|
+
{onRemove && (
|
|
191
|
+
<Button variant="ghost" size="icon-compact" aria-label={removeLabel} disabled={disabled} onClick={onRemove}>
|
|
192
|
+
<Remove strokeWidth={1.5} />
|
|
193
|
+
</Button>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export type { FileRowProps, FileRowStatus }
|
|
200
|
+
export { FileRow }
|
|
201
|
+
export default FileRow
|