@douglasneuroinformatics/libui 3.4.2 → 3.5.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@douglasneuroinformatics/libui",
3
3
  "type": "module",
4
- "version": "3.4.2",
4
+ "version": "3.5.0",
5
5
  "packageManager": "pnpm@9.11.0",
6
6
  "description": "Generic UI components for DNP projects, built using React and Tailwind CSS",
7
7
  "author": "Joshua Unrau",
@@ -102,6 +102,7 @@
102
102
  "framer-motion": "^11.5.2",
103
103
  "lodash-es": "^4.17.21",
104
104
  "lucide-react": "^0.438.0",
105
+ "react-dropzone": "^14.2.3",
105
106
  "react-error-boundary": "^4.0.13",
106
107
  "react-resizable-panels": "^2.1.2",
107
108
  "recharts": "^2.12.7",
@@ -0,0 +1,69 @@
1
+ import { fireEvent, render, screen } from '@testing-library/react';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { FileDropzone } from './FileDropzone';
5
+
6
+ describe('FileDropzone', () => {
7
+ const testfile = new File([new Blob()], 'testfile.csv');
8
+ it('should render', () => {
9
+ render(
10
+ <FileDropzone
11
+ acceptedFileTypes={{
12
+ 'text/csv': ['.csv'],
13
+ 'text/plain': ['.csv', '.tsv']
14
+ }}
15
+ file={null}
16
+ setFile={function (file: File): void {
17
+ throw new Error('Function not implemented. File name is ' + file.name);
18
+ }}
19
+ />
20
+ );
21
+ expect(screen.getByTestId('dropzoneText')).contains(String, "Drag'n'drop files or click on box to upload");
22
+ });
23
+
24
+ it('should have file', () => {
25
+ render(
26
+ <FileDropzone
27
+ acceptedFileTypes={{
28
+ 'text/csv': ['.csv'],
29
+ 'text/plain': ['.csv', '.tsv']
30
+ }}
31
+ file={testfile}
32
+ setFile={function (file: File): void {
33
+ throw new Error('Function not implemented. File name is ' + file.name);
34
+ }}
35
+ />
36
+ );
37
+ expect(screen.getByText('testfile.csv')).toBeInTheDocument();
38
+ });
39
+ it('drag active should work', () => {
40
+ render(
41
+ <FileDropzone
42
+ acceptedFileTypes={{
43
+ 'text/csv': ['.csv'],
44
+ 'text/plain': ['.csv', '.tsv']
45
+ }}
46
+ file={null}
47
+ setFile={function (file: File): void {
48
+ throw new Error('Function not implemented. File name is ' + file.name);
49
+ }}
50
+ />
51
+ );
52
+
53
+ const fileDropzoneElement = screen.getByTestId('dropzone');
54
+
55
+ fireEvent.dragOver(fileDropzoneElement, {
56
+ dataTransfer: {
57
+ files: [testfile]
58
+ }
59
+ });
60
+
61
+ fireEvent.drop(fileDropzoneElement, {
62
+ dataTransfer: {
63
+ files: [testfile]
64
+ }
65
+ });
66
+
67
+ expect(screen.getByTestId('dropzoneText')).contain(String, 'testfile.csv');
68
+ });
69
+ });
@@ -0,0 +1,33 @@
1
+ import { useState } from 'react';
2
+
3
+ import type { Meta, StoryObj } from '@storybook/react';
4
+
5
+ import { FileDropzone } from './FileDropzone.js';
6
+
7
+ const meta: Meta<typeof FileDropzone> = {
8
+ component: FileDropzone
9
+ };
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof FileDropzone>;
14
+
15
+ export const Default: Story = {
16
+ decorators: [
17
+ (Story) => {
18
+ const [file, setFile] = useState<File | undefined>();
19
+ return (
20
+ <Story
21
+ args={{
22
+ acceptedFileTypes: {
23
+ 'text/csv': ['.csv'],
24
+ 'text/plain': ['.csv', '.tsv']
25
+ },
26
+ file,
27
+ setFile
28
+ }}
29
+ />
30
+ );
31
+ }
32
+ ]
33
+ };
@@ -0,0 +1,52 @@
1
+ import { useCallback } from 'react';
2
+
3
+ import { type FileRejection, useDropzone } from 'react-dropzone';
4
+
5
+ import { useTranslation } from '@/hooks/useTranslation';
6
+
7
+ export type FileDropzoneProps = {
8
+ acceptedFileTypes: {
9
+ [key: string]: string[];
10
+ };
11
+ file: File | null;
12
+ setFile: (file: File) => void;
13
+ };
14
+
15
+ export const FileDropzone = ({ acceptedFileTypes, file, setFile }: FileDropzoneProps) => {
16
+ const { t } = useTranslation();
17
+
18
+ const handleDrop = useCallback(
19
+ (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
20
+ for (const { errors, file } of rejectedFiles) {
21
+ console.error(errors, file);
22
+ }
23
+ setFile(acceptedFiles[0]!);
24
+ },
25
+ [setFile]
26
+ );
27
+ const { getInputProps, getRootProps, isDragActive } = useDropzone({
28
+ accept: acceptedFileTypes,
29
+ maxFiles: 1,
30
+ onDrop: handleDrop
31
+ });
32
+
33
+ return (
34
+ <div data-testid="dropzone" {...getRootProps()}>
35
+ <p className="mt-1 border border-dashed p-4 text-center text-sm" data-testid="dropzoneText">
36
+ {file
37
+ ? file.name
38
+ : isDragActive
39
+ ? t({
40
+ en: 'File to upload',
41
+ fr: 'fichier à télécharger'
42
+ })
43
+ : t({
44
+ en: "Drag'n'drop files or click on box to upload",
45
+ fr: 'Glissez-déposez les fichiers ou cliquez sur la case pour les télécharger'
46
+ })}
47
+ </p>
48
+
49
+ <input {...getInputProps()} />
50
+ </div>
51
+ );
52
+ };
@@ -0,0 +1 @@
1
+ export * from './FileDropzone';
@@ -20,6 +20,7 @@ export * from './DropdownButton';
20
20
  export * from './DropdownMenu';
21
21
  export * from './ErrorBoundary';
22
22
  export * from './ErrorFallback';
23
+ export * from './FileDropzone';
23
24
  export * from './Form';
24
25
  export * from './Heading';
25
26
  export * from './HoverCard';