You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I understand this is a feature request and questions should be posted in the Community Forum
I searched issues and couldn’t find anything (or linked relevant results below)
Problem
One size fits no one
Developers starting with Uppy are forced to choose between the full-featured, bundle size heavy, and non-customizable dashboard or the overly barebones drag-drop.
After years of speaking to developers on GitHub, the community forum, and with Transloadit customers – the reality seems to be that majority of people have their needs fall somewhere in between dashboard and drag-drop. Countless issues have been posted about them wanting mostly X but doing Y different for their use case.
@uppy/dashboard has tried to accommodate for some of these requests over the years which introduced overly specific "toggles", such as showLinkToFileUploadResult, showProgressDetails, hideUploadButton, hideRetryButton, hidePauseResumeButton, hideCancelButton, hideProgressAfterFinish, showRemoveButtonAfterComplete, disableStatusBar, disableInformer, and disableThumbnailGenerator.
Continuing down this path is not maintainable nor will we ever reach a set of "toggles" at this granularity level to support a wide range of use cases and unique requirements.
Fake promise of modular UI components
We built status-bar, progress-bar, image-editor, thumbnail-generator, informer, and provider-views as separate plugins, communicating on a first glance these are standalone components, but they are tightly coupled to dashboard. It's not impossible to use them separately, but this is discouraged, undocumented, and unclear which features won't work outside of dashboard.
Modern expectations
Since Uppy's conception 9 years ago the front-end landscape has significantly changed. Uppy is even built with a "vanilla" first approach because that was still popular at the time over front-end frameworks.
These days developers have high expectations for choosing a library. In a world where everything is component driven, OSS UI libraries are expected to offer truly composable building blocks, which are light on bundle size, and ideally with accessibility kept in mind.
For Uppy to stay relevant in the coming years a major chance is needed in how we let users built their uploading UI.
Solution
At a glance
Composable, headless Uppy UI components. These would be highly granular and without loosing support for our existing frameworks (Angular, Svelte, React, Vue).
It basically boils down to three things, which all work with declarative APIs:
Have "trigger" components, usually buttons
Have components which reveal on trigger
Using a component directly uses the HTML/styles defined us while passing children allows you to compose it yourself while maintaining the same logic
For example:
exportfunctionPopoverDemo(){return(<Popover><PopoverTrigger>{/* Not passing children would render a default button */}<Button>Open popover</Button></PopoverTrigger><PopoverContentclassName="w-80">{/* your content */}</PopoverContent></Popover>)}
Continuing with React as an example, it would mean instead of doing the all-or-nothing approach as we currently have:
...you would compose only UI elements you need. In the case of this pseudo-code example, the OS file picker, Google Drive, and an added file list with thumbnails.
functionComponent(){const[uppy]=React.useState(()=>newUppy().use(GoogleDrive));constfiles=useUppyState(uppy,(state)=>state.files);// Create a UI based on errorsconsterrors=useUppyState(uppy,(state)=>state.errors);// ...or toast error notificationsuseUppyEvent(uppy,"upload-error",createErrorToast);return(<UppyContextuppy={uppy}><ProvidersdefaultValue="add-files"><ProviderContentvalue="add-files">{/* Drop your files anywhere */}<DragDropArea><ProviderGrid><ProviderTriggervalue="my-device"><MyDeviceIcon/><Button>My Device</Button></ProviderTrigger><ProviderTriggervalue="google-drive">{/* Put your own icon, style it different, whatever */}<GoogleDriveIcon/><button>Google Drive</button></ProviderTrigger></ProviderGrid></DragDropArea></ProviderContent><ProviderContentvalue="google-drive">{/* Makes the child components aware which provider data to look for */}<RemoteProviderContextproviderId="GoogleDrive"><RemoteProvider><RemoteProviderLogin><RemoteProviderLoginTrigger><GoogleDriveIcon/><Button>Login</Button></RemoteProviderLoginTrigger></RemoteProviderLogin>{/* After successful OAuth */}<RemoteProviderContent>{/* * Table with checkboxes (multi-select), thumbnails, * and "select x" status bar. Should probably be broken up too * to make it customizable, but it's a bit harder because * of the abstracted complexity in here * (and I don't want to make this example too big) */}<RemoteFilesTable/></RemoteProviderContent></RemoteProvider></RemoteProviderContext></ProviderContent><ProviderContentvalue="file-list"><ProviderTriggervalue="add-files"><Button>Add more files</Button></ProviderTrigger><FileGrid>{files.map((file)=>(<FileCard><FileCardThumbnailfile={file}/><p>{file.name}</p></FileCard>))}</FileGrid><StatusBar>{/* Abstracts pre, uploading, and post-processing progresss */}<ProgressBarfiles={files}/><EstimatedTimefiles={files}/><PauseResumeButton/><CancelButton/></StatusBar></ProviderContent></Providers></UppyContext>);}
What would this mean?
The long-term vision would be that this could replace dashboard, drag-drop, status-bar, progress-bar, informer, provider-views, and file-input in favor of highly granular components and one or two preconfigured compositions by us. The first phase could mean keeping all plugins around and slowly building these components. But it's better to try to build a dashboard-like experience with these components to 1) dogfood yourself and see what's needed and 2) eventually replace the monolith dashboard with a preconfigured composition.
Challenges
Finding the right balance of granularity. You want to allow flexibility for almost all use cases but abstract enough to keep the integration code from exploding. And does every component have a default render if no children are provider? Or only some?
Supporting multiple frameworks. Creating headless UI libraries is already challenging but we also must maintain support for Angular, Svelte, React, and Vue.
Making internal state easily accessible. Let's say you want to create a table based on the remote files from Google Drive or a table for the files already added. You can't do this without looping over some state and creating rows yourself. In React we have useUppyState because it's hard to integrate/sync external state into the state of a framework. We don't have this for other frameworks. Uppy's state also tends to be clumsy. For instance, there is no way to know in which state (idle, uploading, error, post-processing, etc) Uppy is in because it's not a finite state machine. This limits UI building.
Potential technical approaches
Mitosis
Mitosis provides a unified development experience across all frameworks, enabling you to build components in a single codebase and compile them to React, Vue, Angular, Svelte, Solid, Alpine, Qwik, and more.
Pros
Single source of truth. No more manually writing framework wrappers for every component like we currently do.
Supports more frameworks than we currently do. We could target a bigger market for free.
Some big companies have created fairly complex design systems with it. Somewhat battle-tested, in other words.
Vendor lock-in. If this ever becomes unmaintained we fully bought into this and a different technical approach would be a big undertaking at that point.
Limitations. Naturally, if you cross compile to many frameworks you have constraints. Luckily, you can make custom plugins per framework in case you need an escape hatch.
Uncertainties
React has useSyncExternalStore (used in useUppyState) to subscribe to third-party state inside the React lifecycle but we don't have that for other frameworks. How would other frameworks sync state with Uppy's internal state when using mitosis?
HTML-first
Franken UI proves that you can turn shadcn/ui (React only) into a framework agnostic component library while maintaining the same flexibility. It's mostly just a styling wrapper (to match shadcn styles) around UIKit.
How that would like inside React for tabs component:
import'@uppy/components/switcher/index.js'import'@uppy/components/switcher/styles.js'functionComponent(){return(<><uluppy-tabs><li><ahref="#">Item</a></li><li><ahref="#">Item</a></li><li><ahref="#">Item</a></li></ul><divclass="uppy-tabs">{/* by default switches on the top elements in the same order */}<div>Hello!</div><div>Hello again!</div><div>Bazinga!</div></div></>);};
Pros
Works everywhere. Even if you don't use a front-end framework.
No vendor lock-in.
Single source of truth. You don't need any wrapper components, generated or manually, it's all just HTML and JS.
Potentially less work due to inspiration and/or using UIKit.
Cons
No first-class framework support. You have to learn and understand how this work with data attributes and CSS classes.
Most likely unforeseen limitations.
How to move forward from here
The only way to find out is creating some proof of concepts and see the limitations first hand.
The text was updated successfully, but these errors were encountered:
Initial checklist
Problem
One size fits no one
Developers starting with Uppy are forced to choose between the full-featured, bundle size heavy, and non-customizable
dashboard
or the overly barebonesdrag-drop
.After years of speaking to developers on GitHub, the community forum, and with Transloadit customers – the reality seems to be that majority of people have their needs fall somewhere in between
dashboard
anddrag-drop
. Countless issues have been posted about them wanting mostly X but doing Y different for their use case.@uppy/dashboard
has tried to accommodate for some of these requests over the years which introduced overly specific "toggles", such asshowLinkToFileUploadResult
,showProgressDetails
,hideUploadButton
,hideRetryButton
,hidePauseResumeButton
,hideCancelButton
,hideProgressAfterFinish
,showRemoveButtonAfterComplete
,disableStatusBar
,disableInformer
, anddisableThumbnailGenerator
.Continuing down this path is not maintainable nor will we ever reach a set of "toggles" at this granularity level to support a wide range of use cases and unique requirements.
Fake promise of modular UI components
We built
status-bar
,progress-bar
,image-editor
,thumbnail-generator
,informer
, andprovider-views
as separate plugins, communicating on a first glance these are standalone components, but they are tightly coupled todashboard
. It's not impossible to use them separately, but this is discouraged, undocumented, and unclear which features won't work outside ofdashboard
.Modern expectations
Since Uppy's conception 9 years ago the front-end landscape has significantly changed. Uppy is even built with a "vanilla" first approach because that was still popular at the time over front-end frameworks.
These days developers have high expectations for choosing a library. In a world where everything is component driven, OSS UI libraries are expected to offer truly composable building blocks, which are light on bundle size, and ideally with accessibility kept in mind.
For Uppy to stay relevant in the coming years a major chance is needed in how we let users built their uploading UI.
Solution
At a glance
Composable, headless Uppy UI components. These would be highly granular and without loosing support for our existing frameworks (Angular, Svelte, React, Vue).
Good examples of headless UI libraries:
It basically boils down to three things, which all work with declarative APIs:
For example:
Continuing with React as an example, it would mean instead of doing the all-or-nothing approach as we currently have:
...you would compose only UI elements you need. In the case of this pseudo-code example, the OS file picker, Google Drive, and an added file list with thumbnails.
What would this mean?
The long-term vision would be that this could replace
dashboard
,drag-drop
,status-bar
,progress-bar
,informer
,provider-views
, andfile-input
in favor of highly granular components and one or two preconfigured compositions by us. The first phase could mean keeping all plugins around and slowly building these components. But it's better to try to build a dashboard-like experience with these components to 1) dogfood yourself and see what's needed and 2) eventually replace the monolith dashboard with a preconfigured composition.Challenges
useUppyState
because it's hard to integrate/sync external state into the state of a framework. We don't have this for other frameworks. Uppy's state also tends to be clumsy. For instance, there is no way to know in which state (idle, uploading, error, post-processing, etc) Uppy is in because it's not a finite state machine. This limits UI building.Potential technical approaches
Mitosis
Pros
Cons
Uncertainties
useSyncExternalStore
(used inuseUppyState
) to subscribe to third-party state inside the React lifecycle but we don't have that for other frameworks. How would other frameworks sync state with Uppy's internal state when using mitosis?HTML-first
Franken UI proves that you can turn shadcn/ui (React only) into a framework agnostic component library while maintaining the same flexibility. It's mostly just a styling wrapper (to match shadcn styles) around UIKit.
How that would like inside React for tabs component:
Pros
Cons
How to move forward from here
The only way to find out is creating some proof of concepts and see the limitations first hand.
The text was updated successfully, but these errors were encountered: