Skip to content
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(file-upload) #3832

Open
wants to merge 12 commits into
base: canary
Choose a base branch
from
Open

Conversation

Arian94
Copy link

@Arian94 Arian94 commented Oct 1, 2024

Closes #3772,

📝 Description

This will add the new long-awaited component called "File Upload".

🚀 New behavior

This adds a customizable and flexible component to upload files. It constitutes three sections:
Top Bar: It is to display information regarding number of allowed files, file sizes etc.
Items: A container for the files a user has selected from his/her device.
Buttons: A container for the buttons such as the browse, add or reset button etc.

Developers are allowed to customize all three sections. There are many props for modifications and/or using customizable parts in these sections.

💣 Is this a breaking change (Yes/No):

No

📝 Additional Information

Summary by CodeRabbit

  • New Features

    • Introduced a new "File Upload" component, allowing users to upload multiple files with customizable buttons.
    • Added new routes in the documentation for "File Upload" and several updated components.
    • Comprehensive documentation for the File Upload component, including installation instructions and usage examples.
  • Bug Fixes

    • Updated sitemap to reflect new URLs and modifications for better navigation.
  • Documentation

    • New README file for the @nextui-org/file-upload package detailing functionality and installation.
    • Added Storybook stories for the File Upload component to showcase various configurations.
  • Tests

    • Implemented unit tests for the File Upload component to ensure functionality and behavior under different conditions.

Copy link

changeset-bot bot commented Oct 1, 2024

⚠️ No Changeset found

Latest commit: 7ca7d22

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.

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

Copy link

vercel bot commented Oct 1, 2024

@Arian94 is attempting to deploy a commit to the NextUI Inc Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Oct 1, 2024

Walkthrough

The pull request introduces a new file upload component to the NextUI framework, along with comprehensive documentation and testing. It adds several new routes to the documentation site, including a dedicated section for the file upload feature. The implementation includes multiple React components for file upload functionalities, such as handling multiple files, custom buttons, and a top bar. Additionally, the sitemap has been updated to reflect new URLs related to the file upload documentation, and a new README file has been created for the @nextui-org/file-upload package.

Changes

File Path Change Summary
apps/docs/config/routes.json Added new route for "File Upload" and marked several existing routes as updated.
apps/docs/content/components/file-upload/controlled.ts Introduced App component with a promise-based function for file handling.
apps/docs/content/components/file-upload/file-item.ts Added App component to display individual file items.
apps/docs/content/components/file-upload/index.ts Created an index file to aggregate file upload related modules.
apps/docs/content/components/file-upload/multiple.ts Introduced App component for multiple file uploads.
apps/docs/content/components/file-upload/topbar.ts Added App component for file upload with a top bar.
apps/docs/content/components/file-upload/usage.ts Created an example component demonstrating file upload usage.
apps/docs/content/docs/components/file-upload.mdx Added documentation for the File Upload component, including usage examples and API details.
apps/docs/public/sitemap-0.xml Updated timestamps and added new URLs related to file upload documentation.
packages/components/file-upload/README.md Created README for @nextui-org/file-upload package detailing installation and functionality.
packages/components/file-upload/tests/file-upload.test.tsx Added unit tests for the FileUpload component covering rendering and state behaviors.
packages/components/file-upload/package.json Introduced package.json for the @nextui-org/file-upload package.
packages/components/file-upload/src/file-upload-item.tsx Added FileUploadItem component for displaying uploaded files.
packages/components/file-upload/src/file-upload-topbar.tsx Created FileUploadTopbar component for managing upload constraints.
packages/components/file-upload/src/file-upload.tsx Introduced FileUpload component for managing file uploads.
packages/components/file-upload/src/index.ts Aggregated exports for file upload components and types.
packages/components/file-upload/src/use-file-upload.ts Added useFileUpload custom hook for managing file uploads.
packages/components/file-upload/stories/file-upload.stories.tsx Created Storybook stories for demonstrating FileUpload component variations.
packages/components/file-upload/tsconfig.json Introduced TypeScript configuration for the file upload package.
packages/components/file-upload/tsup.config.ts Added build configuration for the file upload component.
packages/core/react/package.json Updated to include @nextui-org/file-upload as a dependency.
packages/core/theme/src/components/file-upload.ts Introduced fileUpload component for Tailwind CSS styling.
packages/core/theme/src/components/index.ts Exported file-upload component for broader accessibility.
apps/docs/content/components/file-upload/buttonsContainer.ts Added buttonsContainer component for file upload actions.
apps/docs/content/components/file-upload/customButtons.ts Introduced DeleteIcon and custom button functionalities for file uploads.

Assessment against linked issues

Objective Addressed Explanation
Ability to select the desired file format (3772)
Ability to upload single or multiple files (3772)

Possibly related PRs


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?

❤️ Share
🪧 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 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.

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.

@Arian94
Copy link
Author

Arian94 commented Oct 1, 2024

@wingkwong pls have a look.

@wingkwong wingkwong added this to the v2.5.0 milestone Oct 5, 2024
Copy link

vercel bot commented Oct 5, 2024

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

Name Status Preview Comments Updated (UTC)
nextui-docs-v2 ❌ Failed (Inspect) Nov 10, 2024 3:47pm
nextui-storybook-v2 ✅ Ready (Inspect) Visit Preview 💬 Add feedback Nov 10, 2024 3:47pm

Copy link
Member

@wingkwong wingkwong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. please add the documentation as well
  2. please cover all the test cases
  3. please cover all the cases in storybook

@Arian94 Arian94 marked this pull request as ready for review November 5, 2024 16:34
@Arian94 Arian94 requested a review from jrgarciadev as a code owner November 5, 2024 16:34
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🧹 Outside diff range and nitpick comments (39)
apps/docs/content/components/file-upload/usage.ts (2)

1-7: Enhance the example implementation for better documentation.

The current example is too basic and doesn't demonstrate practical usage. Consider:

  1. Adding TypeScript types for the files parameter
  2. Implementing a basic example in the onChange handler
  3. Including common use cases like file validation
-const App = `import {FileUpload} from "@nextui-org/react";
+const App = `import {FileUpload, FileUploadProps} from "@nextui-org/react";

 export default function App() {
+  const handleFileChange: FileUploadProps['onChange'] = (files) => {
+    if (files.length > 0) {
+      console.log('Selected files:', files);
+      // Example: Check file types
+      const validFiles = files.filter(file => 
+        file.type.startsWith('image/') || file.type === 'application/pdf'
+      );
+      console.log('Valid files:', validFiles);
+    }
+  };
+
   return (
-    <FileUpload onChange={(files) => { /* handle files */}} />
+    <FileUpload 
+      accept=".jpg,.png,.pdf"
+      maxSize={5242880} // 5MB
+      onChange={handleFileChange}
+    />
   );
 }`;

9-15: Consider using TypeScript extension for the example path.

Since NextUI supports TypeScript out of the box, consider using .tsx extension to better align with the TypeScript ecosystem.

 const react = {
-  "/App.jsx": App,
+  "/App.tsx": App,
 };

-export default {
-  ...react,
-};
+export default react;
apps/docs/content/components/file-upload/multiple.ts (2)

1-7: Enhance the example with proper types and error handling.

The example could be more comprehensive to better demonstrate the component's capabilities:

  1. Add TypeScript types for the files parameter
  2. Implement basic error handling
  3. Show commonly used props

Consider updating the example:

-const App = `import {FileUpload} from "@nextui-org/react";
+const App = `import {FileUpload, FileUploadProps} from "@nextui-org/react";

 export default function App() {
+  const handleChange: FileUploadProps['onChange'] = (files) => {
+    try {
+      console.log('Selected files:', files);
+      // Process files here
+    } catch (error) {
+      console.error('Error handling files:', error);
+    }
+  };
+
   return (
-    <FileUpload multiple onChange={(files) => { /* handle files */}} />
+    <FileUpload
+      multiple
+      onChange={handleChange}
+      maxFileSize={10 * 1024 * 1024} // 10MB
+      accept={".jpg,.png,.pdf"}
+      onError={(error) => console.error(error)}
+    />
   );
 }`;

13-15: Consider making the export more explicit.

While spreading the react object works, being explicit about what's being exported can improve code readability.

-export default {
-  ...react,
-};
+export default react;
apps/docs/content/components/file-upload/topbar.ts (2)

1-7: Enhance the example to be more comprehensive.

The current example is quite basic. Consider improving it by:

  1. Using a more standardized format for maxAllowedSize (e.g., "1MB" without space)
  2. Adding error handling and validation examples
  3. Demonstrating more props and customization options
-const App = `import {FileUpload, FileUploadTopbar} from "@nextui-org/react";
+const App = `import {FileUpload, FileUploadTopbar, FileUploadProps} from "@nextui-org/react";

 export default function App() {
+  const handleError = (error: Error) => {
+    console.log("Upload error:", error);
+  };
+
   return (
-    <FileUpload topbar={<FileUploadTopbar maxAllowedSize="1 MB" />} />
+    <FileUpload
+      topbar={
+        <FileUploadTopbar
+          maxAllowedSize="1MB"
+          allowedTypes={[".jpg", ".png"]}
+        />
+      }
+      onError={handleError}
+      maxFileSize={1024 * 1024} // 1MB in bytes
+    />
   );
 }`;

9-15: Improve the export structure and path format.

Consider the following improvements:

  1. Remove the leading slash from the path
  2. Use .tsx extension since this is a TypeScript example
  3. Simplify the export structure
 const react = {
-  "/App.jsx": App,
+  "App.tsx": App,
 };

-export default {
-  ...react,
-};
+export default react;
apps/docs/content/components/file-upload/index.ts (1)

9-17: Consider adding TypeScript type definitions.

While the code is functional, adding TypeScript interfaces would improve maintainability and provide better documentation of the expected shape of each imported module.

+interface FileUploadModule {
+  App: React.ComponentType;
+  getStaticProps?: () => Promise<{ props: Record<string, unknown> }>;
+}
+
+export const fileUploadContent: Record<string, FileUploadModule> = {
   usage,
   multiple,
   topbar,
   fileItem,
   button,
   buttons,
   controlled,
 };
packages/components/file-upload/src/index.ts (1)

6-8: Consider using a more explicit type export syntax.

While the current syntax is valid, using the more explicit export type syntax can make the type exports more obvious.

-export type {FileUploadProps} from "./file-upload";
-export type {FileUploadItemProps} from "./file-upload-item";
-export type {FileUploadTopbarProps} from "./file-upload-topbar";
+export type {FileUploadProps} from "./file-upload" type;
+export type {FileUploadItemProps} from "./file-upload-item" type;
+export type {FileUploadTopbarProps} from "./file-upload-topbar" type;
apps/docs/content/components/file-upload/button.ts (1)

17-23: Consider adding TypeScript types for better maintainability.

While the export structure is correct, adding TypeScript types would improve code maintainability and provide better IDE support.

Consider adding types:

+interface CodeExample {
+  [path: string]: string;
+}
+
-const react = {
+const react: CodeExample = {
  "/App.jsx": App,
};

-export default {
+export default: CodeExample {
  ...react,
};
apps/docs/content/components/file-upload/buttons.ts (1)

9-15: Improve button accessibility and code structure.

The buttons section could benefit from the following improvements:

  1. Add aria-labels for better accessibility
  2. Use the imported DeleteIcon for the reset button
  3. Improve code formatting for better readability
       buttons={(onBrowse, onAdd, onReset) => {
-        return <div>
-          <Button onClick={() => onBrowse()}><SearchIcon /></Button>
-          <Button onClick={() => onAdd()}>Add New File</Button>
-          <Button onClick={() => onReset()}>Remove All</Button>
-        </div>
+        return (
+          <div className="flex gap-2">
+            <Button 
+              onClick={onBrowse}
+              aria-label="Browse files"
+            >
+              <SearchIcon />
+            </Button>
+            <Button 
+              onClick={onAdd}
+              aria-label="Add new file"
+            >
+              Add New File
+            </Button>
+            <Button 
+              onClick={onReset}
+              aria-label="Remove all files"
+            >
+              <DeleteIcon />
+              Remove All
+            </Button>
+          </div>
+        );
       }}
packages/components/file-upload/src/file-upload-item.tsx (1)

5-9: Add documentation and improve type safety.

Consider these enhancements for better maintainability and type safety:

  1. Add JSDoc documentation for the interface and its props
  2. Consider using a more specific type for accepted files
  3. Make the callback more type-safe by using the File type
+/**
+ * Props for the FileUploadItem component
+ */
 export interface FileUploadItemProps extends HTMLNextUIProps<"div"> {
+  /** The file object to display */
   file: File;
-  onFileRemove: (name: string) => void;
+  /** Callback when file is removed */
+  onFileRemove: (file: File) => void;
+  /** Whether the remove button is disabled */
   isDisabled?: boolean;
 }
packages/core/theme/src/components/index.ts (1)

41-41: Consider maintaining alphabetical ordering of exports.

While the export statement for the new file-upload component is correctly formatted, it would be better to maintain alphabetical ordering for consistency and easier maintenance. Consider moving it between "divider" and "image" exports.

export * from "./date-picker";
-export * from "./file-upload";
export * from "./alert";
export * from "./drawer";
export * from "./divider";
+export * from "./file-upload";
export * from "./image";
packages/components/file-upload/package.json (1)

5-7: Enhance package discoverability with additional keywords

Consider adding more relevant keywords to improve package discoverability, such as: "nextui", "react", "upload", "component", "drag-drop", "file-input".

   "keywords": [
-    "file-upload"
+    "file-upload",
+    "nextui",
+    "react",
+    "upload",
+    "component",
+    "drag-drop",
+    "file-input"
   ],
packages/core/theme/src/components/file-upload.ts (2)

6-23: Fix copy-paste error in JSDoc comment

The JSDoc comment refers to "Card" component instead of "FileUpload".

-* Card **Tailwind Variants** component
+* FileUpload **Tailwind Variants** component

24-70: Consider enhancing the theme configuration

While the basic styling is good, consider these improvements for better user experience:

  1. Add hover/focus states for interactive elements
  2. Include responsive design classes
  3. Consider dark mode variants

Example enhancement for the base slot:

 base: [
   "flex",
   "flex-col",
   "relative",
   "overflow-hidden",
   "h-auto",
   "outline-none",
   "text-foreground",
   "box-border",
   "bg-content1",
+  "transition-opacity",
+  "dark:bg-content1-dark",
+  "sm:min-h-[200px]",
+  "md:min-h-[300px]",
   ...dataFocusVisibleClasses,
 ],
packages/components/file-upload/__tests__/file-upload.test.tsx (4)

8-18: Consider adding error case test coverage.

While the test setup is good, consider adding test cases for error scenarios such as:

  • File size limits
  • Invalid file types
  • Upload failures

Would you like me to provide examples of these test cases?


25-30: Strengthen ref testing.

Consider enhancing ref testing by:

  • Verifying ref properties
  • Testing ref methods if any
  • Checking ref updates

32-44: Improve disabled state test assertions.

The test could be more explicit about click handler expectations:

 it("should not browse when isDisabled", async () => {
+  const onBrowse = jest.fn();
-  let renderResult = render(<FileUpload isDisabled />);
+  let renderResult = render(<FileUpload isDisabled onBrowse={onBrowse} />);
   let browseButton = renderResult.container.querySelector("button");
 
   expect(browseButton).toBeDisabled();
 
   renderResult = render(<FileUpload isDisabled browseButton={<Button>Browse files</Button>} />);
   browseButton = renderResult.container.querySelector("button");
 
   browseButton && (await user.click(browseButton));
 
   expect(browseButton).toBeDisabled();
+  expect(onBrowse).not.toHaveBeenCalled();
 });

46-65: Enhance button disabled state test.

Consider these improvements:

  1. Use a more realistic file mock
  2. Add assertions for button labels
  3. Test interaction attempts
 files={[new File([], "file1", {type: "jpg"})]}
+files={[
+  new File(['test content'], 'test.jpg', {
+    type: 'image/jpeg',
+  }),
+]}
packages/components/file-upload/src/file-upload-topbar.tsx (2)

4-4: Consider enhancing the FileSize type for better validation and flexibility.

The current FileSize type could be improved to:

  1. Support more size units (bytes, GB)
  2. Prevent invalid sizes (negative or zero)

Consider this implementation:

type SizeUnit = 'B' | 'KB' | 'MB' | 'GB';
type FileSize = `${number} ${SizeUnit}`;

// With validation helper
const isValidFileSize = (size: FileSize): boolean => {
  const [value, unit] = size.split(' ');
  return Number(value) > 0 && ['B', 'KB', 'MB', 'GB'].includes(unit);
};

6-47: Consider enhancing type safety and documentation.

  1. The maxItems prop should prevent negative values
  2. The inherited props from HTMLNextUIProps should be documented

Consider these improvements:

export interface FileUploadTopbarProps extends HTMLNextUIProps<"div"> {
  /**
   * Max number of items.
   * @default 1
   * @minimum 1
   */
  maxItems?: number & { __brand: 'PositiveNumber' };
  
  // ... other props ...

  /** 
   * @inheritdoc
   * Additional HTML attributes and NextUI specific props
   */
}

// Helper type guard
function isPositiveNumber(n: number): n is number & { __brand: 'PositiveNumber' } {
  return n > 0;
}
packages/components/file-upload/stories/file-upload.stories.tsx (3)

8-23: Consider expanding argTypes configuration

The story configuration could be enhanced by adding controls for other important props of the FileUpload component, such as:

  • accept for file type restrictions
  • maxFileSize for size limits
  • onFileSelect callback
 argTypes: {
   multiple: {
     control: {
       type: "boolean",
     },
   },
   isDisabled: {
     control: {
       type: "boolean",
     },
   },
+  accept: {
+    control: "text",
+    description: "Accepted file types",
+    defaultValue: "*",
+  },
+  maxFileSize: {
+    control: "number",
+    description: "Maximum file size in bytes",
+  },
 },

55-70: Enhance ButtonsContainer template styling

The buttons in ButtonsContainerTemplate lack proper spacing and styling, which might not represent the best practices for button layouts.

 return (
   <div>
-    <Button onClick={onBrowse}>Browse Files</Button>
-    <Button onClick={onReset}>Remove All</Button>
-    <Button onClick={onAdd}>Add New File</Button>
+    <div className="flex gap-2">
+      <Button onClick={onBrowse}>Browse Files</Button>
+      <Button onClick={onReset}>Remove All</Button>
+      <Button onClick={onAdd}>Add New File</Button>
+    </div>
   </div>
 );

72-108: Consider adding error state story

The stories cover various use cases but lack demonstration of error states (e.g., file size exceeded, invalid file type).

export const WithError = {
  render: Template,
  args: {
    ...defaultProps,
    maxFileSize: 1000, // 1KB to easily trigger size error
    onError: (error) => console.log('Error:', error),
  },
};
apps/docs/content/docs/components/file-upload.mdx (2)

69-70: Improve sentence clarity in custom buttons description

The sentence is split across lines and reads awkwardly. Consider combining and rephrasing for better readability.

-In case you need to upload files prior to submitting the form, you can implement a custom button. In addition, there are placeholders for
-other buttons such as the Add Button to place your custom button (component) instead.
+In case you need to upload files before form submission, you can implement a custom button. Additionally, there are placeholders for custom button components, such as the Add Button.
🧰 Tools
🪛 LanguageTool

[style] ~69-~69: ‘prior to’ might be wordy. Consider a shorter alternative.
Context: ...ttons In case you need to upload files prior to submitting the form, you can implement ...

(EN_WORDINESS_PREMIUM_PRIOR_TO)


157-159: Enhance FileSize type definition

Consider improving the FileSize type to:

  1. Support additional units (B, GB, TB)
  2. Add validation for number values
  3. Include a helper function for conversion
-export type FileSize = `${number} KB` | `${number} MB`;
+export type FileSizeUnit = 'B' | 'KB' | 'MB' | 'GB' | 'TB';
+export type FileSize = `${number} ${FileSizeUnit}`;
+
+// Helper function for conversion
+export function convertFileSize(size: number, fromUnit: FileSizeUnit, toUnit: FileSizeUnit): FileSize;
apps/docs/config/routes.json (1)

257-263: Consider expanding keywords for better discoverability.

While the route configuration is correctly structured, consider enhancing the keywords to improve searchability. Include related terms that users might search for.

-          "keywords": "file, upload, browse",
+          "keywords": "file upload, file input, drag and drop, file browser, attachment, document upload, multiple files",
packages/components/file-upload/src/use-file-upload.ts (3)

10-65: Standardize JSDoc comments in the Props interface

To enhance readability and maintain consistent documentation, ensure that all JSDoc comments within the Props interface follow the same style and punctuation. For instance, some comments start with uppercase letters while others don't, and some end with periods while others don't.


43-47: Include onUpload in the buttons callback parameters

Considering that there is an uploadButton prop, it would be beneficial to include an onUpload callback in the buttons function parameters. This allows developers to customize the upload action within the custom buttons rendering function.


69-96: Add unit tests for the useFileUpload hook

Implementing unit tests for the useFileUpload hook will help ensure its reliability and catch potential issues early. Tests can cover various scenarios, such as default behaviors, handling of props, and memoization logic.

Would you like assistance in creating unit tests for this hook or should I open a new GitHub issue to track this task?

packages/components/file-upload/src/file-upload.tsx (9)

35-39: Ensure Dependency Array Completeness in useEffect

While the useEffect hook updates the files state when initialFiles changes, consider adding setFiles to the dependency array to prevent potential stale closures.


47-48: Clarify the Purpose of Input Resetting

The comments explaining why input values are reset to "" can be clearer to enhance maintainability.

Consider updating the comments:

-          // Setting input values to "" in order to ignore previously-selected file(s).
-          // This will fix some bugs when "removing" and re-adding "the exact same" file(s) (e.g. removing foo.txt and adding foo.txt again).
+          // Reset input values to allow re-selection of the same files.
+          // Prevents issues when a user removes and re-adds the same file(s).

215-219: Simplify File Array Creation Using Modern JavaScript

The loop used to construct newFiles from ev.target.files can be simplified for better readability.

Apply this diff:

-          const newFiles: File[] = [];
-          for (let index = 0; index < ev.target.files.length; index++) {
-            const file = ev.target.files.item(index);
-            file && newFiles.push(file);
-          }
+          const newFiles = Array.from(ev.target.files);

116-119: Provide Default Text for Browse Button

Ensure that a default text is displayed when browseButtonText is not provided to enhance user experience.

Apply this diff:

             <Button disabled={props.isDisabled} onClick={() => onBrowse()}>
-              {browseButtonText}
+              {browseButtonText || "Browse Files"}
             </Button>

91-94: Consistent Handling of Disabled State in Event Handlers

In onBrowse, the function checks props.isDisabled before proceeding. Ensure that all event handlers consistently handle the disabled state to prevent unintended interactions when the component is disabled.


205-222: Consider Accept Attribute for File Input

To enhance user experience and file validation, consider adding an accept attribute to the file input to specify the types of files that can be selected.

Example:

           type="file"
+          accept={props.accept}

146-151: Event Ordering in Button Click Handlers

In the cloned button elements, the custom onClick handlers are called after the internal handlers. Consider whether the order should be reversed to allow custom handlers to prevent default behavior if necessary.

Apply this diff:

                resetButton.props.onClick?.(ev);
                onReset();

253-255: Export Component with Named Export for Consistency

While the default export is acceptable, consider using a named export to maintain consistency and facilitate tree-shaking in bundlers.

Apply this diff:

-export default FileUpload;
+export { FileUpload };

39-40: Prevent Unnecessary State Updates

In the useEffect, add a check to prevent updating the files state if initialFiles is the same as the current files to avoid unnecessary re-renders.

Apply this diff:

         initialFiles && setFiles(initialFiles);
+      }, [initialFiles, setFiles]);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1091377 and 6926e7b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (25)
  • apps/docs/config/routes.json (1 hunks)
  • apps/docs/content/components/file-upload/button.ts (1 hunks)
  • apps/docs/content/components/file-upload/buttons.ts (1 hunks)
  • apps/docs/content/components/file-upload/controlled.ts (1 hunks)
  • apps/docs/content/components/file-upload/file-item.ts (1 hunks)
  • apps/docs/content/components/file-upload/index.ts (1 hunks)
  • apps/docs/content/components/file-upload/multiple.ts (1 hunks)
  • apps/docs/content/components/file-upload/topbar.ts (1 hunks)
  • apps/docs/content/components/file-upload/usage.ts (1 hunks)
  • apps/docs/content/docs/components/file-upload.mdx (1 hunks)
  • apps/docs/public/sitemap-0.xml (1 hunks)
  • packages/components/file-upload/README.md (1 hunks)
  • packages/components/file-upload/__tests__/file-upload.test.tsx (1 hunks)
  • packages/components/file-upload/package.json (1 hunks)
  • packages/components/file-upload/src/file-upload-item.tsx (1 hunks)
  • packages/components/file-upload/src/file-upload-topbar.tsx (1 hunks)
  • packages/components/file-upload/src/file-upload.tsx (1 hunks)
  • packages/components/file-upload/src/index.ts (1 hunks)
  • packages/components/file-upload/src/use-file-upload.ts (1 hunks)
  • packages/components/file-upload/stories/file-upload.stories.tsx (1 hunks)
  • packages/components/file-upload/tsconfig.json (1 hunks)
  • packages/components/file-upload/tsup.config.ts (1 hunks)
  • packages/core/react/package.json (1 hunks)
  • packages/core/theme/src/components/file-upload.ts (1 hunks)
  • packages/core/theme/src/components/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • apps/docs/content/components/file-upload/file-item.ts
  • packages/components/file-upload/tsconfig.json
  • packages/components/file-upload/tsup.config.ts
🧰 Additional context used
🪛 LanguageTool
apps/docs/content/docs/components/file-upload.mdx

[style] ~69-~69: ‘prior to’ might be wordy. Consider a shorter alternative.
Context: ...ttons In case you need to upload files prior to submitting the form, you can implement ...

(EN_WORDINESS_PREMIUM_PRIOR_TO)


[typographical] ~109-~109: Consider adding a comma after ‘Usually’ for more clarity.
Context: ... | The contents of the collection. Usually the array of FileUploadItem ...

(RB_LY_COMMA)


[style] ~113-~113: This phrase is redundant. Consider using “outside”.
Context: ...l values or files to be controlled from outside of the FileUpload. ...

(OUTSIDE_OF)


[style] ~120-~120: In American English, abbreviations like “etc.” require a period.
Context: ...| Custom buttons for browsing, adding, .etc or any other ones that are needed for t...

(ETC_PERIOD)

packages/components/file-upload/README.md

[typographical] ~17-~17: Usually, a comma is necessary before ‘Yes’ at the end of a sentence. Did you mean “, please”?
Context: ...rg/file-upload ``` ## Contribution Yes please! See the [contributing guidelines](http...

