-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
base: canary
Are you sure you want to change the base?
feat(file-upload) #3832
Changes from all commits
7a9a013
f1717ee
42e77ca
26b5247
9c39fed
836ef12
fa81b7e
71e5ceb
2f56ea5
a58f3fd
6926e7b
7ca7d22
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,25 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+1
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const react = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"/App.jsx": App, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export default { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
...react, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
const App = ` | ||
import React from "react"; | ||
import {FileUpload} from "@nextui-org/react"; | ||
import {filesFromApi} from "./filesFromApi"; | ||
|
||
const filesFromApi= new Promise((res) => { | ||
res([ | ||
new File(['dummy content'], "file1.jpg", {type: "image/jpeg"}), | ||
new File(['dummy content'], "file2.jpg", {type: "image/jpeg"}), | ||
new File(['dummy content'], "file3.jpg", {type: "image/jpeg"}), | ||
]); | ||
}); | ||
|
||
export default function App() { | ||
const [files, setFiles] = React.useState([]); | ||
|
||
React.useEffect(() => { | ||
filesFromApi.then(files => setFiles(files)); | ||
}, []); | ||
|
||
return ( | ||
<FileUpload files={files} /> | ||
); | ||
}`; | ||
|
||
const react = { | ||
"/App.jsx": App, | ||
}; | ||
|
||
export default { | ||
...react, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
const DeleteIcon = `export const DeleteIcon = ({ | ||
fill = 'currentColor', | ||
filled, | ||
size, | ||
height, | ||
width, | ||
label, | ||
...props | ||
}) => { | ||
return ( | ||
<svg aria-hidden="true" fill="none" focusable="false" height="1em" role="presentation" | ||
viewBox="0 0 20 20" width="1em"> | ||
<path | ||
d="M17.5 4.98332C14.725 4.70832 11.9333 4.56665 9.15 4.56665C7.5 4.56665 5.85 4.64998 4.2 4.81665L2.5 4.98332" | ||
stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5"></path> | ||
<path | ||
d="M7.08331 4.14169L7.26665 3.05002C7.39998 2.25835 7.49998 1.66669 8.90831 1.66669H11.0916C12.5 1.66669 12.6083 2.29169 12.7333 3.05835L12.9166 4.14169" | ||
stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5"></path> | ||
<path | ||
d="M15.7084 7.61664L15.1667 16.0083C15.075 17.3166 15 18.3333 12.675 18.3333H7.32502C5.00002 18.3333 4.92502 17.3166 4.83335 16.0083L4.29169 7.61664" | ||
stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5"></path> | ||
<path d="M8.60834 13.75H11.3833" stroke="currentColor" strokeLinecap="round" | ||
strokeLinejoin="round" strokeWidth="1.5"></path> | ||
<path d="M7.91669 10.4167H12.0834" stroke="currentColor" strokeLinecap="round" | ||
strokeLinejoin="round" strokeWidth="1.5"></path> | ||
</svg> | ||
); | ||
};`; | ||
|
||
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>} | ||
/> | ||
); | ||
}`; | ||
Comment on lines
+30
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
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>}
/>
);
}`;
|
||
|
||
const react = { | ||
"/App.jsx": App, | ||
"/DeleteIcon.jsx": DeleteIcon, | ||
}; | ||
|
||
export default { | ||
...react, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
const App = ` | ||
import {FileUpload} from "@nextui-org/react"; | ||
|
||
export default function App() { | ||
return ( | ||
<FileUpload | ||
fileItemElement={(file) => <div className="text-red-500">{file.name}</div>} | ||
/> | ||
); | ||
}`; | ||
|
||
const react = { | ||
"/App.jsx": App, | ||
}; | ||
|
||
export default { | ||
...react, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import usage from "./usage"; | ||
import multiple from "./multiple"; | ||
import topbar from "./topbar"; | ||
import fileItem from "./file-item"; | ||
import customButtons from "./customButtons"; | ||
import buttonsContainer from "./buttonsContainer"; | ||
import controlled from "./controlled"; | ||
|
||
export const fileUploadContent = { | ||
usage, | ||
multiple, | ||
topbar, | ||
fileItem, | ||
customButtons, | ||
buttonsContainer, | ||
controlled, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
const App = `import {FileUpload} from "@nextui-org/react"; | ||
|
||
export default function App() { | ||
return ( | ||
<FileUpload multiple onChange={(files) => { /* handle files */}} /> | ||
); | ||
}`; | ||
|
||
const react = { | ||
"/App.jsx": App, | ||
}; | ||
|
||
export default { | ||
...react, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
const App = `import {FileUpload, FileUploadTopbar} from "@nextui-org/react"; | ||
|
||
export default function App() { | ||
return ( | ||
<FileUpload topbar={<FileUploadTopbar maxAllowedSize="1 MB" />} /> | ||
); | ||
}`; | ||
|
||
const react = { | ||
"/App.jsx": App, | ||
}; | ||
|
||
export default { | ||
...react, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
const App = `import {FileUpload} from "@nextui-org/react"; | ||
|
||
export default function App() { | ||
return ( | ||
<FileUpload onChange={(files) => { /* handle files */}} /> | ||
); | ||
}`; | ||
|
||
const react = { | ||
"/App.jsx": App, | ||
}; | ||
|
||
export default { | ||
...react, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
--- | ||
title: "File Upload" | ||
description: "File Upload adds/removes file(s) selected by user to upload on a server." | ||
--- | ||
|
||
import {fileUploadContent} from "@/content/components/file-upload"; | ||
|
||
# File Upload | ||
|
||
File Upload adds/removes file(s) selected by user to upload on a server. | ||
|
||
<ComponentLinks component="file-upload" /> | ||
|
||
--- | ||
|
||
|
||
## Installation | ||
|
||
<PackageManagers | ||
showGlobalInstallWarning | ||
commands={{ | ||
cli: "npx nextui-cli@latest add file-upload", | ||
npm: "npm install @nextui-org/file-upload", | ||
yarn: "yarn add @nextui-org/file-upload", | ||
pnpm: "pnpm add @nextui-org/file-upload", | ||
bun: "bun add @nextui-org/file-upload" | ||
}} | ||
/> | ||
|
||
## Import | ||
|
||
NextUI exports 3 fileupload-related components: | ||
|
||
- **FileUpload**: The main component to display file(s) that is/are to be uploaded. | ||
- **FileUploadItem**: The item component to display a single file item. | ||
- **FileUploadTopbar**: The topbar component to display information like the number of files to upload, max file size, etc. | ||
|
||
<ImportTabs | ||
commands={{ | ||
main: 'import {FileUpload, FileUploadItem, FileUploadTopbar} from "@nextui-org/react";', | ||
individual: 'import {FileUpload, FileUploadItem, FileUploadTopbar} from "@nextui-org/file-upload";', | ||
}} | ||
/> | ||
|
||
## Usage | ||
|
||
<CodeDemo title="Usage" files={fileUploadContent.usage} /> | ||
|
||
### Upload Multiple Items | ||
|
||
If you set `multiple` to `true`, then the `FileUpload` will allow multiple files to select and upload. | ||
|
||
<CodeDemo title="Upload Multiple Items" files={fileUploadContent.multiple} /> | ||
|
||
### With Topbar Section | ||
|
||
If you want to show some information for the user, you can use your custom component or use the predefined `FileUploadTopbar` component. | ||
|
||
<CodeDemo title="With Topbar Section" files={fileUploadContent.topbar} /> | ||
|
||
### With Custom File Item | ||
|
||
There is a built-in component to display information regarding the selected files called `FileUploadItem`. You can implement your own | ||
|
||
<CodeDemo title="With Custom Upload, Browse, Add and Reset Buttons" files={fileUploadContent.fileItem} /> | ||
|
||
### With Custom Upload, Browse, Add and Reset Buttons | ||
|
||
If you need to have your custom buttons for browsing, adding or reseting, you can implement one for each. | ||
In addition, there is an `uploadButton` prop for you in case uploading files prior to submitting the form is required. | ||
|
||
<CodeDemo title="With Custom Upload, Browse, Add and Reset Buttons" files={fileUploadContent.customButtons} /> | ||
|
||
### With A Custom Container For Buttons Section | ||
|
||
You can replace all the buttons entirely with your custom buttons container using the `buttons` prop. | ||
It provides three handlers for adding a single file (`onAdd`), browsing (`onBrowse`) and removing all files (`onReset`). | ||
|
||
<CodeDemo title="With Custom Buttons" files={fileUploadContent.buttonsContainer} /> | ||
|
||
### Controlled | ||
|
||
In case there are some files that were previously uploaded on a server and one wants to show them as uploaded files, | ||
the `files` prop can be set initially. | ||
|
||
<CodeDemo title="Controlled" files={fileUploadContent.controlled} /> | ||
|
||
## Slots | ||
|
||
- **base**: The main wrapper for the fileupload component. Modify the fileupload base styles. | ||
- **items**: The class name of the fileupload items wrapper. | ||
- **buttons**: The class name of the fileupload buttons wrapper. | ||
|
||
## Data Attributes | ||
|
||
`FileUpload` has the following attributes on the `base` element: | ||
|
||
- **data-disabled**: | ||
When the fileupload is disabled. | ||
|
||
<Spacer y={4} /> | ||
|
||
## 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. | - | | ||
| browseButtonText | `string` | Custom text for the default Browse Button. | - | | ||
| browseButton | `ReactElement<ButtonProps>` | Custom button for browsing files. | - | | ||
| addButton | `ReactElement<ButtonProps>` | Custom button for adding a single file. | - | | ||
| resetButton | `ReactElement<ButtonProps>` | Custom button for reseting files to an empty array. | - | | ||
| uploadButton | `ReactElement<ButtonProps>` | Custom button for uploading files to a server. | - | | ||
| topbar | `ReactElement<HTMLElement>` | Custom topbar to show information regarding files. | - | | ||
| buttons | `ReactElement<HTMLElement>` | Custom buttons for browsing, adding, .etc or any other ones that are needed for the user. | - | | ||
| fileItemElement | `ReactElement<HTMLElement>` | 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. | | | ||
|
||
### FileSize | ||
|
||
```ts | ||
export type FileSize = `${number} KB` | `${number} MB`; | ||
``` | ||
|
||
--- | ||
|
||
### FileUpload classNames | ||
|
||
```ts | ||
export type FileUploadClassnames = { | ||
base?: string; | ||
items?: string; | ||
buttons?: string; | ||
}; | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add missing Button import.
The code uses the Button component but doesn't import it from @nextui-org/react.
📝 Committable suggestion