uploadthing icon indicating copy to clipboard operation
uploadthing copied to clipboard

feat: composable primitive components

Open veloii opened this issue 1 year ago • 18 comments

This PR adds unstyled composable components through generateUploadPrimitives which returns:

  • <Root>
  • <Dropzone>
  • <Button>
  • <AllowedContent>

I've also added examples for minimal-appdir, minimal-pagedir, with-clerk-appdir, with-clerk-pagedir. I can add it to all the rest of the react ones if needed.

All these components accept parameters as children like ({ isUploading }) => <></>. There is no asChild or as={YourComponent} prop at the moment as I'm not sure which one to use. I haven't implemented the docs either as I'm not sure if the current styling page should be split up.

Summary by CodeRabbit

  • New Features

    • Introduced unstyled "primitive" upload components, including Root, Button, Dropzone, AllowedContent, and ClearButton, enabling full customization of upload interfaces.
    • Added new upload experiences to example apps and playground, showcasing drag-and-drop, manual and auto upload modes, and allowed file type displays.
    • Exposed a function to generate these primitives for use in custom applications.
  • Documentation

    • Expanded theming documentation to explain usage and customization of the new primitive components, with detailed examples.
  • Refactor

    • Refactored existing upload button and dropzone components to delegate logic to the new primitive components, simplifying their structure and improving flexibility.
  • Chores

    • Updated exports and internal utilities to support the new primitive upload system.

veloii avatar Sep 14 '24 18:09 veloii

⚠️ No Changeset found

Latest commit: 2552d058f3436cfabf268bda41908de07de7ff64

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

changeset-bot[bot] avatar Sep 14 '24 18:09 changeset-bot[bot]

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
docs-uploadthing ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 25, 2025 7:19pm
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
legacy-docs-uploadthing ⬜️ Ignored (Inspect) Visit Preview Apr 25, 2025 7:19pm

vercel[bot] avatar Sep 14 '24 18:09 vercel[bot]

I like how this is looking! I would probably add some basic styling to the examples, to at least identify the component, since right now, it kind of just looks like the page is broken, rather than there is another dropzone: image

I've also added examples for minimal-appdir, minimal-pagedir, with-clerk-appdir, with-clerk-pagedir. I can add it to all the rest of the react ones if needed.

I think this is plenty in the examples, i'd potentially want to add an example with-composable-components that shows off an example of what one could do with them, but I wouldn't block on this.

I haven't implemented the docs either as I'm not sure if the current styling page should be split up.

Tentatively I would say it doesn't need to be split for now, but cc @juliusmarminge -- thoughts?

markflorkowski avatar Sep 16 '24 17:09 markflorkowski

Tentatively I would say it doesn't need to be split for now, but cc @juliusmarminge -- thoughts?

Kinda feels like it should be split?


Should our default component use these primitives? A lot of code duplication if not?

juliusmarminge avatar Sep 16 '24 17:09 juliusmarminge

Kinda feels like it should be split?

How would you want it split? I felt like the sub sections in theming wasn't bad image

Should our default component use these primitives? A lot of code duplication if not?

This is a good point, and also makes sure that they stay in sync. I don't see why we couldn't.

markflorkowski avatar Sep 16 '24 17:09 markflorkowski

How would you want it split? I felt like the sub sections in theming wasn't bad

I guess not, perhaps a leading section with the level of theming possible with links to respective section could be sufficient

juliusmarminge avatar Sep 16 '24 22:09 juliusmarminge

Walkthrough

This update introduces a new set of unstyled "primitive" upload components for React, including Root, Button, Dropzone, AllowedContent, and ClearButton. These primitives are exposed through a new generateUploadPrimitives function, allowing developers to build fully custom upload flows with granular control over UI and behavior. Existing styled components (UploadButton, UploadDropzone) are refactored to delegate their internal logic to these primitives, simplifying their implementation. The documentation is expanded to explain the new primitives and provide usage examples. Example apps and playground code are updated to demonstrate and utilize the new primitives.

Changes

File(s) Change Summary
packages/react/src/components/primitive/root.tsx
button.tsx
dropzone.tsx
clear-button.tsx
allowed-content.tsx
Introduce primitive upload components: Root, Button, Dropzone, AllowedContent, ClearButton, and supporting context/state logic.
packages/react/src/components/button.tsx
dropzone.tsx
Refactor UploadButton and UploadDropzone to delegate upload logic and state to primitive components via render prop pattern.
packages/react/src/components/index.tsx
packages/react/src/index.ts
Export generateUploadPrimitives, update exports to include new primitives, and adjust dropzone export path.
packages/react/src/utils/useControllableState.ts Add a new hook for controlled/uncontrolled state management.
docs/src/app/(docs)/concepts/theming/page.mdx Expand documentation to cover the new primitive components, with usage examples and explanations.
examples/minimal-appdir/src/app/page.tsx
examples/minimal-pagedir/src/pages/index.tsx
examples/with-clerk-appdir/src/app/page.tsx
examples/with-clerk-pagesdir/src/pages/index.tsx
Update example apps to use and demonstrate the new upload primitives in their upload flows.
playground/app/layout.tsx Refactor navigation markup for clarity; not directly related to upload primitives.
playground/app/primitives/page.tsx Add a new page demonstrating a custom uploader built with the new primitives.
playground/components/text.tsx Add reusable text/heading components for playground UI.
playground/components/uploader.tsx Remove old upload helper usage; switch to using primitives via a local module.
playground/lib/uploadthing.ts Add a local bridge module exporting UT primitives, UTButton, and useUploadThing for playground use.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Root as UT.Root
    participant Dropzone as UT.Dropzone
    participant Button as UT.Button
    participant AllowedContent as UT.AllowedContent

    User->>Dropzone: Drag and drop or select file
    Dropzone->>Root: Notify files selected
    Root->>Button: Enable upload action
    User->>Button: Click to start upload
    Button->>Root: Trigger upload process
    Root->>Dropzone: Update progress/state
    Root->>AllowedContent: Show allowed file types
    Root->>User: Notify on upload completion

