-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: composable primitive components #947
base: main
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
1 Skipped Deployment
|
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:
I think this is plenty in the examples, i'd potentially want to add an example
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? |
I guess not, perhaps a leading section with the level of theming possible with links to respective section could be sufficient |
WalkthroughThe changes introduce a new upload functionality across multiple components in the application. Key additions include the Changes
Possibly related PRs
Suggested reviewers
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
I'm currently refactoring the prebuilt components to use the primitives. For the dropzone, you can get the 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 <Primitive.Dropzone className={({ dropzone: { isDragActive }) => "your class"} /> This feels a bit hacky imo as it still means you can't use <Primitive.Dropzone
dropzone={dropzone}
onDropzoneChange={setDropzone}
className={`${dropzone.isDragActive ? "bg-blue-500/10" : ""`}
/> |
We hope to be able to get this in a minor release, meaning no breaking changes so 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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Outside diff range and nitpick comments (2)
packages/react/src/components/primitive/root.tsx (2)
122-166
: LGTM: Well-defined types with a minor suggestionThe type definitions are well-structured and provide good type safety. However, there's a TODO comment indicating that documentation is needed for
RootPrimitiveComponentProps
.Consider adding the documentation for
RootPrimitiveComponentProps
as indicated by the TODO comment. This will improve the overall clarity and usability of the component.
168-264
: LGTM: Well-implemented Root component with a minor suggestionThe
Root
component is well-implemented, handling various aspects of file uploads efficiently. The use of hooks for state management and the memoization of theuploadFiles
function are good practices.However, there's a minor improvement that can be made in the paste handling logic:
In the paste handling logic, consider using the
filesToUpload
variable instead offiles
when callinguploadFiles
:- if (mode === "auto") void uploadFiles(files); + if (mode === "auto") void uploadFiles(filesToUpload);This ensures that the newly pasted files are included in the upload when in auto mode.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (5)
- packages/react/src/components/button.tsx (2 hunks)
- packages/react/src/components/dropzone.tsx (2 hunks)
- packages/react/src/components/primitive/button.tsx (1 hunks)
- packages/react/src/components/primitive/dropzone.tsx (1 hunks)
- packages/react/src/components/primitive/root.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/react/src/components/primitive/button.tsx
🧰 Additional context used
🪛 Biome
packages/react/src/components/button.tsx
[error] 164-164: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 165-165: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
packages/react/src/components/dropzone.tsx
[error] 154-154: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 155-155: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
🔇 Additional comments (11)
packages/react/src/components/primitive/root.tsx (6)
1-74
: LGTM: Well-structured imports and type definitionsThe imports and type definitions are comprehensive and well-organized. The
PrimitiveContextValues
type provides a clear structure for managing file upload state and functionality.
76-96
: LGTM: Well-implemented context and merge providerThe
PrimitiveContext
andPrimitiveContextMergeProvider
are well-implemented. The merge provider correctly checks for nested usage and properly merges new values with the existing context.
98-105
: LGTM: Well-implemented custom hookThe
usePrimitiveValues
hook is well-implemented with proper error handling. It ensures the hook is used within the correct provider context and provides a descriptive error message.
107-120
: LGTM: Flexible and well-implemented PrimitiveSlot componentThe
PrimitiveSlot
component is well-implemented, handling both function and non-function children correctly. It provides a good abstraction for rendering children with context values and includes a default fallback.
266-309
: LGTM: Well-structured context provision and component renderingThe creation of
primitiveValues
and the component's return structure are well-implemented. ThePrimitiveContext.Provider
ensures that all child components have access to the necessary context values, and the use ofPrimitiveSlot
for rendering children is a good pattern for flexible component composition.
1-309
: Excellent implementation with minor suggestions for improvementOverall, this file implements a robust and flexible file upload component architecture. The use of React context, custom hooks, and well-defined types provides a clean and maintainable structure. The implementation follows good React practices and offers a high degree of customization.
Key strengths:
- Well-structured context and provider implementation
- Comprehensive type definitions for improved type safety
- Flexible rendering with the PrimitiveSlot component
- Efficient state management using React hooks
Minor suggestions for improvement:
- Add documentation for RootPrimitiveComponentProps as noted in the TODO comment
- Update the paste handling logic to use the most recent file list when uploading
Great job on creating a solid foundation for file upload functionality!
packages/react/src/components/dropzone.tsx (4)
17-17
: LGTM: Introduction of Primitive componentsThe import of
Primitive
components aligns well with the PR objective of introducing unstyled composable components. This change sets the foundation for a more modular and flexible component structure.
106-120
: LGTM: Improved component structure using PrimitivesThe refactoring to use
Primitive.Root
andPrimitive.Dropzone
components significantly improves the overall structure and composability of theUploadDropzone
. The render prop pattern inPrimitive.Dropzone
provides a flexible way to access and utilize various state properties and functions.This new structure aligns perfectly with the PR objectives of creating unstyled composable components. It separates concerns effectively, allowing for easier customization and maintenance.
165-261
: LGTM: Well-structured rendering with flexible stylingThe rendering structure of the
UploadDropzone
component is well-organized and makes excellent use of the new Primitive components. Here are some notable points:
- The combination of hardcoded classes and dynamic styles from the
appearance
prop provides a good balance between default styling and customization options.- The use of the
cn
function for class name merging is a good practice, allowing for easy combination of multiple class sources.- The component structure is semantic and accessible, with appropriate ARIA attributes and state indicators.
- The styling approach allows for easy theming and customization without compromising the core functionality.
This implementation aligns well with the PR objectives of creating composable and customizable components.
Line range hint
1-263
: Overall assessment: Excellent refactoring towards composable componentsThe changes in this file represent a significant step towards achieving the PR's objective of introducing unstyled composable components. The refactoring of the
UploadDropzone
component to use Primitive components and render props has resulted in a more flexible, maintainable, and customizable structure.Key improvements:
- Introduction of Primitive components for better composability.
- Use of render props for flexible state management and rendering.
- Improved styling approach with a balance of default and customizable styles.
The suggested minor improvements (refining type casting and simplifying the switch statement) will further enhance the code quality. Overall, this refactoring sets a solid foundation for the new component architecture.
🧰 Tools
🪛 Biome
[error] 154-154: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 155-155: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
packages/react/src/components/button.tsx (1)
Line range hint
17-207
: Excellent Refactor Enhancing Modularity and ReadabilityThe refactoring of the
UploadButton
component to utilize thePrimitive
components significantly improves code modularity and separation of concerns. By leveragingPrimitive.Root
,Primitive.Button
,Primitive.AllowedContent
, andPrimitive.ClearButton
, the codebase becomes more maintainable and extensible. The changes enhance readability and align with best practices in component-based architecture.🧰 Tools
🪛 Biome
[error] 164-164: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 165-165: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
FYI - Just merged #980 |
Hi, I'm quite busy at the moment but I'm planning to get this PR ready with documentation by next week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (7)
packages/react/src/components/index.tsx (1)
63-65
: Add JSDoc documentation for the new functionThis function is a key part of the new primitive components feature. Consider adding JSDoc documentation to describe its purpose, parameters, and return value.
Example documentation:
/** * Generates typed primitive components for file uploads * @param opts - Optional configuration for the upload components * @returns Object containing typed primitive components (Root, Dropzone, Button, AllowedContent) */examples/minimal-appdir/src/app/page.tsx (3)
Line range hint
31-73
: Consider refactoring existing components to use primitives.Based on the PR discussion, the existing
UploadButton
andUploadDropzone
components should be refactored to use the new primitive components to avoid code duplication and maintain consistency.Consider replacing these implementations with the new primitive components:
- <UploadButton - endpoint={(routeRegistry) => routeRegistry.videoAndImage} - onClientUploadComplete={(res) => { - console.log(`onClientUploadComplete`, res); - alert("Upload Completed"); - }} - onUploadBegin={() => { - console.log("upload begin"); - }} - config={{ appendOnPaste: true, mode: "manual" }} - /> - <UploadDropzone - endpoint={(routeRegistry) => routeRegistry.videoAndImage} - onUploadAborted={() => { - alert("Upload Aborted"); - }} - onClientUploadComplete={(res) => { - console.log(`onClientUploadComplete`, res); - alert("Upload Completed"); - }} - onUploadBegin={() => { - console.log("upload begin"); - }} - /> + <UT.Root + endpoint="videoAndImage" + onClientUploadComplete={(res) => { + console.log(`onClientUploadComplete`, res); + alert("Upload Completed"); + }} + onUploadBegin={() => { + console.log("upload begin"); + }} + > + <UT.Button config={{ appendOnPaste: true, mode: "manual" }}> + {({ isUploading }) => ( + isUploading ? "Uploading" : "Upload file" + )} + </UT.Button> + <UT.Dropzone> + {({ dropzone, isUploading }) => ( + // Your dropzone UI here + )} + </UT.Dropzone> + </UT.Root>
84-110
: Extract dropzone styles to maintain consistency.The inline styles for the dropzone should be extracted to maintain consistency across the application and improve maintainability.
Consider creating a shared styles object or using a styling solution:
const dropzoneStyles = { container: { borderWidth: 2, borderStyle: "dashed", padding: 16, }, active: { borderColor: "#2563f5", }, inactive: { borderColor: "#11182725", }, };
74-83
: Add error handling for upload callbacks.The upload callbacks should include error handling to provide better user feedback.
<UT.Root endpoint="videoAndImage" onClientUploadComplete={(res) => { console.log(`onClientUploadComplete`, res); alert("Upload Completed"); }} onUploadBegin={() => { console.log("upload begin"); }} + onUploadError={(error) => { + console.error("Upload failed:", error); + alert("Upload failed: " + error.message); + }} >packages/react/src/components/button.tsx (1)
109-204
: Well-structured component with clear separation of concerns.The component's architecture effectively separates rendering logic into distinct functions (
renderAllowedContent
,renderClearButton
,renderButton
), making the code more maintainable and easier to test. The use of primitive components aligns with the PR's goal of creating composable components.Consider extracting the style-related logic into a custom hook to further improve separation of concerns:
function useButtonStyles(state: string, styleFieldArg: ButtonStyleFieldCallbackArgs) { const buttonClassName = cn( "group relative flex h-10 w-36...", state === "disabled" && "cursor-not-allowed bg-blue-400", // ... other state-based styles ); return { buttonClassName }; }🧰 Tools
🪛 Biome
[error] 164-164: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 165-165: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
packages/react/src/components/dropzone.tsx (2)
206-220
: Enhance accessibility for the file input labelThe label could benefit from additional accessibility attributes to improve screen reader support.
Apply these accessibility enhancements:
<label className={cn( "relative mt-4 flex w-64 cursor-pointer items-center justify-center text-sm font-semibold leading-6 text-gray-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-blue-600 focus-within:ring-offset-2 hover:text-blue-500", ready ? "text-blue-600" : "text-gray-500", styleFieldToClassName(appearance?.label, styleFieldArg), )} style={styleFieldToCssObject(appearance?.label, styleFieldArg)} data-ut-element="label" data-state={state} + role="button" + aria-controls="file-upload" + aria-describedby="file-upload-description" > {contentFieldToContent(content?.label, styleFieldArg) ?? (ready ? `Choose ${options.multiple ? "file(s)" : "a file"} or drag and drop` : `Loading...`)} + <span id="file-upload-description" className="sr-only"> + {`Click to select ${options.multiple ? "files" : "a file"} or drag and drop them here`} + </span> </label>
17-17
: Add JSDoc documentation for the Primitive moduleThe PR objectives mention that documentation is pending. Since this is a significant refactor introducing new primitive components, comprehensive JSDoc documentation would be valuable for developers.
Would you like me to help generate the JSDoc documentation for the Primitive module and its components?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (7)
- examples/minimal-appdir/src/app/page.tsx (2 hunks)
- examples/minimal-pagedir/src/pages/index.tsx (2 hunks)
- examples/with-clerk-appdir/src/app/page.tsx (2 hunks)
- examples/with-clerk-pagesdir/src/pages/index.tsx (2 hunks)
- packages/react/src/components/button.tsx (2 hunks)
- packages/react/src/components/dropzone.tsx (2 hunks)
- packages/react/src/components/index.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- examples/minimal-pagedir/src/pages/index.tsx
- examples/with-clerk-appdir/src/app/page.tsx
🧰 Additional context used
🪛 Biome
packages/react/src/components/button.tsx
[error] 164-164: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 165-165: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
packages/react/src/components/dropzone.tsx
[error] 154-154: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 155-155: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
🔇 Additional comments (10)
examples/with-clerk-pagesdir/src/pages/index.tsx (2)
4-4
: Consider removing older component imports.Since you're implementing the new composable UT components, consider removing the older
UploadButton
andUploadDropzone
imports to avoid duplication and potential confusion.
39-76
: Well-structured implementation with good practices!
- Good use of render prop pattern for state management
- Proper implementation of basic styling with dynamic drag state
- Semantic HTML through 'as' prop usage
packages/react/src/components/index.tsx (3)
17-18
: LGTM: New imports are well-structuredThe imports are properly organized and align with the PR's objective of introducing primitive components.
68-73
: Consider using a type-safe alternative toas any
The type casting with
as any
reduces type safety. Consider using a more type-safe approach:
74-74
: Verify type safety of primitive components spreadThe spread operator on primitives might need explicit typing to ensure type safety of the returned components.
✅ Verification successful
Let me try to find the correct path and examine the primitive components and their types.
Let me try a different approach to find and examine the components and their type definitions.
Let me check the primitive components' exports and their type definitions.
Based on the examination of the code, I can now provide a final response regarding the type safety of primitive components spread.
Primitive components are properly typed and safe to spread
The spread of primitives in the
generateUploadPrimitives
function is type-safe because:
- All primitive components are properly typed with TypeScript and export their types
- The primitive components (Root, Button, Dropzone, AllowedContent, ClearButton) all use proper type definitions and generic constraints
- The spread operation preserves the type information as the primitives are imported and re-exported with their original types
The only modification in the spread is the
Root
component which is explicitly typed with the generic parameters<TRouter, TEndpoint>
, ensuring type safety throughout the component hierarchy.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Check if primitives export has proper type definitions ast-grep --pattern 'export { $_ } from "./primitive"' "./packages/react/src/components/primitive/index.ts"Length of output: 204
Script:
#!/bin/bash # First, let's find the primitive components file fd primitive --type f # Then let's examine the content of any relevant files fd primitive --type f --exec cat {}Length of output: 63
Script:
#!/bin/bash # Let's first look at the directory structure around the file in question ls -R packages/react/src/components/ # Let's examine the content of the index.tsx file to understand the primitives cat packages/react/src/components/index.tsx # Let's also search for any type definitions related to these components rg -t typescript "type.*primitive" packages/react/src/Length of output: 3297
Script:
#!/bin/bash # Check the primitive components' exports cat packages/react/src/components/primitive/index.tsx # Look at the implementation of individual primitive components cat packages/react/src/components/primitive/root.tsx cat packages/react/src/components/primitive/button.tsx cat packages/react/src/components/primitive/dropzone.tsx cat packages/react/src/components/primitive/allowed-content.tsx cat packages/react/src/components/primitive/clear-button.tsxLength of output: 24506
examples/minimal-appdir/src/app/page.tsx (1)
7-7
: LGTM: Import statement is correctly placed.The
UT
import for primitive components is properly grouped with other uploadthing imports.packages/react/src/components/button.tsx (3)
17-17
: LGTM: Primitive components import aligns with PR objectives.The introduction of primitive components supports the goal of creating composable, unstyled components.
164-165
:⚠️ Potential issueRemove redundant case clauses in the switch statement.
The
case "disabled"
andcase "ready"
clauses are unnecessary as they fall through to thedefault
case.Apply this diff to remove the redundant cases:
- case "disabled": - case "ready": default: { if (options.mode === "manual" && files.length > 0) { return `Upload ${files.length} file${files.length === 1 ? "" : "s"}`;Likely invalid or redundant comment.
🧰 Tools
🪛 Biome
[error] 164-164: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 165-165: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
86-92
:⚠️ Potential issueConsider using a more type-safe approach for props destructuring.
The current implementation uses type casting with
unknown
andany
, which bypasses TypeScript's type checking. This could hide potential type errors.Consider refactoring to use proper type inference:
- } = props as unknown as UploadButtonProps<TRouter, TEndpoint> & - UploadThingInternalProps; + } = props as UploadButtonProps<TRouter, TEndpoint> & UploadThingInternalProps;Likely invalid or redundant comment.
packages/react/src/components/dropzone.tsx (1)
93-101
: Consider removing the deprecatedonDrop
prop in next major versionThe
onDrop
prop is marked as deprecated in favor ofonChange
. Since this is a significant refactor, it might be a good time to plan for its removal in the next major version to maintain API clarity.Let's check for usage of the deprecated prop:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
🧹 Outside diff range and nitpick comments (4)
packages/react/src/components/primitive/root.tsx (1)
74-92
: Enhance error message for better debuggingThe error message could be more descriptive to help developers understand the component hierarchy requirement.
- "<PrimitiveContextMergeProvider> must be used within a <UT.Root>", + "<PrimitiveContextMergeProvider> must be used within a <UT.Root> component. Ensure this component is rendered as a child of <UT.Root>.",packages/react/src/components/dropzone.tsx (2)
101-102
: Consider using a more type-safe approach for internal propsThe type casting
as unknown as
bypasses TypeScript's type checking. While this works, it could be made more type-safe.Consider defining a union type for the props:
type UploadDropzoneFullProps<TRouter extends FileRouter, TEndpoint extends keyof TRouter> = UploadDropzoneProps<TRouter, TEndpoint> & Partial<UploadThingInternalProps>;Then use it in the component signature:
export function UploadDropzone<TRouter extends FileRouter, TEndpoint extends keyof TRouter>( props: FileRouter extends TRouter ? ErrorMessage<"You forgot to pass the generic"> : UploadDropzoneFullProps<TRouter, TEndpoint> )
232-249
: Enhance button accessibilityWhile the button implementation is functional, it could benefit from improved accessibility.
Consider adding these accessibility enhancements:
<Primitive.Button as="button" + role="button" + aria-label={`Upload ${files.length} file${files.length === 1 ? '' : 's'}`} + aria-busy={state === 'uploading'} className={cn( // ... existing classes ... )} // ... other props ... > {getUploadButtonContents()} </Primitive.Button>packages/react/src/components/primitive/dropzone.tsx (1)
49-51
: Add migration guidance for deprecated prop.The
onFilesDropped
prop is marked as deprecated, but there's no clear guidance on how to migrate to the newonFilesChange
prop in<UT.Root>
. Consider adding a migration example in the JSDoc comment.* @param acceptedFiles - The files that were accepted. - * @deprecated Use `onFilesChange` in `<UT.Root>` + * @deprecated Use `onFilesChange` in `<UT.Root>`. Example: + * ```tsx + * <UT.Root onFilesChange={(files) => console.log(files)}> + * <UT.Dropzone /> + * </UT.Root> + * ``` */
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (7)
- packages/react/src/components/button.tsx (2 hunks)
- packages/react/src/components/dropzone.tsx (2 hunks)
- packages/react/src/components/index.tsx (2 hunks)
- packages/react/src/components/primitive/button.tsx (1 hunks)
- packages/react/src/components/primitive/clear-button.tsx (1 hunks)
- packages/react/src/components/primitive/dropzone.tsx (1 hunks)
- packages/react/src/components/primitive/root.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/react/src/components/primitive/button.tsx
🧰 Additional context used
🪛 Biome
packages/react/src/components/button.tsx
[error] 166-166: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 167-167: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
packages/react/src/components/dropzone.tsx
[error] 147-147: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 148-148: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
packages/react/src/components/primitive/clear-button.tsx
[error] 35-35: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
🔇 Additional comments (9)
packages/react/src/components/primitive/clear-button.tsx (2)
40-48
: LGTM! Well-implemented type definitions.The type definitions and exports are well-structured:
- Proper typing for polymorphic components
- Correct usage of forwardRefWithAs
- Clear internal type definitions
21-21
: Verify primitive context integration.Let's verify that the ClearButton component is properly integrated with other primitive components.
✅ Verification successful
Let me gather more information to verify the primitive context integration properly.
Let me check one more aspect to verify the Root component's implementation and provider pattern.
Based on the gathered information, I can now provide a final response:
ClearButton is properly integrated with primitive system
The verification shows that:
- ClearButton correctly uses
usePrimitiveValues
hook which enforces proper context usage- The component is properly exported through the primitive index
- PrimitiveContext is properly implemented with error boundaries for usage outside Root
- The context provides necessary values (
setFiles
,state
) that ClearButton consumes- ClearButton is being used correctly within the Button component's implementation
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for proper primitive context usage and integration # Test 1: Verify ClearButton is used within Root component context echo "Checking for proper context usage..." ast-grep --pattern 'const { $$ } = usePrimitiveValues("ClearButton")' # Test 2: Check for Root component imports where ClearButton is used echo "Checking for proper component composition..." rg -l "import.*ClearButton" | xargs -I {} rg "import.*Root.*from.*primitive/root" {}Length of output: 344
Script:
#!/bin/bash # Let's check the implementation and usage patterns more thoroughly # Check the ClearButton implementation echo "=== ClearButton Implementation ===" rg -A 10 "export.*ClearButton" packages/react/src/components/primitive/clear-button.tsx # Check where ClearButton is being used echo -e "\n=== ClearButton Usage ===" rg "ClearButton" -A 3 # Check the usePrimitiveValues hook implementation echo -e "\n=== usePrimitiveValues Implementation ===" rg -A 5 "export.*usePrimitiveValues" # Check for Root component provider pattern echo -e "\n=== Root Provider Pattern ===" rg -A 5 "export.*Root.*Provider"Length of output: 5358
Script:
#!/bin/bash # Check the Root component implementation and context setup # Check the Root component and PrimitiveContext implementation echo "=== Root Component and Context Implementation ===" rg -A 10 "PrimitiveContext" packages/react/src/components/primitive/root.tsx # Check where Root component is being used echo -e "\n=== Root Component Usage ===" rg -l "import.*Root.*from.*primitive" | xargs -I {} rg "Root.*Provider|<Root|<UT\.Root" {} # Check if ClearButton is properly exported through the primitive index echo -e "\n=== Primitive Component Exports ===" cat packages/react/src/components/primitive/index.tsxLength of output: 2801
packages/react/src/components/index.tsx (2)
17-18
: LGTM! Clean import structure.The imports are well-organized, using a namespace import for primitives and a specific type import for better type clarity.
66-70
: Verify all required peer dependencies are checked.The peer dependency check is crucial for runtime stability. Let's verify all required dependencies are validated:
✅ Verification successful
Let me gather more information about the peer dependencies by checking the imports and dependencies from the shared package.
Let me check the React package's package.json and the actual implementation of the peer dependency check.
Based on the gathered information, I can now provide the final response:
Peer dependency checks are correctly implemented
The current implementation correctly validates the critical peer dependency:
- The package has three peer dependencies:
next
(optional),react
, anduploadthing
- The
warnIfInvalidPeerDependency
function is specifically checking the version compatibility ofuploadthing
, which is the most critical dependency for the package's functionality- The
next
dependency is marked as optional inpeerDependenciesMeta
, so it doesn't require validation- React version compatibility is typically handled by the package manager itself during installation
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check if all peer dependencies in package.json are validated in the code # Get peer dependencies from package.json echo "Peer dependencies from package.json:" jq '.peerDependencies' package.json # Find all warnIfInvalidPeerDependency calls echo -e "\nPeer dependency checks in code:" rg "warnIfInvalidPeerDependency" -A 3Length of output: 2996
Script:
#!/bin/bash # Check shared package's peerDependencies and imports echo "Checking @uploadthing/shared package.json:" cat packages/shared/package.json echo -e "\nChecking imports in components/index.tsx:" rg "^import .* from" packages/react/src/components/index.tsx echo -e "\nChecking peerDependencies constant:" rg "peerDependencies = {" -A 5Length of output: 3056
Script:
#!/bin/bash # Check React package's package.json and peer dependency implementation echo "Checking @uploadthing/react package.json:" cat packages/react/package.json echo -e "\nChecking peerDependencies implementation:" cat packages/shared/src/utils.ts | grep -A 15 "export function warnIfInvalidPeerDependency"Length of output: 4129
packages/react/src/components/button.tsx (1)
166-167
:⚠️ Potential issueRemove redundant case clauses
The
case "disabled"
andcase "ready"
clauses are redundant as they fall through to thedefault
case. This adds unnecessary complexity to the code.switch (state) { case "readying": { return "Loading..."; } case "uploading": { if (uploadProgress >= 100) return <Spinner />; return ( <span className="z-50"> <span className="block group-hover:hidden"> {uploadProgress}% </span> <Cancel cn={cn} className="hidden size-4 group-hover:block" /> </span> ); } - case "disabled": - case "ready": default: { if (options.mode === "manual" && files.length > 0) { return `Upload ${files.length} file${files.length === 1 ? "" : "s"}`; } return `Choose File${options.multiple ? `(s)` : ``}`; } }Likely invalid or redundant comment.
🧰 Tools
🪛 Biome
[error] 166-166: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 167-167: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
packages/react/src/components/primitive/root.tsx (2)
30-70
: LGTM: Well-structured context type definitionThe
PrimitiveContextValues
type is comprehensive and well-documented, with clear separation of concerns between state management, file handling, and UI interactions.
94-101
: LGTM: Well-implemented context hookThe hook provides type-safe access to context values and includes helpful error messages for debugging.
packages/react/src/components/dropzone.tsx (1)
107-107
:⚠️ Potential issueRemove unsafe type casting
Using
as any
bypasses TypeScript's type checking. This could lead to runtime errors.Consider properly typing the root props or using a more specific type assertion if absolutely necessary:
-<Primitive.Root<TRouter, TEndpoint> {...(rootProps as any)}> +<Primitive.Root<TRouter, TEndpoint> {...rootProps}>Likely invalid or redundant comment.
packages/react/src/components/primitive/dropzone.tsx (1)
54-100
: LGTM! Well-structured component implementation.The
DropzoneFn
component follows React best practices with proper ref forwarding, context integration, and prop handling.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🧹 Outside diff range and nitpick comments (7)
examples/with-clerk-pagesdir/src/pages/index.tsx (1)
Line range hint
13-76
: Remove duplicate upload implementations.The example currently shows both old and new upload implementations. This could confuse users about which approach to follow. Consider:
- Removing the older
UploadButton
andUploadDropzone
components- Adding comments explaining the composable nature of the new implementation
- Adding links to documentation for the new components
examples/minimal-appdir/src/app/page.tsx (1)
84-93
: Consider extracting styles to a design system.While the inline styles work for a minimal example, consider extracting them to a design system or CSS module for better maintainability in a real application.
+// styles/dropzone.module.css +.dropzone { + margin-top: 24px; +} + +.container { + border-width: 2px; + border-style: dashed; + padding: 16px; +} + +.container[data-active="true"] { + border-color: #2563f5; +} + +.container[data-active="false"] { + border-color: #11182725; +}packages/react/src/components/primitive/root.tsx (2)
31-71
: Enhance type safety and documentation for PrimitiveContextValuesConsider these improvements to the type definitions:
- Define state as a constant union type for better type safety:
- state: "readying" | "ready" | "uploading" | "disabled"; + type UploadState = "readying" | "ready" | "uploading" | "disabled"; + state: UploadState;
- Add JSDoc for the dropzone property:
/** * @remarks This will be only defined when nested in a <Dropzone> + * @property isDragActive - Indicates whether a file is being dragged over the dropzone */ dropzone?: { isDragActive: boolean; };
155-163
: Add comprehensive documentation for RootPrimitiveComponentPropsThe TODO comment indicates missing documentation. Consider adding the following JSDoc:
+/** + * Props for the Root primitive component + * @template TRouter - The file router type + * @template TEndpoint - The endpoint key type + * @property {PrimitiveComponentChildren} children - Render function or React nodes + * @property {File[]} files - Controlled files state + * @property {(_: File[]) => void} onFilesChange - Callback for files state changes + */ export type RootPrimitiveComponentProps< TRouter extends FileRouter, TEndpoint extends keyof TRouter, > = UploadthingComponentProps<TRouter, TEndpoint> & { - // TODO: add @see comment for docs children?: PrimitiveComponentChildren; files?: File[]; onFilesChange?: (_: File[]) => void; };docs/src/app/(docs)/concepts/theming/page.mdx (3)
518-526
: Enhance setup instructions for better clarity.Consider the following improvements to make the setup instructions more robust:
- Add explicit type imports
- Use absolute paths instead of the
~
alias which might not work in all setups- Add context about file placement
```ts {{ title: 'src/utils/uploadthing.ts' }} +import type { FileRouter } from "uploadthing/next"; import { generateUploadPrimitives } from "@uploadthing/react"; -import type { OurFileRouter } from "~/server/uploadthing"; +import type { OurFileRouter } from "@/server/uploadthing"; export const UT = generateUploadPrimitives<OurFileRouter>();Add a note explaining where this file should be placed in the project structure and how it relates to the server-side configuration. --- `528-531`: **Enhance usage documentation with complete API reference.** The current overview is too brief. Consider adding: 1. A comprehensive list of all available state properties 2. TypeScript interfaces for the child function parameters Add a code block showing the TypeScript interfaces: ```ts type UploadState = "ready" | "uploading" | "error"; interface ChildFunctionProps { files: File[]; state: UploadState; dropzone?: { isDragActive: boolean; // ... other dropzone properties }; // ... document other available properties }
555-563
: Enhance Button example with more features.The current example is too basic. Consider enhancing it to show:
- Error handling
- Upload progress
- File selection feedback
<UT.Root endpoint="mockRoute"> <UT.Button> - {({ state }) => state === "uploading" ? "Uploading" : "Upload file"} + {({ state, files, error, uploadProgress }) => { + if (error) return `Error: ${error.message}`; + if (state === "uploading") { + return `Uploading... ${uploadProgress}%`; + } + return files.length > 0 + ? `Upload ${files.length} files` + : "Select files"; + }} </UT.Button> + <UT.AllowedContent /> </UT.Root>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (8)
- docs/src/app/(docs)/concepts/theming/page.mdx (1 hunks)
- examples/minimal-appdir/src/app/page.tsx (2 hunks)
- examples/minimal-pagedir/src/pages/index.tsx (2 hunks)
- examples/with-clerk-appdir/src/app/page.tsx (2 hunks)
- examples/with-clerk-pagesdir/src/pages/index.tsx (2 hunks)
- packages/react/src/components/primitive/button.tsx (1 hunks)
- packages/react/src/components/primitive/clear-button.tsx (1 hunks)
- packages/react/src/components/primitive/root.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- examples/minimal-pagedir/src/pages/index.tsx
- examples/with-clerk-appdir/src/app/page.tsx
- packages/react/src/components/primitive/button.tsx
🧰 Additional context used
🪛 Biome
packages/react/src/components/primitive/clear-button.tsx
[error] 37-37: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
🔇 Additional comments (7)
packages/react/src/components/primitive/clear-button.tsx (1)
42-50
: LGTM! Well-structured type definitions and exports.The type definitions provide good type safety for the polymorphic component while maintaining proper ref forwarding.
examples/with-clerk-pagesdir/src/pages/index.tsx (1)
4-4
: Consider removing older component imports.Since you're introducing the new composable
UT
components, consider removing the olderUploadButton
andUploadDropzone
imports to avoid confusion and maintain a single pattern.examples/minimal-appdir/src/app/page.tsx (4)
7-7
: LGTM: Import of primitive components.The import of
UT
is correctly added alongside existing imports from the same module.
74-83
: LGTM: Root component configuration.The
UT.Root
component is properly configured with the endpoint and necessary callbacks, maintaining consistency with the existing implementation above.
104-107
: Utilize render props for better content customization.Based on the previous discussion,
AllowedContent
supports render props for content customization. Consider utilizing this capability for better flexibility.<UT.AllowedContent as="p" style={{ fontSize: 12, width: "fit-content" }} + children={({ fileTypes }) => ( + `Allowed types: ${fileTypes.join(", ")}` + )} />
74-111
: Verify coexistence with existing upload components.The new primitive components are added alongside existing
UploadButton
andUploadDropzone
. Ensure they don't interfere with each other's functionality.✅ Verification successful
Based on the search results, I can now provide a final response since I have enough information to verify the coexistence of components:
No conflicts between primitive and existing upload components
The primitive components (
UT.Root
,UT.Dropzone
, etc.) and the existing components (UploadButton
,UploadDropzone
) are designed to work independently and do not share any state or event handlers that could cause interference. Each component:
- Maintains its own local state for files and upload progress
- Has its own instance of
useUploadThing
/createUploadThing
hooks- Handles its own lifecycle events (
onClientUploadComplete
,onUploadBegin
, etc.)The components can be safely used together on the same page as demonstrated in multiple example implementations (e.g., minimal-appdir, minimal-pagedir).
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for potential conflicts between old and new components # Test: Search for any shared state or conflicting event handlers rg -A 5 'onClientUploadComplete|onUploadBegin|startUpload' # Test: Look for any shared DOM elements that might conflict ast-grep --pattern $'<(UploadButton|UploadDropzone|UT.Root)$$$'Length of output: 109022
docs/src/app/(docs)/concepts/theming/page.mdx (1)
514-517
:⚠️ Potential issueRemove or update the
as
prop reference.According to the PR objectives, the
as
prop hasn't been implemented yet. This documentation should either remove the reference or be updated once the implementation is finalized.✅ Verification successful
Let me try a different search strategy to verify the implementation of the
as
prop.
Based on the search results, I can now generate a final response. The results show that the
as
prop is actually implemented in the primitive components:
- Found in
packages/react/src/components/primitive/button.tsx
:{ children, onClick, as, ...props }: PrimitiveButtonProps<TTag>
- Found in
packages/react/src/components/primitive/clear-button.tsx
:{ children, onClick, as, ...props }: PrimitiveClearButtonProps<TTag>
- Found in
packages/react/src/components/primitive/allowed-content.tsx
:{ children, as, ...props }: PrimitiveAllowedContentProps<TTag>
Documentation about the
as
prop is accurateThe documentation correctly states that unstyled primitive components "can be assigned specific HTML tags through the
as
prop". This is implemented in the codebase's primitive components, making the documentation accurate and consistent with the implementation.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify if the `as` prop is implemented in the codebase rg -A 5 "as: (string|React\.ElementType|ComponentType)"Length of output: 57
Script:
#!/bin/bash # Search for type definitions or interfaces that might include the `as` prop rg -g "*.ts*" -A 3 "interface.*Props|type.*Props" # Search for any usage or implementation of `as` prop in components ast-grep --pattern 'const $_ = $$$as$$$' # Search for any direct references to `as` prop in the codebase rg "\bas\b.*prop"Length of output: 21761
}} | ||
ref={ref} | ||
> | ||
<PrimitiveSlot default="Clear" children={children} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use JSX children pattern instead of children prop.
The children
prop should be passed using JSX syntax rather than as a prop to follow React conventions.
-<PrimitiveSlot default="Clear" children={children} />
+<PrimitiveSlot default="Clear">{children}</PrimitiveSlot>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<PrimitiveSlot default="Clear" children={children} /> | |
<PrimitiveSlot default="Clear">{children}</PrimitiveSlot> |
🧰 Tools
🪛 Biome
[error] 37-37: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
onClick={(e) => { | ||
if (state === "disabled") return; | ||
onClick?.(e); | ||
setFiles([]); | ||
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add basic error handling for setFiles.
Consider adding basic error handling when clearing files to gracefully handle potential failures.
onClick={(e) => {
if (state === "disabled") return;
onClick?.(e);
- setFiles([]);
+ try {
+ setFiles([]);
+ } catch (error) {
+ console.error("Failed to clear files:", error);
+ }
}}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
onClick={(e) => { | |
if (state === "disabled") return; | |
onClick?.(e); | |
setFiles([]); | |
}} | |
onClick={(e) => { | |
if (state === "disabled") return; | |
onClick?.(e); | |
try { | |
setFiles([]); | |
} catch (error) { | |
console.error("Failed to clear files:", error); | |
} | |
}} |
<UT.Button as="button"> | ||
{state === "uploading" ? "Uploading" : "Upload file"} | ||
</UT.Button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance button loading state feedback.
The button's state indication could be more informative. Consider adding a loading spinner and disabled state during upload.
<UT.Button as="button">
- {state === "uploading" ? "Uploading" : "Upload file"}
+ {state === "uploading" ? (
+ <>
+ <LoadingSpinner className="mr-2" />
+ <span>Uploading...</span>
+ </>
+ ) : (
+ "Upload file"
+ )}
</UT.Button>
Committable suggestion was skipped due to low confidence.
const state = (() => { | ||
if (props.__internal_state) return props.__internal_state; | ||
|
||
const ready = | ||
props.__internal_ready ?? | ||
(props.__internal_state === "ready" || fileTypes.length > 0); | ||
|
||
if (fileTypes.length === 0 || !!props.disabled) return "disabled"; | ||
if (!ready) return "readying"; | ||
if (ready && !isUploading) return "ready"; | ||
return "uploading"; | ||
})(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Simplify state computation logic
The state computation can be simplified using early returns:
const state = (() => {
if (props.__internal_state) return props.__internal_state;
+ if (fileTypes.length === 0 || props.disabled) return "disabled";
const ready =
props.__internal_ready ??
(props.__internal_state === "ready" || fileTypes.length > 0);
- if (fileTypes.length === 0 || !!props.disabled) return "disabled";
if (!ready) return "readying";
- if (ready && !isUploading) return "ready";
- return "uploading";
+ return isUploading ? "uploading" : "ready";
})();
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const state = (() => { | |
if (props.__internal_state) return props.__internal_state; | |
const ready = | |
props.__internal_ready ?? | |
(props.__internal_state === "ready" || fileTypes.length > 0); | |
if (fileTypes.length === 0 || !!props.disabled) return "disabled"; | |
if (!ready) return "readying"; | |
if (ready && !isUploading) return "ready"; | |
return "uploading"; | |
})(); | |
const state = (() => { | |
if (props.__internal_state) return props.__internal_state; | |
if (fileTypes.length === 0 || props.disabled) return "disabled"; | |
const ready = | |
props.__internal_ready ?? | |
(props.__internal_state === "ready" || fileTypes.length > 0); | |
if (!ready) return "readying"; | |
return isUploading ? "uploading" : "ready"; | |
})(); |
startUpload(files, fileRouteInput).catch((e) => { | ||
if (e instanceof UploadAbortedError) { | ||
void onUploadAborted?.(); | ||
} else { | ||
throw e; | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance error handling in uploadFiles
Consider adding specific error handling for common upload scenarios:
startUpload(files, fileRouteInput).catch((e) => {
if (e instanceof UploadAbortedError) {
void onUploadAborted?.();
+ } else if (e instanceof TypeError) {
+ // Handle network errors
+ throw new Error(`Upload failed due to network error: ${e.message}`);
+ } else if (e.name === "QuotaExceededError") {
+ // Handle storage quota errors
+ throw new Error(`Upload failed due to storage quota: ${e.message}`);
} else {
throw e;
}
});
Committable suggestion was skipped due to low confidence.
export function PrimitiveSlot({ | ||
children, | ||
componentName, | ||
default: defaultChildren, | ||
}: { | ||
children: PrimitiveComponentChildren; | ||
componentName?: string; | ||
default?: React.ReactNode; | ||
}) { | ||
if (!children) return defaultChildren; | ||
return typeof children === "function" | ||
? children?.(usePrimitiveValues(componentName)) | ||
: children; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize PrimitiveSlot component with memoization
The component re-renders on every parent update. Consider using useMemo
for the function result:
export function PrimitiveSlot({
children,
componentName,
default: defaultChildren,
}: {
children: PrimitiveComponentChildren;
componentName?: string;
default?: React.ReactNode;
}) {
if (!children) return defaultChildren;
- return typeof children === "function"
+ return useMemo(() => (typeof children === "function"
? children?.(usePrimitiveValues(componentName))
- : children;
+ : children), [children, componentName]);
}
Committable suggestion was skipped due to low confidence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (3)
packages/react/src/components/index.tsx (1)
63-81
: Add JSDoc documentation for the new function.Consider adding JSDoc documentation to explain the purpose, usage, and return value of
generateUploadPrimitives
. This would help developers understand how to use the composable primitive components effectively.Example documentation:
/** * Generates unstyled composable upload components that can be customized. * @param opts - Optional configuration options for the upload components * @returns An object containing primitive components: Root, Dropzone, Button, and AllowedContent * @example * const UT = generateUploadPrimitives(); * * function Upload() { * return ( * <UT.Root endpoint="imageUploader"> * <UT.Dropzone> * {({ isUploading }) => ( * <UT.Button>Upload a file</UT.Button> * )} * </UT.Dropzone> * </UT.Root> * ); * } */docs/src/app/(docs)/concepts/theming/page.mdx (2)
22-23
: Fix grammatical issues in the introduction.There are a couple of grammatical issues that should be addressed:
- "These comes" should be "These come" (subject-verb agreement)
- Add a comma before "but" as it connects two independent clauses
-These comes with behavior built-in but you have full control over what's rendered when and where. +These come with behavior built-in, but you have full control over what's rendered when and where.🧰 Tools
🪛 LanguageTool
[grammar] ~22-~22: The verb ‘comes’ is singular. Did you mean: “this comes” or “These come”?
Context: ...tives](#unstyled-primitive-components). These comes with behavior built-in but you have ful...(SINGULAR_VERB_AFTER_THESE_OR_THOSE)
[uncategorized] ~23-~23: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...nts). These comes with behavior built-in but you have full control over what's rende...(COMMA_COMPOUND_SENTENCE)
527-612
: Consider enhancing the Unstyled Primitive Components documentation.While the current documentation provides a good foundation, consider adding:
- A complete list of available state properties that can be accessed in children functions
- Examples of manual mode usage for both Button and Dropzone
- Examples of using the ClearButton component
- TypeScript examples showing proper type inference
Would you like me to help generate these additional documentation sections?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
- docs/src/app/(docs)/concepts/theming/page.mdx (2 hunks)
- packages/react/src/components/index.tsx (2 hunks)
🧰 Additional context used
🪛 LanguageTool
docs/src/app/(docs)/concepts/theming/page.mdx
[grammar] ~22-~22: The verb ‘comes’ is singular. Did you mean: “this comes” or “These come”?
Context: ...tives](#unstyled-primitive-components). These comes with behavior built-in but you have ful...(SINGULAR_VERB_AFTER_THESE_OR_THOSE)
[uncategorized] ~23-~23: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...nts). These comes with behavior built-in but you have full control over what's rende...(COMMA_COMPOUND_SENTENCE)
[style] ~27-~27: Opting for a less wordy alternative here can improve the clarity of your writing.
Context: ...e-upload-thing) hook that allows you to not only customize the look but also have full control of the behavior. ## Uploa...(NOT_ONLY_ALSO)
🔇 Additional comments (3)
packages/react/src/components/index.tsx (2)
17-18
: LGTM! Clean import statements for new primitive components.The imports are well-structured and follow TypeScript best practices.
63-81
: Verify the primitive components implementation.Let's ensure all the primitive components mentioned in the PR objectives are properly implemented.
✅ Verification successful
All primitive components are properly implemented
The verification confirms that all required primitive components are properly implemented in the codebase:
Root
component (root.tsx
): Implements the core upload functionality, context provider, and state managementDropzone
component (dropzone.tsx
): Implements drag-and-drop functionality with proper file validationButton
component (button.tsx
): Implements file selection and upload trigger functionalityAllowedContent
component (allowed-content.tsx
): Implements content display for allowed file typesEach component follows best practices with proper TypeScript typing, client-side directive, and accessibility considerations. The implementation matches the PR objectives and maintains consistency across the component suite.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify the existence and implementation of primitive components # Test 1: Check if all primitive components exist echo "Checking for primitive component files..." fd --type f --glob "*.tsx" --glob "*.ts" . packages/react/src/components/primitive # Test 2: Verify the exports from primitive/index echo -e "\nChecking primitive exports..." rg --type ts "export.*from" packages/react/src/components/primitive/index.ts # Test 3: Look for component implementations echo -e "\nChecking component implementations..." for component in Root Dropzone Button AllowedContent; do echo -e "\nLooking for $component implementation..." ast-grep --pattern "export function $component" --pattern "export const $component" doneLength of output: 8324
Script:
#!/bin/bash # Let's verify each primitive component individually # Check Root component implementation echo "Checking Root component..." ast-grep --pattern 'export const Root = ' echo -e "\nChecking Dropzone component..." ast-grep --pattern 'export const Dropzone = ' echo -e "\nChecking Button component..." ast-grep --pattern 'export const Button = ' echo -e "\nChecking AllowedContent component..." ast-grep --pattern 'export const AllowedContent = ' # Check exports from primitive/index.ts echo -e "\nChecking primitive/index.ts contents..." cat packages/react/src/components/primitive/index.ts # Check individual component files echo -e "\nChecking individual component implementations..." for file in root.tsx dropzone.tsx button.tsx allowed-content.tsx; do echo -e "\nContents of packages/react/src/components/primitive/$file:" cat "packages/react/src/components/primitive/$file" doneLength of output: 24707
docs/src/app/(docs)/concepts/theming/page.mdx (1)
585-602
: Update Dropzone example for accuracy and completeness.The example needs several improvements:
- Remove
as
prop usage since it's not implemented yet (as mentioned in PR objectives)- Add error handling example
- Demonstrate more state properties
<UT.Root endpoint="mockRoute"> <UT.Dropzone> - {({ state }) => ( + {({ state, files, error }) => ( <div> <p>Drag and drop</p> + {error && <p style={{ color: 'red' }}>{error.message}</p>} + {files.length > 0 && ( + <p>Selected files: {files.map(f => f.name).join(', ')}</p> + )} - <UT.Button as="button"> + <UT.Button> {state === "uploading" ? "Uploading" : "Upload file"} </UT.Button> <UT.AllowedContent - as="p" style={{ fontSize: 12, width: "fit-content" }} /> </div> )} </UT.Dropzone> </UT.Root>
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
oras={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
Bug Fixes
Documentation
Refactor