Skip to content

Commit 8947b1f

Browse files
committed
feat: Add enable_folder_selection prop to Upload component
- Add enable_folder_selection prop to allow explicit folder-only mode - When enable_folder_selection=True with multiple=True, file picker shows folders only - When enable_folder_selection=False (default), file picker shows files only - Both modes support drag-and-drop for files and folders - This fixes the accept prop issue where webkitdirectory blocked file selection - Users can now create separate Upload components for files vs folders This addresses the issue where the accept prop didn't work with the file chooser button because webkitdirectory was always enabled with multiple=True. Now folder selection is opt-in via the enable_folder_selection prop.
1 parent 2004c71 commit 8947b1f

File tree

2 files changed

+43
-29
lines changed

2 files changed

+43
-29
lines changed

components/dash-core-components/src/components/Upload.react.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,21 @@ Upload.propTypes = {
111111

112112
/**
113113
* Allow dropping multiple files.
114-
* When true, also enables folder selection and drag-and-drop,
115-
* allowing users to upload entire folders. The folder hierarchy
116-
* is preserved in the filenames (e.g., 'folder/subfolder/file.txt').
117-
* Note: Folder support is available in Chrome, Edge, and Opera.
118-
* Other browsers will fall back to file-only mode.
114+
* When true, enables folder drag-and-drop support.
115+
* The folder hierarchy is preserved in filenames (e.g., 'folder/subfolder/file.txt').
116+
* Note: Folder drag-and-drop is supported in Chrome, Edge, and Opera.
119117
*/
120118
multiple: PropTypes.bool,
121119

120+
/**
121+
* Enable folder selection in the file picker dialog.
122+
* When true with multiple=True, the file picker allows selecting folders instead of files.
123+
* Note: When folder selection is enabled, individual files cannot be selected via the button.
124+
* Use separate Upload components if you need both file and folder selection options.
125+
* Folder selection is supported in Chrome, Edge, and Opera.
126+
*/
127+
enable_folder_selection: PropTypes.bool,
128+
122129
/**
123130
* HTML class name of the component
124131
*/
@@ -171,6 +178,7 @@ Upload.defaultProps = {
171178
max_size: -1,
172179
min_size: 0,
173180
multiple: false,
181+
enable_folder_selection: false,
174182
style: {},
175183
style_active: {
176184
borderStyle: 'solid',

components/dash-core-components/src/fragments/Upload.react.js

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,24 @@ export default class Upload extends Component {
2323

2424
return acceptList.some(acceptItem => {
2525
const item = acceptItem.trim().toLowerCase();
26-
26+
2727
// Exact MIME type match
2828
if (item === fileType) {
2929
return true;
3030
}
31-
31+
3232
// Wildcard MIME type (e.g., image/*)
3333
if (item.endsWith('/*')) {
3434
const wildcardSuffixLength = 2;
3535
const baseType = item.slice(0, -wildcardSuffixLength);
3636
return fileType.startsWith(baseType + '/');
3737
}
38-
38+
3939
// File extension match (e.g., .jpg)
4040
if (item.startsWith('.')) {
4141
return fileName.endsWith(item);
4242
}
43-
43+
4444
return false;
4545
});
4646
}
@@ -49,30 +49,30 @@ export default class Upload extends Component {
4949
async traverseFileTree(item, path = '') {
5050
const {accept} = this.props;
5151
const files = [];
52-
52+
5353
if (item.isFile) {
54-
return new Promise((resolve) => {
55-
item.file((file) => {
54+
return new Promise(resolve => {
55+
item.file(file => {
5656
// Check if file matches accept criteria
5757
if (!this.fileMatchesAccept(file, accept)) {
5858
resolve([]);
5959
return;
6060
}
61-
61+
6262
// Preserve folder structure in file name
6363
const relativePath = path + file.name;
6464
Object.defineProperty(file, 'name', {
6565
writable: true,
66-
value: relativePath
66+
value: relativePath,
6767
});
6868
resolve([file]);
6969
});
7070
});
7171
} else if (item.isDirectory) {
7272
const dirReader = item.createReader();
73-
return new Promise((resolve) => {
73+
return new Promise(resolve => {
7474
const readEntries = () => {
75-
dirReader.readEntries(async (entries) => {
75+
dirReader.readEntries(async entries => {
7676
if (entries.length === 0) {
7777
resolve(files);
7878
} else {
@@ -97,7 +97,7 @@ export default class Upload extends Component {
9797
// Custom data transfer handler that supports folders
9898
async getDataTransferItems(event) {
9999
const {multiple} = this.props;
100-
100+
101101
// If multiple is not enabled, use default behavior (files only)
102102
if (!multiple) {
103103
if (event.dataTransfer) {
@@ -112,10 +112,12 @@ export default class Upload extends Component {
112112
if (event.dataTransfer && event.dataTransfer.items) {
113113
const items = Array.from(event.dataTransfer.items);
114114
const files = [];
115-
115+
116116
for (const item of items) {
117117
if (item.kind === 'file') {
118-
const entry = item.webkitGetAsEntry ? item.webkitGetAsEntry() : null;
118+
const entry = item.webkitGetAsEntry
119+
? item.webkitGetAsEntry()
120+
: null;
119121
if (entry) {
120122
const entryFiles = await this.traverseFileTree(entry);
121123
files.push(...entryFiles);
@@ -130,17 +132,17 @@ export default class Upload extends Component {
130132
}
131133
return files;
132134
}
133-
135+
134136
// Handle file picker (already works with webkitdirectory attribute)
135137
if (event.target && event.target.files) {
136138
return Array.from(event.target.files);
137139
}
138-
140+
139141
// Fallback
140142
if (event.dataTransfer && event.dataTransfer.files) {
141143
return Array.from(event.dataTransfer.files);
142144
}
143-
145+
144146
return [];
145147
}
146148

@@ -189,6 +191,7 @@ export default class Upload extends Component {
189191
max_size,
190192
min_size,
191193
multiple,
194+
enable_folder_selection,
192195
className,
193196
className_active,
194197
className_reject,
@@ -203,13 +206,16 @@ export default class Upload extends Component {
203206
const disabledStyle = className_disabled ? undefined : style_disabled;
204207
const rejectStyle = className_reject ? undefined : style_reject;
205208

206-
// For react-dropzone v4.1.2, we need to add webkitdirectory attribute manually
207-
// when multiple is enabled to support folder selection
208-
const inputProps = multiple ? {
209-
webkitdirectory: 'true',
210-
directory: 'true',
211-
mozdirectory: 'true'
212-
} : {};
209+
// Enable folder selection in file picker when explicitly requested
210+
// Note: This makes individual files unselectable in the file picker
211+
const inputProps =
212+
multiple && enable_folder_selection
213+
? {
214+
webkitdirectory: 'true',
215+
directory: 'true',
216+
mozdirectory: 'true',
217+
}
218+
: {};
213219

214220
return (
215221
<LoadingElement id={id}>

0 commit comments

Comments
 (0)