Possibly related PRs

  • pingdotgg/uploadthing#980: Fixes internal dropzone event handling and multiple file upload bugs, closely related to the dropzone logic refactored in this PR.
  • pingdotgg/uploadthing#1151: Refines upload progress granularity and styling in upload components, which are now built on the primitives introduced here.

Suggested reviewers

  • markflorkowski

[!WARNING] There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

Scope: all 3 workspace projects Progress: resolved 0, reused 0, downloaded 1, added 0 /tmp/eslint/packages/uploadthing:  ERR_PNPM_WORKSPACE_PKG_NOT_FOUND  In packages/uploadthing: "@uploadthing/mime-types@workspace:*" is in the dependencies but no package named "@uploadthing/mime-types" is present in the workspace

This error happened while installing a direct dependency of /tmp/eslint/packages/uploadthing

Packages found in the workspace:

✨ Finishing Touches
  • [ ] 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

coderabbitai[bot] avatar Sep 20 '24 19:09 coderabbitai[bot]

I'm currently refactoring the prebuilt components to use the primitives. For the dropzone, you can get the isDragActive property through the children as a parameter or if you want to change a className based on it you use the data-drag attribute (ex: data-[drag]:bg-blue-500/30). In the current prebuilt button style API the isDragActive property is passed a parameter for the class but we can't access it.

Exmaple:

<Primitive.Dropzone
  className={cn(
    "data-[drag]:bg-blue-600/10",
    className,
    // isDragActive isn't defined here
    styleFieldToClassName(appearance?.container, { isDragActive }),
  )}
  // or here
  style={styleFieldToCssObject(appearance?.container, { isDragActive )}
>
  {({ dropzone: { isDragActive } }) => /* we have it here */}
</Primitive.Dropzone>

Should the className and style property accept a parameter like?

<Primitive.Dropzone className={({ dropzone: { isDragActive }) => "your class"} />

This feels a bit hacky imo as it still means you can't use isDragActive to compute any other property (unless we did an Object.entries on the props and made a ts helper to convert all the normal attributes to accept functions too). Another way is with a controlled parameter? Imo this feels better.

<Primitive.Dropzone
  dropzone={dropzone}
  onDropzoneChange={setDropzone}
  className={`${dropzone.isDragActive ? "bg-blue-500/10" : ""`}
/>

veloii avatar Sep 20 '24 20:09 veloii

cc @juliusmarminge

veloii avatar Sep 21 '24 19:09 veloii

We hope to be able to get this in a minor release, meaning no breaking changes so styleFieldToClassName($props.appearance?.container, styleFieldArg) should if possible work as before.

Can we not just wrap in another div or something?

<Primitive.Dropzone>
  {({ dropzone: { isDragActive } }) => (
    <div
    className={cn(
      "data-[drag]:bg-blue-600/10",
      className,
      // isDragActive isn't defined here
      styleFieldToClassName(appearance?.container, { isDragActive }),
    )}
    // or here
    style={styleFieldToCssObject(appearance?.container, { isDragActive )}
   >
     ...
   </div>
</Primitive.Dropzone>

juliusmarminge avatar Sep 25 '24 08:09 juliusmarminge

Still needs to be implemented in the docs, but I just want to make sure the public facing api is all good first.

veloii avatar Sep 26 '24 20:09 veloii

Still needs to be implemented in the docs, but I just want to make sure the public facing api is all good first.

cc @juliusmarminge

veloii avatar Sep 28 '24 12:09 veloii

FYI - Just merged #980

juliusmarminge avatar Oct 07 '24 21:10 juliusmarminge

Hi, I'm quite busy at the moment but I'm planning to get this PR ready with documentation by next week.

veloii avatar Oct 09 '24 17:10 veloii

Sorry this took so long. I've fixed all the merge conflicts, but I'm not very experienced with documentation and lack the time at the moment so I've added some extremely basic examples for the docs. Except from that the PR is ready for review.

veloii avatar Oct 23 '24 18:10 veloii

Just played with this a bit and I love this - found some issues with pasting files. I'll try and have a look at fixing that when i got some time

juliusmarminge avatar Nov 26 '24 19:11 juliusmarminge

Thanks! In the meantime, should I fix the linting issues?

veloii avatar Nov 27 '24 20:11 veloii

[!WARNING] This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite. Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

juliusmarminge avatar Apr 25 '25 19:04 juliusmarminge