@justanarthur/modo-ui 0.6.0-canary.9 → 0.6.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/README.md +66 -164
- package/dist/base.css +4 -141
- package/dist/blocks/sidebar-app/index.js +26 -40
- package/dist/components/AI/input-message/index.js +16 -23
- package/dist/components/AI/thinking-indicator/index.js +6 -9
- package/dist/components/accordion/index.d.ts +11 -39
- package/dist/components/accordion/index.js +13 -18
- package/dist/components/badge/index.d.ts +11 -18
- package/dist/components/badge/index.js +7 -12
- package/dist/components/button/index.d.ts +17 -53
- package/dist/components/button/index.js +6 -11
- package/dist/components/card/index.d.ts +9 -32
- package/dist/components/card/index.js +11 -17
- package/dist/components/copy/index.d.ts +8 -41
- package/dist/components/copy/index.js +9 -15
- package/dist/components/inputs/checkbox/index.d.ts +9 -28
- package/dist/components/inputs/checkbox/index.js +13 -18
- package/dist/components/inputs/color-picker/index.d.ts +14 -22
- package/dist/components/inputs/color-picker/index.js +18 -26
- package/dist/components/inputs/combobox/index.d.ts +20 -85
- package/dist/components/inputs/combobox/index.js +18 -27
- package/dist/components/inputs/date-time/calendar/index.d.ts +18 -24
- package/dist/components/inputs/date-time/calendar/index.js +11 -15
- package/dist/components/inputs/date-time/date-picker/index.d.ts +16 -105
- package/dist/components/inputs/date-time/date-picker/index.js +25 -35
- package/dist/components/inputs/date-time/date-range-picker/index.d.ts +12 -106
- package/dist/components/inputs/date-time/date-range-picker/index.js +24 -35
- package/dist/components/inputs/date-time/duration-picker/index.d.ts +13 -66
- package/dist/components/inputs/date-time/duration-picker/index.js +24 -33
- package/dist/components/inputs/date-time/month-picker/index.d.ts +10 -60
- package/dist/components/inputs/date-time/month-picker/index.js +23 -32
- package/dist/components/inputs/date-time/time-picker/index.d.ts +10 -58
- package/dist/components/inputs/date-time/time-picker/index.js +24 -33
- package/dist/components/inputs/input/index.d.ts +13 -95
- package/dist/components/inputs/input/index.js +9 -18
- package/dist/components/inputs/radio/index.d.ts +5 -20
- package/dist/components/inputs/radio/index.js +12 -16
- package/dist/components/inputs/search/index.d.ts +3 -3
- package/dist/components/inputs/search/index.js +18 -26
- package/dist/components/inputs/select/index.d.ts +23 -99
- package/dist/components/inputs/select/index.js +14 -29
- package/dist/components/inputs/slider/index.d.ts +0 -9
- package/dist/components/inputs/slider/index.js +5 -8
- package/dist/components/inputs/switch/index.d.ts +2 -8
- package/dist/components/inputs/switch/index.js +12 -15
- package/dist/components/inputs/textarea/index.d.ts +1 -20
- package/dist/components/inputs/textarea/index.js +6 -9
- package/dist/components/navigation/sidebar/index.d.ts +21 -44
- package/dist/components/navigation/sidebar/index.js +31 -42
- package/dist/components/navigation/tabs/index.d.ts +2 -8
- package/dist/components/navigation/tabs/index.js +10 -15
- package/dist/components/navigation/tabs-subtle/index.js +10 -13
- package/dist/components/overlays/alert-dialog/index.d.ts +16 -135
- package/dist/components/overlays/alert-dialog/index.js +13 -23
- package/dist/components/overlays/command/index.d.ts +20 -25
- package/dist/components/overlays/command/index.js +17 -25
- package/dist/components/overlays/context-menu/index.d.ts +17 -39
- package/dist/components/overlays/context-menu/index.js +18 -29
- package/dist/components/overlays/dialog/index.d.ts +16 -42
- package/dist/components/overlays/dialog/index.js +12 -20
- package/dist/components/overlays/dropdown/index.d.ts +34 -304
- package/dist/components/overlays/dropdown/index.js +17 -44
- package/dist/components/overlays/popover/index.d.ts +12 -38
- package/dist/components/overlays/popover/index.js +9 -13
- package/dist/components/overlays/preview-card/index.js +8 -12
- package/dist/components/overlays/sheet/index.d.ts +8 -25
- package/dist/components/overlays/sheet/index.js +13 -19
- package/dist/components/overlays/toast/index.d.ts +50 -209
- package/dist/components/overlays/toast/index.js +10 -23
- package/dist/components/overlays/tooltip/index.d.ts +21 -123
- package/dist/components/overlays/tooltip/index.js +6 -16
- package/dist/components/table/index.d.ts +8 -63
- package/dist/components/table/index.js +9 -20
- package/dist/index.d.ts +1048 -2999
- package/dist/index.js +82 -231
- package/dist/preset.d.ts +0 -22
- package/dist/preset.js +2 -24
- package/dist/primitives/code/index.js +5 -8
- package/dist/primitives/craft/index.js +6 -10
- package/dist/primitives/fluid-hover/index.js +8 -10
- package/dist/primitives/morph/index.js +10 -16
- package/dist/primitives/motion/index.js +8 -11
- package/dist/primitives/scroll-area/index.js +3 -5
- package/dist/primitives/sizes/index.js +4 -6
- package/dist/primitives/surface/index.js +4 -6
- package/dist/shared/{components-c7rwx0ap.js → components-0q0sqfh4.js} +49 -27
- package/dist/shared/components-0sn7ypjw.js +61 -0
- package/dist/shared/{components-vc864c51.js → components-11da9ajg.js} +5 -8
- package/dist/shared/components-1434p1mh.js +123 -0
- package/dist/shared/{components-c74gf16d.js → components-1z6tdadk.js} +24 -40
- package/dist/shared/{components-bae8sbjx.js → components-27nnvrwe.js} +14 -15
- package/dist/shared/{components-qh1cvj6p.js → components-2dxkv6zm.js} +7 -8
- package/dist/shared/{components-y0kfy2g4.js → components-2ecg8svx.js} +152 -234
- package/dist/shared/{components-c5w5zk0s.js → components-2ph6t61s.js} +34 -37
- package/dist/shared/{components-4j3t39dd.js → components-39jq9va1.js} +24 -29
- package/dist/shared/components-3ky44908.js +9 -0
- package/dist/shared/{components-syynwm1r.js → components-3mkepka5.js} +25 -28
- package/dist/shared/{components-wgtjt2af.js → components-4700p9kz.js} +22 -24
- package/dist/shared/{components-yhgq0qxx.js → components-59efxjd9.js} +12 -13
- package/dist/shared/{components-91zw3d9f.js → components-5vgpnxgy.js} +27 -51
- package/dist/shared/{components-kz5q11pp.js → components-5xc2j8jz.js} +18 -19
- package/dist/shared/{components-zkk3fmw8.js → components-6p8tfcd6.js} +29 -57
- package/dist/shared/{components-4nck8acq.js → components-7r3etddy.js} +54 -19
- package/dist/shared/{components-svkt577j.js → components-8snj490h.js} +32 -78
- package/dist/shared/{components-ctqmge8t.js → components-b0eac83j.js} +30 -9
- package/dist/shared/{components-9sttq78t.js → components-b2wyq2cq.js} +59 -41
- package/dist/shared/{components-pv1dyrtn.js → components-b3c5et5b.js} +38 -49
- package/dist/shared/{components-rrbpmfx4.js → components-c6s6b43g.js} +2 -3
- package/dist/shared/{components-pwayrkw9.js → components-chacnk61.js} +19 -19
- package/dist/shared/{components-71nbt03m.js → components-cv16mryv.js} +7 -26
- package/dist/shared/{components-hgv6tygd.js → components-d6xtjht3.js} +30 -35
- package/dist/shared/{components-z8fedth5.js → components-dk0gjr5a.js} +18 -19
- package/dist/shared/{components-cet3jpa0.js → components-fzrq6tr3.js} +34 -37
- package/dist/shared/{components-cnead5qm.js → components-gc5vtq2f.js} +30 -66
- package/dist/shared/components-gf5s6ap9.js +42 -0
- package/dist/shared/{components-0h78g3b5.js → components-gz8zf3ec.js} +55 -56
- package/dist/shared/{components-mattt5jp.js → components-h2ajmhwn.js} +35 -107
- package/dist/shared/{components-m06yq8wk.js → components-hqeq3w9q.js} +23 -38
- package/dist/shared/{components-7qx17na9.js → components-hw9yk32z.js} +24 -36
- package/dist/shared/components-hyq23phv.js +310 -0
- package/dist/shared/{components-9jmjnkqd.js → components-jbbmdr74.js} +22 -41
- package/dist/shared/{components-dcc02hs2.js → components-jjw69c4c.js} +4 -7
- package/dist/shared/{components-303jf96s.js → components-jthe1pwb.js} +420 -139
- package/dist/shared/{components-dtca4s6b.js → components-jwst3vtj.js} +76 -224
- package/dist/shared/{components-dc5q749x.js → components-kvr7f9hy.js} +10 -19
- package/dist/shared/{components-s8yqk2dc.js → components-kwa6mrf9.js} +11 -46
- package/dist/shared/{components-de99ykt7.js → components-men63rwj.js} +2 -3
- package/dist/shared/{components-qx27excf.js → components-mjthm1kf.js} +9 -13
- package/dist/shared/{components-1e64t8aj.js → components-n42c79bz.js} +22 -23
- package/dist/shared/{components-tz1fv35x.js → components-n7mjr357.js} +2 -3
- package/dist/shared/components-ng72k97e.js +128 -0
- package/dist/shared/{components-w2jqa6hx.js → components-pbmqctfa.js} +65 -169
- package/dist/shared/{components-px2zm7dc.js → components-q5xj5c16.js} +8 -9
- package/dist/shared/components-raknhppb.js +64 -0
- package/dist/shared/{components-2a7hcand.js → components-rr7a6ja9.js} +27 -59
- package/dist/shared/{components-005jt2nz.js → components-s083f38z.js} +34 -164
- package/dist/shared/components-s5ngg32c.js +267 -0
- package/dist/shared/components-sn1rxx1d.js +143 -0
- package/dist/shared/{components-r8eqs7gz.js → components-ta7shw28.js} +4 -4
- package/dist/shared/{components-9as3rf68.js → components-vc7vq5nz.js} +3 -6
- package/dist/shared/{components-ft3zdc79.js → components-vfbhxf5r.js} +5 -6
- package/dist/shared/components-vynjtrk6.js +19 -0
- package/dist/shared/{components-p0fm0bab.js → components-w1r67ag9.js} +149 -294
- package/dist/shared/{components-xd3emmy4.js → components-w90endve.js} +72 -71
- package/dist/shared/{components-t5pk5nmc.js → components-wvp1m52g.js} +102 -149
- package/dist/shared/components-wy9jpqny.js +146 -0
- package/dist/shared/{components-yxmxn2jp.js → components-y9y0g8er.js} +28 -52
- package/dist/shared/components-ypp01w3p.js +986 -0
- package/dist/shared/{components-czdvj1a8.js → components-zfnmn5d4.js} +36 -82
- package/dist/shared/{components-jqpefjsn.js → components-zrkzcayz.js} +6 -54
- package/dist/shared/{components-vf45j12c.js → components-zx6b6yby.js} +18 -4
- package/dist/styles.css +32 -320
- package/dist/tailwind.css +0 -16
- package/dist/tokens.css +0 -67
- package/package.json +4 -227
- package/DESIGN.md +0 -623
- package/blocks/sidebar-app/examples.mdx +0 -33
- package/blocks/sidebar-app/index.tsx +0 -218
- package/blocks/sidebar-app/inset-topbar.tsx +0 -25
- package/blocks/sidebar-app/menu-grid.ts +0 -26
- package/blocks/sidebar-app/nav-data.ts +0 -36
- package/blocks/sidebar-app/search-field.tsx +0 -48
- package/blocks/sidebar-app/user-footer.tsx +0 -67
- package/blocks/sidebar-app/workspace-header.tsx +0 -129
- package/components/AI/input-message/examples.mdx +0 -130
- package/components/AI/input-message/file-thumbnail.tsx +0 -173
- package/components/AI/input-message/index.tsx +0 -1385
- package/components/AI/thinking-indicator/examples.mdx +0 -27
- package/components/AI/thinking-indicator/index.tsx +0 -210
- package/components/accordion/examples.mdx +0 -233
- package/components/accordion/index.tsx +0 -1071
- package/components/avatar/examples.mdx +0 -95
- package/components/avatar/index.tsx +0 -129
- package/components/badge/change-demo.tsx +0 -29
- package/components/badge/examples.mdx +0 -124
- package/components/badge/index.tsx +0 -225
- package/components/badge/variants.ts +0 -73
- package/components/button/examples.mdx +0 -168
- package/components/button/index.tsx +0 -424
- package/components/button/loading-demo.tsx +0 -25
- package/components/button/variants.ts +0 -168
- package/components/card/examples.mdx +0 -251
- package/components/card/index.tsx +0 -1070
- package/components/copy/examples.mdx +0 -125
- package/components/copy/index.tsx +0 -662
- package/components/inputs/checkbox/examples.mdx +0 -87
- package/components/inputs/checkbox/index.tsx +0 -611
- package/components/inputs/checkbox/select-all-demo.tsx +0 -48
- package/components/inputs/color-picker/examples.mdx +0 -111
- package/components/inputs/color-picker/index.tsx +0 -1981
- package/components/inputs/color-picker/variants.ts +0 -401
- package/components/inputs/combobox/examples.mdx +0 -366
- package/components/inputs/combobox/index.tsx +0 -1527
- package/components/inputs/combobox/remote-demo.tsx +0 -105
- package/components/inputs/date-time/calendar/examples.mdx +0 -45
- package/components/inputs/date-time/calendar/index.tsx +0 -370
- package/components/inputs/date-time/calendar/use-month-window.ts +0 -173
- package/components/inputs/date-time/date-picker/examples.mdx +0 -243
- package/components/inputs/date-time/date-picker/index.tsx +0 -476
- package/components/inputs/date-time/date-picker/masked-date-input.tsx +0 -348
- package/components/inputs/date-time/date-range-picker/examples.mdx +0 -93
- package/components/inputs/date-time/date-range-picker/index.tsx +0 -525
- package/components/inputs/date-time/duration-picker/examples.mdx +0 -40
- package/components/inputs/date-time/duration-picker/index.tsx +0 -253
- package/components/inputs/date-time/month-picker/examples.mdx +0 -45
- package/components/inputs/date-time/month-picker/index.tsx +0 -254
- package/components/inputs/date-time/picker-shell.tsx +0 -581
- package/components/inputs/date-time/time-columns.tsx +0 -94
- package/components/inputs/date-time/time-picker/examples.mdx +0 -57
- package/components/inputs/date-time/time-picker/index.tsx +0 -227
- package/components/inputs/field/examples.mdx +0 -117
- package/components/inputs/field/index.tsx +0 -268
- package/components/inputs/file-dropzone/examples.mdx +0 -83
- package/components/inputs/file-dropzone/file-row.tsx +0 -201
- package/components/inputs/file-dropzone/index.tsx +0 -316
- package/components/inputs/file-dropzone/upload-demo.tsx +0 -67
- package/components/inputs/file-dropzone/variants.ts +0 -94
- package/components/inputs/input/examples.mdx +0 -166
- package/components/inputs/input/index.tsx +0 -494
- package/components/inputs/input/password-demo.tsx +0 -30
- package/components/inputs/label/examples.mdx +0 -67
- package/components/inputs/label/index.tsx +0 -85
- package/components/inputs/number-input/examples.mdx +0 -69
- package/components/inputs/number-input/index.tsx +0 -221
- package/components/inputs/radio/examples.mdx +0 -77
- package/components/inputs/radio/index.tsx +0 -636
- package/components/inputs/search/examples.mdx +0 -74
- package/components/inputs/search/index.tsx +0 -333
- package/components/inputs/select/examples.mdx +0 -296
- package/components/inputs/select/index.tsx +0 -1044
- package/components/inputs/select/variants.ts +0 -32
- package/components/inputs/slider/examples.mdx +0 -153
- package/components/inputs/slider/index.tsx +0 -1867
- package/components/inputs/switch/examples.mdx +0 -96
- package/components/inputs/switch/index.tsx +0 -504
- package/components/inputs/textarea/examples.mdx +0 -126
- package/components/inputs/textarea/index.tsx +0 -207
- package/components/navigation/navigation-menu/examples.mdx +0 -136
- package/components/navigation/navigation-menu/index.tsx +0 -704
- package/components/navigation/pagination/examples.mdx +0 -74
- package/components/navigation/pagination/index.tsx +0 -401
- package/components/navigation/sidebar/examples.mdx +0 -325
- package/components/navigation/sidebar/index.tsx +0 -453
- package/components/navigation/sidebar/sidebar-core.tsx +0 -1408
- package/components/navigation/sidebar/sidebar-menu.tsx +0 -1495
- package/components/navigation/sidebar/sidebar.css +0 -74
- package/components/navigation/sidebar/variants.ts +0 -55
- package/components/navigation/tabs/examples.mdx +0 -89
- package/components/navigation/tabs/index.tsx +0 -494
- package/components/navigation/tabs-subtle/examples.mdx +0 -49
- package/components/navigation/tabs-subtle/index.tsx +0 -512
- package/components/overlays/alert-dialog/examples.mdx +0 -123
- package/components/overlays/alert-dialog/index.tsx +0 -276
- package/components/overlays/alert-dialog/pending-demo.tsx +0 -36
- package/components/overlays/command/examples.mdx +0 -113
- package/components/overlays/command/index.tsx +0 -425
- package/components/overlays/context-menu/examples.mdx +0 -56
- package/components/overlays/context-menu/index.tsx +0 -279
- package/components/overlays/dialog/examples.mdx +0 -165
- package/components/overlays/dialog/index.tsx +0 -451
- package/components/overlays/dropdown/dropdown-search.tsx +0 -395
- package/components/overlays/dropdown/examples.mdx +0 -444
- package/components/overlays/dropdown/index.tsx +0 -1241
- package/components/overlays/dropdown/menu-item.tsx +0 -510
- package/components/overlays/dropdown/menu-parts.tsx +0 -725
- package/components/overlays/popover/anchored-demo.tsx +0 -59
- package/components/overlays/popover/examples.mdx +0 -203
- package/components/overlays/popover/index.tsx +0 -317
- package/components/overlays/preview-card/examples.mdx +0 -61
- package/components/overlays/preview-card/index.tsx +0 -204
- package/components/overlays/sheet/examples.mdx +0 -101
- package/components/overlays/sheet/index.tsx +0 -307
- package/components/overlays/toast/examples.mdx +0 -85
- package/components/overlays/toast/global-demo.tsx +0 -72
- package/components/overlays/toast/index.tsx +0 -544
- package/components/overlays/toast/manager.ts +0 -193
- package/components/overlays/tooltip/examples.mdx +0 -106
- package/components/overlays/tooltip/index.tsx +0 -539
- package/components/progress/examples.mdx +0 -59
- package/components/progress/index.tsx +0 -174
- package/components/progress/progress.css +0 -49
- package/components/progress/step-demo.tsx +0 -27
- package/components/table/examples.mdx +0 -188
- package/components/table/index.tsx +0 -535
- package/components/table/sort-demo.tsx +0 -61
- package/dist/components/avatar/index.d.ts +0 -46
- package/dist/components/avatar/index.js +0 -19
- package/dist/components/badge/variants.d.ts +0 -33
- package/dist/components/badge/variants.js +0 -10
- package/dist/components/button/variants.d.ts +0 -39
- package/dist/components/button/variants.js +0 -8
- package/dist/components/inputs/color-picker/variants.d.ts +0 -61
- package/dist/components/inputs/color-picker/variants.js +0 -28
- package/dist/components/inputs/field/index.d.ts +0 -116
- package/dist/components/inputs/field/index.js +0 -29
- package/dist/components/inputs/file-dropzone/index.d.ts +0 -167
- package/dist/components/inputs/file-dropzone/index.js +0 -33
- package/dist/components/inputs/file-dropzone/variants.d.ts +0 -35
- package/dist/components/inputs/file-dropzone/variants.js +0 -12
- package/dist/components/inputs/label/index.d.ts +0 -32
- package/dist/components/inputs/label/index.js +0 -11
- package/dist/components/inputs/number-input/index.d.ts +0 -87
- package/dist/components/inputs/number-input/index.js +0 -21
- package/dist/components/inputs/select/variants.d.ts +0 -5
- package/dist/components/inputs/select/variants.js +0 -6
- package/dist/components/navigation/navigation-menu/index.d.ts +0 -126
- package/dist/components/navigation/navigation-menu/index.js +0 -41
- package/dist/components/navigation/pagination/index.d.ts +0 -75
- package/dist/components/navigation/pagination/index.js +0 -41
- package/dist/components/navigation/sidebar/variants.d.ts +0 -25
- package/dist/components/navigation/sidebar/variants.js +0 -28
- package/dist/components/progress/index.d.ts +0 -56
- package/dist/components/progress/index.js +0 -15
- package/dist/docs/index.d.ts +0 -83
- package/dist/docs/index.js +0 -288
- package/dist/docs/vite.d.ts +0 -31
- package/dist/docs/vite.js +0 -45
- package/dist/lib/date-locale.d.ts +0 -26
- package/dist/lib/date-locale.js +0 -24
- package/dist/lib/field-classes.d.ts +0 -149
- package/dist/lib/field-classes.js +0 -41
- package/dist/lib/field-variants.d.ts +0 -47
- package/dist/lib/field-variants.js +0 -26
- package/dist/lib/fluid-hover-highlight.d.ts +0 -112
- package/dist/lib/fluid-hover-highlight.js +0 -17
- package/dist/lib/focus-ring.d.ts +0 -18
- package/dist/lib/focus-ring.js +0 -11
- package/dist/lib/goo-indicator.d.ts +0 -46
- package/dist/lib/goo-indicator.js +0 -14
- package/dist/lib/icon-context.d.ts +0 -30
- package/dist/lib/icon-context.js +0 -12
- package/dist/lib/inert.d.ts +0 -9
- package/dist/lib/inert.js +0 -8
- package/dist/lib/morph-layers.d.ts +0 -93
- package/dist/lib/morph-layers.js +0 -13
- package/dist/lib/morph-part.d.ts +0 -21
- package/dist/lib/morph-part.js +0 -12
- package/dist/lib/popup.d.ts +0 -20
- package/dist/lib/popup.js +0 -12
- package/dist/lib/reduced-motion.d.ts +0 -9
- package/dist/lib/reduced-motion.js +0 -6
- package/dist/lib/row-index.d.ts +0 -10
- package/dist/lib/row-index.js +0 -8
- package/dist/lib/row-order.d.ts +0 -28
- package/dist/lib/row-order.js +0 -10
- package/dist/lib/scroll-column.d.ts +0 -31
- package/dist/lib/scroll-column.js +0 -19
- package/dist/lib/shape-context.d.ts +0 -30
- package/dist/lib/shape-context.js +0 -13
- package/dist/lib/shape-radius.d.ts +0 -19
- package/dist/lib/shape-radius.js +0 -6
- package/dist/lib/size-context.d.ts +0 -94
- package/dist/lib/size-context.js +0 -20
- package/dist/lib/size-scale.d.ts +0 -79
- package/dist/lib/size-scale.js +0 -8
- package/dist/lib/slot.d.ts +0 -39
- package/dist/lib/slot.js +0 -14
- package/dist/lib/springs.d.ts +0 -44
- package/dist/lib/springs.js +0 -8
- package/dist/lib/surface-classes.d.ts +0 -19
- package/dist/lib/surface-classes.js +0 -10
- package/dist/lib/surface-context.d.ts +0 -9
- package/dist/lib/surface-context.js +0 -8
- package/dist/lib/use-controllable-state.d.ts +0 -2
- package/dist/lib/use-controllable-state.js +0 -6
- package/dist/lib/use-fluid-hover.d.ts +0 -138
- package/dist/lib/use-fluid-hover.js +0 -14
- package/dist/lib/use-is-mobile.d.ts +0 -2
- package/dist/lib/use-is-mobile.js +0 -6
- package/dist/lib/use-keyboard-nav-gate.d.ts +0 -20
- package/dist/lib/use-keyboard-nav-gate.js +0 -7
- package/dist/lib/use-merge-split.d.ts +0 -47
- package/dist/lib/use-merge-split.js +0 -15
- package/dist/lib/use-morph.d.ts +0 -101
- package/dist/lib/use-morph.js +0 -20
- package/dist/lib/use-touch-primary.d.ts +0 -2
- package/dist/lib/use-touch-primary.js +0 -6
- package/dist/lib/utils.d.ts +0 -3
- package/dist/lib/utils.js +0 -6
- package/dist/primitives/separator/index.d.ts +0 -25
- package/dist/primitives/separator/index.js +0 -9
- package/dist/primitives/skeleton/index.d.ts +0 -35
- package/dist/primitives/skeleton/index.js +0 -13
- package/dist/shared/components-01w9qcfa.js +0 -277
- package/dist/shared/components-124epb7f.js +0 -53
- package/dist/shared/components-26snnbs9.js +0 -322
- package/dist/shared/components-2krsc300.js +0 -254
- package/dist/shared/components-2qbqqpgq.js +0 -1545
- package/dist/shared/components-34txk9rj.js +0 -35
- package/dist/shared/components-3h92d94m.js +0 -23
- package/dist/shared/components-4vfm04wq.js +0 -82
- package/dist/shared/components-54nq988h.js +0 -123
- package/dist/shared/components-7s7pykk5.js +0 -281
- package/dist/shared/components-868v7scv.js +0 -399
- package/dist/shared/components-8jrk5eye.js +0 -448
- package/dist/shared/components-adkarj8n.js +0 -35
- package/dist/shared/components-b1e239g3.js +0 -28
- package/dist/shared/components-b94t6qtj.js +0 -143
- package/dist/shared/components-bsqynhaj.js +0 -179
- package/dist/shared/components-c9yr4ed1.js +0 -44
- package/dist/shared/components-d3shbwqq.js +0 -95
- package/dist/shared/components-dhgk6mbt.js +0 -16
- package/dist/shared/components-drxrtnp2.js +0 -25
- package/dist/shared/components-f7h57m6t.js +0 -59
- package/dist/shared/components-g3eafs70.js +0 -337
- package/dist/shared/components-gx4h5rbb.js +0 -40
- package/dist/shared/components-gyt7my41.js +0 -328
- package/dist/shared/components-h6p86be3.js +0 -19
- package/dist/shared/components-h83az0bz.js +0 -33
- package/dist/shared/components-hdzrqqpx.js +0 -55
- package/dist/shared/components-krtx3m5b.js +0 -84
- package/dist/shared/components-kswxnm3h.js +0 -24
- package/dist/shared/components-mhza2wdn.js +0 -482
- package/dist/shared/components-mrbaxt8q.js +0 -62
- package/dist/shared/components-pt5ftm4p.js +0 -38
- package/dist/shared/components-rtadhd9j.js +0 -64
- package/dist/shared/components-tv7b8tet.js +0 -271
- package/dist/shared/components-tz4gaf1v.js +0 -23
- package/dist/shared/components-wbbckbw8.js +0 -34
- package/dist/shared/components-wknae3m4.js +0 -298
- package/dist/shared/components-xdqmt6ed.js +0 -20
- package/dist/shared/components-xjkdvnbg.js +0 -107
- package/dist/shared/components-z3v0n0de.js +0 -11
- package/dist/tokens/colors.css +0 -230
- package/dist/tokens/motion.css +0 -40
- package/dist/tokens/radius.css +0 -10
- package/dist/tokens/spacing.css +0 -9
- package/dist/tokens/typography.css +0 -119
- package/docs/chrome.css +0 -120
- package/docs/chrome.tsx +0 -283
- package/docs/index.ts +0 -9
- package/docs/vite.ts +0 -85
- package/index.ts +0 -60
- package/lib/date-locale.ts +0 -195
- package/lib/field-classes.ts +0 -121
- package/lib/field-variants.ts +0 -117
- package/lib/fluid-hover-highlight.tsx +0 -139
- package/lib/focus-ring.tsx +0 -46
- package/lib/goo-indicator.tsx +0 -261
- package/lib/icon-context.tsx +0 -310
- package/lib/inert.ts +0 -22
- package/lib/morph-layers.tsx +0 -102
- package/lib/morph-part.tsx +0 -49
- package/lib/popup.ts +0 -52
- package/lib/reduced-motion.ts +0 -19
- package/lib/row-index.tsx +0 -29
- package/lib/row-order.tsx +0 -126
- package/lib/scroll-column.tsx +0 -273
- package/lib/shape-context.tsx +0 -138
- package/lib/shape-radius.ts +0 -67
- package/lib/size-context.tsx +0 -66
- package/lib/size-scale.ts +0 -115
- package/lib/slot.ts +0 -115
- package/lib/springs.ts +0 -62
- package/lib/surface-classes.ts +0 -58
- package/lib/surface-context.tsx +0 -21
- package/lib/use-controllable-state.ts +0 -44
- package/lib/use-fluid-hover.ts +0 -541
- package/lib/use-is-mobile.ts +0 -25
- package/lib/use-keyboard-nav-gate.ts +0 -45
- package/lib/use-merge-split.tsx +0 -209
- package/lib/use-morph.ts +0 -575
- package/lib/use-touch-primary.tsx +0 -43
- package/lib/utils.ts +0 -37
- package/preset.ts +0 -199
- package/primitives/code/examples.mdx +0 -27
- package/primitives/code/index.tsx +0 -80
- package/primitives/craft/demos.tsx +0 -173
- package/primitives/craft/examples.mdx +0 -142
- package/primitives/craft/index.tsx +0 -76
- package/primitives/fluid-hover/examples.mdx +0 -84
- package/primitives/fluid-hover/index.tsx +0 -316
- package/primitives/morph/examples.mdx +0 -139
- package/primitives/morph/index.tsx +0 -192
- package/primitives/motion/examples.mdx +0 -100
- package/primitives/motion/index.tsx +0 -160
- package/primitives/scroll-area/examples.mdx +0 -58
- package/primitives/scroll-area/index.tsx +0 -204
- package/primitives/separator/examples.mdx +0 -30
- package/primitives/separator/index.tsx +0 -53
- package/primitives/sizes/examples.mdx +0 -167
- package/primitives/sizes/index.tsx +0 -89
- package/primitives/skeleton/examples.mdx +0 -51
- package/primitives/skeleton/index.tsx +0 -72
- package/primitives/skeleton/skeleton.css +0 -15
- package/primitives/surface/examples.mdx +0 -173
- package/primitives/surface/index.tsx +0 -109
- package/primitives/surface/surface.css +0 -176
- /package/dist/shared/{components-3ey0hzpb.js → components-1v7rxytx.js} +0 -0
- /package/dist/shared/{components-jcnjay5g.js → components-ezxhrmer.js} +0 -0
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import FileDropzone from './index'
|
|
2
|
-
import UploadDemo from './upload-demo'
|
|
3
|
-
import Field from '../field'
|
|
4
|
-
|
|
5
|
-
# Basics
|
|
6
|
-
|
|
7
|
-
## Dropzone
|
|
8
|
-
|
|
9
|
-
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.
|
|
10
|
-
|
|
11
|
-
<UploadDemo multiple accept=".pdf,image/*" maxSize={5000000} description="PDF or images, up to 5 MB each, three at most" />
|
|
12
|
-
|
|
13
|
-
## Single file
|
|
14
|
-
|
|
15
|
-
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.
|
|
16
|
-
|
|
17
|
-
<div className="w-96 max-w-full">
|
|
18
|
-
<FileDropzone accept="image/*" description="PNG or JPG" />
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
## Compact
|
|
22
|
-
|
|
23
|
-
The compact step tightens the padding and steps the text down.
|
|
24
|
-
|
|
25
|
-
<div className="w-80 max-w-full">
|
|
26
|
-
<FileDropzone size="compact" label="Drop a CSV or click to browse" accept=".csv" />
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
## Style and attributes
|
|
30
|
-
|
|
31
|
-
Props a `<label>` takes (`style`, `title`, `data-*`, `aria-*`, handlers) reach the zone's root. Here an inline `maxWidth` and a tooltip.
|
|
32
|
-
|
|
33
|
-
<div className="w-96 max-w-full">
|
|
34
|
-
<FileDropzone style={{ maxWidth: 280 }} title="Drop your avatar here" label="Drop an avatar" accept="image/*" />
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
## Translated
|
|
38
|
-
|
|
39
|
-
Every line is a prop.
|
|
40
|
-
|
|
41
|
-
<div className="w-96 max-w-full">
|
|
42
|
-
<FileDropzone
|
|
43
|
-
multiple
|
|
44
|
-
label="Presuňte súbory sem alebo kliknite"
|
|
45
|
-
activeLabel="Pustite a pridajte"
|
|
46
|
-
partialLabel="Niektoré súbory sa vynechajú"
|
|
47
|
-
rejectLabel="Tieto súbory nemožno pridať"
|
|
48
|
-
description="PDF, najviac 10 MB"
|
|
49
|
-
accept=".pdf"
|
|
50
|
-
/>
|
|
51
|
-
</div>
|
|
52
|
-
|
|
53
|
-
# States
|
|
54
|
-
|
|
55
|
-
Invalid takes the field's destructive ring; disabled dims the zone; read-only takes no files.
|
|
56
|
-
|
|
57
|
-
<div className="flex flex-col gap-3 w-96 max-w-full">
|
|
58
|
-
<Field invalid>
|
|
59
|
-
<Field.Label>Contract</Field.Label>
|
|
60
|
-
<FileDropzone accept=".pdf" />
|
|
61
|
-
<Field.Error>Attach the signed contract.</Field.Error>
|
|
62
|
-
</Field>
|
|
63
|
-
<FileDropzone disabled label="Uploads are paused" />
|
|
64
|
-
<FileDropzone readOnly label="No file attached" />
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
# File rows
|
|
68
|
-
|
|
69
|
-
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.
|
|
70
|
-
|
|
71
|
-
<div className="flex flex-col w-96 max-w-full">
|
|
72
|
-
<FileDropzone.Row name="Quarterly report.pdf" fileSize={2480000} type="application/pdf" status="done" onRemove={() => {}} />
|
|
73
|
-
<FileDropzone.Row name="Site photo.jpg" fileSize={5320000} type="image/jpeg" status="uploading" progress={64} onRemove={() => {}} />
|
|
74
|
-
<FileDropzone.Row name="Budget.xlsx" fileSize={84000} status="error" error="Upload failed. Try again." onRemove={() => {}} />
|
|
75
|
-
<FileDropzone.Row name="Contract draft.docx" fileSize={1200000} description="Uploaded yesterday" />
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
## Compact rows
|
|
79
|
-
|
|
80
|
-
<div className="flex flex-col w-80 max-w-full">
|
|
81
|
-
<FileDropzone.Row size="compact" name="Invoice 0042.pdf" fileSize={182000} locale="sk-SK" onRemove={() => {}} />
|
|
82
|
-
<FileDropzone.Row size="compact" name="Receipt.png" fileSize={940000} type="image/png" status="uploading" onRemove={() => {}} />
|
|
83
|
-
</div>
|
|
@@ -1,201 +0,0 @@
|
|
|
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
|
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Local addition (not part of Fluid Functionalism): a drag-and-drop zone that
|
|
3
|
-
* hands picked files over and keeps none (no upload logic), and the row a
|
|
4
|
-
* picked or uploaded file shows in (`file-row.tsx`). The zone is a label
|
|
5
|
-
* around a visually hidden file input, joined to Base UI's Field like Input.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Field } from '@base-ui/react/field'
|
|
9
|
-
import { motion } from 'motion/react'
|
|
10
|
-
import {
|
|
11
|
-
type ChangeEvent,
|
|
12
|
-
type CSSProperties,
|
|
13
|
-
type DragEvent,
|
|
14
|
-
type ForwardRefExoticComponent,
|
|
15
|
-
forwardRef,
|
|
16
|
-
type LabelHTMLAttributes,
|
|
17
|
-
type ReactNode,
|
|
18
|
-
type RefAttributes,
|
|
19
|
-
useRef,
|
|
20
|
-
useState,
|
|
21
|
-
} from 'react'
|
|
22
|
-
import { useFieldState } from '../../../lib/field-classes'
|
|
23
|
-
import { type IconComponent, useIcon } from '../../../lib/icon-context'
|
|
24
|
-
import { useReduceMotion } from '../../../lib/reduced-motion'
|
|
25
|
-
import { useShape } from '../../../lib/shape-context'
|
|
26
|
-
import { type SizeVariant, useSize } from '../../../lib/size-context'
|
|
27
|
-
import { spring } from '../../../lib/springs'
|
|
28
|
-
import { cn } from '../../../lib/utils'
|
|
29
|
-
import { FileRow } from './file-row'
|
|
30
|
-
import { type DragVerdict, dragVerdict, type FileRejection, sortFiles } from './variants'
|
|
31
|
-
|
|
32
|
-
interface FileDropzoneProps
|
|
33
|
-
extends Omit<
|
|
34
|
-
LabelHTMLAttributes<HTMLLabelElement>,
|
|
35
|
-
'children' | 'className' | 'style' | 'id' | 'onChange' | 'htmlFor' | 'form'
|
|
36
|
-
> {
|
|
37
|
-
/** Called with the files that pass `accept`, `maxSize` and the count, after a pick or a drop. */
|
|
38
|
-
onFilesChange?: (files: File[]) => void
|
|
39
|
-
/** Called with the files turned away and why (`type`, `size`, `count`), after a pick or a drop. */
|
|
40
|
-
onFilesRejected?: (rejections: FileRejection[]) => void
|
|
41
|
-
/** File types the zone takes, as the file input reads them: `.pdf,image/*`. Defaults to any. */
|
|
42
|
-
accept?: string
|
|
43
|
-
/** Takes several files at once. Defaults to `false`. */
|
|
44
|
-
multiple?: boolean
|
|
45
|
-
/** Most files one pick or drop may add; the rest are rejected as `count`. Pass what is left when you keep a list. */
|
|
46
|
-
maxFiles?: number
|
|
47
|
-
/** Largest file in bytes; bigger ones are rejected as `size`. */
|
|
48
|
-
maxSize?: number
|
|
49
|
-
/** The main line. Defaults to `"Drop a file here or click to browse"` (`"files"` with `multiple`). */
|
|
50
|
-
label?: ReactNode
|
|
51
|
-
/** The line under it: the types and sizes the zone takes, e.g. "PDF or PNG, up to 10 MB". */
|
|
52
|
-
description?: ReactNode
|
|
53
|
-
/** The main line while files the zone takes are dragged over it. Defaults to `"Drop to add"`. */
|
|
54
|
-
activeLabel?: ReactNode
|
|
55
|
-
/** The main line while some of the dragged files will be taken and the rest turned away (a wrong type, past `maxFiles`). Defaults to `"Some files will be skipped"`. */
|
|
56
|
-
partialLabel?: ReactNode
|
|
57
|
-
/** The main line while none of the dragged files can be taken; the drop is refused. Defaults to `"These files can't be added"`. */
|
|
58
|
-
rejectLabel?: ReactNode
|
|
59
|
-
/** Glyph above the labels. Defaults to the `upload` icon. */
|
|
60
|
-
icon?: IconComponent
|
|
61
|
-
/** Content in place of the glyph and labels. */
|
|
62
|
-
children?: ReactNode
|
|
63
|
-
/** Id of the file input, for a `Label`'s `htmlFor`. Inside a Field it is wired for you. */
|
|
64
|
-
id?: string
|
|
65
|
-
/** Tells assistive tech a file is required (`aria-required`). The zone keeps no files, so native validation never blocks a submit: check your own list. Defaults to the surrounding Field's `required`. */
|
|
66
|
-
required?: boolean
|
|
67
|
-
/** Turns the zone destructive. Defaults to the surrounding Field's `invalid`. */
|
|
68
|
-
invalid?: boolean
|
|
69
|
-
/** Dims the zone and turns files away. Defaults to the surrounding Field's `disabled`. */
|
|
70
|
-
disabled?: boolean
|
|
71
|
-
/** Shows the zone without taking files: no browse, no drop, no hover. Defaults to the surrounding Field's `readOnly`. */
|
|
72
|
-
readOnly?: boolean
|
|
73
|
-
/** Pins the zone to one step of the size ladder: compact tightens the padding and steps the text down. Defaults to the surrounding SizeProvider. */
|
|
74
|
-
size?: SizeVariant
|
|
75
|
-
/** Classes for the zone. */
|
|
76
|
-
className?: string
|
|
77
|
-
/** Inline styles for the zone (its `<label>` root). The other attributes of a `<label>` (`title`, `data-*`, `aria-*`, `onClick`) reach it too, and drag handlers run before the zone's own. */
|
|
78
|
-
style?: CSSProperties
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
type DragState = 'idle' | DragVerdict
|
|
82
|
-
|
|
83
|
-
// Literal tables: UnoCSS only generates the classes it can read whole.
|
|
84
|
-
const PAD = { default: 'px-6 py-8', compact: 'px-4 py-5' }
|
|
85
|
-
|
|
86
|
-
type FileDropzoneComponent = ForwardRefExoticComponent<FileDropzoneProps & RefAttributes<HTMLInputElement>> & {
|
|
87
|
-
Row: typeof FileRow
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* A zone to drop files on, or click to pick them.
|
|
92
|
-
*
|
|
93
|
-
* Drag files over it and it lights up; the line changes to say whether it
|
|
94
|
-
* will take them, reading their types while they are still in the air: all
|
|
95
|
-
* of them, some (the rest will be skipped), or none, when it turns
|
|
96
|
-
* destructive and refuses the drop. Dropped or picked files are sorted
|
|
97
|
-
* against `accept`, `maxSize` and `maxFiles`: the ones that pass go to
|
|
98
|
-
* `onFilesChange`, the rest to `onFilesRejected` with a reason. The zone
|
|
99
|
-
* keeps none (its input is cleared after every pick, so nothing submits with
|
|
100
|
-
* a native form and `required` is only announced), so the list, the upload
|
|
101
|
-
* and its errors stay yours. Show each
|
|
102
|
-
* file in a `FileDropzone.Row`. Every line is a prop, for translation. The
|
|
103
|
-
* zone is a label around a hidden file input, so a click or Enter opens the
|
|
104
|
-
* picker, and inside a Field it is labelled and takes the field's state. The
|
|
105
|
-
* ref reaches the input: `ref.current.click()` reopens the picker, e.g. to
|
|
106
|
-
* replace a file.
|
|
107
|
-
*
|
|
108
|
-
* Statics:
|
|
109
|
-
* - `FileDropzone.Row` (also `FileRow`) — one file: its glyph or
|
|
110
|
-
* `thumbnail`, name, size, and a remove button with `onRemove`: `file` (or
|
|
111
|
-
* `name` / `fileSize` / `type`), `status` (`idle`, `uploading`, `done`,
|
|
112
|
-
* `error`), `progress` (0–100), `error`, `description`, `actions`,
|
|
113
|
-
* `removeLabel`, `locale`.
|
|
114
|
-
*
|
|
115
|
-
* Also exported from this item: `formatFileSize(bytes, locale)`, `sortFiles`
|
|
116
|
-
* and `acceptsFile`, plain functions a server can call too.
|
|
117
|
-
*
|
|
118
|
-
* @example {@include ./examples.mdx}
|
|
119
|
-
*/
|
|
120
|
-
const FileDropzone = forwardRef<HTMLInputElement, FileDropzoneProps>(
|
|
121
|
-
(
|
|
122
|
-
{
|
|
123
|
-
onFilesChange,
|
|
124
|
-
onFilesRejected,
|
|
125
|
-
accept,
|
|
126
|
-
multiple = false,
|
|
127
|
-
maxFiles,
|
|
128
|
-
maxSize,
|
|
129
|
-
label,
|
|
130
|
-
description,
|
|
131
|
-
activeLabel = 'Drop to add',
|
|
132
|
-
partialLabel = 'Some files will be skipped',
|
|
133
|
-
rejectLabel = "These files can't be added",
|
|
134
|
-
icon,
|
|
135
|
-
children,
|
|
136
|
-
id,
|
|
137
|
-
required,
|
|
138
|
-
invalid,
|
|
139
|
-
disabled,
|
|
140
|
-
readOnly,
|
|
141
|
-
size: sizeProp,
|
|
142
|
-
className,
|
|
143
|
-
style,
|
|
144
|
-
onDragEnter: dragEnter,
|
|
145
|
-
onDragOver: dragOver,
|
|
146
|
-
onDragLeave: dragLeave,
|
|
147
|
-
onDrop: dropped,
|
|
148
|
-
...rest
|
|
149
|
-
},
|
|
150
|
-
ref,
|
|
151
|
-
) => {
|
|
152
|
-
const field = useFieldState()
|
|
153
|
-
const size = useSize(sizeProp)
|
|
154
|
-
const shape = useShape()
|
|
155
|
-
const reduced = useReduceMotion()
|
|
156
|
-
const Upload = useIcon('upload')
|
|
157
|
-
const Glyph = icon ?? Upload
|
|
158
|
-
const compact = size.variant === 'compact'
|
|
159
|
-
|
|
160
|
-
const isInvalid = invalid ?? field?.invalid ?? false
|
|
161
|
-
const isDisabled = disabled ?? field?.disabled ?? false
|
|
162
|
-
const isReadOnly = readOnly ?? field?.readOnly ?? false
|
|
163
|
-
const closed = isDisabled || isReadOnly
|
|
164
|
-
|
|
165
|
-
const [drag, setDrag] = useState<DragState>('idle')
|
|
166
|
-
// dragenter / dragleave fire for every child crossed; count them so the
|
|
167
|
-
// zone only goes idle when the pointer really leaves.
|
|
168
|
-
const depth = useRef(0)
|
|
169
|
-
|
|
170
|
-
const hand = (files: File[]) => {
|
|
171
|
-
if (!files.length) return
|
|
172
|
-
const { accepted, rejected } = sortFiles(files, { accept, maxSize, maxFiles, multiple })
|
|
173
|
-
if (accepted.length) onFilesChange?.(accepted)
|
|
174
|
-
if (rejected.length) onFilesRejected?.(rejected)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const onDragEnter = (e: DragEvent<HTMLLabelElement>) => {
|
|
178
|
-
dragEnter?.(e)
|
|
179
|
-
if (closed || !e.dataTransfer.types.includes('Files')) return
|
|
180
|
-
e.preventDefault()
|
|
181
|
-
depth.current++
|
|
182
|
-
const types = [...e.dataTransfer.items].filter(item => item.kind === 'file').map(item => item.type)
|
|
183
|
-
// Only a drag where nothing can pass is refused; a mixed one drops, and sortFiles splits it.
|
|
184
|
-
setDrag(dragVerdict(types, accept, multiple ? (maxFiles ?? Number.POSITIVE_INFINITY) : 1))
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const onDragOver = (e: DragEvent<HTMLLabelElement>) => {
|
|
188
|
-
dragOver?.(e)
|
|
189
|
-
if (closed || drag === 'idle') return
|
|
190
|
-
e.preventDefault()
|
|
191
|
-
e.dataTransfer.dropEffect = drag === 'reject' ? 'none' : 'copy'
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const onDragLeave = (e: DragEvent<HTMLLabelElement>) => {
|
|
195
|
-
dragLeave?.(e)
|
|
196
|
-
if (drag === 'idle') return
|
|
197
|
-
depth.current = Math.max(depth.current - 1, 0)
|
|
198
|
-
if (depth.current === 0) setDrag('idle')
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const onDrop = (e: DragEvent<HTMLLabelElement>) => {
|
|
202
|
-
dropped?.(e)
|
|
203
|
-
if (closed) return
|
|
204
|
-
e.preventDefault()
|
|
205
|
-
depth.current = 0
|
|
206
|
-
setDrag('idle')
|
|
207
|
-
hand([...e.dataTransfer.files])
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
211
|
-
hand([...(e.target.files ?? [])])
|
|
212
|
-
// Clear it, so picking the same file again still reports it.
|
|
213
|
-
e.target.value = ''
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const line =
|
|
217
|
-
drag === 'accept'
|
|
218
|
-
? activeLabel
|
|
219
|
-
: drag === 'partial'
|
|
220
|
-
? partialLabel
|
|
221
|
-
: drag === 'reject'
|
|
222
|
-
? rejectLabel
|
|
223
|
-
: (label ?? (multiple ? 'Drop files here or click to browse' : 'Drop a file here or click to browse'))
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
// biome-ignore lint/a11y/noLabelWithoutControl: the file input inside is the control (Field.Control renders it).
|
|
227
|
-
<label
|
|
228
|
-
{...rest}
|
|
229
|
-
style={style}
|
|
230
|
-
onDragEnter={onDragEnter}
|
|
231
|
-
onDragOver={onDragOver}
|
|
232
|
-
onDragLeave={onDragLeave}
|
|
233
|
-
onDrop={onDrop}
|
|
234
|
-
data-dragging={drag === 'idle' ? undefined : drag}
|
|
235
|
-
data-invalid={isInvalid || undefined}
|
|
236
|
-
data-disabled={isDisabled || undefined}
|
|
237
|
-
data-readonly={isReadOnly || undefined}
|
|
238
|
-
className={cn(
|
|
239
|
-
'group/zone relative flex cursor-pointer flex-col items-center justify-center text-center',
|
|
240
|
-
'border border-dashed border-border transition-[background-color,border-color,opacity] duration-fast',
|
|
241
|
-
'hover:bg-muted/50 has-[:focus-visible]:ring-1 has-[:focus-visible]:ring-focus-ring',
|
|
242
|
-
'data-[dragging=accept]:border-solid data-[dragging=accept]:!border-border data-[dragging=accept]:!bg-muted',
|
|
243
|
-
'data-[dragging=partial]:border-solid data-[dragging=partial]:!border-border data-[dragging=partial]:!bg-muted',
|
|
244
|
-
'data-[invalid]:border-destructive/50 data-[invalid]:hover:bg-destructive-light/60',
|
|
245
|
-
'data-[dragging=reject]:border-solid data-[dragging=reject]:border-destructive/50 data-[dragging=reject]:!bg-destructive-light/60',
|
|
246
|
-
'data-[readonly]:cursor-default data-[readonly]:hover:bg-transparent',
|
|
247
|
-
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
248
|
-
size.gap,
|
|
249
|
-
PAD[size.variant],
|
|
250
|
-
shape.container,
|
|
251
|
-
className,
|
|
252
|
-
)}
|
|
253
|
-
>
|
|
254
|
-
{/* Field.Control: on its own a plain file input, inside a Field it
|
|
255
|
-
joins the field's label, description and error. */}
|
|
256
|
-
<Field.Control
|
|
257
|
-
ref={ref}
|
|
258
|
-
id={id}
|
|
259
|
-
disabled={closed}
|
|
260
|
-
// Not native `required`: the input is cleared after every pick, so it would always be missing.
|
|
261
|
-
aria-required={(required ?? field?.required) || undefined}
|
|
262
|
-
aria-invalid={isInvalid || undefined}
|
|
263
|
-
render={<input type="file" accept={accept} multiple={multiple} onChange={onChange} className="sr-only" />}
|
|
264
|
-
/>
|
|
265
|
-
{children ?? (
|
|
266
|
-
<>
|
|
267
|
-
<motion.span
|
|
268
|
-
className={cn(
|
|
269
|
-
'flex text-muted-foreground transition-[color] duration-fast group-hover/zone:text-foreground group-data-[dragging]/zone:text-foreground',
|
|
270
|
-
drag === 'reject' && 'text-destructive group-data-[dragging]/zone:text-destructive',
|
|
271
|
-
)}
|
|
272
|
-
initial={false}
|
|
273
|
-
// The glyph rises to meet the files; with reduced motion it only darkens.
|
|
274
|
-
animate={{ y: (drag === 'accept' || drag === 'partial') && !reduced ? -2 : 0 }}
|
|
275
|
-
transition={spring.fast}
|
|
276
|
-
>
|
|
277
|
-
<Glyph
|
|
278
|
-
size={size.icon}
|
|
279
|
-
strokeWidth={1.5}
|
|
280
|
-
className="transition-[stroke-width] duration-fast group-hover/zone:stroke-[2] group-data-[dragging]/zone:stroke-[2]"
|
|
281
|
-
/>
|
|
282
|
-
</motion.span>
|
|
283
|
-
<span className="flex flex-col">
|
|
284
|
-
<span
|
|
285
|
-
className={cn('weight-normal', size.text, drag === 'reject' ? 'text-destructive' : 'text-foreground')}
|
|
286
|
-
>
|
|
287
|
-
{line}
|
|
288
|
-
</span>
|
|
289
|
-
{description != null && (
|
|
290
|
-
<span
|
|
291
|
-
className={cn(
|
|
292
|
-
'text-muted-foreground weight-normal',
|
|
293
|
-
compact ? 'text-caption-compact' : 'text-caption',
|
|
294
|
-
)}
|
|
295
|
-
>
|
|
296
|
-
{description}
|
|
297
|
-
</span>
|
|
298
|
-
)}
|
|
299
|
-
</span>
|
|
300
|
-
</>
|
|
301
|
-
)}
|
|
302
|
-
</label>
|
|
303
|
-
)
|
|
304
|
-
},
|
|
305
|
-
) as FileDropzoneComponent
|
|
306
|
-
|
|
307
|
-
FileDropzone.displayName = 'FileDropzone'
|
|
308
|
-
|
|
309
|
-
Object.assign(FileDropzone, { Row: FileRow })
|
|
310
|
-
|
|
311
|
-
export type { FileRowProps, FileRowStatus } from './file-row'
|
|
312
|
-
export type { DragVerdict, FileRejection, FileRejectionReason } from './variants'
|
|
313
|
-
export { acceptsFile, dragVerdict, formatFileSize, sortFiles } from './variants'
|
|
314
|
-
export type { FileDropzoneProps }
|
|
315
|
-
export { FileDropzone, FileRow }
|
|
316
|
-
export default FileDropzone
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Local addition (not part of Fluid Functionalism): the docs stage of the
|
|
3
|
-
* FileDropzone page. It keeps the picked files, plays a pretend upload for
|
|
4
|
-
* each and shows the rejections in a Field's error, so the example stays
|
|
5
|
-
* hook-free. The zone itself keeps no files.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useEffect, useState } from 'react'
|
|
9
|
-
import Field from '../field'
|
|
10
|
-
import FileDropzone, { type FileDropzoneProps, type FileRejection } from './index'
|
|
11
|
-
|
|
12
|
-
interface Upload {
|
|
13
|
-
file: File
|
|
14
|
-
progress: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const REASONS = { type: "isn't a PDF or an image", size: 'is over 5 MB', count: "doesn't fit: three files at most" }
|
|
18
|
-
|
|
19
|
-
export default function UploadDemo(props: FileDropzoneProps) {
|
|
20
|
-
const [uploads, setUploads] = useState<Upload[]>([])
|
|
21
|
-
const [rejected, setRejected] = useState<FileRejection[]>([])
|
|
22
|
-
|
|
23
|
-
// A pretend upload: every file gains a little each tick until it is done.
|
|
24
|
-
const busy = uploads.some(upload => upload.progress < 100)
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (!busy) return
|
|
27
|
-
const timer = setInterval(
|
|
28
|
-
() => setUploads(list => list.map(upload => ({ ...upload, progress: Math.min(upload.progress + 12, 100) }))),
|
|
29
|
-
300,
|
|
30
|
-
)
|
|
31
|
-
return () => clearInterval(timer)
|
|
32
|
-
}, [busy])
|
|
33
|
-
|
|
34
|
-
const left = 3 - uploads.length
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<Field invalid={rejected.length > 0} className="w-96 max-w-full gap-2">
|
|
38
|
-
<Field.Label>Attachments</Field.Label>
|
|
39
|
-
<FileDropzone
|
|
40
|
-
{...props}
|
|
41
|
-
maxFiles={left}
|
|
42
|
-
disabled={left <= 0}
|
|
43
|
-
onFilesChange={files => {
|
|
44
|
-
setRejected([])
|
|
45
|
-
setUploads(list => [...list, ...files.map(file => ({ file, progress: 0 }))])
|
|
46
|
-
}}
|
|
47
|
-
onFilesRejected={setRejected}
|
|
48
|
-
/>
|
|
49
|
-
<Field.Error>
|
|
50
|
-
{rejected.length > 0 && rejected.map(({ file, reason }) => `${file.name} ${REASONS[reason]}.`).join(' ')}
|
|
51
|
-
</Field.Error>
|
|
52
|
-
{uploads.length > 0 && (
|
|
53
|
-
<div className="flex flex-col">
|
|
54
|
-
{uploads.map(upload => (
|
|
55
|
-
<FileDropzone.Row
|
|
56
|
-
key={`${upload.file.name}-${upload.file.lastModified}`}
|
|
57
|
-
file={upload.file}
|
|
58
|
-
status={upload.progress < 100 ? 'uploading' : 'done'}
|
|
59
|
-
progress={upload.progress}
|
|
60
|
-
onRemove={() => setUploads(list => list.filter(item => item !== upload))}
|
|
61
|
-
/>
|
|
62
|
-
))}
|
|
63
|
-
</div>
|
|
64
|
-
)}
|
|
65
|
-
</Field>
|
|
66
|
-
)
|
|
67
|
-
}
|