Skip to content

Commit 0ca0156

Browse files
authored
Merge pull request #1 from openscript/develop
Make dsv import compositable
2 parents 59b12ef + 740e4a9 commit 0ca0156

File tree

16 files changed

+429
-178
lines changed

16 files changed

+429
-178
lines changed

.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ module.exports = {
1414
sourceType: "module"
1515
},
1616
rules: {
17-
"@typescript-eslint/explicit-function-return-type": "off"
17+
"@typescript-eslint/explicit-function-return-type": "off",
18+
"react/prop-types": "off"
1819
},
1920
settings: {
2021
react: {

README.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# react-dsv-import
2-
Flexible, typed and easy to use React Component ⚛ to provide CSV, TSV and other delimiter-separated values formats (DSV) import functionality.
2+
Flexible, typed and easy to use React Component ⚛ to provide CSV, TSV and other delimiter-separated values formats ([DSV](https://en.wikipedia.org/wiki/Delimiter-separated_values)) import functionality.
33

44
![Travis (.com)](https://img.shields.io/travis/com/openscript/react-dsv-import) ![npm](https://img.shields.io/npm/v/react-dsv-import) ![npm peer dependency version](https://img.shields.io/npm/dependency-version/react-dsv-import/peer/react) ![GitHub](https://img.shields.io/github/license/openscript/react-dsv-import)
55

@@ -12,30 +12,36 @@ Add the package with the package manager of choice to your project:
1212

1313
### TypeScript
1414
```
15-
import { ColumnsType, DSVImport } from 'react-dsv-import';
15+
import { DSVImport, ColumnsType, TablePreview, TextareaInput } from 'react-dsv-import';
1616
17-
type ImportType = { forename: string; surname: string; email: string };
17+
type BasicType = { forename: string; surname: string; email: string };
1818
19-
const columns: ColumnsType<ImportType> = [
19+
const columns: ColumnsType<BasicType> = [
2020
{ key: 'forename', label: 'Forename' },
2121
{ key: 'surname', label: 'Surname' },
2222
{ key: 'email', label: 'Email' }
2323
];
2424
25-
<DSVImport<ImportType> columns={columns} />
25+
<DSVImport<BasicType> columns={columns}>
26+
<TextareaInput />
27+
<TablePreview />
28+
</DSVImport>
2629
```
2730

2831
### JavaScript
2932
```
30-
import { DSVImport } from 'react-dsv-import';
33+
import { DSVImport, TablePreview, TextareaInput } from 'react-dsv-import';
3134
3235
const columns = [
3336
{ key: 'forename', label: 'Forename' },
3437
{ key: 'surname', label: 'Surname' },
3538
{ key: 'email', label: 'Email' }
3639
];
3740
38-
<DSVImport columns={columns} />
41+
<DSVImport columns={columns}>
42+
<TextareaInput />
43+
<TablePreview />
44+
</DSVImport>
3945
```
4046

4147
## Project
@@ -46,10 +52,10 @@ The most important features of this component are:
4652

4753
- ✅ Type definitions and type safety
4854
- ✅ DSV format detection
55+
- ✅ Fully compositable
4956
- ❌ Input validation
5057
-[Material UI](https://material-ui.com/) integration
5158
-[ant.design](https://ant.design/) integration
52-
- ❌ Fully compositable
5359

5460
✅ means the feature is implemented and released. ❌ indicates that a feature is planned.
5561

package.json

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
{
22
"name": "react-dsv-import",
33
"description": "Flexible, typed and easy to use React Component ⚛ to provide CSV, TSV and other delimiter-separated values formats (DSV) import functionality.",
4-
"keywords": ["react-component", "typescript", "react"],
4+
"keywords": [
5+
"react-component",
6+
"typescript",
7+
"react"
8+
],
59
"homepage": "https://openscript.github.io/react-dsv-import/",
6-
"version": "0.0.7",
10+
"version": "0.1.1",
711
"main": "dist/index.js",
812
"module": "dist/es/index.js",
913
"types": "dist/index.d.ts",
1014
"dependencies": {},
1115
"devDependencies": {
1216
"@babel/core": "^7.9.0",
13-
"@rollup/plugin-typescript": "^4.0.0",
17+
"@rollup/plugin-typescript": "^4.1.1",
1418
"@storybook/addon-actions": "^5.3.18",
1519
"@storybook/addon-docs": "^5.3.18",
1620
"@storybook/addon-info": "^5.3.18",
@@ -22,17 +26,17 @@
2226
"@types/node": "^13.11.1",
2327
"@types/react": "^16.9.34",
2428
"@types/react-dom": "^16.9.6",
25-
"@typescript-eslint/eslint-plugin": "^2.27.0",
26-
"@typescript-eslint/parser": "^2.27.0",
29+
"@typescript-eslint/eslint-plugin": "^2.28.0",
30+
"@typescript-eslint/parser": "^2.28.0",
2731
"babel-loader": "^8.1.0",
2832
"babel-preset-react-app": "^9.1.2",
2933
"eslint": "^6.8.0",
3034
"eslint-config-prettier": "^6.10.1",
31-
"eslint-plugin-prettier": "^3.1.2",
35+
"eslint-plugin-prettier": "^3.1.3",
3236
"eslint-plugin-react": "^7.19.0",
3337
"prettier": "^2.0.4",
3438
"react-is": "^16.13.1",
35-
"rollup": "^2.6.0",
39+
"rollup": "^2.6.1",
3640
"ts-node": "^8.8.2",
3741
"tslib": "^1.11.1",
3842
"typescript": "^3.8.3"

src/DSVImport.stories.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
import { DSVImport, ColumnsType, TablePreview, TextareaInput } from './';
3+
import { action } from '@storybook/addon-actions';
4+
5+
export default { title: 'Usage' };
6+
7+
type BasicType = { forename: string; surname: string; email: string };
8+
9+
const columns: ColumnsType<BasicType> = [
10+
{ key: 'forename', label: 'Forename' },
11+
{ key: 'surname', label: 'Surname' },
12+
{ key: 'email', label: 'Email' }
13+
];
14+
15+
const onChangeAction = action('Parsed value has changed');
16+
17+
export const BasicUsage = () => {
18+
return (
19+
<DSVImport<BasicType> columns={columns} onChange={onChangeAction}>
20+
<TextareaInput />
21+
<TablePreview />
22+
</DSVImport>
23+
);
24+
};
25+
BasicUsage.story = { name: 'Basic usage' };

src/DSVImport.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React, { PropsWithChildren, useReducer } from 'react';
2+
import { ColumnsType } from './models/column';
3+
import { getDSVImportContext } from './features/context';
4+
import { createSimpleParserMiddleware } from './middlewares/simpleParserMiddleware';
5+
import { State } from './models/state';
6+
7+
interface Props<T> {
8+
onChange?: (value: T[]) => void;
9+
columns: ColumnsType<T>;
10+
}
11+
12+
export const DSVImport = <T extends { [key: string]: string }>(props: PropsWithChildren<Props<T>>) => {
13+
const DSVImportContext = getDSVImportContext<T>();
14+
const middleware = createSimpleParserMiddleware<T>(props.onChange);
15+
const initialValues: State<T> = { columns: props.columns };
16+
17+
return (
18+
<DSVImportContext.Provider value={useReducer(middleware, initialValues)}>
19+
{props.children}
20+
</DSVImportContext.Provider>
21+
);
22+
};

src/components/DSVImport.stories.tsx

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/components/DSVImport.tsx

Lines changed: 0 additions & 79 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
import { useDSVImport } from '../../features/context';
3+
4+
export const TextareaInput: React.FC = () => {
5+
const [, dispatch] = useDSVImport();
6+
7+
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
8+
dispatch({ type: 'setRaw', raw: event.target.value });
9+
};
10+
11+
return <textarea onChange={handleChange}></textarea>;
12+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import { useDSVImport } from '../../features/context';
3+
4+
export const TablePreview: React.FC = () => {
5+
const [context] = useDSVImport();
6+
7+
return (
8+
<table>
9+
<thead>
10+
<tr>
11+
{context.columns.map((column, columnIndex) => (
12+
<th key={columnIndex}>{column.label}</th>
13+
))}
14+
</tr>
15+
</thead>
16+
<tbody>
17+
{context.parsed
18+
? context.parsed.map((row, rowIndex) => (
19+
<tr key={rowIndex}>
20+
{context.columns.map((column, columnIndex) => {
21+
return <td key={columnIndex}>{row[column.key]}</td>;
22+
})}
23+
</tr>
24+
))
25+
: null}
26+
</tbody>
27+
</table>
28+
);
29+
};

src/features/context.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { State, emptyState } from '../models/state';
2+
import { createContext, Dispatch, useContext } from 'react';
3+
4+
export type Actions = { type: 'setRaw'; raw: string };
5+
6+
export const reducer = <T>(state: State<T>, action: Actions) => {
7+
switch (action.type) {
8+
case 'setRaw':
9+
return { ...state, raw: action.raw };
10+
default:
11+
return state;
12+
}
13+
};
14+
15+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
16+
let contextSingleton: React.Context<[State<any>, Dispatch<Actions>]>;
17+
export const getDSVImportContext = <T>() => {
18+
if (!contextSingleton) {
19+
contextSingleton = createContext<[State<T>, Dispatch<Actions>]>([
20+
(emptyState as unknown) as State<T>,
21+
() => {
22+
throw new Error('Not initialized');
23+
}
24+
]);
25+
}
26+
return contextSingleton as React.Context<[State<T>, Dispatch<Actions>]>;
27+
};
28+
export const useDSVImport = <T = { [key: string]: string }>() => useContext(getDSVImportContext<T>());

0 commit comments

Comments
 (0)