(COMMA_BEFORE_PLEASE)

🔇 Additional comments (25)
apps/docs/content/components/file-upload/multiple.ts (1)

9-11: LGTM!

The React code mapping follows the expected pattern for documentation examples.

apps/docs/content/components/file-upload/index.ts (1)

1-7: LGTM! Clean and well-organized imports.

The imports follow a consistent pattern and are logically grouped based on their functionality (basic usage, variants, and sub-components).

packages/components/file-upload/src/index.ts (1)

1-14: LGTM! Well-organized barrel file structure.

The file follows a clean and organized structure with clear separation between different types of exports (components, types, and hooks).

Let's verify that all imported files exist:

✅ Verification successful

All imported files exist and match the exports

The verification confirms that all required files exist in the correct directory:

  • file-upload.tsx
  • file-upload-item.tsx
  • file-upload-topbar.tsx
  • use-file-upload.ts

Note that while the imports reference .ts files, finding .tsx files is perfectly valid as TypeScript allows importing .tsx files without the extension.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence of all imported files
# Expected: All files should exist in the same directory

# Get the directory of the current file
cd "$(dirname "packages/components/file-upload/src/index.ts")"

# Check for the existence of each imported file
for file in "file-upload.ts" "file-upload-item.ts" "file-upload-topbar.ts" "use-file-upload.ts"; do
  fd -t f "^${file}(x|)"
done

Length of output: 711

apps/docs/content/components/file-upload/buttons.ts (2)

20-26: LGTM! Export configuration is well-structured.

The export configuration follows the documentation pattern correctly, making the example available at the specified path.


1-26: Verify documentation integration.

Let's ensure this example is properly referenced in the documentation system and follows the established pattern.

✅ Verification successful

Let me verify the documentation integration by checking the content of the index file and the buttons example.


Documentation integration is properly set up

The example is correctly integrated into the documentation system:

  • The buttons.ts example is properly exported in the index file
  • The file structure follows the established pattern with other examples
  • The example code is properly formatted and follows the documentation standards
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the documentation integration
# Test 1: Check if this example is referenced in the main documentation file
echo "Checking main documentation file..."
rg -l "FileUpload.*buttons" apps/docs/content/components/file-upload/

# Test 2: Verify the file structure matches other example patterns
echo "Verifying file structure..."
fd -e ts -e tsx . apps/docs/content/components/ -x echo "Found example file: {}"

Length of output: 44737


Script:

#!/bin/bash
# Check if the buttons example is properly referenced in the index file
echo "Checking index file for buttons reference..."
cat apps/docs/content/components/file-upload/index.ts

# Check the content of the buttons example
echo -e "\nChecking buttons example content..."
cat apps/docs/content/components/file-upload/buttons.ts

Length of output: 1227

apps/docs/content/components/file-upload/controlled.ts (1)

25-31: Verify the export pattern consistency.

The export configuration looks correct, but let's verify if this pattern is consistent with other example files in the docs.

✅ Verification successful

Export pattern is consistent across example files

The search results show that all example files in the docs follow the same pattern of exporting an object that spreads the react variable. This is a consistent pattern used throughout the codebase for example files.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if other example files follow the same export pattern

# Test: Search for similar export patterns in other example files
rg -g '*.ts' -g '*.tsx' "const react = {" "apps/docs/content/components/"

Length of output: 167


Script:

#!/bin/bash
# Let's fix the regex escape issue and search for export patterns
rg -g '*.ts' -g '*.tsx' 'const react \=' "apps/docs/content/components/"

# Also search for similar export configurations
rg -g '*.ts' -g '*.tsx' 'export default \{' -A 2 "apps/docs/content/components/"

# Let's also check if there are other export patterns
rg -g '*.ts' -g '*.tsx' '^export' "apps/docs/content/components/"

Length of output: 211229

packages/components/file-upload/src/file-upload-item.tsx (3)

1-3: LGTM! Imports are appropriate and well-organized.

The imports correctly utilize NextUI's internal components and types.


29-31: LGTM! Component metadata and export are well-defined.

The displayName is properly set for debugging purposes, and the export is appropriate.


1-31: Verify integration with parent FileUpload component.

Let's ensure this component integrates properly with its parent.

✅ Verification successful

Let me gather more information about the parent component's implementation to ensure complete verification.


Integration with parent FileUpload component is properly implemented

The FileUploadItem component is correctly integrated with its parent FileUpload component:

  • The parent correctly passes all required props (file, onFileRemove, isDisabled)
  • The onFileRemove handler in FileUpload properly manages file state and triggers the onChange callback
  • The disabled state is properly propagated from parent to child
  • The component is used exclusively within the FileUpload component as intended
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify FileUploadItem usage and integration

# Test 1: Check how FileUploadItem is used in parent components
echo "Checking FileUploadItem usage patterns..."
ast-grep --pattern 'import { FileUploadItem } from "$_"'

# Test 2: Verify consistent event handling
echo "Checking onFileRemove implementation in parent components..."
rg -A 5 'onFileRemove'

Length of output: 4135


Script:

#!/bin/bash
# Check the FileUpload component implementation
echo "Checking FileUpload component implementation..."
cat packages/components/file-upload/src/file-upload.tsx

# Check if FileUploadItem is used anywhere else
echo "Checking other FileUploadItem usages..."
rg -l "FileUploadItem"

Length of output: 7211

packages/components/file-upload/package.json (3)

36-54: Dependencies are well-structured

The dependency structure follows best practices:

  • Appropriate peer dependencies for React and NextUI core packages
  • Correct usage of workspace references for internal dependencies
  • Development dependencies are properly separated

27-35: Build and development scripts are comprehensive

The scripts section includes all necessary commands for development, building, and type checking, following NextUI's standard patterns.


2-4: Consider starting with version 1.0.0

Starting with version 2.0.0 for a new package is unusual unless this is meant to align with NextUI's major version. If this isn't intentional, consider starting with 1.0.0 to follow semantic versioning conventions.

✅ Verification successful

Version 2.0.0 aligns with NextUI's major version

Based on the version information from other NextUI packages, starting with version 2.0.0 is correct as it aligns with the major version (v2.x.x) used across all NextUI components in the monorepo. All packages in the components directory follow the v2 major version, ranging from 2.0.x to 2.2.x.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check the major version of other NextUI packages
echo "Checking major versions of other NextUI packages:"
fd -e json -x cat {} \; -p "packages/components" | grep "\"version\":" | sort | uniq

Length of output: 762

packages/core/theme/src/components/file-upload.ts (3)

1-4: LGTM: Imports are appropriate for theme configuration

The necessary dependencies for theme configuration are properly imported.


72-76: LGTM: Type exports are well-defined

The types are properly derived from the configuration and provide good type safety for consumers.


1-76: Verify theme integration with FileUpload components

Let's ensure these theme configurations are properly utilized by the FileUpload components.

✅ Verification successful

