mirror of
https://github.com/grafana/grafana.git
synced 2026-09-05 04:40:13 -05:00
UI: FileDropzone component to handle file list overwrite (#37685)
* UI: FileDropzone component to handle file list overwrite * FileListItem: use type=button everywhere * Update packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx Co-authored-by: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> * FileListItem: add aria-hidden and change cancel text * Update packages/grafana-ui/src/components/FileDropzone/FileListItem.test.tsx Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> Co-authored-by: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
This commit is contained in:
co-authored by
Tobias Skarhed
Alex Khomenko
parent
3e124c854e
commit
ad6a4edfeb
@@ -21,3 +21,7 @@ export default {
|
||||
export const Basic: Story<FileDropzoneProps> = (args) => {
|
||||
return <FileDropzone {...args} />;
|
||||
};
|
||||
|
||||
export const WithCustomFileList: Story<FileDropzoneProps> = () => {
|
||||
return <FileDropzone fileListRenderer={(file) => <div>Custom rendered item {file.file.name}</div>} />;
|
||||
};
|
||||
|
||||
@@ -107,6 +107,17 @@ describe('The FileDropzone component', () => {
|
||||
|
||||
screen.getByText('Custom dropzone text');
|
||||
});
|
||||
|
||||
it('should handle file list overwrite when fileListRenderer is passed', async () => {
|
||||
render(<FileDropzone fileListRenderer={() => null} />);
|
||||
|
||||
dispatchEvt(screen.getByTestId('dropzone'), 'drop', mockData([file({})]));
|
||||
|
||||
// need to await this in order to have the drop finished
|
||||
await screen.findByTestId('dropzone');
|
||||
|
||||
expect(screen.queryByText('ping.json')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
function dispatchEvt(node: HTMLElement, type: string, data: any) {
|
||||
|
||||
@@ -30,6 +30,11 @@ export interface FileDropzoneProps {
|
||||
* Use the onLoad function to get the result from FileReader.
|
||||
*/
|
||||
onLoad?: (result: string | ArrayBuffer | null) => void;
|
||||
/**
|
||||
* The fileListRenderer property can be used to overwrite the list of files. To not to show
|
||||
* any list return null in the function.
|
||||
*/
|
||||
fileListRenderer?: (file: DropzoneFile, removeFile: (file: DropzoneFile) => void) => ReactNode;
|
||||
}
|
||||
|
||||
export interface DropzoneFile {
|
||||
@@ -41,7 +46,7 @@ export interface DropzoneFile {
|
||||
retryUpload?: () => void;
|
||||
}
|
||||
|
||||
export function FileDropzone({ options, children, readAs, onLoad }: FileDropzoneProps) {
|
||||
export function FileDropzone({ options, children, readAs, onLoad, fileListRenderer }: FileDropzoneProps) {
|
||||
const [files, setFiles] = useState<DropzoneFile[]>([]);
|
||||
|
||||
const setFileProperty = useCallback(
|
||||
@@ -133,7 +138,12 @@ export function FileDropzone({ options, children, readAs, onLoad }: FileDropzone
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({ ...options, onDrop });
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme, isDragActive);
|
||||
const fileList = files.map((file) => <FileListItem key={file.id} file={file} removeFile={removeFile} />);
|
||||
const fileList = files.map((file) => {
|
||||
if (fileListRenderer) {
|
||||
return fileListRenderer(file, removeFile);
|
||||
}
|
||||
return <FileListItem key={file.id} file={file} removeFile={removeFile} />;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('The FileListItem component', () => {
|
||||
it('should show a progressbar when the progress prop has a value', () => {
|
||||
render(<FileListItem file={{ file: file({}), id: '1', error: null, progress: 6 }} />);
|
||||
|
||||
expect(screen.queryByText('Cancel')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Cancel upload')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('46%')).toBeInTheDocument();
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
});
|
||||
@@ -45,7 +45,7 @@ describe('The FileListItem component', () => {
|
||||
const abortUpload = jest.fn();
|
||||
render(<FileListItem file={{ file: file({}), id: '1', error: null, progress: 6, abortUpload }} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
|
||||
|
||||
expect(abortUpload).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -24,11 +24,19 @@ export function FileListItem({ file: customFile, removeFile }: FileListItemProps
|
||||
<>
|
||||
<span className={styles.error}>{error.message}</span>
|
||||
{retryUpload && (
|
||||
<IconButton aria-label="Retry" name="sync" tooltip="Retry" tooltipPlacement="top" onClick={retryUpload} />
|
||||
<IconButton
|
||||
type="button"
|
||||
aria-label="Retry"
|
||||
name="sync"
|
||||
tooltip="Retry"
|
||||
tooltipPlacement="top"
|
||||
onClick={retryUpload}
|
||||
/>
|
||||
)}
|
||||
{removeFile && (
|
||||
<IconButton
|
||||
className={retryUpload ? styles.marginLeft : ''}
|
||||
type="button"
|
||||
name="trash-alt"
|
||||
onClick={() => removeFile(customFile)}
|
||||
tooltip={REMOVE_FILE}
|
||||
@@ -46,7 +54,7 @@ export function FileListItem({ file: customFile, removeFile }: FileListItemProps
|
||||
<span className={styles.paddingLeft}>{Math.round((progress / file.size) * 100)}%</span>
|
||||
{abortUpload && (
|
||||
<Button variant="secondary" type="button" fill="text" onClick={abortUpload}>
|
||||
Cancel
|
||||
Cancel upload
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
@@ -59,6 +67,7 @@ export function FileListItem({ file: customFile, removeFile }: FileListItemProps
|
||||
onClick={() => removeFile(customFile)}
|
||||
tooltip={REMOVE_FILE}
|
||||
aria-label={REMOVE_FILE}
|
||||
type="button"
|
||||
tooltipPlacement="top"
|
||||
/>
|
||||
)
|
||||
@@ -70,7 +79,7 @@ export function FileListItem({ file: customFile, removeFile }: FileListItemProps
|
||||
return (
|
||||
<div className={styles.fileListContainer}>
|
||||
<span className={styles.fileNameWrapper}>
|
||||
<Icon name="file-blank" size="lg" />
|
||||
<Icon name="file-blank" size="lg" aria-hidden={true} />
|
||||
<span className={styles.padding}>{trimFileName(file.name)}</span>
|
||||
<span>{formattedValueToString(valueFormat)}</span>
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user