@splunk/react-ui 5.7.1 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Accordion.js +6 -6
- package/Box.js +83 -34
- package/CHANGELOG.md +29 -0
- package/CollapsiblePanel.js +11 -11
- package/ComboBox.js +31 -27
- package/ControlGroup.js +92 -91
- package/DefinitionList.js +9 -9
- package/Drawer.d.ts +2 -0
- package/Drawer.js +679 -0
- package/DualListbox.js +1 -1
- package/JSONTree.js +73 -72
- package/Link.js +2 -2
- package/MIGRATION.md +10 -0
- package/Menu.js +338 -240
- package/Modal.js +127 -109
- package/Multiselect.js +437 -351
- package/Paginator.js +14 -12
- package/Popover.js +4 -1
- package/README.md +11 -0
- package/RadioBar.js +1 -1
- package/Search.js +103 -88
- package/Select.js +42 -40
- package/SelectBase.js +374 -328
- package/SidePanel.js +346 -167
- package/SlidingPanels.js +11 -11
- package/StepBar.js +7 -7
- package/Switch.js +5 -5
- package/Text.js +24 -24
- package/TextArea.js +7 -7
- package/TransitionOpen.js +188 -169
- package/docs-llm/Accordion.md +267 -0
- package/docs-llm/Anchor Menu.md +115 -0
- package/docs-llm/Anchor.md +54 -0
- package/docs-llm/AnimationToggle.md +254 -0
- package/docs-llm/Avatar.md +298 -0
- package/docs-llm/Badge.md +212 -0
- package/docs-llm/Breadcrumbs.md +306 -0
- package/docs-llm/Button Group.md +53 -0
- package/docs-llm/Button.md +361 -0
- package/docs-llm/Card Layout.md +286 -0
- package/docs-llm/Card.md +619 -0
- package/docs-llm/Checkbox.md +218 -0
- package/docs-llm/Chip.md +291 -0
- package/docs-llm/Clickable.md +160 -0
- package/docs-llm/Code.md +292 -0
- package/docs-llm/Collapsible Panel.md +744 -0
- package/docs-llm/Color.md +253 -0
- package/docs-llm/Column Layout.md +391 -0
- package/docs-llm/Combo Box.md +540 -0
- package/docs-llm/Control Group.md +594 -0
- package/docs-llm/Date.md +270 -0
- package/docs-llm/Definition List.md +278 -0
- package/docs-llm/Divider.md +216 -0
- package/docs-llm/Drawer.md +414 -0
- package/docs-llm/Dropdown.md +472 -0
- package/docs-llm/Dual Listbox.md +325 -0
- package/docs-llm/File.md +653 -0
- package/docs-llm/Form Rows.md +374 -0
- package/docs-llm/Heading.md +179 -0
- package/docs-llm/Image.md +109 -0
- package/docs-llm/JSON Tree.md +260 -0
- package/docs-llm/Layer.md +74 -0
- package/docs-llm/Layout.md +50 -0
- package/docs-llm/Link.md +318 -0
- package/docs-llm/List.md +189 -0
- package/docs-llm/Markdown.md +179 -0
- package/docs-llm/Menu.md +735 -0
- package/docs-llm/Message Bar.md +236 -0
- package/docs-llm/Message.md +248 -0
- package/docs-llm/Modal.md +443 -0
- package/docs-llm/Monogram.md +159 -0
- package/docs-llm/Multiselect.md +937 -0
- package/docs-llm/Number.md +298 -0
- package/docs-llm/Paginator.md +395 -0
- package/docs-llm/Paragraph.md +148 -0
- package/docs-llm/Phone Number.md +254 -0
- package/docs-llm/Popover.md +166 -0
- package/docs-llm/Progress.md +141 -0
- package/docs-llm/Radio Bar.md +303 -0
- package/docs-llm/Radio List.md +350 -0
- package/docs-llm/Resize.md +362 -0
- package/docs-llm/Screen Reader Content.md +73 -0
- package/docs-llm/Scroll Container Context.md +155 -0
- package/docs-llm/Scroll.md +152 -0
- package/docs-llm/Search.md +381 -0
- package/docs-llm/Select.md +985 -0
- package/docs-llm/Side Panel.md +777 -0
- package/docs-llm/Slider.md +339 -0
- package/docs-llm/Sliding Panels.md +340 -0
- package/docs-llm/Split Button.md +295 -0
- package/docs-llm/Static Content.md +90 -0
- package/docs-llm/Step Bar.md +292 -0
- package/docs-llm/Switch.md +268 -0
- package/docs-llm/Tab Bar.md +439 -0
- package/docs-llm/Tab Layout.md +398 -0
- package/docs-llm/Table.md +2642 -0
- package/docs-llm/Text Area.md +253 -0
- package/docs-llm/Text.md +339 -0
- package/docs-llm/Tooltip.md +325 -0
- package/docs-llm/Transition Open.md +406 -0
- package/docs-llm/Tree.md +586 -0
- package/docs-llm/Typography.md +125 -0
- package/docs-llm/Wait Spinner.md +121 -0
- package/docs-llm/llms.txt +97 -0
- package/package.json +6 -5
- package/types/src/Box/Box.d.ts +2 -10
- package/types/src/Drawer/Body.d.ts +17 -0
- package/types/src/Drawer/Drawer.d.ts +114 -0
- package/types/src/Drawer/DrawerContext.d.ts +11 -0
- package/types/src/Drawer/Footer.d.ts +25 -0
- package/types/src/Drawer/Header.d.ts +41 -0
- package/types/src/Drawer/docs/examples/Basic.d.ts +6 -0
- package/types/src/Drawer/docs/examples/ContainerPosition.d.ts +7 -0
- package/types/src/Drawer/docs/examples/InitialFocus.d.ts +9 -0
- package/types/src/Drawer/docs/examples/InlinePosition.d.ts +7 -0
- package/types/src/Drawer/docs/examples/PagePosition.d.ts +7 -0
- package/types/src/Drawer/index.d.ts +2 -0
- package/types/src/JSONTree/JSONTree.d.ts +12 -5
- package/types/src/JSONTree/renderTreeItems.d.ts +2 -1
- package/types/src/Menu/Item.d.ts +2 -1
- package/types/src/Menu/docs/examples/SelectableCheckbox.d.ts +7 -0
- package/types/src/Modal/Modal.d.ts +1 -2
- package/types/src/Select/Option.d.ts +6 -3
- package/types/src/Select/Select.d.ts +8 -5
- package/types/src/Select/docs/examples/Dimmed.d.ts +7 -0
- package/types/src/SelectBase/OptionBase.d.ts +6 -3
- package/types/src/SelectBase/SelectBase.d.ts +8 -3
- package/types/src/SidePanel/SidePanel.d.ts +43 -2
- package/types/src/SidePanel/docs/examples/DockLayout.d.ts +17 -0
- package/types/src/SidePanel/docs/examples/InitialFocus.d.ts +9 -0
- package/types/src/TransitionOpen/TransitionOpen.d.ts +29 -4
- package/types/src/useKeyPress/index.d.ts +9 -2
- package/types/src/useOnClickOutside/index.d.ts +2 -0
- package/types/src/useOnClickOutside/useOnClickOutside.d.ts +4 -0
- package/useKeyPress.js +23 -18
- package/useOnClickOutside.d.ts +2 -0
- package/useOnClickOutside.js +79 -0
- package/types/src/RadioList/docs/examples/Row.d.ts +0 -6
package/docs-llm/File.md
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
# File
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
> Image: Illustration of a File component.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## When to use this component
|
|
10
|
+
- When users are required to upload one or more files.
|
|
11
|
+
- When file type or size needs to be enforced at upload.
|
|
12
|
+
|
|
13
|
+
## When to use another component
|
|
14
|
+
- If the user needs to provide information that is not a file, use the appropriate data entry components instead.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### Labels and attributes
|
|
19
|
+
Use clear labels and include a supporting message specifying accepted file types.
|
|
20
|
+
|
|
21
|
+
> Image: In the first example with a heart eyes emoji, the File component includes a supporting message providing further context to the user. In the second example with a grimacing emoji, no supporting message is included in the File component.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Progress
|
|
25
|
+
Whenever possible, display progress on the File.Item to avoid frustration.
|
|
26
|
+
|
|
27
|
+
> Image: In the first example with a heart eyes emoji, the upload status of the File is visible to the user. In the second example with a grimacing emoji, the upload status is not visible to the user.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Error messaging
|
|
31
|
+
Offer clear, actionable error messages to help users understand and fix the issue.
|
|
32
|
+
|
|
33
|
+
Use when file upload is a required field.
|
|
34
|
+
> Image: In the first example with a heart eyes emoji, the user is given additional context on the error, that is, no file was uploaded. In the second example with a grimacing emoji, no further context is provided.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Use when the type of file uploaded is not accepted.
|
|
38
|
+
> Image: In the first example with a heart eyes emoji, the user is given additional context on the error, that is, the uploaded file type was not accepted. In the second example with a grimacing emoji, no further context is provided.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Examples
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### Single File
|
|
45
|
+
|
|
46
|
+
Here, selecting a new file will replace the existing file.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import React, { Component } from 'react';
|
|
50
|
+
|
|
51
|
+
import File, { FileRequestAddHandler } from '@splunk/react-ui/File';
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Single extends Component<object, { filename?: string }> {
|
|
55
|
+
private fileReader: FileReader;
|
|
56
|
+
|
|
57
|
+
constructor(props: object) {
|
|
58
|
+
super(props);
|
|
59
|
+
|
|
60
|
+
this.state = {};
|
|
61
|
+
|
|
62
|
+
this.fileReader = new FileReader();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
handleAddFiles: FileRequestAddHandler = (files) => {
|
|
66
|
+
if (files.length > 0) {
|
|
67
|
+
const file = files[0];
|
|
68
|
+
|
|
69
|
+
if (this.fileReader.readyState === 1) {
|
|
70
|
+
this.fileReader.abort();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.fileReader.onload = () => {
|
|
74
|
+
// can access this.fileReader.result
|
|
75
|
+
this.setState({ filename: file.name });
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
this.fileReader.readAsDataURL(file);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
handleRemoveFile = () => {
|
|
83
|
+
if (this.fileReader.readyState === 1) {
|
|
84
|
+
this.fileReader.abort();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.setState({ filename: undefined });
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
render() {
|
|
91
|
+
return (
|
|
92
|
+
<div style={{ maxWidth: 300 }}>
|
|
93
|
+
<File onRequestAdd={this.handleAddFiles} onRequestRemove={this.handleRemoveFile}>
|
|
94
|
+
{this.state.filename && <File.Item name={this.state.filename} />}
|
|
95
|
+
</File>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default Single;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
### Disabled
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import React, { Component } from 'react';
|
|
110
|
+
|
|
111
|
+
import File, { FileRequestAddHandler } from '@splunk/react-ui/File';
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Disabled extends Component<object, { filename?: string }> {
|
|
115
|
+
private fileReader: FileReader;
|
|
116
|
+
|
|
117
|
+
constructor(props: object) {
|
|
118
|
+
super(props);
|
|
119
|
+
|
|
120
|
+
this.state = {};
|
|
121
|
+
|
|
122
|
+
this.fileReader = new FileReader();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
handleAddFiles: FileRequestAddHandler = (files) => {
|
|
126
|
+
const file = files[0];
|
|
127
|
+
|
|
128
|
+
if (this.fileReader.readyState === 1) {
|
|
129
|
+
this.fileReader.abort();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.fileReader.onload = () => {
|
|
133
|
+
// can access this.fileReader.result
|
|
134
|
+
this.setState({ filename: file.name });
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
this.fileReader.readAsDataURL(file);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
handleRemoveFile = () => {
|
|
141
|
+
if (this.fileReader.readyState === 1) {
|
|
142
|
+
this.fileReader.abort();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.setState({ filename: undefined });
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
render() {
|
|
149
|
+
return (
|
|
150
|
+
<div style={{ width: 300 }}>
|
|
151
|
+
<File
|
|
152
|
+
help="This is some help content suggesting a max size of 2MB."
|
|
153
|
+
onRequestAdd={this.handleAddFiles}
|
|
154
|
+
onRequestRemove={this.handleRemoveFile}
|
|
155
|
+
disabled
|
|
156
|
+
>
|
|
157
|
+
{this.state.filename && <File.Item name={this.state.filename} />}
|
|
158
|
+
</File>
|
|
159
|
+
<File
|
|
160
|
+
help="This is some help content suggesting a max size of 2MB."
|
|
161
|
+
onRequestAdd={this.handleAddFiles}
|
|
162
|
+
onRequestRemove={this.handleRemoveFile}
|
|
163
|
+
disabled
|
|
164
|
+
>
|
|
165
|
+
<File.Item name="Apple" />
|
|
166
|
+
<File.Item name="Cupcake" />
|
|
167
|
+
<File.Item name="Banana" />
|
|
168
|
+
<File.Item name="Orange" />
|
|
169
|
+
</File>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export default Disabled;
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
### Multifile
|
|
181
|
+
|
|
182
|
+
By storing an array of items, multiple files can be supported.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import React, { Component } from 'react';
|
|
186
|
+
|
|
187
|
+
import File, { FileRequestAddHandler, FileRequestRemoveHandler } from '@splunk/react-ui/File';
|
|
188
|
+
|
|
189
|
+
interface FileItem {
|
|
190
|
+
name: string;
|
|
191
|
+
value?: string | ArrayBuffer | null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class Multi extends Component<object, { files: FileItem[] }> {
|
|
196
|
+
static loadFile(file: globalThis.File) {
|
|
197
|
+
const fileItem: FileItem = { name: file.name };
|
|
198
|
+
|
|
199
|
+
const fileReader = new FileReader();
|
|
200
|
+
fileReader.onload = () => {
|
|
201
|
+
fileItem.value = fileReader.result;
|
|
202
|
+
};
|
|
203
|
+
fileReader.readAsDataURL(file);
|
|
204
|
+
|
|
205
|
+
return fileItem;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
constructor(props: object) {
|
|
209
|
+
super(props);
|
|
210
|
+
|
|
211
|
+
this.state = {
|
|
212
|
+
files: [],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
handleAddFiles: FileRequestAddHandler = (files) => {
|
|
217
|
+
const newItems = files.map(Multi.loadFile);
|
|
218
|
+
|
|
219
|
+
this.setState((state) => ({
|
|
220
|
+
files: [...state.files, ...newItems],
|
|
221
|
+
}));
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
handleRemoveFile: FileRequestRemoveHandler = ({ index }) => {
|
|
225
|
+
this.setState((state) => {
|
|
226
|
+
const files = state.files.slice(0);
|
|
227
|
+
files.splice(index, 1);
|
|
228
|
+
return { files };
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
render() {
|
|
233
|
+
const children = this.state.files.map((item) => (
|
|
234
|
+
<File.Item itemId={item.name} name={item.name} key={item.name} />
|
|
235
|
+
));
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<div style={{ width: 300 }}>
|
|
239
|
+
<File
|
|
240
|
+
onRequestAdd={this.handleAddFiles}
|
|
241
|
+
onRequestRemove={this.handleRemoveFile}
|
|
242
|
+
allowMultiple
|
|
243
|
+
>
|
|
244
|
+
{children}
|
|
245
|
+
</File>
|
|
246
|
+
</div>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export default Multi;
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
### Customized supports message and help
|
|
257
|
+
|
|
258
|
+
The default supports message can be customized. A help message can also be added.
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import React from 'react';
|
|
262
|
+
|
|
263
|
+
import { noop } from 'lodash';
|
|
264
|
+
|
|
265
|
+
import File from '@splunk/react-ui/File';
|
|
266
|
+
import Link from '@splunk/react-ui/Link';
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
export default function CustomizedMessages() {
|
|
270
|
+
return (
|
|
271
|
+
<div style={{ width: 300 }}>
|
|
272
|
+
<File
|
|
273
|
+
accept="image/*"
|
|
274
|
+
onRequestAdd={noop}
|
|
275
|
+
onRequestRemove={noop}
|
|
276
|
+
supportsMessage={<>Supports image file types: e.g. .jpg, .gif, or .png</>}
|
|
277
|
+
help={
|
|
278
|
+
<>
|
|
279
|
+
Learn more about <Link>supported image formats</Link>.
|
|
280
|
+
</>
|
|
281
|
+
}
|
|
282
|
+
>
|
|
283
|
+
<File.Item name="Cupcake" uploadPercentage={40} />
|
|
284
|
+
</File>
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
### Progress
|
|
293
|
+
|
|
294
|
+
The File.Item can display its upload progress.
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import React from 'react';
|
|
298
|
+
|
|
299
|
+
import { noop } from 'lodash';
|
|
300
|
+
|
|
301
|
+
import File from '@splunk/react-ui/File';
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
export default function Progress() {
|
|
305
|
+
return (
|
|
306
|
+
<div style={{ width: 300 }}>
|
|
307
|
+
<File onRequestAdd={noop} onRequestRemove={noop}>
|
|
308
|
+
<File.Item name="Cupcake" uploadPercentage={40} />
|
|
309
|
+
</File>
|
|
310
|
+
</div>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
### Drop Anywhere
|
|
318
|
+
|
|
319
|
+
The File.Item can be dropped anywhere on the page. This example must open in a modal since the drop zone is the entire page and affects the other Files on this page.
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import React, { Component } from 'react';
|
|
323
|
+
|
|
324
|
+
import Button from '@splunk/react-ui/Button';
|
|
325
|
+
import File, { FileRequestAddHandler } from '@splunk/react-ui/File';
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class DropAnywhere extends Component<object, { filename?: string; open: boolean }> {
|
|
329
|
+
private fileReader: FileReader;
|
|
330
|
+
|
|
331
|
+
constructor(props: object) {
|
|
332
|
+
super(props);
|
|
333
|
+
|
|
334
|
+
this.state = {
|
|
335
|
+
open: false,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
this.fileReader = new FileReader();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
handleAddFiles: FileRequestAddHandler = (files) => {
|
|
342
|
+
if (files.length > 0) {
|
|
343
|
+
const file = files[0];
|
|
344
|
+
|
|
345
|
+
if (this.fileReader.readyState === 1) {
|
|
346
|
+
this.fileReader.abort();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
this.fileReader.onload = () => {
|
|
350
|
+
// can access this.fileReader.result
|
|
351
|
+
this.setState({ filename: file.name });
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
this.fileReader.readAsDataURL(file);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
handleRemoveFile = () => {
|
|
359
|
+
if (this.fileReader.readyState === 1) {
|
|
360
|
+
this.fileReader.abort();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
this.setState({ filename: undefined });
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
handleOpen = () => {
|
|
367
|
+
this.setState({
|
|
368
|
+
open: true,
|
|
369
|
+
});
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
handleClose = () => {
|
|
373
|
+
this.setState({
|
|
374
|
+
open: false,
|
|
375
|
+
});
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
render() {
|
|
379
|
+
const modalStyles: React.CSSProperties = {
|
|
380
|
+
position: 'relative',
|
|
381
|
+
top: 0,
|
|
382
|
+
right: 0,
|
|
383
|
+
bottom: 0,
|
|
384
|
+
left: 0,
|
|
385
|
+
zIndex: 100,
|
|
386
|
+
padding: 10,
|
|
387
|
+
margin: '0 -10px 4px',
|
|
388
|
+
height: 140,
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const closeStyles: React.CSSProperties = {
|
|
392
|
+
position: 'relative',
|
|
393
|
+
left: 'calc(50% - 60px)',
|
|
394
|
+
marginTop: 10,
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
return (
|
|
398
|
+
<div>
|
|
399
|
+
<Button onClick={this.handleOpen} label="Show drop anywhere example" />
|
|
400
|
+
|
|
401
|
+
{this.state.open && (
|
|
402
|
+
<div style={modalStyles}>
|
|
403
|
+
<File
|
|
404
|
+
dropAnywhere
|
|
405
|
+
help="This is some help content suggesting a max size of 2MB."
|
|
406
|
+
onRequestAdd={this.handleAddFiles}
|
|
407
|
+
onRequestRemove={this.handleRemoveFile}
|
|
408
|
+
>
|
|
409
|
+
{this.state.filename && <File.Item name={this.state.filename} />}
|
|
410
|
+
</File>
|
|
411
|
+
|
|
412
|
+
<Button
|
|
413
|
+
onClick={this.handleClose}
|
|
414
|
+
label="Close example"
|
|
415
|
+
style={closeStyles}
|
|
416
|
+
/>
|
|
417
|
+
</div>
|
|
418
|
+
)}
|
|
419
|
+
</div>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export default DropAnywhere;
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
### Full Screen
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
import React, { useRef, useState } from 'react';
|
|
433
|
+
|
|
434
|
+
import styled from 'styled-components';
|
|
435
|
+
|
|
436
|
+
import Button from '@splunk/react-ui/Button';
|
|
437
|
+
import File, { FileRequestAddHandler } from '@splunk/react-ui/File';
|
|
438
|
+
import { variables } from '@splunk/themes';
|
|
439
|
+
|
|
440
|
+
const StyledFullscreenOverlay = styled.div`
|
|
441
|
+
position: fixed;
|
|
442
|
+
top: 0;
|
|
443
|
+
right: 0;
|
|
444
|
+
bottom: 0;
|
|
445
|
+
left: 0;
|
|
446
|
+
z-index: ${variables.zindexModal};
|
|
447
|
+
background-color: ${variables.backgroundColorPage};
|
|
448
|
+
display: flex;
|
|
449
|
+
justify-content: center;
|
|
450
|
+
align-items: center;
|
|
451
|
+
`;
|
|
452
|
+
|
|
453
|
+
const StyledButton = styled(Button)`
|
|
454
|
+
position: absolute;
|
|
455
|
+
bottom: 20px;
|
|
456
|
+
left: 50%;
|
|
457
|
+
transform: translateX(-50%);
|
|
458
|
+
`;
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
function Fullscreen() {
|
|
462
|
+
const fileReader = useRef(new FileReader());
|
|
463
|
+
const fullscreenToggle = useRef<HTMLButtonElement | null>(null);
|
|
464
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
465
|
+
const [fileName, setFileName] = useState<string | undefined>(undefined);
|
|
466
|
+
|
|
467
|
+
const handleAddFiles: FileRequestAddHandler = (files) => {
|
|
468
|
+
const currentFileReader = fileReader.current;
|
|
469
|
+
if (files.length > 0) {
|
|
470
|
+
const file = files[0];
|
|
471
|
+
|
|
472
|
+
if (currentFileReader.readyState === 1) {
|
|
473
|
+
currentFileReader.abort();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
currentFileReader.onload = () => {
|
|
477
|
+
// can access this.fileReader.result
|
|
478
|
+
setFileName(file.name);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
currentFileReader.readAsDataURL(file);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const handleRemoveFile = () => {
|
|
486
|
+
const currentFileReader = fileReader.current;
|
|
487
|
+
if (currentFileReader.readyState === 1) {
|
|
488
|
+
currentFileReader.abort();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
setFileName(undefined);
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const handleOpen = () => {
|
|
495
|
+
setOpen(true);
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const handleClose = () => {
|
|
499
|
+
fullscreenToggle.current?.focus();
|
|
500
|
+
setOpen(false);
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
return (
|
|
504
|
+
<div style={{ width: 300 }}>
|
|
505
|
+
<Button
|
|
506
|
+
elementRef={fullscreenToggle}
|
|
507
|
+
onClick={handleOpen}
|
|
508
|
+
label="Open full screen file example"
|
|
509
|
+
/>
|
|
510
|
+
|
|
511
|
+
{open && (
|
|
512
|
+
<StyledFullscreenOverlay>
|
|
513
|
+
<File
|
|
514
|
+
fullscreen
|
|
515
|
+
help="This is some help content suggesting a max size of 2MB."
|
|
516
|
+
onRequestAdd={handleAddFiles}
|
|
517
|
+
onRequestRemove={handleRemoveFile}
|
|
518
|
+
>
|
|
519
|
+
{fileName && <File.Item name={fileName} />}
|
|
520
|
+
</File>
|
|
521
|
+
|
|
522
|
+
<StyledButton onClick={handleClose} label="Close example" />
|
|
523
|
+
</StyledFullscreenOverlay>
|
|
524
|
+
)}
|
|
525
|
+
</div>
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export default Fullscreen;
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
### Error
|
|
535
|
+
|
|
536
|
+
The File and File.Item have separate error states. Generally, they should not be used together. Either the File is in error (for example, required field), or an Item is in error (for example, incorrect file type).
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
import React from 'react';
|
|
540
|
+
|
|
541
|
+
import { noop } from 'lodash';
|
|
542
|
+
|
|
543
|
+
import File from '@splunk/react-ui/File';
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
export default function FileErrors() {
|
|
547
|
+
return (
|
|
548
|
+
<div style={{ width: 300 }}>
|
|
549
|
+
<File onRequestAdd={noop} onRequestRemove={noop} error />
|
|
550
|
+
<br />
|
|
551
|
+
<br />
|
|
552
|
+
<File onRequestAdd={noop} onRequestRemove={noop} onRequestRetry={noop}>
|
|
553
|
+
<File.Item name="Apple" />
|
|
554
|
+
<File.Item name="Cupcake" error />
|
|
555
|
+
<File.Item name="Banana" />
|
|
556
|
+
<File.Item name="Orange" />
|
|
557
|
+
</File>
|
|
558
|
+
</div>
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
## API
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
### File API
|
|
570
|
+
|
|
571
|
+
File provides the ability to accept files and present uploaded files. It does not provide
|
|
572
|
+
file readers, only a reference to the file. This can be used to post binary content, or
|
|
573
|
+
upload using an array buffer.
|
|
574
|
+
|
|
575
|
+
#### Props
|
|
576
|
+
|
|
577
|
+
| Name | Type | Required | Default | Description |
|
|
578
|
+
|------|------|------|------|------|
|
|
579
|
+
| accept | string | no | | The accept attribute for the file browser. This does not filter dropped items, which must be filtered manually. File will create a default "supports" message based on this value. |
|
|
580
|
+
| allowMultiple | boolean | no | | Allow the user to upload multiple files. |
|
|
581
|
+
| children | React.ReactNode | no | | |
|
|
582
|
+
| disabled | boolean | no | | Prevents user from dropping files. |
|
|
583
|
+
| dropAnywhere | boolean | no | | File can be dropped anywhere on the page. |
|
|
584
|
+
| elementRef | React.Ref<HTMLDivElement> | no | | A React ref which is set to the DOM element when the component mounts, and null when it unmounts. |
|
|
585
|
+
| error | boolean | no | | Show the component in an error state. This has no effect on the full-screen File. Note: File.Item has a separate error property. |
|
|
586
|
+
| fullscreen | boolean | no | | There can only be one File component on the page as it will take all files dropped on the page. |
|
|
587
|
+
| help | React.ReactNode | no | | Show help text. |
|
|
588
|
+
| inputId | string | no | | An id for the input, which may be necessary for accessibility, such as for aria attributes. |
|
|
589
|
+
| name | string | no | | The name is returned with onRequestAdd and onRequestRemove events, which can be used to identify the control when multiple controls share an onChange callback. |
|
|
590
|
+
| onRequestAdd | FileRequestAddHandler | no | | A callback for when the user selects one or more files. The function is passed a file reference, which can then be used to read the file. This may be used to enforce file constraints or upload the file. |
|
|
591
|
+
| onRequestRemove | FileRequestRemoveHandler | no | | A callback for when the user requests to remove a file. The function is passed the event and an object with the Item's index and name: `(event, {index, name})`. |
|
|
592
|
+
| onRequestRetry | (data: { event: { itemId?: string; name: string }; filename: string; index: number; itemId?: string; name?: string; }) => void | no | | A callback for when the user requests to retry the upload after upload resulted in error. The function is passed the event and an object with the Item's index and name: `(event, {index, name})`. |
|
|
593
|
+
| supportsMessage | React.ReactNode | no | | |
|
|
594
|
+
|
|
595
|
+
#### Types
|
|
596
|
+
|
|
597
|
+
| Name | Type | Description |
|
|
598
|
+
|------|------|------|
|
|
599
|
+
| FileRequestAddHandler | (files: globalThis.File[], data: { name?: string }) => void | |
|
|
600
|
+
| FileRequestRemoveHandler | (data: { event: { itemId?: string; name: string; }; filename: string; index: number; itemId?: string; name?: string; }) => void | |
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
### File.Item API
|
|
605
|
+
|
|
606
|
+
#### Props
|
|
607
|
+
|
|
608
|
+
| Name | Type | Required | Default | Description |
|
|
609
|
+
|------|------|------|------|------|
|
|
610
|
+
| disabled | boolean | no | | |
|
|
611
|
+
| elementRef | React.Ref<HTMLDivElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
|
|
612
|
+
| error | boolean | no | false | Show the Item in an error state. |
|
|
613
|
+
| itemId | string | no | | A unique for this file. |
|
|
614
|
+
| name | string | yes | | The name is displayed on the item. |
|
|
615
|
+
| uploadPercentage | number | no | | If the uploadPercentage is 0, the item is assumed to be queued. If the upload is complete or not applicable, uploadPercentage must be undefined. |
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
## Accessibility
|
|
622
|
+
|
|
623
|
+
## Visual Design
|
|
624
|
+
- Color contrast ratio **MUST** be:
|
|
625
|
+
- >=4.5:1 for [SC 1.4.3][1]:
|
|
626
|
+
- Any text (static, link, etc.) to background color
|
|
627
|
+
- >=3:1 for [SC 1.4.11][2]
|
|
628
|
+
- Drop-area (usually a dotted line) to background color
|
|
629
|
+
- Any functional icon (refresh, close, delete) to background color
|
|
630
|
+
|
|
631
|
+
## Content
|
|
632
|
+
- **SHOULD** have a descriptive button, such as "Browse Files" or "Upload File"; avoid verb-only phrases such as "Browse".
|
|
633
|
+
|
|
634
|
+
## States
|
|
635
|
+
- Feedback mechanism **MUST** be provided for upload:
|
|
636
|
+
- In progress
|
|
637
|
+
- Success
|
|
638
|
+
- Error
|
|
639
|
+
- Single file
|
|
640
|
+
- One or more files in multi-upload
|
|
641
|
+
|
|
642
|
+
## Interaction Model
|
|
643
|
+
- **MUST** have keyboard navigation [SC 2.1][3]:
|
|
644
|
+
- <kbd>Tab</kbd> and <kbd>Shift+Tab</kbd>: focuses button to upload file and any icons associated with uploaded files, regardless of state.
|
|
645
|
+
|
|
646
|
+
## Implementation
|
|
647
|
+
- When more than one file is uploaded, screen reader **MUST** announce file count, i.e. "1 of n" [SC 4.1.2][4]
|
|
648
|
+
|
|
649
|
+
[1]: https://www.w3.org/TR/WCAG21/#contrast-minimum
|
|
650
|
+
[2]: https://www.w3.org/TR/WCAG21/#non-text-contrast
|
|
651
|
+
[3]: https://www.w3.org/TR/WCAG21/#keyboard-accessible
|
|
652
|
+
[4]: https://www.w3.org/TR/WCAG21/#name-role-value
|
|
653
|
+
|