Theme configurations are properly integrated with FileUpload component

The verification shows that the theme slots defined in file-upload.ts are correctly used throughout the FileUpload component implementation:

  • The base slot is applied to the root component with proper class merging
  • The topbar, items, item, and buttons slots are all utilized in their respective component sections
  • The theme variants (like isDisabled) are properly handled
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of fileUpload theme configurations

# Test: Search for imports of fileUpload theme
echo "Checking fileUpload theme imports:"
rg "from.*file-upload\"" --type ts

# Test: Search for usage of theme slots
echo -e "\nChecking usage of theme slots:"
rg "className=\{.*\.(base|topbar|items|item|buttons)\(" --type tsx

Length of output: 760


Script:

#!/bin/bash
# Let's try a different approach to find the component usage

# Search for FileUpload component files
echo "Searching for FileUpload component files:"
fd "file-upload" --type f

# Search for theme usage in these files with proper file extension
echo -e "\nChecking theme usage in component files:"
rg "className=\{.*\.(base|topbar|items|item|buttons)\(" --type ts

# Look for the actual component implementation
echo -e "\nChecking component implementation:"
ast-grep --pattern 'const FileUpload = $$$'

Length of output: 21618

packages/components/file-upload/__tests__/file-upload.test.tsx (1)

1-7: LGTM! Imports are well-organized and appropriate.

The imports cover all necessary testing utilities and components.

packages/components/file-upload/src/file-upload-topbar.tsx (1)

85-87: LGTM! Proper component naming and export.

The component follows React best practices by setting a descriptive displayName and using default export.

packages/components/file-upload/stories/file-upload.stories.tsx (3)

1-7: Well-structured imports with proper type definitions

The imports are well-organized, properly typed, and follow NextUI's conventions for theming integration.


25-28: LGTM! Good use of theme variants

The default props are well-defined and properly integrate with NextUI's theming system.


30-53: Well-structured templates demonstrating component flexibility

The templates effectively showcase various customization options of the FileUpload component, making it easy for users to understand the component's capabilities.

apps/docs/config/routes.json (1)

Line range hint 1-624: LGTM! Route structure maintains consistency.

The changes maintain proper alphabetical ordering and follow the established route configuration pattern. The JSON structure remains valid and well-organized.

packages/components/file-upload/src/use-file-upload.ts (1)

78-85: Verify useMemo dependencies for completeness

Ensure that all dependencies used within the useMemo hook are included in the dependency array. Specifically, confirm whether the fileUpload function is stable. If fileUpload can change between renders, it should be included in the dependencies to prevent stale styles.

If needed, consider updating the dependency array:

 useMemo(
   () =>
     fileUpload({
       ...variantProps,
       className,
     }),
-  [objectToDeps(variantProps), className],
+  [fileUpload, objectToDeps(variantProps), className],
 );
packages/components/file-upload/src/file-upload.tsx (3)

125-140: Handling of Optional Callback Props

When cloning elements like addButton, ensure that their original onClick handlers are preserved and called appropriately.


23-25: Type Definitions for Button Props

Ensure that the types for browseButton, addButton, resetButton, and uploadButton props are correctly defined in the FileUploadProps interface to provide better TypeScript support.


169-176: ⚠️ Potential issue

Include uploadButtonElement in Rendered Output

The uploadButtonElement is defined but not rendered. Verify if it should be included in the buttons section.

Apply this diff to include it:

           {browseButtonElement}
+          {uploadButtonElement}

Likely invalid or redundant comment.

Comment on lines 1 to 4
const App = `
import {FileUpload} from "@nextui-org/react";
import {SearchIcon, DeleteIcon} from "@nextui-org/shared-icons";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing Button import and consider code structure improvements.

The code is missing the Button import while using it in the component. Additionally, since this is documentation code, consider using a more maintainable structure.

Apply this diff to fix the issues:

const App = `
import {FileUpload} from "@nextui-org/react";
+import {Button} from "@nextui-org/react";
import {SearchIcon, DeleteIcon} from "@nextui-org/shared-icons";
📝 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.

Suggested change
const App = `
import {FileUpload} from "@nextui-org/react";
import {SearchIcon, DeleteIcon} from "@nextui-org/shared-icons";
const App = `
import {FileUpload} from "@nextui-org/react";
import {Button} from "@nextui-org/react";
import {SearchIcon, DeleteIcon} from "@nextui-org/shared-icons";

Comment on lines 7 to 13
<FileUpload
multiple
browseButton={<Button><SearchIcon /></Button>}
uploadButton={<Button onClick={() => alert("Files to upload are:\n" + files.map(file => file.name).join("\n"));}>Upload Files</Button>}
addButton={<Button>+</Button>}
resetButton={<Button><DeleteIcon /></Button>}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix undefined files variable and improve user feedback.

The upload button's onClick handler references an undefined files variable, which will cause a runtime error.

Apply this diff to fix the issue:

    <FileUpload
      multiple
      browseButton={<Button><SearchIcon /></Button>}
-      uploadButton={<Button onClick={() => alert("Files to upload are:\n" + files.map(file => file.name).join("\n"));}>Upload Files</Button>}
+      uploadButton={
+        <Button 
+          onClick={(files) => {
+            if (files?.length) {
+              alert("Files to upload are:\n" + files.map(file => file.name).join("\n"));
+            }
+          }}
+        >
+          Upload Files
+        </Button>
+      }
      addButton={<Button>+</Button>}
      resetButton={<Button><DeleteIcon /></Button>}
    />

Committable suggestion skipped: line range outside the PR's diff.


🛠️ Refactor suggestion

Consider enhancing the example with proper file handling.

The current example lacks important features that should be demonstrated in documentation:

  1. File type validation
  2. File size limits
  3. Proper error handling
  4. User-friendly feedback instead of alerts

Consider enhancing the example:

    <FileUpload
      multiple
+     accept=".jpg,.png,.pdf"
+     maxFileSize={5 * 1024 * 1024} // 5MB
      browseButton={<Button><SearchIcon /></Button>}
      uploadButton={
        <Button 
          onClick={(files) => {
            try {
              // Show proper upload feedback UI instead of alert
              console.log("Uploading files:", files);
            } catch (error) {
              console.error("Upload failed:", error);
            }
          }}
        >
          Upload Files
        </Button>
      }
+     onError={(error) => console.error("Error:", error)}
      addButton={<Button>+</Button>}
      resetButton={<Button><DeleteIcon /></Button>}
    />

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 1 to 4
const App = `
import {FileUpload} from "@nextui-org/react";
import {SearchIcon, DeleteIcon} from "@nextui-org/shared-icons";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing Button import statement.

The code uses the Button component but doesn't import it. Add the import to ensure the example is complete:

const App = `
import {FileUpload} from "@nextui-org/react";
+import {Button} from "@nextui-org/react";
import {SearchIcon, DeleteIcon} from "@nextui-org/shared-icons";
📝 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.

Suggested change
const App = `
import {FileUpload} from "@nextui-org/react";
import {SearchIcon, DeleteIcon} from "@nextui-org/shared-icons";
const App = `
import {FileUpload} from "@nextui-org/react";
import {Button} from "@nextui-org/react";
import {SearchIcon, DeleteIcon} from "@nextui-org/shared-icons";

apps/docs/content/components/file-upload/controlled.ts Outdated Show resolved Hide resolved
Comment on lines 13 to 23
export default function App() {
const [files, setFiles] = useState<File[]>();

useEffect(() => {
filesFromApi().then(files => setFiles(files));
}, []);

return (
<FileUpload files={files} />
);
}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling and loading states.

The component should handle loading and error states for better user experience.

Apply this diff to improve the implementation:

 export default function App() {
-  const [files, setFiles] = useState<File[]>();
+  const [files, setFiles] = useState<File[]>([]);
+  const [isLoading, setIsLoading] = useState(false);
+  const [error, setError] = useState<Error | null>(null);

   useEffect(() => {
-    filesFromApi().then(files => setFiles(files));
+    const fetchFiles = async () => {
+      setIsLoading(true);
+      try {
+        const result = await filesFromApi();
+        setFiles(result);
+      } catch (err) {
+        setError(err instanceof Error ? err : new Error('Failed to fetch files'));
+      } finally {
+        setIsLoading(false);
+      }
+    };
+    
+    fetchFiles();
   }, []);

   return (
-    <FileUpload files={files} />
+    <>
+      {error && <div>Error: {error.message}</div>}
+      <FileUpload 
+        files={files}
+        isDisabled={isLoading}
+      />
+    </>
   );
📝 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.

Suggested change
export default function App() {
const [files, setFiles] = useState<File[]>();
useEffect(() => {
filesFromApi().then(files => setFiles(files));
}, []);
return (
<FileUpload files={files} />
);
}`;
export default function App() {
const [files, setFiles] = useState<File[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchFiles = async () => {
setIsLoading(true);
try {
const result = await filesFromApi();
setFiles(result);
} catch (err) {
setError(err instanceof Error ? err : new Error('Failed to fetch files'));
} finally {
setIsLoading(false);
}
};
fetchFiles();
}, []);
return (
<>
{error && <div>Error: {error.message}</div>}
<FileUpload
files={files}
isDisabled={isLoading}
/>
</>
);
}`;

Comment on lines 103 to 154
## API

### FileUpload Props

| Attribute | Type | Description | Default |
| ------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------- |
| children | `ReactNode` \| `ReactNode[]` | The contents of the collection. Usually the array of `FileUploadItem` | |
| multiple | `boolean` | Whether multiple files can be selected from the device. | false |
| isDisabled | `boolean` | Whether the FileUpload items and buttons are disabled. | false |
| classNames | `Record<"base"| "items"| "buttons, string>` | Allows to set custom class names for the FileUpload slots. | - |
| files | `File[]` | Files as initial values or files to be controlled from outside of the FileUpload. | - |
| browseButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for browsing files. | - |
| browseButtonText | `Record<"base"| "items"| "buttons, string>` | Custom text for the default Browse Button. | - |
| addButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for adding a single file. | - |
| resetButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for reseting files to an empty array. | - |
| uploadButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for uploading files to a server. | - |
| topbar | `Record<"base"| "items"| "buttons, string>` | Custom topbar to show information regarding files. | - |
| buttons | `Record<"base"| "items"| "buttons, string>` | Custom buttons for browsing, adding, .etc or any other ones that are needed for the user. | - |
| fileItemElement | `Record<"base"| "items"| "buttons, string>` | Custom element for representing an item that is selected from the device. | - |

### FileUpload Events

| Attribute | Type | Description |
| ------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| onChange | `(files: File[]) => void` | Handler called when file(s) are selected from device or removed from the list (`onFileRemove` on an item should be called). |

### FileUpload Item Props

| Attribute | Type | Description | Default |
|---------------------------|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|---------|
| file | `File` | A file that is selected from the device. | |

### FileUpload Item Events

| Attribute | Type | Description |
| ------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| onFileRemove | `(name: string) => void` | Handler called when a file is removed from the list of items. |

### FileUpload Topbar Props

| Attribute | Type | Description | Default |
|---------------------------|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|---------|
| maxItems | `number` | Max number of items. | 1 |
| maxItemsText | `string` | Max number of items text. | |
| maxItemsElement | `ReactElement` | Custom Element to show Max number of items. | |
| maxAllowedSize | `FileSize` | Max file size allowed. | |
| maxAllowedSizeText | `string` | Max file size text. | "Max File Size" |
| maxAllowedSizeElement | `ReactElement` | Custom Element to show Max file size. | |
| totalMaxAllowedSize | `FileSize` | Total max size allowed for multiple files combined. | |
| totalMaxAllowedSizeText | `string` | Total max file size text. | "Total Max Files Size" |
| totalMaxAllowedSizeElement | `ReactElement` | Custom Element to show Total Max file size. | |

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add essential documentation sections

The documentation would benefit from additional sections covering:

  1. Error Handling:

    • How to handle file validation errors
    • Error states and error messages
    • Example of error handling implementation
  2. Accessibility:

    • ARIA attributes
    • Keyboard navigation
    • Screen reader support
  3. Browser Compatibility:

    • Supported browsers
    • Known limitations
    • Fallback behavior

Would you like me to help generate content for these sections?

🧰 Tools
🪛 LanguageTool

[typographical] ~109-~109: Consider adding a comma after ‘Usually’ for more clarity.
Context: ... | The contents of the collection. Usually the array of FileUploadItem ...

(RB_LY_COMMA)


[style] ~113-~113: This phrase is redundant. Consider using “outside”.
Context: ...l values or files to be controlled from outside of the FileUpload. ...

(OUTSIDE_OF)


[style] ~120-~120: In American English, abbreviations like “etc.” require a period.
Context: ...| Custom buttons for browsing, adding, .etc or any other ones that are needed for t...

(ETC_PERIOD)


import {fileUploadContent} from "@/content/components/file-upload";

# Field Upload
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in the title: "Field Upload" should be "File Upload"

The title contains a typo that should be corrected to maintain consistency with the component name used throughout the documentation.

-# Field Upload
+# File Upload
📝 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.

Suggested change
# Field Upload
# File Upload

Comment on lines 112 to 121
| classNames | `Record<"base"| "items"| "buttons, string>` | Allows to set custom class names for the FileUpload slots. | - |
| files | `File[]` | Files as initial values or files to be controlled from outside of the FileUpload. | - |
| browseButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for browsing files. | - |
| browseButtonText | `Record<"base"| "items"| "buttons, string>` | Custom text for the default Browse Button. | - |
| addButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for adding a single file. | - |
| resetButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for reseting files to an empty array. | - |
| uploadButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for uploading files to a server. | - |
| topbar | `Record<"base"| "items"| "buttons, string>` | Custom topbar to show information regarding files. | - |
| buttons | `Record<"base"| "items"| "buttons, string>` | Custom buttons for browsing, adding, .etc or any other ones that are needed for the user. | - |
| fileItemElement | `Record<"base"| "items"| "buttons, string>` | Custom element for representing an item that is selected from the device. | - |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix inconsistent and incorrect type definitions in props table

Several props have type definition issues that need to be addressed:

  1. The classNames prop has incorrect syntax in the type definition
  2. Several props are using the same incorrect type
  3. Some props are marked as HTML elements but should be React elements

Apply these corrections:

-| classNames                | `Record<"base"| "items"| "buttons, string>`   |
+| classNames                | `Record<"base" | "items" | "buttons", string>`  |

-| browseButton              | `ReactHTMLElement<HTMLButtonElement>`           |
+| browseButton              | `ReactElement`                                  |

-| browseButtonText          | `Record<"base"| "items"| "buttons, string>`   |
+| browseButtonText          | `string`                                        |

-| addButton                 | `ReactHTMLElement<HTMLButtonElement>`           |
+| addButton                 | `ReactElement`                                  |

-| resetButton               | `ReactHTMLElement<HTMLButtonElement>`           |
+| resetButton               | `ReactElement`                                  |

-| uploadButton              | `ReactHTMLElement<HTMLButtonElement>`           |
+| uploadButton              | `ReactElement`                                  |

-| topbar                    | `Record<"base"| "items"| "buttons, string>`   |
+| topbar                    | `ReactElement`                                  |

-| buttons                   | `Record<"base"| "items"| "buttons, string>`   |
+| buttons                   | `ReactElement`                                  |

-| fileItemElement           | `Record<"base"| "items"| "buttons, string>`   |
+| fileItemElement           | `ReactElement`                                  |
📝 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.

Suggested change
| classNames | `Record<"base""items""buttons, string>` | Allows to set custom class names for the FileUpload slots. | - |
| files | `File[]` | Files as initial values or files to be controlled from outside of the FileUpload. | - |
| browseButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for browsing files. | - |
| browseButtonText | `Record<"base"| "items"| "buttons, string>` | Custom text for the default Browse Button. | - |
| addButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for adding a single file. | - |
| resetButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for reseting files to an empty array. | - |
| uploadButton | `ReactHTMLElement<HTMLButtonElement>` | Custom button for uploading files to a server. | - |
| topbar | `Record<"base"| "items"| "buttons, string>` | Custom topbar to show information regarding files. | - |
| buttons | `Record<"base"| "items"| "buttons, string>` | Custom buttons for browsing, adding, .etc or any other ones that are needed for the user. | - |
| fileItemElement | `Record<"base"| "items"| "buttons, string>` | Custom element for representing an item that is selected from the device. | - |
| classNames | `Record<"base" | "items" | "buttons", string>` | Allows to set custom class names for the FileUpload slots. | - |
| files | `File[]` | Files as initial values or files to be controlled from outside of the FileUpload. | - |
| browseButton | `ReactElement` | Custom button for browsing files. | - |
| browseButtonText | `string` | Custom text for the default Browse Button. | - |
| addButton | `ReactElement` | Custom button for adding a single file. | - |
| resetButton | `ReactElement` | Custom button for reseting files to an empty array. | - |
| uploadButton | `ReactElement` | Custom button for uploading files to a server. | - |
| topbar | `ReactElement` | Custom topbar to show information regarding files. | - |
| buttons | `ReactElement` | Custom buttons for browsing, adding, .etc or any other ones that are needed for the user. | - |
| fileItemElement | `ReactElement` | Custom element for representing an item that is selected from the device. | - |
🧰 Tools
🪛 LanguageTool

[style] ~113-~113: This phrase is redundant. Consider using “outside”.
Context: ...l values or files to be controlled from outside of the FileUpload. ...

(OUTSIDE_OF)


[style] ~120-~120: In American English, abbreviations like “etc.” require a period.
Context: ...| Custom buttons for browsing, adding, .etc or any other ones that are needed for t...

(ETC_PERIOD)

Comment on lines 234 to 235
files.push(singleFile);
updateFiles([...files]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid Direct State Mutation for files

In the onChange handler for the single file input, the files array is mutated directly using files.push(singleFile). Direct mutation of state can lead to unexpected behavior.

Apply this diff to prevent direct mutation:

-              files.push(singleFile);
-              updateFiles([...files]);
+              updateFiles([...files, singleFile]);
📝 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.

Suggested change
files.push(singleFile);
updateFiles([...files]);
updateFiles([...files, singleFile]);

Comment on lines 63 to 88
files.map((file) => {
if (!fileItemElement) {
return (
<FileUploadItem
key={file.name}
className={styles.item()}
file={file}
isDisabled={props.isDisabled}
onFileRemove={(name) => {
if (props.isDisabled) return;
const newFiles = files.filter((file) => file.name !== name);

updateFiles(newFiles);
}}
/>
);
}

const customFileElm = fileItemElement(file);

return cloneElement(customFileElm, {
key: file.name,
className: styles.item({class: clsx(classNames?.item, customFileElm.props.className)}),
});
}),
[styles, classNames, files, fileItemElement, updateFiles],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential Duplicate Keys in File List Rendering

Currently, the key prop for each file item is set to file.name. Since multiple files can have the same name, this may lead to rendering issues.

Apply this diff to use a more unique identifier:

-          key={file.name}
+          key={`${file.name}-${file.lastModified}-${file.size}`}
📝 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.

Suggested change
files.map((file) => {
if (!fileItemElement) {
return (
<FileUploadItem
key={file.name}
className={styles.item()}
file={file}
isDisabled={props.isDisabled}
onFileRemove={(name) => {
if (props.isDisabled) return;
const newFiles = files.filter((file) => file.name !== name);
updateFiles(newFiles);
}}
/>
);
}
const customFileElm = fileItemElement(file);
return cloneElement(customFileElm, {
key: file.name,
className: styles.item({class: clsx(classNames?.item, customFileElm.props.className)}),
});
}),
[styles, classNames, files, fileItemElement, updateFiles],
files.map((file) => {
if (!fileItemElement) {
return (
<FileUploadItem
key={`${file.name}-${file.lastModified}-${file.size}`}
className={styles.item()}
file={file}
isDisabled={props.isDisabled}
onFileRemove={(name) => {
if (props.isDisabled) return;
const newFiles = files.filter((file) => file.name !== name);
updateFiles(newFiles);
}}
/>
);
}
const customFileElm = fileItemElement(file);
return cloneElement(customFileElm, {
key={`${file.name}-${file.lastModified}-${file.size}`},
className: styles.item({class: clsx(classNames?.item, customFileElm.props.className)}),
});
}),
[styles, classNames, files, fileItemElement, updateFiles],

@Arian94 Arian94 force-pushed the feat/file-upload_#3772 branch from 38d4056 to 7ca7d22 Compare November 6, 2024 18:44
@Arian94 Arian94 requested a review from wingkwong November 6, 2024 18:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Outside diff range and nitpick comments (12)
apps/docs/content/components/file-upload/buttonsContainer.ts (1)

9-13: Enhance accessibility and user experience.

The buttons section could benefit from improved accessibility and visual organization:

  1. Add aria-labels for screen readers
  2. Add spacing between buttons
  3. Consider adding icons for visual clarity
-        return <div>
-          <Button onClick={() => onBrowse()}>Browse Files</Button>
-          <Button onClick={() => onAdd()}>Add New File</Button>
-          <Button onClick={() => onReset()}>Remove All</Button>
+        return <div className="flex gap-2">
+          <Button
+            aria-label="Browse files"
+            onClick={() => onBrowse()}
+            startContent={<BrowseIcon />}
+          >
+            Browse Files
+          </Button>
+          <Button
+            aria-label="Add new file"
+            onClick={() => onAdd()}
+            startContent={<AddIcon />}
+          >
+            Add New File
+          </Button>
+          <Button
+            aria-label="Remove all files"
+            onClick={() => onReset()}
+            startContent={<DeleteIcon />}
+            color="danger"
+          >
+            Remove All
+          </Button>
         </div>
packages/components/file-upload/src/file-upload-item.tsx (2)

5-9: Consider adding file validation types to the interface.

The interface could benefit from additional type safety for file validation:

+type ValidFileType = string | string[];
+type ValidFileSize = number;

 export interface FileUploadItemProps extends HTMLNextUIProps<"div"> {
   file: File;
   onFileRemove: (file: File) => void;
   isDisabled?: boolean;
+  allowedTypes?: ValidFileType;
+  maxFileSize?: ValidFileSize;
 }

11-18: Consider optimizing and relocating the format utility.

The formatFileSize function is well-implemented but could be:

  1. Memoized for performance
  2. Moved to a shared utilities file for reuse across components
+import {useMemo} from 'react';
+
-const formatFileSize = (bytes: number) => {
+const useFormattedFileSize = (bytes: number) => useMemo(() => {
   if (bytes === 0) return "0 Bytes";
   const k = 1024;
   const sizes = ["Bytes", "KB", "MB", "GB"];
   const i = Math.floor(Math.log(bytes) / Math.log(k));
   return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
-};
+}, [bytes]);
packages/components/file-upload/__tests__/file-upload.test.tsx (2)

8-18: Consider enhancing test setup with common utilities

While the basic setup is good, consider adding common test utilities:

  • Mock for File API
  • Common test files/data
  • Helper functions for repeated operations
 beforeEach(() => {
   user = userEvent.setup();
+  // Setup common test files
+  global.testFile = new File(['test'], 'test.png', { type: 'image/png' });
+  // Mock File API if needed
+  global.URL.createObjectURL = jest.fn();
 });

 afterEach(() => {
   jest.clearAllMocks();
+  // Clean up any created object URLs
+  if (global.URL.createObjectURL.mockClear) {
+    global.URL.createObjectURL.mockClear();
+  }
 });

55-55: Improve file mock creation

The current file mock is created with empty content and might not represent real-world scenarios accurately.

-files={[new File([], "file1", {type: "jpg"})]}
+files={[new File(['test content'], "file1.jpg", {type: "image/jpeg"})]}
apps/docs/content/components/file-upload/customButtons.ts (1)

1-28: Add TypeScript types and improve accessibility

Consider the following improvements for the DeleteIcon component:

  1. Add TypeScript interface for props
  2. Include aria-label for better accessibility
  3. Consider using a more maintainable format instead of template string
-const DeleteIcon = `export const DeleteIcon = ({
+const DeleteIcon = `
+interface DeleteIconProps {
+  fill?: string;
+  filled?: boolean;
+  size?: number;
+  height?: number;
+  width?: number;
+  label?: string;
+}
+
+export const DeleteIcon = ({
   fill = 'currentColor',
   filled,
   size,
   height,
   width,
   label,
   ...props
-}) => {
+}: DeleteIconProps) => {
   return (
     <svg aria-hidden="true" fill="none" focusable="false" height="1em" role="presentation"
-      viewBox="0 0 20 20" width="1em">
+      viewBox="0 0 20 20" width="1em" aria-label={label || "Delete"}>
`;
packages/components/file-upload/src/file-upload-topbar.tsx (3)

5-5: Consider enhancing the FileSize type definition.

The current type definition has limitations:

  1. Missing common size units (B, GB, TB)
  2. Allows inconsistent spacing in format ("10 KB" vs "10KB")

Consider this improved type definition:

-type FileSize = `${number} KB` | `${number} MB` | `${number}KB` | `${number}MB`;
+type FileSizeUnit = "B" | "KB" | "MB" | "GB" | "TB";
+type FileSize = `${number} ${FileSizeUnit}`;

7-48: Consider adding validation constraints to size-related props.

While the interface is well-documented, it lacks validation constraints for size-related props. This could lead to unrealistic or invalid size configurations.

Consider adding zod schema or prop-types validation:

// Example validation using zod
const FileSizeSchema = z.string().regex(/^\d+\s*(B|KB|MB|GB|TB)$/, {
  message: "Invalid file size format. Example: '10 MB'"
});

// Add runtime validation in component
if (maxAllowedSize && !FileSizeSchema.safeParse(maxAllowedSize).success) {
  console.warn("Invalid maxAllowedSize format");
}

50-90: Consider adding internationalization support.

The component uses hardcoded English strings for default text values. For better global usability, consider extracting these strings into a localization system.

Example approach using a translation hook:

+import {useTranslation} from 'next-intl';
 
 const FileUploadTopbar: React.FC<FileUploadTopbarProps> = ({
-  maxItemsText = "Max number of items",
-  maxAllowedSizeText = "Max File Size",
-  totalMaxAllowedSizeText = "Total Max Files Size",
+  maxItemsText,
+  maxAllowedSizeText,
+  totalMaxAllowedSizeText,
   // ... other props
 }) => {
+  const {t} = useTranslation();
+  const defaultTexts = {
+    maxItems: t('fileUpload.maxItems', 'Max number of items'),
+    maxSize: t('fileUpload.maxSize', 'Max File Size'),
+    totalMaxSize: t('fileUpload.totalMaxSize', 'Total Max Files Size'),
+  };
+
+  const finalMaxItemsText = maxItemsText ?? defaultTexts.maxItems;
+  // ... similar for other text props
packages/components/file-upload/src/use-file-upload.ts (2)

73-90: Optimize useMemo dependency and add cleanup

  1. The objectToDeps usage in the dependency array could lead to unnecessary re-renders. Consider memoizing the variant props separately.

  2. Consider adding cleanup logic for any ongoing file operations.

Here's the suggested optimization:

+ const memoizedVariantProps = useMemo(
+   () => variantProps,
+   [objectToDeps(variantProps)]
+ );

  const styles = useMemo(
    () =>
      fileUpload({
-       ...variantProps,
+       ...memoizedVariantProps,
        className,
      }),
-   [objectToDeps(variantProps), className],
+   [memoizedVariantProps, className],
  );

71-72: Add JSDoc comments for exported types

Consider adding comprehensive JSDoc comments for the exported types to improve documentation:

+/**
+ * Props for the useFileUpload hook, combining base props with variant props
+ */
 export type UseFileUploadProps = Props & FileUploadVariantProps;

+/**
+ * Return type of the useFileUpload hook containing component props and styling information
+ */
 export type UseFileUploadReturn = ReturnType<typeof useFileUpload>;

Also applies to: 102-102

packages/components/file-upload/src/file-upload.tsx (1)

172-205: Optimize buttonsElement dependency array.

The dependency array includes derived values that are already memoized. Consider removing redundant dependencies.

Apply this diff:

  }, [
    onBrowse,
    onAdd,
    onReset,
    styles,
    multiple,
    files.length,
-    browseButtonElement,
-    addButtonElement,
-    resetButtonElement,
    uploadButton,
+    buttons,
+    props.isDisabled,
+    classNames?.buttons
  ]);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6926e7b and 7ca7d22.

⛔ Files ignored due to path filters (1)
  • packages/components/file-upload/__tests__/__snapshots__/file-upload.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (14)
  • apps/docs/content/components/file-upload/buttonsContainer.ts (1 hunks)
  • apps/docs/content/components/file-upload/controlled.ts (1 hunks)
  • apps/docs/content/components/file-upload/customButtons.ts (1 hunks)
  • apps/docs/content/components/file-upload/file-item.ts (1 hunks)
  • apps/docs/content/components/file-upload/index.ts (1 hunks)
  • apps/docs/content/docs/components/file-upload.mdx (1 hunks)
  • packages/components/file-upload/__tests__/file-upload.test.tsx (1 hunks)
  • packages/components/file-upload/package.json (1 hunks)
  • packages/components/file-upload/src/file-upload-item.tsx (1 hunks)
  • packages/components/file-upload/src/file-upload-topbar.tsx (1 hunks)
  • packages/components/file-upload/src/file-upload.tsx (1 hunks)
  • packages/components/file-upload/src/use-file-upload.ts (1 hunks)
  • packages/components/file-upload/stories/file-upload.stories.tsx (1 hunks)
  • packages/core/react/src/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/docs/content/components/file-upload/controlled.ts
  • apps/docs/content/components/file-upload/file-item.ts
  • apps/docs/content/components/file-upload/index.ts
  • packages/components/file-upload/package.json
  • packages/components/file-upload/stories/file-upload.stories.tsx
🧰 Additional context used
🪛 LanguageTool
apps/docs/content/docs/components/file-upload.mdx

[style] ~70-~70: ‘prior to’ might be wordy. Consider a shorter alternative.
Context: ...n` prop for you in case uploading files prior to submitting the form is required. <Code...

(EN_WORDINESS_PREMIUM_PRIOR_TO)


[typographical] ~109-~109: Consider adding a comma after ‘Usually’ for more clarity.
Context: ... | The contents of the collection. Usually the array of FileUploadItem ...

(RB_LY_COMMA)


[style] ~113-~113: This phrase is redundant. Consider using “outside”.
Context: ...l values or files to be controlled from outside of the FileUpload. ...

(OUTSIDE_OF)


[style] ~120-~120: In American English, abbreviations like “etc.” require a period.
Context: ...| Custom buttons for browsing, adding, .etc or any other ones that are needed for t...

(ETC_PERIOD)

🔇 Additional comments (12)
apps/docs/content/components/file-upload/buttonsContainer.ts (2)

7-7: Add validation for multiple file uploads.

The multiple prop is set without any file validation or error handling.

Consider adding validation props like maxFiles and accept to control the upload behavior.

     <FileUpload
-      multiple
+      multiple
+      maxFiles={5}
+      accept={{
+        'image/*': ['.png', '.jpg', '.jpeg', '.gif'],
+        'application/pdf': ['.pdf']
+      }}
+      onError={(error) => {
+        console.error('Upload error:', error);
+      }}

19-25: LGTM: Path mapping and export structure.

The path mapping and export structure follows a clean pattern.

packages/components/file-upload/src/file-upload-item.tsx (1)

43-45: LGTM! Component exports follow React best practices.

The display name is properly set and the export is clean.

packages/core/react/src/index.ts (1)

48-48: LGTM! The export follows the established patterns.

The new export for the file-upload module maintains consistency with other NextUI package exports and preserves the alphabetical ordering.

Let's verify that the package exists and is properly set up:

✅ Verification successful

The file-upload package exists and is properly integrated

The package is correctly set up:

  • Located at packages/components/file-upload/
  • Has necessary source files (file-upload.tsx, file-upload-item.tsx, etc.)
  • Is properly listed as a dependency in packages/core/react/package.json
  • Has documentation at apps/docs/content/docs/components/file-upload.mdx
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence and setup of the file-upload package

# Test 1: Check if package.json exists for the file-upload package
fd -t f "package.json" -p "packages/**/file-upload"

# Test 2: Check if the package exports a main entry point
fd -t f "index.ts" -p "packages/**/file-upload/src"

Length of output: 329


Script:

#!/bin/bash
# Let's verify if the file-upload package exists in a more comprehensive way

# Check if the package exists in the workspace packages
fd "file-upload" packages/

# Check package.json in core/react to see if the dependency is listed
cat packages/core/react/package.json

# Check if there are any references to file-upload in the codebase
rg "@nextui-org/file-upload" -l

Length of output: 4141

packages/components/file-upload/__tests__/file-upload.test.tsx (1)

1-7: LGTM: Imports are well-organized and complete

All necessary testing utilities and components are properly imported with appropriate typing.

apps/docs/content/components/file-upload/customButtons.ts (1)

51-58: LGTM!

The export configuration is clean and follows good practices.

packages/components/file-upload/src/file-upload-topbar.tsx (1)

92-94: LGTM!

The component export is properly configured with a meaningful displayName for better debugging experience.

packages/components/file-upload/src/use-file-upload.ts (1)

1-9: LGTM! Well-organized imports

The imports are well-structured and appropriately sourced from NextUI internal packages, showing good separation of concerns.

packages/components/file-upload/src/file-upload.tsx (4)

1-32: LGTM! Well-structured component setup.

The component follows React best practices with proper prop typing and ref forwarding.


34-52: LGTM! Clean state management implementation.

The state management is well-implemented with proper cleanup of file inputs to handle edge cases.


89-92: Use unique key for custom file items.

Using only file.name as key could cause issues with duplicate file names.


246-262: LGTM! Clean component rendering.

The component's render logic is well-structured with proper conditional rendering and className handling.

@@ -0,0 +1,25 @@
const App = `
import {FileUpload} from "@nextui-org/react";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing Button import.

The code uses the Button component but doesn't import it from @nextui-org/react.

-import {FileUpload} from "@nextui-org/react";
+import {FileUpload, Button} from "@nextui-org/react";
📝 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.

Suggested change
import {FileUpload} from "@nextui-org/react";
import {FileUpload, Button} from "@nextui-org/react";

Comment on lines +1 to +17
const App = `
import {FileUpload} from "@nextui-org/react";

export default function App() {
return (
<FileUpload
multiple
buttons={(onBrowse, onAdd, onReset) => {
return <div>
<Button onClick={() => onBrowse()}>Browse Files</Button>
<Button onClick={() => onAdd()}>Add New File</Button>
<Button onClick={() => onReset()}>Remove All</Button>
</div>
}}
/>
);
}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using TypeScript for better type safety.

The component is defined as a template string without proper TypeScript types for the callback functions. This could lead to type-related issues and makes the code harder to maintain.

Consider this implementation:

-const App = `
-import {FileUpload} from "@nextui-org/react";
+const App = `
+import {FileUpload, Button} from "@nextui-org/react";
+
+interface FileUploadHandlers {
+  onBrowse: () => void;
+  onAdd: () => void;
+  onReset: () => void;
+}

 export default function App() {
   return (
     <FileUpload
       multiple
-      buttons={(onBrowse, onAdd, onReset) => {
+      buttons={({onBrowse, onAdd, onReset}: FileUploadHandlers) => {
         return <div>
           <Button onClick={() => onBrowse()}>Browse Files</Button>
           <Button onClick={() => onAdd()}>Add New File</Button>
           <Button onClick={() => onReset()}>Remove All</Button>
         </div>
       }}
     />
   );
 }`;
📝 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.

Suggested change
const App = `
import {FileUpload} from "@nextui-org/react";
export default function App() {
return (
<FileUpload
multiple
buttons={(onBrowse, onAdd, onReset) => {
return <div>
<Button onClick={() => onBrowse()}>Browse Files</Button>
<Button onClick={() => onAdd()}>Add New File</Button>
<Button onClick={() => onReset()}>Remove All</Button>
</div>
}}
/>
);
}`;
const App = `
import {FileUpload, Button} from "@nextui-org/react";
interface FileUploadHandlers {
onBrowse: () => void;
onAdd: () => void;
onReset: () => void;
}
export default function App() {
return (
<FileUpload
multiple
buttons={({onBrowse, onAdd, onReset}: FileUploadHandlers) => {
return <div>
<Button onClick={() => onBrowse()}>Browse Files</Button>
<Button onClick={() => onAdd()}>Add New File</Button>
<Button onClick={() => onReset()}>Remove All</Button>
</div>
}}
/>
);
}`;

Comment on lines +27 to +39
<div {...otherProps}>
<Button
aria-label={`Remove ${file.name}`}
isDisabled={isDisabled}
role="listitem"
onClick={() => onFileRemove(file)}
>
<CloseIcon />
</Button>
<span>{file.name}</span>
<span>{formatFileSize(file.size)}</span>
<span>{file.type.split("/").at(1)?.toUpperCase()}</span>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve component structure and accessibility.

Several structural improvements are needed:

-    <div {...otherProps}>
+    <div role="listitem" className="file-upload-item" {...otherProps}>
+      <div className="file-info">
+        <span className="file-name" title={file.name}>{file.name}</span>
+        <span className="file-size">{formatFileSize(file.size)}</span>
+        <span className="file-type">
+          {file.type ? file.type.split("/").at(1)?.toUpperCase() ?? 'UNKNOWN' : 'UNKNOWN'}
+        </span>
+      </div>
       <Button
         aria-label={`Remove ${file.name}`}
         isDisabled={isDisabled}
-        role="listitem"
         onClick={() => onFileRemove(file)}
+        className="remove-button"
       >
         <CloseIcon />
       </Button>
-      <span>{file.name}</span>
-      <span>{formatFileSize(file.size)}</span>
-      <span>{file.type.split("/").at(1)?.toUpperCase()}</span>
     </div>
📝 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.

Suggested change
<div {...otherProps}>
<Button
aria-label={`Remove ${file.name}`}
isDisabled={isDisabled}
role="listitem"
onClick={() => onFileRemove(file)}
>
<CloseIcon />
</Button>
<span>{file.name}</span>
<span>{formatFileSize(file.size)}</span>
<span>{file.type.split("/").at(1)?.toUpperCase()}</span>
</div>
<div role="listitem" className="file-upload-item" {...otherProps}>
<div className="file-info">
<span className="file-name" title={file.name}>{file.name}</span>
<span className="file-size">{formatFileSize(file.size)}</span>
<span className="file-type">
{file.type ? file.type.split("/").at(1)?.toUpperCase() ?? 'UNKNOWN' : 'UNKNOWN'}
</span>
</div>
<Button
aria-label={`Remove ${file.name}`}
isDisabled={isDisabled}
onClick={() => onFileRemove(file)}
className="remove-button"
>
<CloseIcon />
</Button>
</div>

Comment on lines +48 to +68
it("buttons should be disabled when isDisabled", () => {
const {container} = render(
<FileUpload
isDisabled
multiple
addButton={<Button>Add file</Button>}
browseButton={<Button>Browse files</Button>}
files={[new File([], "file1", {type: "jpg"})]}
resetButton={<Button>Remove all files</Button>}
/>,
);

const buttons = container.getElementsByTagName("button");

for (let index = 0; index < buttons.length; index++) {
const btn = buttons.item(index);

expect(btn).toBeDisabled();
}
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add missing test scenarios

The disabled state test is good, but several critical scenarios are missing:

  • File selection events
  • Multiple file handling
  • Error states (invalid file types, size limits)

Would you like me to provide example implementations for these test cases?

Comment on lines +19 to +25
it("should render correctly", () => {
const wrapper = render(<FileUpload />);

expect(wrapper.getByRole("button")).toBeInTheDocument();
expect(wrapper.container).toMatchSnapshot();
expect(() => wrapper.unmount()).not.toThrow();
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance basic render test with accessibility checks

While the basic render test has been improved, consider adding accessibility-related assertions:

 it("should render correctly", () => {
   const wrapper = render(<FileUpload />);

   expect(wrapper.getByRole("button")).toBeInTheDocument();
+  // Check for proper ARIA attributes
+  expect(wrapper.getByRole("button")).toHaveAttribute("aria-label");
+  // Verify the input is properly hidden but accessible
+  expect(wrapper.container.querySelector('input[type="file"]')).toHaveAttribute("aria-hidden", "true");
   expect(wrapper.container).toMatchSnapshot();
   expect(() => wrapper.unmount()).not.toThrow();
 });
📝 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.

Suggested change
it("should render correctly", () => {
const wrapper = render(<FileUpload />);
expect(wrapper.getByRole("button")).toBeInTheDocument();
expect(wrapper.container).toMatchSnapshot();
expect(() => wrapper.unmount()).not.toThrow();
});
it("should render correctly", () => {
const wrapper = render(<FileUpload />);
expect(wrapper.getByRole("button")).toBeInTheDocument();
// Check for proper ARIA attributes
expect(wrapper.getByRole("button")).toHaveAttribute("aria-label");
// Verify the input is properly hidden but accessible
expect(wrapper.container.querySelector('input[type="file"]')).toHaveAttribute("aria-hidden", "true");
expect(wrapper.container).toMatchSnapshot();
expect(() => wrapper.unmount()).not.toThrow();
});

Comment on lines +8 to +68
describe("FileUpload", () => {
let user: UserEvent;

beforeEach(() => {
user = userEvent.setup();
});

afterEach(() => {
jest.clearAllMocks();
});

it("should render correctly", () => {
const wrapper = render(<FileUpload />);

expect(wrapper.getByRole("button")).toBeInTheDocument();
expect(wrapper.container).toMatchSnapshot();
expect(() => wrapper.unmount()).not.toThrow();
});

it("ref should be forwarded", () => {
const ref = React.createRef<HTMLDivElement>();

render(<FileUpload ref={ref} />);
expect(ref.current).not.toBeNull();
});

it("should not browse when isDisabled", async () => {
let renderResult = render(<FileUpload isDisabled />);
let browseButton = renderResult.container.querySelector("button");

expect(browseButton).toBeDisabled();

renderResult = render(<FileUpload isDisabled browseButton={<Button>Browse files</Button>} />);
browseButton = renderResult.container.querySelector("button");

browseButton && (await user.click(browseButton));

expect(browseButton).toBeDisabled();
});

it("buttons should be disabled when isDisabled", () => {
const {container} = render(
<FileUpload
isDisabled
multiple
addButton={<Button>Add file</Button>}
browseButton={<Button>Browse files</Button>}
files={[new File([], "file1", {type: "jpg"})]}
resetButton={<Button>Remove all files</Button>}
/>,
);

const buttons = container.getElementsByTagName("button");

for (let index = 0; index < buttons.length; index++) {
const btn = buttons.item(index);

expect(btn).toBeDisabled();
}
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Additional test coverage needed for key features

The review comment is correct. After examining the component files, several important features lack test coverage:

  1. Top Bar functionality:

    • maxItems constraint display and validation
    • maxAllowedSize constraint display and validation
    • totalMaxAllowedSize constraint display and validation
  2. File Item container features:

    • File removal functionality
    • File size formatting
    • File type display
    • Multiple file handling
  3. File restrictions:

    • File type validation through accept prop
    • File size restrictions
    • Multiple file upload constraints
  4. Event handlers:

    • onChange callback when files are added/removed
    • Custom button click handlers
    • File selection events

The current tests only cover basic rendering, ref forwarding, and disabled state. Additional test cases should be added to ensure complete feature coverage.

🔗 Analysis chain

Verify test coverage for all component features

Based on the PR objectives, several key features of the FileUpload component are not covered by tests:

  • Top Bar information display
  • Items container functionality
  • File type restrictions

Let's verify the component's exported features:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check exported features that need test coverage

# Find all exported features from the component
ast-grep --pattern 'export interface FileUploadProps {
  $$$
}'

# Find all event handlers that need testing
ast-grep --pattern 'on[A-Z][a-zA-Z]+?: ($$$) => $$$'

Length of output: 120


Script:

#!/bin/bash
# Let's try a different approach to find the component's features

# Find the component file
fd "file-upload" --type f --extension tsx --exclude "__tests__"

# Search for prop types and component implementation
rg -U "interface.*Props|^export.*function FileUpload" --type tsx -A 10

Length of output: 394


Script:

#!/bin/bash
# Let's examine the component files directly to understand their features

# Check the main component implementation
cat "packages/components/file-upload/src/file-upload.tsx"

# Check the item component
cat "packages/components/file-upload/src/file-upload-item.tsx"

# Check the topbar component
cat "packages/components/file-upload/src/file-upload-topbar.tsx"

Length of output: 10899

Comment on lines +30 to +49
const App = `
import React from "react";
import {FileUpload} from "@nextui-org/react";
import {Button} from "@nextui-org/react";
import {DeleteIcon} from './DeleteIcon';

export default function App() {
const [files, setFiles] = React.useState([]);

return (
<FileUpload
multiple
onChange={newFiles => setFiles(newFiles)}
browseButton={<Button>Search</Button>}
uploadButton={<Button onClick={() => console.log("Files to upload:\\n" + files.map(file => file.name).join("\\n"))}>Upload Files</Button>}
addButton={<Button>+</Button>}
resetButton={<Button><DeleteIcon /></Button>}
/>
);
}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance example with proper validation and error handling

The current example could be improved to demonstrate best practices:

  1. Remove console.log and show proper upload handling
  2. Add file type and size restrictions
  3. Include error handling
 const App = `
 import React from "react";
-import {FileUpload} from "@nextui-org/react";
-import {Button} from "@nextui-org/react";
+import {FileUpload, Button} from "@nextui-org/react";
 import {DeleteIcon} from './DeleteIcon';

 export default function App() {
   const [files, setFiles] = React.useState([]);
+  const [error, setError] = React.useState("");
+
+  const handleUpload = async () => {
+    try {
+      // Add your upload logic here
+      const formData = new FormData();
+      files.forEach(file => formData.append('files', file));
+      // await uploadFiles(formData);
+    } catch (err) {
+      setError("Upload failed");
+    }
+  };

   return (
     <FileUpload
       multiple
+      accept=".jpg,.png,.pdf"
+      maxSize={5242880} // 5MB
       onChange={newFiles => setFiles(newFiles)}
       browseButton={<Button>Search</Button>}
-      uploadButton={<Button onClick={() => console.log("Files to upload:\\n" + files.map(file => file.name).join("\\n"))}>Upload Files</Button>}
+      uploadButton={<Button onClick={handleUpload}>Upload Files</Button>}
       addButton={<Button>+</Button>}
       resetButton={<Button><DeleteIcon /></Button>}
     />
   );
 }`;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +10 to +69
interface Props extends Omit<HTMLNextUIProps<"div">, "onChange"> {
/**
* Ref to the DOM node.
*/
ref?: ReactRef<HTMLElement | null>;
classNames?: SlotsToClasses<FileUploadSlots>;
/**
* A property to set initial files (which might be fetched) or to control files from outside of the component.
*/
files?: File[];
/**
* Custom Browse Button.
*/
browseButton?: ReactElement<ButtonProps>;
/**
* A different text for the browse button.
*/
browseButtonText?: string;
/**
* Custom Add Button.
*/
addButton?: ReactElement<ButtonProps>;
/**
* Custom Reset Button.
*/
resetButton?: ReactElement<ButtonProps>;
/**
* Custom Upload Button.
*/
uploadButton?: ReactElement<ButtonProps>;
/**
* Custom element to display buttons such as the browse button in customized design and order.
*/
buttons?: (
onBrowse: () => void,
onAdd: () => void,
onReset: () => void,
) => ReactElement<HTMLElement>;
/**
* If set to true, multiple files can be selected from the device.
* @default false
*/
multiple?: boolean;
/**
* Accept certain file format(s).
*/
accept?: string;
/**
* Custom Element for an Upload File Item.
*/
fileItemElement?: (file: File) => ReactElement<HTMLElement>;
/**
* Custom Element for topbar of the component.
*/
topbar?: ReactElement<HTMLElement>;
/**
* Triggered when file(s) selected, added or removed.
*/
onChange?: (files: File[]) => void;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding essential file upload properties

While the Props interface is well-documented and provides good customization options, consider adding these essential properties for a more robust file upload component:

  1. File validation props:

    • maxFileSize?: number
    • maxTotalSize?: number
    • validateFile?: (file: File) => boolean | Promise<boolean>
  2. Error handling props:

    • onError?: (error: Error, file?: File) => void
    • errorMessage?: string | ReactNode
  3. Loading state props:

    • onProgress?: (progress: number, file: File) => void
    • loading?: boolean

These additions would enhance the component's reliability and user experience.

interface Props extends Omit<HTMLNextUIProps<"div">, "onChange"> {
  // ... existing props ...
+  /**
+   * Maximum size allowed for each file in bytes
+   */
+  maxFileSize?: number;
+  /**
+   * Maximum total size allowed for all files in bytes
+   */
+  maxTotalSize?: number;
+  /**
+   * Custom validation function for files
+   */
+  validateFile?: (file: File) => boolean | Promise<boolean>;
+  /**
+   * Callback fired when an error occurs
+   */
+  onError?: (error: Error, file?: File) => void;
+  /**
+   * Custom error message
+   */
+  errorMessage?: string | ReactNode;
+  /**
+   * Callback fired during file upload progress
+   */
+  onProgress?: (progress: number, file: File) => void;
+  /**
+   * Loading state of the upload process
+   */
+  loading?: boolean;
}
📝 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.

Suggested change
interface Props extends Omit<HTMLNextUIProps<"div">, "onChange"> {
/**
* Ref to the DOM node.
*/
ref?: ReactRef<HTMLElement | null>;
classNames?: SlotsToClasses<FileUploadSlots>;
/**
* A property to set initial files (which might be fetched) or to control files from outside of the component.
*/
files?: File[];
/**
* Custom Browse Button.
*/
browseButton?: ReactElement<ButtonProps>;
/**
* A different text for the browse button.
*/
browseButtonText?: string;
/**
* Custom Add Button.
*/
addButton?: ReactElement<ButtonProps>;
/**
* Custom Reset Button.
*/
resetButton?: ReactElement<ButtonProps>;
/**
* Custom Upload Button.
*/
uploadButton?: ReactElement<ButtonProps>;
/**
* Custom element to display buttons such as the browse button in customized design and order.
*/
buttons?: (
onBrowse: () => void,
onAdd: () => void,
onReset: () => void,
) => ReactElement<HTMLElement>;
/**
* If set to true, multiple files can be selected from the device.
* @default false
*/
multiple?: boolean;
/**
* Accept certain file format(s).
*/
accept?: string;
/**
* Custom Element for an Upload File Item.
*/
fileItemElement?: (file: File) => ReactElement<HTMLElement>;
/**
* Custom Element for topbar of the component.
*/
topbar?: ReactElement<HTMLElement>;
/**
* Triggered when file(s) selected, added or removed.
*/
onChange?: (files: File[]) => void;
}
interface Props extends Omit<HTMLNextUIProps<"div">, "onChange"> {
/**
* Ref to the DOM node.
*/
ref?: ReactRef<HTMLElement | null>;
classNames?: SlotsToClasses<FileUploadSlots>;
/**
* A property to set initial files (which might be fetched) or to control files from outside of the component.
*/
files?: File[];
/**
* Custom Browse Button.
*/
browseButton?: ReactElement<ButtonProps>;
/**
* A different text for the browse button.
*/
browseButtonText?: string;
/**
* Custom Add Button.
*/
addButton?: ReactElement<ButtonProps>;
/**
* Custom Reset Button.
*/
resetButton?: ReactElement<ButtonProps>;
/**
* Custom Upload Button.
*/
uploadButton?: ReactElement<ButtonProps>;
/**
* Custom element to display buttons such as the browse button in customized design and order.
*/
buttons?: (
onBrowse: () => void,
onAdd: () => void,
onReset: () => void,
) => ReactElement<HTMLElement>;
/**
* If set to true, multiple files can be selected from the device.
* @default false
*/
multiple?: boolean;
/**
* Accept certain file format(s).
*/
accept?: string;
/**
* Custom Element for an Upload File Item.
*/
fileItemElement?: (file: File) => ReactElement<HTMLElement>;
/**
* Custom Element for topbar of the component.
*/
topbar?: ReactElement<HTMLElement>;
/**
* Triggered when file(s) selected, added or removed.
*/
onChange?: (files: File[]) => void;
/**
* Maximum size allowed for each file in bytes
*/
maxFileSize?: number;
/**
* Maximum total size allowed for all files in bytes
*/
maxTotalSize?: number;
/**
* Custom validation function for files
*/
validateFile?: (file: File) => boolean | Promise<boolean>;
/**
* Callback fired when an error occurs
*/
onError?: (error: Error, file?: File) => void;
/**
* Custom error message
*/
errorMessage?: string | ReactNode;
/**
* Callback fired during file upload progress
*/
onProgress?: (progress: number, file: File) => void;
/**
* Loading state of the upload process
*/
loading?: boolean;
}

Comment on lines +211 to +229
<input
ref={inputFileRef}
accept={accept}
className="hidden"
multiple={multiple}
title="file upload"
type="file"
onChange={(ev) => {
if (!ev.target.files?.length) return;
const newFiles: File[] = [];

for (let index = 0; index < ev.target.files.length; index++) {
const file = ev.target.files.item(index);

file && newFiles.push(file);
}
updateFiles(newFiles);
}}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

File validation is indeed missing and should be implemented

The review comment is accurate. After examining the hook implementation and component code, there's no validation for:

  • File size limits
  • File type validation beyond the HTML accept attribute
  • Maximum number of files when multiple is true

Key locations that need attention:

  • packages/components/file-upload/src/file-upload.tsx: The onChange handler where files are processed
  • packages/components/file-upload/src/use-file-upload.ts: The hook where validation logic could be centralized
🔗 Analysis chain

Consider adding file validation.

The file input handlers could benefit from additional validation:

  • File size limits
  • More robust file type checking
  • Maximum number of files validation

Let's check if there's any file validation in the hook:


Let me check the implementation of the hook and any validation logic:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for file validation in the useFileUpload hook
rg -A 5 "useFileUpload" packages/components/file-upload/src/

Length of output: 2222


Script:

#!/bin/bash
# Check the hook implementation and validation logic
cat packages/components/file-upload/src/use-file-upload.ts

Length of output: 2724

Comment on lines +130 to +146
const addButtonElement = useMemo(
() =>
addButton ? (
cloneElement(addButton, {
disabled: props.isDisabled,
onClick: (ev) => {
onAdd();
addButton.props.onClick?.(ev);
},
})
) : (
<Button color="secondary" disabled={props.isDisabled} onClick={() => onAdd()}>
Add
</Button>
),
[addButton, onAdd],
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing dependency to addButtonElement useMemo.

The props.isDisabled value is used inside the useMemo but is missing from the dependency array.

Apply this diff:

-    [addButton, onAdd],
+    [addButton, onAdd, props.isDisabled],
📝 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.

Suggested change
const addButtonElement = useMemo(
() =>
addButton ? (
cloneElement(addButton, {
disabled: props.isDisabled,
onClick: (ev) => {
onAdd();
addButton.props.onClick?.(ev);
},
})
) : (
<Button color="secondary" disabled={props.isDisabled} onClick={() => onAdd()}>
Add
</Button>
),
[addButton, onAdd],
);
const addButtonElement = useMemo(
() =>
addButton ? (
cloneElement(addButton, {
disabled: props.isDisabled,
onClick: (ev) => {
onAdd();
addButton.props.onClick?.(ev);
},
})
) : (
<Button color="secondary" disabled={props.isDisabled} onClick={() => onAdd()}>
Add
</Button>
),
[addButton, onAdd, props.isDisabled],
);

Copy link
Member

@wingkwong wingkwong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please check all the coderabbitai conversations. mark resolved if completed.

@wingkwong wingkwong added this to the v2.7.0 milestone Nov 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

File Upload
2 participants