diff --git a/.babelrc b/.babelrc deleted file mode 100644 index bb29451..0000000 --- a/.babelrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "presets": [ - "@babel/react" - ], - "plugins": [ - "@babel/plugin-proposal-class-properties" - ] -} diff --git a/.editorconfig b/.editorconfig index b3dfee7..c6c8b36 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1,9 @@ root = true [*] -end_of_line = lf -insert_final_newline = true indent_style = space indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..64373b2 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,49 @@ +module.exports = { + parser: "@typescript-eslint/parser", + + extends: [ + "plugin:react/recommended", + "plugin:@typescript-eslint/recommended", + "prettier/@typescript-eslint", + "plugin:prettier/recommended", + ], + + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + project: "./tsconfig.build.json", + tsconfigRootDir: "./", + ecmaFeatures: { + jsx: true, + }, + }, + + rules: { + "react/display-name": "off", + "react/prop-types": "off", + + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-use-before-define": "off", + + // For the next couple of months ;) + "@typescript-eslint/no-empty-function": "off", + }, + + settings: { + react: { + version: "detect", + }, + "import/resolver": { + node: { + extensions: [ + ".js", + ".jsx", + ".ts", + ".tsx", + ], + }, + }, + }, +}; diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..1c49b44 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: true, + trailingComma: 'all', + singleQuote: false, + printWidth: 80, + arrowParens: 'always', +}; diff --git a/package.json b/package.json index fb75637..5263e5f 100644 --- a/package.json +++ b/package.json @@ -6,19 +6,18 @@ "license": "MIT", "scripts": { "start": "react-scripts start", - "js:lint": "eslint .", "js:test": "react-scripts test", "js:test:coverage": "yarn js:test --coverage", "js:build": "react-scripts build", - "ts:lint": "tslint {,src/**/}*.{ts,tsx} -p tsconfig.json", - "ts:build": "tsc -p tsconfig.json", + "ts:lint": "eslint ./src/ --ext ts,tsx", + "ts:build": "tsc -p tsconfig.build.json", "test": "yarn lint && yarn js:test", "test:coverage": "CI=true yarn js:test:coverage && yarn test:codecov", "test:codecov": "codecov", - "lint": "yarn js:lint && yarn ts:lint", + "lint": "yarn ts:lint", "build": "yarn js:build", - "build:babel": "NODE_ENV=production babel src --out-dir dist", - "prepublishOnly": "rm -rf ./dist && yarn build:babel" + "build:ts": "NODE_ENV=production yarn ts:build", + "prepublishOnly": "rm -rf ./dist && yarn build:ts" }, "dependencies": { "@date-io/moment": "^2.6.0", @@ -38,7 +37,6 @@ "react-dom": "^16.13.1", "react-jss": "^10.1.1", "react-router-dom": "^5.1.2", - "recompose": "^0.30.0", "typescript": "^3.8.3", "vanilla-store": "^0.4.0" }, @@ -48,22 +46,32 @@ "react-dom": "^16.13.1" }, "devDependencies": { - "@babel/cli": "^7.8.4", - "@babel/core": "^7.9.6", - "@babel/plugin-proposal-class-properties": "^7.8.3", - "@babel/preset-react": "^7.9.4", + "@types/classnames": "^2.2.10", + "@types/enzyme": "^3.10.5", + "@types/enzyme-adapter-react-16": "^1.0.6", + "@types/is-url": "^1.2.28", + "@types/jest": "^25.2.1", + "@types/react-router-dom": "^5.1.5", + "@typescript-eslint/eslint-plugin": "^2.30.0", + "@typescript-eslint/eslint-plugin-tslint": "^2.30.0", + "@typescript-eslint/parser": "^2.30.0", "babel-polyfill": "^6.26.0", "codecov": "^3.6.5", "concurrently": "^5.2.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", "enzyme-to-json": "^3.4.4", + "eslint": "^6.8.0", + "eslint-config-airbnb": "^18.1.0", + "eslint-config-prettier": "^6.11.0", + "eslint-config-react": "^1.1.7", + "eslint-plugin-prettier": "^3.1.3", + "eslint-plugin-react": "^7.19.0", "express": "^4.17.1", "jest-enzyme": "^7.1.2", + "prettier": "^2.0.5", "react-mock-router": "^1.0.15", - "react-scripts": "^3.4.1", - "tslint": "^6.1.2", - "tslint-react": "^5.0.0" + "react-scripts": "^3.4.1" }, "keywords": [ "react", diff --git a/src/AddButton/AddButton.jsx b/src/AddButton/AddButton.jsx deleted file mode 100644 index c652cec..0000000 --- a/src/AddButton/AddButton.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import { - withStyles, - Fab, -} from '@material-ui/core' -import AddIcon from '@material-ui/icons/Add' - -const styles = theme => ({ - button: { - position: 'fixed', - right: theme.spacing(3), - bottom: theme.spacing(3), - }, -}) - -const AddButton = ({ onClick, classes }) => ( - - - -) - -AddButton.propTypes = { - onClick: PropTypes.func.isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -export default withStyles(styles)(AddButton) diff --git a/src/AddButton/AddButton.test.jsx b/src/AddButton/AddButton.test.jsx deleted file mode 100644 index 0572cc0..0000000 --- a/src/AddButton/AddButton.test.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import AddButton from '.' - -it('renders correctly', () => { - const tree = shallow( {}} />) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/AddButton/AddButton.test.tsx b/src/AddButton/AddButton.test.tsx new file mode 100644 index 0000000..4bfa40f --- /dev/null +++ b/src/AddButton/AddButton.test.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import AddButton from "."; + +it("renders correctly", () => { + const tree = shallow( {}} />); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/AddButton/AddButton.tsx b/src/AddButton/AddButton.tsx new file mode 100644 index 0000000..a662c1f --- /dev/null +++ b/src/AddButton/AddButton.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { withStyles, Fab } from "@material-ui/core"; +import AddIcon from "@material-ui/icons/Add"; + +const styles = (theme: any) => ({ + button: { + position: "fixed", + right: theme.spacing(3), + bottom: theme.spacing(3), + }, +}); + +type AddButtonProps = { + onClick: (...args: any[]) => any; + classes: { + [key: string]: string; + }; +}; + +const AddButton: React.SFC = ({ onClick, classes }) => ( + + + +); +export default withStyles(styles as any)(AddButton); diff --git a/src/AddButton/index.jsx b/src/AddButton/index.jsx deleted file mode 100644 index 53689c2..0000000 --- a/src/AddButton/index.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import AddButton from './AddButton' - -export default AddButton diff --git a/src/AddButton/index.tsx b/src/AddButton/index.tsx new file mode 100644 index 0000000..b6647e5 --- /dev/null +++ b/src/AddButton/index.tsx @@ -0,0 +1,3 @@ +import AddButton from "./AddButton"; + +export default AddButton; diff --git a/src/AppContainer/AppContainer.jsx b/src/AppContainer/AppContainer.jsx deleted file mode 100644 index fead79e..0000000 --- a/src/AppContainer/AppContainer.jsx +++ /dev/null @@ -1,54 +0,0 @@ -import React, { Fragment } from 'react' -import PropTypes from 'prop-types' -import { createMuiTheme } from '@material-ui/core' -import { - blueGrey, - orange, -} from '@material-ui/core/colors' - -import withRoot from './withRoot' - -const defaultTheme = { - typography: { - useNextVariants: true, - }, - palette: { - primary: { - light: blueGrey[300], - main: blueGrey[500], - dark: blueGrey[700], - }, - secondary: { - light: orange[300], - main: orange[500], - dark: orange[700], - }, - }, -} - -const AppContainer = ({ theme, children }) => { - const BaseComponent = () => ( - - {children} - - ) - - const Root = withRoot(BaseComponent, { - theme: createMuiTheme(theme), - }) - - return ( - - ) -} - -AppContainer.propTypes = { - theme: PropTypes.objectOf(PropTypes.object), - children: PropTypes.node.isRequired, -} - -AppContainer.defaultProps = { - theme: defaultTheme, -} - -export default AppContainer diff --git a/src/AppContainer/AppContainer.test.jsx b/src/AppContainer/AppContainer.test.jsx deleted file mode 100644 index 3a67727..0000000 --- a/src/AppContainer/AppContainer.test.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import AppContainer from '.' - -describe('Component Info', () => { - it('renders correctly', () => { - const tree = shallow(( - - Foo - - )) - - expect(tree).toMatchSnapshot() - }) -}) diff --git a/src/AppContainer/AppContainer.test.tsx b/src/AppContainer/AppContainer.test.tsx new file mode 100644 index 0000000..26bb75c --- /dev/null +++ b/src/AppContainer/AppContainer.test.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import AppContainer from "."; + +describe("Component Info", () => { + it("renders correctly", () => { + const tree = shallow(Foo); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/src/AppContainer/AppContainer.tsx b/src/AppContainer/AppContainer.tsx new file mode 100644 index 0000000..b617de4 --- /dev/null +++ b/src/AppContainer/AppContainer.tsx @@ -0,0 +1,56 @@ +import React, { ReactNode } from "react"; +import { blueGrey, orange } from "@material-ui/core/colors"; +import { withStyles, ThemeProvider } from "@material-ui/styles"; +import { createMuiTheme } from "@material-ui/core"; + +const defaultTheme = { + typography: { + useNextVariants: true, + }, + palette: { + primary: { + light: blueGrey[300], + main: blueGrey[500], + dark: blueGrey[700], + }, + secondary: { + light: orange[300], + main: orange[500], + dark: orange[700], + }, + }, +}; + +type AppContainerProps = { + theme?: { + [key: string]: any; + }; +}; + +// Apply some reset +const styles = (theme: any) => ({ + "@global": { + html: { + background: theme.palette.background.default, + WebkitFontSmoothing: "antialiased", // Antialiasing. + MozOsxFontSmoothing: "grayscale", // Antialiasing. + }, + body: { + margin: 0, + }, + }, +}); + +const BaseComponent = withStyles(styles)((props) => props.children as any); +const AppContainer: React.SFC = ({ + theme = defaultTheme, + children, +}) => { + return ( + + {children} + + ); +}; + +export default AppContainer; diff --git a/src/AppContainer/index.js b/src/AppContainer/index.js deleted file mode 100644 index 79ee5d8..0000000 --- a/src/AppContainer/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import AppContainer from './AppContainer' - -export default AppContainer diff --git a/src/AppContainer/index.ts b/src/AppContainer/index.ts new file mode 100644 index 0000000..4814015 --- /dev/null +++ b/src/AppContainer/index.ts @@ -0,0 +1,3 @@ +import AppContainer from "./AppContainer"; + +export default AppContainer; diff --git a/src/AppContainer/withRoot.jsx b/src/AppContainer/withRoot.jsx deleted file mode 100644 index 57bedbc..0000000 --- a/src/AppContainer/withRoot.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { Component } from 'react' -import wrapDisplayName from 'recompose/wrapDisplayName' - -import { withStyles, MuiThemeProvider } from '@material-ui/core/styles' - -// Apply some reset -const styles = theme => ({ - '@global': { - html: { - background: theme.palette.background.default, - WebkitFontSmoothing: 'antialiased', // Antialiasing. - MozOsxFontSmoothing: 'grayscale', // Antialiasing. - }, - body: { - margin: 0, - }, - }, -}) - -let AppWrapper = props => props.children - -AppWrapper = withStyles(styles)(AppWrapper) - -const withRoot = (BaseComponent, props) => { - class WithRoot extends Component { - render() { - return ( - - - - - - ) - } - } - - if (process.env.NODE_ENV !== 'production') { - WithRoot.displayName = wrapDisplayName(BaseComponent, 'withRoot') - } - - return WithRoot -} - -export default withRoot diff --git a/src/BackButton/BackButton.jsx b/src/BackButton/BackButton.jsx deleted file mode 100644 index 2f1788c..0000000 --- a/src/BackButton/BackButton.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { withRouter } from 'react-router-dom' - -import BackButtonBranch from './BackButtonBranch' - -const withBackButton = Component => class extends React.Component { - static propTypes = { - url: PropTypes.string.isRequired, - history: PropTypes.objectOf(PropTypes.any).isRequired, - } - - constructor() { - super() - - this.handleBack = this.handleBack.bind(this) - } - - handleBack() { - const { url, history } = this.props - - history.push(url) - } - - render() { - return ( - - ) - } -} - -const BackButton = withBackButton(BackButtonBranch) - -export default withRouter(BackButton) diff --git a/src/BackButton/BackButton.test.jsx b/src/BackButton/BackButton.test.jsx deleted file mode 100644 index 5b5ae03..0000000 --- a/src/BackButton/BackButton.test.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import MockRouter from 'react-mock-router' -import { shallow } from 'enzyme' - -import BackButton from '.' - -describe('Back Button', () => { - it('renders correctly', () => { - const tree = shallow(()) - - expect(tree).toMatchSnapshot() - }) - - it('updates history on click', () => { - const push = jest.fn() - const button = shallow() - - button.find(BackButton).simulate('click') - - expect(push).toHaveBeenLastCalledWith('/') - }) -}) diff --git a/src/BackButton/BackButton.test.tsx b/src/BackButton/BackButton.test.tsx new file mode 100644 index 0000000..ca27235 --- /dev/null +++ b/src/BackButton/BackButton.test.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import MockRouter from "react-mock-router"; +import { shallow } from "enzyme"; + +import BackButton from "."; + +describe("Back Button", () => { + it("renders correctly", () => { + const tree = shallow( + + + , + ); + + expect(tree).toMatchSnapshot(); + }); + + it("updates history on click", () => { + const push = jest.fn(); + const button = shallow( + + + , + ); + + button.find(BackButton).simulate("click"); + + expect(push).toHaveBeenLastCalledWith("/"); + }); +}); diff --git a/src/BackButton/BackButton.tsx b/src/BackButton/BackButton.tsx new file mode 100644 index 0000000..aab8331 --- /dev/null +++ b/src/BackButton/BackButton.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { withRouter } from "react-router-dom"; +import BackButtonBranch from "./BackButtonBranch"; + +type WithBackButtonProps = { + url: string; + history: { + [key: string]: any; + }; +}; + +const withBackButton = (Component: any) => + class WithBackButton extends React.Component { + constructor(props: WithBackButtonProps) { + super(props); + this.handleBack = this.handleBack.bind(this); + } + handleBack() { + const { url, history } = this.props; + history.push(url); + } + render() { + return ; + } + }; + +const BackButton = withBackButton(BackButtonBranch); + +export default withRouter(BackButton as any) as any; diff --git a/src/BackButton/BackButtonBranch.jsx b/src/BackButton/BackButtonBranch.jsx deleted file mode 100644 index 97cf71d..0000000 --- a/src/BackButton/BackButtonBranch.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import { - Button, - withStyles, -} from '@material-ui/core' -import BackIcon from '@material-ui/icons/ArrowBackIos' - -const styles = theme => ({ - leftIcon: { - marginRight: theme.spacing(), - }, -}) - -const BackButtonBranch = ({ - onNavigateBack, - classes, -}) => ( - -) - -BackButtonBranch.propTypes = { - onNavigateBack: PropTypes.func.isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -export default withStyles(styles)(BackButtonBranch) diff --git a/src/BackButton/BackButtonBranch.tsx b/src/BackButton/BackButtonBranch.tsx new file mode 100644 index 0000000..91bdea0 --- /dev/null +++ b/src/BackButton/BackButtonBranch.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Button, withStyles } from "@material-ui/core"; +import BackIcon from "@material-ui/icons/ArrowBackIos"; +const styles = (theme: any) => ({ + leftIcon: { + marginRight: theme.spacing(), + }, +}); +type BackButtonBranchProps = { + onNavigateBack: (...args: any[]) => any; + classes: { + [key: string]: string; + }; +}; +const BackButtonBranch: React.SFC = ({ + onNavigateBack, + classes, +}) => ( + +); +export default withStyles(styles)(BackButtonBranch); diff --git a/src/BackButton/index.js b/src/BackButton/index.js deleted file mode 100644 index 96118fd..0000000 --- a/src/BackButton/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import BackButton from './BackButton' - -export default BackButton diff --git a/src/BackButton/index.ts b/src/BackButton/index.ts new file mode 100644 index 0000000..0a54c1e --- /dev/null +++ b/src/BackButton/index.ts @@ -0,0 +1,3 @@ +import BackButton from "./BackButton"; + +export default BackButton; diff --git a/src/Base/Base.jsx b/src/Base/Base.jsx deleted file mode 100644 index e43167d..0000000 --- a/src/Base/Base.jsx +++ /dev/null @@ -1,112 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import Cookie from '../CookieInfo/Cookie' -import BaseBranch from './BaseBranch' - -const withBase = Component => class extends React.Component { - static propTypes = { - title: PropTypes.string.isRequired, - menuOpen: PropTypes.bool, - menuData: PropTypes.arrayOf(PropTypes.object).isRequired, - rightContent: PropTypes.node, - isHeaderFixed: PropTypes.bool, - hasHeader: PropTypes.bool, - hasCookieInfo: PropTypes.bool, - history: PropTypes.objectOf(PropTypes.any), - } - - static defaultProps = { - isHeaderFixed: true, - hasHeader: true, - hasCookieInfo: false, - menuOpen: false, - rightContent: null, - history: undefined, - } - - constructor(props) { - super(props) - - this.state = { - open: false, - cookieInfoOpen: false, - } - - if (props.hasCookieInfo && Cookie.getCookie() === undefined) { - Cookie.setCookie(false) - } - - this.handleCookieInfoAccept = this.handleCookieInfoAccept.bind(this) - this.handleDrawerOpen = this.handleDrawerOpen.bind(this) - this.handleDrawerClose = this.handleDrawerClose.bind(this) - this.redirectTo = this.redirectTo.bind(this) - this.onClick = this.onClick.bind(this) - } - - componentDidMount() { - const { hasCookieInfo, menuOpen } = this.props - - this.setState({ - cookieInfoOpen: hasCookieInfo && Cookie.getCookie() === false, - open: menuOpen, - }) - } - - componentWillReceiveProps({ menuOpen }) { - this.setState({ - open: menuOpen, - }) - } - - onClick() { - this.redirectTo('/') - } - - handleDrawerOpen() { - this.setState({ - open: true, - }) - } - - handleDrawerClose() { - this.setState({ - open: false, - }) - } - - redirectTo(link) { - const { history } = this.props - - if (history) { - history.push(link) - } - } - - handleCookieInfoAccept() { - this.setState({ - cookieInfoOpen: false, - }) - } - - render() { - const { rightContent } = this.props - - return ( - - ) - } -} - -const WithBase = withBase(BaseBranch) - -export default WithBase diff --git a/src/Base/Base.test.jsx b/src/Base/Base.test.jsx deleted file mode 100644 index 808afdc..0000000 --- a/src/Base/Base.test.jsx +++ /dev/null @@ -1,151 +0,0 @@ -import React from 'react' -import MockRouter from 'react-mock-router' -import Enzyme, { shallow } from 'enzyme' -import Adapter from 'enzyme-adapter-react-16' -import { Typography, IconButton, Button } from '@material-ui/core' - -import Base from '.' - -import menuData from '../tests/data/menu' -import Header from '../Header' -import Drawer from '../Drawer' -import CookieInfo from '../CookieInfo' - -Enzyme.configure({ adapter: new Adapter() }) - -describe('Base', () => { - it('renders correctly', () => { - const tree = shallow(( - - -
Foo
- -
- )) - - expect(tree).toMatchSnapshot() - }) - - it('renders correctly without header', () => { - const tree = shallow(( - - -
Foo
- -
- )) - - expect(tree).toMatchSnapshot() - }) - - it('renders with drawer open', () => { - const tree = shallow(( - - -
Foo
- -
- )) - - expect(tree).toMatchSnapshot() - }) - - it('click on title', () => { - const tree = shallow(( - - -
Foo
- -
- )) - - tree.find(Header).find(Typography).simulate('click') - - expect(tree).toMatchSnapshot() - }) - - it('click on menu icon if open changes state', () => { - const tree = shallow(( - - -
Foo
- -
- )) - - tree.find(Header).find(IconButton).simulate('click') - - expect(tree).toMatchSnapshot() - }) - - it('click on menu icon if closed changes state', () => { - const tree = shallow(( - - -
Foo
- -
- )) - - tree.find(Drawer).find(IconButton).simulate('click') - - expect(tree).toMatchSnapshot() - }) - - it('call function when cookie is accepted', () => { - const tree = shallow(( - - - -
Foo
- -
- )) - - tree.find(CookieInfo).find(Button).simulate('click') - - expect(tree).toMatchSnapshot() - }) -}) diff --git a/src/Base/Base.test.tsx b/src/Base/Base.test.tsx new file mode 100644 index 0000000..7a87153 --- /dev/null +++ b/src/Base/Base.test.tsx @@ -0,0 +1,134 @@ +import React from "react"; +import MockRouter from "react-mock-router"; +import Enzyme, { shallow } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import { Typography, IconButton, Button } from "@material-ui/core"; + +import Base from "."; + +import menuData from "../tests/data/menu"; +import Header from "../Header"; +import Drawer from "../Drawer"; +import CookieInfo from "../CookieInfo"; + +Enzyme.configure({ adapter: new Adapter() }); + +describe("Base", () => { + it("renders correctly", () => { + const tree = shallow( + + +
Foo
+ +
, + ); + + expect(tree).toMatchSnapshot(); + }); + + it("renders correctly without header", () => { + const tree = shallow( + + +
Foo
+ +
, + ); + + expect(tree).toMatchSnapshot(); + }); + + it("renders with drawer open", () => { + const tree = shallow( + + +
Foo
+ +
, + ); + + expect(tree).toMatchSnapshot(); + }); + + it("click on title", () => { + const tree = shallow( + + +
Foo
+ +
, + ); + + tree.find(Header).find(Typography).simulate("click"); + + expect(tree).toMatchSnapshot(); + }); + + it("click on menu icon if open changes state", () => { + const tree = shallow( + + +
Foo
+ +
, + ); + + tree.find(Header).find(IconButton).simulate("click"); + + expect(tree).toMatchSnapshot(); + }); + + it("click on menu icon if closed changes state", () => { + const tree = shallow( + + +
Foo
+ +
, + ); + + tree.find(Drawer).find(IconButton).simulate("click"); + + expect(tree).toMatchSnapshot(); + }); + + it("call function when cookie is accepted", () => { + const tree = shallow( + + + +
Foo
+ +
, + ); + + tree.find(CookieInfo).find(Button).simulate("click"); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/src/Base/Base.tsx b/src/Base/Base.tsx new file mode 100644 index 0000000..742b587 --- /dev/null +++ b/src/Base/Base.tsx @@ -0,0 +1,92 @@ +import React from "react"; +import Cookie from "../CookieInfo/Cookie"; +import BaseBranch from "./BaseBranch"; +type WithBaseProps = { + title: string; + menuOpen?: boolean; + menuData: object[]; + rightContent?: React.ReactNode; + isHeaderFixed?: boolean; + hasHeader?: boolean; + hasCookieInfo?: boolean; + history?: { + [key: string]: any; + }; +}; + +type WithBaseState = { + cookieInfoOpen: boolean | undefined; + open: boolean | undefined; +}; + +const withBase = (Component: any) => + class WithBase extends React.Component { + constructor(props: WithBaseProps) { + super(props); + this.state = { + open: false, + cookieInfoOpen: false, + }; + if (props.hasCookieInfo && Cookie.getCookie() === undefined) { + Cookie.setCookie(false); + } + this.handleCookieInfoAccept = this.handleCookieInfoAccept.bind(this); + this.handleDrawerOpen = this.handleDrawerOpen.bind(this); + this.handleDrawerClose = this.handleDrawerClose.bind(this); + this.redirectTo = this.redirectTo.bind(this); + this.onClick = this.onClick.bind(this); + } + componentDidMount() { + const { hasCookieInfo, menuOpen } = this.props; + this.setState({ + cookieInfoOpen: hasCookieInfo && Cookie.getCookie() === false, + open: menuOpen, + }); + } + UNSAFE_componentWillReceiveProps({ menuOpen }: WithBaseProps) { + this.setState({ + open: menuOpen, + }); + } + onClick() { + this.redirectTo("/"); + } + handleDrawerOpen() { + this.setState({ + open: true, + }); + } + handleDrawerClose() { + this.setState({ + open: false, + }); + } + redirectTo(link: string) { + const { history } = this.props; + if (history) { + history.push(link); + } + } + handleCookieInfoAccept() { + this.setState({ + cookieInfoOpen: false, + }); + } + render() { + const { rightContent } = this.props; + return ( + + ); + } + }; +const WithBase = withBase(BaseBranch); +export default WithBase; diff --git a/src/Base/BaseBranch.jsx b/src/Base/BaseBranch.tsx similarity index 50% rename from src/Base/BaseBranch.jsx rename to src/Base/BaseBranch.tsx index cee527d..0e40867 100644 --- a/src/Base/BaseBranch.jsx +++ b/src/Base/BaseBranch.tsx @@ -1,22 +1,20 @@ -import React, { Fragment } from 'react' -import PropTypes from 'prop-types' -import classNames from 'classnames' +import React from "react"; +import classNames from "classnames"; +import { withStyles } from "@material-ui/core"; +import Drawer from "../Drawer"; +import Header from "../Header"; +import { MenuDataItem } from "../Menu/Menu"; -import { withStyles } from '@material-ui/core' +const drawerWidth = 280; -import Drawer from '../Drawer' -import Header from '../Header' - -const drawerWidth = 280 - -const styles = theme => ({ +const styles = (theme: any) => ({ appFrame: { - position: 'relative', - display: 'flex', - width: '100%', - height: '100%', - minHeight: '100vh', - transition: '0.25s', + position: "relative", + display: "flex", + width: "100%", + height: "100%", + minHeight: "100vh", + transition: "0.25s", }, appFrameWithCookieInfo: { marginTop: theme.spacing(6), @@ -27,24 +25,42 @@ const styles = theme => ({ flexGrow: 1, backgroundColor: theme.palette.background.default, padding: theme.spacing(3), - transition: theme.transitions.create('margin', { + transition: theme.transitions.create("margin", { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), - height: 'calc(100% - 56px)', + height: "calc(100% - 56px)", marginTop: theme.spacing(8), marginLeft: -drawerWidth, }, contentShift: { - transition: theme.transitions.create('margin', { + transition: theme.transitions.create("margin", { easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen, }), marginLeft: 0, }, -}) +}); -const BaseBranch = ({ +type BaseBranchProps = { + open?: boolean; + title: string; + menuData: MenuDataItem[]; + isHeaderFixed?: boolean; + cookieInfoOpen?: boolean; + onClick: (...args: any[]) => any; + handleDrawerOpen: (...args: any[]) => any; + handleDrawerClose: (...args: any[]) => any; + onCookieInfoAccept: (...args: any[]) => any; + redirectTo: (...args: any[]) => any; + rightContent?: JSX.Element; + hasHeader: boolean; + classes: { + [key: string]: string; + }; +}; + +const BaseBranch: React.SFC = ({ open, title, menuData, @@ -56,13 +72,13 @@ const BaseBranch = ({ onCookieInfoAccept, redirectTo, rightContent, - hasHeader, + hasHeader = true, classes, children, ...rest }) => ( -
- {hasHeader ? ( + <> + {hasHeader && (
{rightContent || null}
- ) : null} + )} -
- {hasHeader ? ( + {hasHeader && ( - ) : null} + )}
- {React.Children.map(children, child => ( - React.cloneElement(child, { + {React.Children.map(children, (child) => + React.cloneElement(child as any, { isOpen: cookieInfoOpen, onAccept: onCookieInfoAccept, ...rest, - }) - ))} + }), + )}
-
-) - -BaseBranch.propTypes = { - open: PropTypes.bool, - title: PropTypes.string.isRequired, - menuData: PropTypes.arrayOf(PropTypes.object).isRequired, - isHeaderFixed: PropTypes.bool, - cookieInfoOpen: PropTypes.bool, - onClick: PropTypes.func.isRequired, - handleDrawerOpen: PropTypes.func.isRequired, - handleDrawerClose: PropTypes.func.isRequired, - onCookieInfoAccept: PropTypes.func.isRequired, - redirectTo: PropTypes.func.isRequired, - rightContent: PropTypes.element, - hasHeader: PropTypes.bool.isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, - children: PropTypes.node.isRequired, -} + +); BaseBranch.defaultProps = { open: false, isHeaderFixed: false, cookieInfoOpen: false, - rightContent: (), -} +}; -export default withStyles(styles)(BaseBranch) +export default withStyles(styles as any)(BaseBranch); diff --git a/src/Base/index.js b/src/Base/index.js deleted file mode 100644 index d11f2ba..0000000 --- a/src/Base/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Base from './Base' - -export default Base diff --git a/src/Base/index.ts b/src/Base/index.ts new file mode 100644 index 0000000..2e06f6b --- /dev/null +++ b/src/Base/index.ts @@ -0,0 +1,3 @@ +import Base from "./Base"; + +export default Base; diff --git a/src/Confirm/Confirm.jsx b/src/Confirm/Confirm.jsx deleted file mode 100644 index 7011248..0000000 --- a/src/Confirm/Confirm.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import ConfirmBranch from './ConfirmBranch' - -const withConfirm = Component => class extends React.Component { - static propTypes = { - isOpen: PropTypes.bool, - title: PropTypes.string, - description: PropTypes.string.isRequired, - agreeText: PropTypes.string, - disagreeText: PropTypes.string, - onConfirm: PropTypes.func.isRequired, - onClose: PropTypes.func, - hasCloseButton: PropTypes.bool, - } - - static defaultProps = { - title: null, - isOpen: false, - agreeText: 'Agree', - disagreeText: 'Disagree', - onClose: () => {}, - hasCloseButton: true, - } - - constructor(props) { - super(props) - - this.state = { - isOpen: false, - } - - this.handleClose = this.handleClose.bind(this) - this.handleConfirm = this.handleConfirm.bind(this) - } - - componentDidMount() { - this.setState({ - isOpen: this.props.isOpen, - }) - } - - componentWillReceiveProps(nextProps) { - if (this.state.isOpen !== nextProps.isOpen) { - this.setState({ - isOpen: nextProps.isOpen, - }) - } - } - - handleClose() { - this.setState({ - isOpen: false, - }) - - this.props.onClose() - } - - handleConfirm() { - this.setState({ - isOpen: false, - }) - - this.props.onConfirm() - } - - render() { - return ( - - ) - } -} - -export default withConfirm(ConfirmBranch) diff --git a/src/Confirm/Confirm.test.jsx b/src/Confirm/Confirm.test.jsx deleted file mode 100644 index 7a74f74..0000000 --- a/src/Confirm/Confirm.test.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react' -import Enzyme, { shallow } from 'enzyme' -import Adapter from 'enzyme-adapter-react-16' - -import Confirm from '.' - -Enzyme.configure({ adapter: new Adapter() }) - -describe('Confirm', () => { - it('renders correctly', () => { - const tree = shallow( { }} - />) - - expect(tree).toMatchSnapshot() - }) - - it('renders correctly without close button', () => { - const tree = shallow( { }} - hasCloseButton={false} - />) - - expect(tree).toMatchSnapshot() - }) - - it('confirm is handled', () => { - const onConfirm = jest.fn() - const confirm = shallow(( - - )) - - expect(confirm.state().isOpen).toBe(true) - confirm.instance().handleConfirm() - - expect(onConfirm).toHaveBeenCalled() - }) - - it('close is handled', () => { - const onClose = jest.fn() - const confirm = shallow(( - {}} - onClose={onClose} - /> - )) - - expect(confirm.state().isOpen).toBe(true) - confirm.instance().handleClose() - - expect(onClose).toHaveBeenCalled() - }) - - it('changes open', () => { - const confirm = shallow(( - { }} - /> - )) - - confirm.setProps({ - isOpen: false, - }) - }) -}) diff --git a/src/Confirm/Confirm.test.tsx b/src/Confirm/Confirm.test.tsx new file mode 100644 index 0000000..608ae4b --- /dev/null +++ b/src/Confirm/Confirm.test.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import Enzyme, { shallow } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; + +import Confirm from "."; + +Enzyme.configure({ adapter: new Adapter() }); + +describe("Confirm", () => { + it("renders correctly", () => { + const tree = shallow( + {}} />, + ); + + expect(tree).toMatchSnapshot(); + }); + + it("renders correctly without close button", () => { + const tree = shallow( + {}} + hasCloseButton={false} + />, + ); + + expect(tree).toMatchSnapshot(); + }); + + it("confirm is handled", () => { + const onConfirm = jest.fn(); + const confirm = shallow( + , + ); + + expect(confirm.state().isOpen).toBe(true); + confirm.instance().handleConfirm(); + + expect(onConfirm).toHaveBeenCalled(); + }); + + it("close is handled", () => { + const onClose = jest.fn(); + const confirm = shallow( + {}} + onClose={onClose} + />, + ); + + expect(confirm.state().isOpen).toBe(true); + confirm.instance().handleClose(); + + expect(onClose).toHaveBeenCalled(); + }); + + it("changes open", () => { + const confirm = shallow( + {}} />, + ); + + confirm.setProps({ + isOpen: false, + }); + }); +}); diff --git a/src/Confirm/Confirm.tsx b/src/Confirm/Confirm.tsx new file mode 100644 index 0000000..99d5a97 --- /dev/null +++ b/src/Confirm/Confirm.tsx @@ -0,0 +1,67 @@ +import React from "react"; +import ConfirmBranch from "./ConfirmBranch"; +type WithConfirmProps = { + isOpen?: boolean; + title?: string; + description: string; + agreeText?: string; + disagreeText?: string; + onConfirm: (...args: any[]) => any; + onClose?: (...args: any[]) => any; + hasCloseButton?: boolean; +}; + +type WithConfirmState = { + isOpen: boolean | undefined; +}; + +const withConfirm = (Component: any) => + class WithConfirm extends React.Component< + WithConfirmProps, + WithConfirmState + > { + constructor(props: WithConfirmProps) { + super(props); + this.state = { + isOpen: false, + }; + this.handleClose = this.handleClose.bind(this); + this.handleConfirm = this.handleConfirm.bind(this); + } + componentDidMount() { + this.setState({ + isOpen: this.props.isOpen, + }); + } + UNSAFE_componentWillReceiveProps(nextProps: WithConfirmProps) { + if (this.state.isOpen !== nextProps.isOpen) { + this.setState({ + isOpen: nextProps.isOpen, + }); + } + } + handleClose() { + this.setState({ + isOpen: false, + }); + this.props.onClose && this.props.onClose(); + } + handleConfirm() { + this.setState({ + isOpen: false, + }); + this.props.onConfirm(); + } + render() { + return ( + + ); + } + }; + +export default withConfirm(ConfirmBranch); diff --git a/src/Confirm/ConfirmBranch.jsx b/src/Confirm/ConfirmBranch.tsx similarity index 57% rename from src/Confirm/ConfirmBranch.jsx rename to src/Confirm/ConfirmBranch.tsx index 9ddf6be..87783a2 100644 --- a/src/Confirm/ConfirmBranch.jsx +++ b/src/Confirm/ConfirmBranch.tsx @@ -1,5 +1,4 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React from "react"; import { Button, Dialog, @@ -8,15 +7,26 @@ import { DialogContentText, DialogTitle, withStyles, -} from '@material-ui/core' - -const styles = theme => ({ +} from "@material-ui/core"; +const styles = (theme: any) => ({ primaryButton: { color: theme.palette.primary.dark, }, -}) - -const ConfirmBranch = ({ +}); +type ConfirmBranchProps = { + isOpen: boolean; + title?: string; + description: string; + agreeText: string; + disagreeText: string; + onClose: (...args: any[]) => any; + onConfirm: (...args: any[]) => any; + hasCloseButton: boolean; + classes: { + [key: string]: string; + }; +}; +const ConfirmBranch: React.SFC = ({ title, description, isOpen, @@ -33,9 +43,7 @@ const ConfirmBranch = ({ aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" > - {title ? ( - {title} - ) : null} + {title ? {title} : null} @@ -59,22 +67,10 @@ const ConfirmBranch = ({ -) - -ConfirmBranch.propTypes = { - isOpen: PropTypes.bool.isRequired, - title: PropTypes.string, - description: PropTypes.string.isRequired, - agreeText: PropTypes.string.isRequired, - disagreeText: PropTypes.string.isRequired, - onClose: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, - hasCloseButton: PropTypes.bool.isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} +); ConfirmBranch.defaultProps = { - title: null, -} + title: undefined, +}; -export default withStyles(styles)(ConfirmBranch) +export default withStyles(styles)(ConfirmBranch); diff --git a/src/Confirm/index.js b/src/Confirm/index.js deleted file mode 100644 index d62fa91..0000000 --- a/src/Confirm/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Confirm from './Confirm' - -export default Confirm diff --git a/src/Confirm/index.ts b/src/Confirm/index.ts new file mode 100644 index 0000000..cbbbe24 --- /dev/null +++ b/src/Confirm/index.ts @@ -0,0 +1,3 @@ +import Confirm from "./Confirm"; + +export default Confirm; diff --git a/src/CookieInfo/Cookie.js b/src/CookieInfo/Cookie.js deleted file mode 100644 index 465f9ab..0000000 --- a/src/CookieInfo/Cookie.js +++ /dev/null @@ -1,34 +0,0 @@ -class Cookie { - constructor() { - this.value = undefined - } - - getCookie() { - if (this.value !== undefined) { - return this.value - } - - const value = `; ${document.cookie}` - const parts = value.split('; cookie_concent=') - - if (parts.length < 2) { - return undefined - } - - const concentValue = parts.pop().split(';').shift() !== 'false' - this.value = concentValue - - return concentValue - } - - setCookie(value) { - this.value = value - - const exdate = new Date() - exdate.setDate(exdate.getDate() + 365) - - document.cookie = `cookie_concent=${value};expires=${exdate.toUTCString()};path=/` - } -} - -export default new Cookie() diff --git a/src/CookieInfo/Cookie.test.js b/src/CookieInfo/Cookie.test.js deleted file mode 100644 index cb5e784..0000000 --- a/src/CookieInfo/Cookie.test.js +++ /dev/null @@ -1,31 +0,0 @@ -import Cookie from './Cookie' - -describe('CookieInfo/Cookie', () => { - const COOKIE_VALUE = 'foocookiefoo' - - it('gets undefined cookie', () => { - const returnValue = Cookie.getCookie() - - expect(returnValue).toBe(undefined) - }) - - it('gets predefined cookie', () => { - document.cookie = `cookie_concent=${COOKIE_VALUE}` - - const returnValue = Cookie.getCookie() - - expect(returnValue).toBe(true) - }) - - it('sets cookie', () => { - Cookie.setCookie(COOKIE_VALUE) - - expect(document.cookie).toBe(`cookie_concent=${COOKIE_VALUE}`) - }) - - it('gets cookie value correctly', () => { - const returnValue = Cookie.getCookie() - - expect(returnValue).toBe(COOKIE_VALUE) - }) -}) diff --git a/src/CookieInfo/Cookie.test.ts b/src/CookieInfo/Cookie.test.ts new file mode 100644 index 0000000..78ddbb7 --- /dev/null +++ b/src/CookieInfo/Cookie.test.ts @@ -0,0 +1,31 @@ +import Cookie from "./Cookie"; + +describe("CookieInfo/Cookie", () => { + const COOKIE_VALUE = "foocookiefoo"; + + it("gets undefined cookie", () => { + const returnValue = Cookie.getCookie(); + + expect(returnValue).toBe(undefined); + }); + + it("gets predefined cookie", () => { + document.cookie = `cookie_concent=${COOKIE_VALUE}`; + + const returnValue = Cookie.getCookie(); + + expect(returnValue).toBe(true); + }); + + it("sets cookie", () => { + Cookie.setCookie(COOKIE_VALUE); + + expect(document.cookie).toBe(`cookie_concent=${COOKIE_VALUE}`); + }); + + it("gets cookie value correctly", () => { + const returnValue = Cookie.getCookie(); + + expect(returnValue).toBe(COOKIE_VALUE); + }); +}); diff --git a/src/CookieInfo/Cookie.tsx b/src/CookieInfo/Cookie.tsx new file mode 100644 index 0000000..fc58c18 --- /dev/null +++ b/src/CookieInfo/Cookie.tsx @@ -0,0 +1,29 @@ +class Cookie { + private value: any; + + constructor() { + this.value = undefined; + } + getCookie() { + if (this.value !== undefined) { + return this.value; + } + const value = `; ${document.cookie}`; + const parts = value.split("; cookie_concent="); + if (parts.length < 2) { + return undefined; + } + const concentValue = parts.pop()?.split(";").shift() !== "false"; + + this.value = concentValue; + return concentValue; + } + + setCookie(value: any) { + this.value = value; + const exdate = new Date(); + exdate.setDate(exdate.getDate() + 365); + document.cookie = `cookie_concent=${value};expires=${exdate.toUTCString()};path=/`; + } +} +export default new Cookie(); diff --git a/src/CookieInfo/CookieInfo.jsx b/src/CookieInfo/CookieInfo.jsx deleted file mode 100644 index c64a54c..0000000 --- a/src/CookieInfo/CookieInfo.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import classNames from 'classnames' - -import { - Button, - withStyles, -} from '@material-ui/core' - -import Cookie from './Cookie' - -const styles = theme => ({ - root: { - position: 'fixed', - top: 0, - left: 0, - right: 0, - zIndex: 3000, - display: 'flex', - backgroundColor: theme.palette.secondary.main, - paddingTop: theme.spacing(), - paddingBottom: theme.spacing(), - paddingLeft: theme.spacing(3), - paddingRight: theme.spacing(3), - transform: 'translate(0, -100%)', - opacity: 0, - transition: 'transform 0.25s, opacity 0s 0.25s', - }, - rootActive: { - transform: 'translate(0, 0)', - opacity: 1, - }, - content: { - display: 'flex', - flex: 1, - opacity: 1, - alignItems: 'center', - }, - button: { - float: 'right', - }, -}) - -class CookieInfo extends React.Component { - static propTypes = { - isOpen: PropTypes.bool, - onAccept: PropTypes.func, - buttonText: PropTypes.string, - children: PropTypes.node.isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, - } - - static defaultProps = { - isOpen: false, - onAccept: null, - buttonText: 'Accept', - } - - constructor(props) { - super(props) - - this.handleClick = this.handleClick.bind(this) - } - - handleClick() { - const { onAccept } = this.props - if (onAccept) { - onAccept() - } - - Cookie.setCookie(true) - } - - render() { - const { - isOpen, - buttonText, - classes, - children, - } = this.props - const className = classNames({ - [classes.root]: true, - [classes.rootActive]: isOpen, - }) - - return ( -
-
- {children} -
- - -
- ) - } -} - -export default withStyles(styles)(CookieInfo) diff --git a/src/CookieInfo/CookieInfo.test.jsx b/src/CookieInfo/CookieInfo.test.jsx deleted file mode 100644 index 7a32b6c..0000000 --- a/src/CookieInfo/CookieInfo.test.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import Enzyme, { shallow } from 'enzyme' -import Adapter from 'enzyme-adapter-react-16' -import { Button } from '@material-ui/core' - -import CookieInfo from '.' - -Enzyme.configure({ adapter: new Adapter() }) - - -describe('Component Info', () => { - it('renders correctly', () => { - const tree = shallow(Foo) - - expect(tree).toMatchSnapshot() - }) - - it('click away', () => { - const mockCallBack = jest.fn() - - const cookieInfo = shallow(( - - Foo - - )) - - cookieInfo.find(Button).simulate('click') - - expect(mockCallBack).toHaveBeenCalled() - }) -}) diff --git a/src/CookieInfo/CookieInfo.test.tsx b/src/CookieInfo/CookieInfo.test.tsx new file mode 100644 index 0000000..2a901ba --- /dev/null +++ b/src/CookieInfo/CookieInfo.test.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import Enzyme, { shallow } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import { Button } from "@material-ui/core"; + +import CookieInfo from "."; + +Enzyme.configure({ adapter: new Adapter() }); + +describe("Component Info", () => { + it("renders correctly", () => { + const tree = shallow(Foo); + + expect(tree).toMatchSnapshot(); + }); + + it("click away", () => { + const mockCallBack = jest.fn(); + + const cookieInfo = shallow( + + Foo + , + ); + + cookieInfo.find(Button).simulate("click"); + + expect(mockCallBack).toHaveBeenCalled(); + }); +}); diff --git a/src/CookieInfo/CookieInfo.tsx b/src/CookieInfo/CookieInfo.tsx new file mode 100644 index 0000000..3fe4668 --- /dev/null +++ b/src/CookieInfo/CookieInfo.tsx @@ -0,0 +1,77 @@ +import React from "react"; +import classNames from "classnames"; +import { Button, withStyles } from "@material-ui/core"; +import Cookie from "./Cookie"; + +const styles = (theme: any) => ({ + root: { + position: "fixed", + top: 0, + left: 0, + right: 0, + zIndex: 3000, + display: "flex", + backgroundColor: theme.palette.secondary.main, + paddingTop: theme.spacing(), + paddingBottom: theme.spacing(), + paddingLeft: theme.spacing(3), + paddingRight: theme.spacing(3), + transform: "translate(0, -100%)", + opacity: 0, + transition: "transform 0.25s, opacity 0s 0.25s", + }, + rootActive: { + transform: "translate(0, 0)", + opacity: 1, + }, + content: { + display: "flex", + flex: 1, + opacity: 1, + alignItems: "center", + }, + button: { + float: "right", + }, +}); + +type CookieInfoProps = { + isOpen?: boolean; + onAccept?: (...args: any[]) => any; + buttonText?: string; + classes: { + [key: string]: string; + }; +}; + +class CookieInfo extends React.Component { + constructor(props: CookieInfoProps) { + super(props); + this.handleClick = this.handleClick.bind(this); + } + handleClick() { + const { onAccept } = this.props; + if (onAccept) { + onAccept(); + } + Cookie.setCookie(true); + } + render() { + const { isOpen, buttonText, classes, children } = this.props; + const className = classNames({ + [classes.root]: true, + [classes.rootActive]: isOpen, + }); + return ( +
+
{children}
+ + +
+ ); + } +} + +export default withStyles(styles as any)(CookieInfo); diff --git a/src/CookieInfo/index.js b/src/CookieInfo/index.js deleted file mode 100644 index 6798f52..0000000 --- a/src/CookieInfo/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import CookieInfo from './CookieInfo' - -export default CookieInfo diff --git a/src/CookieInfo/index.ts b/src/CookieInfo/index.ts new file mode 100644 index 0000000..947e0f2 --- /dev/null +++ b/src/CookieInfo/index.ts @@ -0,0 +1,3 @@ +import CookieInfo from "./CookieInfo"; + +export default CookieInfo; diff --git a/src/Dashboard/Dashboard.jsx b/src/Dashboard/Dashboard.jsx deleted file mode 100644 index b35ea53..0000000 --- a/src/Dashboard/Dashboard.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { withRouter } from 'react-router-dom' - -import DashboardBranch from './DashboardBranch' - -const withDashboard = Component => class extends React.Component { - static propTypes = { - history: PropTypes.objectOf(PropTypes.any).isRequired, - } - - constructor() { - super() - - this.handleClick = this.handleClick.bind(this) - } - - handleClick(url) { - this.props.history.push(url) - } - - render() { - return ( - - ) - } -} - -const Dashboard = withDashboard(DashboardBranch) - -export default withRouter(Dashboard) diff --git a/src/Dashboard/Dashboard.test.jsx b/src/Dashboard/Dashboard.test.jsx deleted file mode 100644 index 923285f..0000000 --- a/src/Dashboard/Dashboard.test.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react' -import { BrowserRouter as Router } from 'react-router-dom' -import { shallow } from 'enzyme' - -import Dashboard from '.' - -it('renders correctly', () => { - const tree = shallow(( - - - - )) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Dashboard/Dashboard.test.tsx b/src/Dashboard/Dashboard.test.tsx new file mode 100644 index 0000000..4d00258 --- /dev/null +++ b/src/Dashboard/Dashboard.test.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { BrowserRouter as Router } from "react-router-dom"; +import { shallow } from "enzyme"; + +import Dashboard from "."; + +it("renders correctly", () => { + const tree = shallow( + + + , + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Dashboard/Dashboard.tsx b/src/Dashboard/Dashboard.tsx new file mode 100644 index 0000000..d8baa8b --- /dev/null +++ b/src/Dashboard/Dashboard.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { withRouter } from "react-router-dom"; +import DashboardBranch from "./DashboardBranch"; +type WithDashboardProps = { + history: { + [key: string]: any; + }; +}; +const withDashboard = (Component: any) => + class WithDashboard extends React.Component { + constructor(props: WithDashboardProps) { + super(props); + + this.handleClick = this.handleClick.bind(this); + } + handleClick(url: string) { + this.props.history.push(url); + } + render() { + return ; + } + }; +const Dashboard = withDashboard(DashboardBranch); +export default withRouter(Dashboard as any) as any; diff --git a/src/Dashboard/DashboardBranch.jsx b/src/Dashboard/DashboardBranch.jsx deleted file mode 100644 index 3729714..0000000 --- a/src/Dashboard/DashboardBranch.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { Typography, withStyles } from '@material-ui/core' - -import DashboardGroup from './DashboardGroup' - -const styles = theme => ({ - root: { - flexGrow: 1, - maxWidth: theme.spacing(140), - marginLeft: 'auto', - marginRight: 'auto', - }, - headline: { - marginTop: theme.spacing(3), - marginBottom: theme.spacing(2), - }, -}) - -const DashboardBranch = ({ data, onClick, classes }) => ( -
-
- {data.title} - {data.description} -
- - {data.groups ? data.groups.map(group => ( - - )) : null} -
-) - -DashboardBranch.propTypes = { - data: PropTypes.objectOf(PropTypes.any).isRequired, - onClick: PropTypes.func.isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -export default withStyles(styles)(DashboardBranch) diff --git a/src/Dashboard/DashboardBranch.tsx b/src/Dashboard/DashboardBranch.tsx new file mode 100644 index 0000000..cc467f7 --- /dev/null +++ b/src/Dashboard/DashboardBranch.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { Typography, withStyles } from "@material-ui/core"; +import DashboardGroup from "./DashboardGroup"; +const styles = (theme: any) => ({ + root: { + flexGrow: 1, + maxWidth: theme.spacing(140), + marginLeft: "auto", + marginRight: "auto", + }, + headline: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(2), + }, +}); + +interface Groups { + id: string; + title: string; + cards: any; +} + +type DashboardBranchProps = { + data: { + groups: Groups[]; + [key: string]: any; + }; + onClick: (...args: any[]) => any; + classes: { + [key: string]: string; + }; +}; + +const DashboardBranch: React.SFC = ({ + data, + onClick, + classes, +}) => ( +
+
+ {data.title} + {data.description} +
+ + {data.groups + ? data.groups.map((group) => ( + + )) + : null} +
+); +export default withStyles(styles)(DashboardBranch); diff --git a/src/Dashboard/DashboardCard.test.jsx b/src/Dashboard/DashboardCard.test.jsx deleted file mode 100644 index 8437ebb..0000000 --- a/src/Dashboard/DashboardCard.test.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import DashboardCard from './DashboardCard' - -it('renders correctly', () => { - const tree = shallow( { }} - />) - - expect(tree).toMatchSnapshot() -}) - - -it('renders disabled', () => { - const tree = shallow(( - { }} - isDisabled - /> - )) - - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Dashboard/DashboardCard.test.tsx b/src/Dashboard/DashboardCard.test.tsx new file mode 100644 index 0000000..493582f --- /dev/null +++ b/src/Dashboard/DashboardCard.test.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import DashboardCard from "./DashboardCard"; + +it("renders correctly", () => { + const tree = shallow( + {}} />, + ); + + expect(tree).toMatchSnapshot(); +}); + +it("renders disabled", () => { + const tree = shallow( + {}} + isDisabled + />, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Dashboard/DashboardCard.jsx b/src/Dashboard/DashboardCard.tsx similarity index 58% rename from src/Dashboard/DashboardCard.jsx rename to src/Dashboard/DashboardCard.tsx index b9936d3..bdb9f3a 100644 --- a/src/Dashboard/DashboardCard.jsx +++ b/src/Dashboard/DashboardCard.tsx @@ -1,26 +1,23 @@ -import React from 'react' -import PropTypes from 'prop-types' -import classNames from 'classnames' - +import React from "react"; +import classNames from "classnames"; import { Card, CardContent, Typography, withStyles, Avatar, -} from '@material-ui/core' - -import DisabledIcon from '@material-ui/icons/Lock' +} from "@material-ui/core"; +import DisabledIcon from "@material-ui/icons/Lock"; -const styles = theme => ({ +const styles = (theme: any) => ({ root: { - cursor: 'pointer', + cursor: "pointer", }, content: { paddingBottom: `${theme.spacing(2)}px !important`, }, avatar: { - float: 'left', + float: "left", marginRight: theme.spacing(2), backgroundColor: theme.palette.secondary.dark, }, @@ -29,16 +26,27 @@ const styles = theme => ({ }, disabled: { opacity: 0.75, - pointerEvents: 'none', - filter: 'grayscale(30%)', + pointerEvents: "none", + filter: "grayscale(30%)", }, icon: { - float: 'right', + float: "right", opacity: 0.5, }, -}) +}); + +type DashboardCardProps = { + title: string; + description?: string; + handleClick: (...args: any[]) => any; + icon?: (...args: any[]) => any; + isDisabled?: boolean; + classes: { + [key: string]: string; + }; +}; -const DashboardCard = ({ +const DashboardCard: React.SFC = ({ title, description, handleClick, @@ -49,14 +57,12 @@ const DashboardCard = ({ const rootClasses = classNames({ [classes.root]: true, [classes.disabled]: isDisabled, - }) + }); return ( - {isDisabled ? ( - - ) : null} + {isDisabled ? : null} {Icon ? ( @@ -73,22 +79,10 @@ const DashboardCard = ({ ) : null} - ) -} - -DashboardCard.propTypes = { - title: PropTypes.string.isRequired, - description: PropTypes.string, - handleClick: PropTypes.func.isRequired, - icon: PropTypes.func, - isDisabled: PropTypes.bool, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - + ); +}; DashboardCard.defaultProps = { - description: null, - icon: null, isDisabled: false, -} +}; -export default withStyles(styles)(DashboardCard) +export default withStyles(styles as any)(DashboardCard); diff --git a/src/Dashboard/DashboardGroup.jsx b/src/Dashboard/DashboardGroup.jsx deleted file mode 100644 index 86e0722..0000000 --- a/src/Dashboard/DashboardGroup.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import { - Grid, - Typography, - withStyles, -} from '@material-ui/core' - -import DashboardCard from './DashboardCard' - -const styles = theme => ({ - headline: { - marginTop: theme.spacing(3), - marginBottom: theme.spacing(2), - }, -}) - -const DashboardGroup = ({ - title, - cards, - onClick, - classes, -}) => ( -
-
- - {title} - -
- - - - {cards ? cards.map(card => ( - - onClick(card.link)} - icon={card.icon} - isDisabled={card.isDisabled} - /> - - )) : null} - - -
-) - -DashboardGroup.propTypes = { - title: PropTypes.string.isRequired, - cards: PropTypes.arrayOf(PropTypes.object).isRequired, - onClick: PropTypes.func.isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -export default withStyles(styles)(DashboardGroup) diff --git a/src/Dashboard/DashboardGroup.test.jsx b/src/Dashboard/DashboardGroup.test.jsx deleted file mode 100644 index a152deb..0000000 --- a/src/Dashboard/DashboardGroup.test.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import DashboardGroup from './DashboardGroup' - -it('renders correctly', () => { - const tree = shallow( {}} - />) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Dashboard/DashboardGroup.test.tsx b/src/Dashboard/DashboardGroup.test.tsx new file mode 100644 index 0000000..6e77345 --- /dev/null +++ b/src/Dashboard/DashboardGroup.test.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import DashboardGroup from "./DashboardGroup"; + +it("renders correctly", () => { + const tree = shallow( + {}} + />, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Dashboard/DashboardGroup.tsx b/src/Dashboard/DashboardGroup.tsx new file mode 100644 index 0000000..d0b0289 --- /dev/null +++ b/src/Dashboard/DashboardGroup.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { Grid, Typography, withStyles } from "@material-ui/core"; +import DashboardCard from "./DashboardCard"; +const styles = (theme: any) => ({ + headline: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(2), + }, +}); + +type DashboardGroupProps = { + title: string; + cards: Record[]; + onClick: (...args: any[]) => any; + classes: { + [key: string]: string; + }; +}; +const DashboardGroup: React.SFC = ({ + title, + cards, + onClick, + classes, +}) => ( +
+
+ {title} +
+ + + {cards + ? cards.map((card) => ( + + onClick(card.link)} + icon={card.icon} + isDisabled={card.isDisabled} + /> + + )) + : null} + +
+); + +export default withStyles(styles)(DashboardGroup); diff --git a/src/Dashboard/index.js b/src/Dashboard/index.ts similarity index 100% rename from src/Dashboard/index.js rename to src/Dashboard/index.ts diff --git a/src/Drawer/Drawer.test.jsx b/src/Drawer/Drawer.test.jsx deleted file mode 100644 index 33a933f..0000000 --- a/src/Drawer/Drawer.test.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import menuData from '../tests/data/menu' - -import Drawer from '.' - -it('renders correctly', () => { - const tree = shallow( {}} - redirectTo={() => {}} - />) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Drawer/Drawer.test.tsx b/src/Drawer/Drawer.test.tsx new file mode 100644 index 0000000..18b143f --- /dev/null +++ b/src/Drawer/Drawer.test.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import menuData from "../tests/data/menu"; + +import Drawer from "."; + +it("renders correctly", () => { + const tree = shallow( + {}} redirectTo={() => {}} />, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Drawer/Drawer.jsx b/src/Drawer/Drawer.tsx similarity index 50% rename from src/Drawer/Drawer.jsx rename to src/Drawer/Drawer.tsx index e078703..539c7c1 100644 --- a/src/Drawer/Drawer.jsx +++ b/src/Drawer/Drawer.tsx @@ -1,34 +1,40 @@ -import React from 'react' -import PropTypes from 'prop-types' - +import React from "react"; import { Drawer as MaterialDrawer, Divider, IconButton, withStyles, -} from '@material-ui/core' -import ChevronLeftIcon from '@material-ui/icons/ChevronLeft' - -import Menu from '../Menu' - -const drawerWidth = 280 - -const styles = theme => ({ +} from "@material-ui/core"; +import ChevronLeftIcon from "@material-ui/icons/ChevronLeft"; +import Menu from "../Menu"; +import { MenuDataItem } from "../Menu/Menu"; +const drawerWidth = 280; +const styles = (theme: any) => ({ drawerPaper: { - position: 'relative', - height: '100%', + position: "relative", + height: "100%", width: drawerWidth, }, drawerHeader: { - display: 'flex', - alignItems: 'center', - justifyContent: 'flex-end', - padding: '0 8px', + display: "flex", + alignItems: "center", + justifyContent: "flex-end", + padding: "0 8px", height: theme.spacing(8), }, -}) +}); + +type DrawerProps = { + data: MenuDataItem[]; + isOpen?: boolean; + onClose: (...args: any[]) => any; + redirectTo: (...args: any[]) => any; + classes: { + [key: string]: string; + }; +}; -const Drawer = ({ +const Drawer: React.SFC = ({ data, isOpen, onClose, @@ -55,19 +61,8 @@ const Drawer = ({ -) - - -Drawer.propTypes = { - data: PropTypes.arrayOf(PropTypes.object).isRequired, - isOpen: PropTypes.bool, - onClose: PropTypes.func.isRequired, - redirectTo: PropTypes.func.isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - +); Drawer.defaultProps = { isOpen: false, -} - -export default withStyles(styles)(Drawer) +}; +export default withStyles(styles as any)(Drawer); diff --git a/src/Drawer/index.jsx b/src/Drawer/index.jsx deleted file mode 100644 index 0451052..0000000 --- a/src/Drawer/index.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import Drawer from './Drawer' - -export default Drawer diff --git a/src/Drawer/index.tsx b/src/Drawer/index.tsx new file mode 100644 index 0000000..e720d38 --- /dev/null +++ b/src/Drawer/index.tsx @@ -0,0 +1,3 @@ +import Drawer from "./Drawer"; + +export default Drawer; diff --git a/src/Form/Form.jsx b/src/Form/Form.jsx deleted file mode 100644 index 35f9e7f..0000000 --- a/src/Form/Form.jsx +++ /dev/null @@ -1,169 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import FormBranch from './FormBranch' -import isValid from './isValid' - -const withForm = Component => class Form extends React.Component { - static propTypes = { - form: PropTypes.arrayOf(PropTypes.object).isRequired, - data: PropTypes.objectOf(PropTypes.any).isRequired, - onDataChanged: PropTypes.func, - onSubmit: PropTypes.func.isRequired, - submitText: PropTypes.string, - isFixedSubmitButton: PropTypes.bool, - } - - static defaultProps = { - onDataChanged: () => {}, - submitText: 'Save', - isFixedSubmitButton: false, - } - - constructor(props, defaultProps) { - super(props, defaultProps) - - this.state = { - data: {}, - loading: false, - } - - this.fields = {} - this.timer = undefined - - this.handleSubmit = this.handleSubmit.bind(this) - this.updateFieldData = this.updateFieldData.bind(this) - } - - componentDidMount() { - const { form, data } = this.props - this.generateFields(form, data) - this.generateMissingData(data) - - this.setState({ - data: this.fields, - }) - } - - componentWillReceiveProps({ form, data }) { - this.generateFields(form, data) - this.generateMissingData(data) - - this.setState({ - data: this.fields, - }) - } - - componentWillUnmount() { - clearTimeout(this.timer) - } - - static getInitialField(field, data) { - let valueName = data[field.id] && data[field.id].value - let submitValue = valueName - - if (!valueName && field.value !== undefined) { - valueName = field.value - } - - if (typeof field.beforeSubmit === 'function') { - submitValue = field.beforeSubmit(submitValue) - } - - return { - value: valueName, - submitValue, - error: !isValid(field.type, field.isRequired, field.validators, submitValue), - } - } - - generateFields(fieldset, data) { - fieldset.forEach((field) => { - if (field.group) { - this.generateFields(field.data, data) - return - } - - this.fields[field.id] = Form.getInitialField(field, data) - }) - } - - generateMissingData(data) { - Object.keys(data).forEach((key) => { - if (this.fields[key]) { - return - } - - this.fields[key] = data[key] - }) - } - - handleSubmit() { - const { - data, - loading, - } = this.state - const { onSubmit } = this.props - - const errors = Object.values(data).map(field => field.error) - - if (errors.indexOf(true) > -1) { - this.setState({ - error: true, - }) - - return - } - - if (!loading) { - this.setState( - { - loading: true, - error: false, - }, - () => { - this.timer = setTimeout(() => { - this.setState({ - loading: false, - }) - }, 1000) - }, - ) - } - - onSubmit(data) - } - - updateFieldData(fieldId, value, submitValue, error) { - const { onDataChanged } = this.props - const { data: stateData } = this.state - const data = { - ...stateData, - } - - data[fieldId] = { - value, - submitValue, - error, - } - - onDataChanged(data) - - this.setState({ - data, - }) - } - - render() { - return ( - - ) - } -} - -export default withForm(FormBranch) diff --git a/src/Form/Form.test.jsx b/src/Form/Form.test.jsx deleted file mode 100644 index 0e9655e..0000000 --- a/src/Form/Form.test.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react' -import { MemoryRouter } from 'react-router-dom' -import { shallow } from 'enzyme' - -import Form from '.' - -import formData from '../tests/data/form' - -it('renders correctly', () => { - const tree = shallow(( - -
- - )) - - expect(tree).toMatchSnapshot() -}) - -it('renders correctly with content', () => { - const tree = shallow(( - - -

Foo

- -
- )) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Form/Form.test.tsx b/src/Form/Form.test.tsx new file mode 100644 index 0000000..266fa62 --- /dev/null +++ b/src/Form/Form.test.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { MemoryRouter } from "react-router-dom"; +import { shallow } from "enzyme"; + +import Form from "."; + +import formData from "../tests/data/form"; + +it("renders correctly", () => { + const tree = shallow( + +
+ , + ); + + expect(tree).toMatchSnapshot(); +}); + +it("renders correctly with content", () => { + const tree = shallow( + + +

Foo

+ +
, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Form/Form.tsx b/src/Form/Form.tsx new file mode 100644 index 0000000..4b3cfcf --- /dev/null +++ b/src/Form/Form.tsx @@ -0,0 +1,154 @@ +import React from "react"; +import FormBranch from "./FormBranch"; +import isValid from "./isValid"; + +type FormProps = { + form: object[]; + data: { + [key: string]: any; + }; + onDataChanged?: (...args: any[]) => any; + onSubmit: (...args: any[]) => any; + submitText?: string; + isFixedSubmitButton?: boolean; +}; + +type FormState = { + loading: boolean; + error?: boolean; + data: {} | any; +}; + +const withForm = (Component: any) => + class Form extends React.Component { + private fields: any; + private timer: any; + + constructor(props: FormProps, defaultProps: any) { + super(props, defaultProps); + + this.state = { + data: {}, + loading: false, + }; + this.fields = {}; + this.timer = undefined; + this.handleSubmit = this.handleSubmit.bind(this); + this.updateFieldData = this.updateFieldData.bind(this); + } + componentDidMount() { + const { form, data } = this.props; + this.generateFields(form, data); + this.generateMissingData(data); + this.setState({ + data: this.fields, + }); + } + UNSAFE_componentWillReceiveProps({ form, data }: FormProps) { + this.generateFields(form, data); + this.generateMissingData(data); + this.setState({ + data: this.fields, + }); + } + componentWillUnmount() { + clearTimeout(this.timer); + } + + static getInitialField(field: any, data: any) { + let valueName = data[field.id] && data[field.id].value; + let submitValue = valueName; + if (!valueName && field.value !== undefined) { + valueName = field.value; + } + if (typeof field.beforeSubmit === "function") { + submitValue = field.beforeSubmit(submitValue); + } + return { + value: valueName, + submitValue, + error: !isValid( + field.type, + field.isRequired, + field.validators, + submitValue, + ), + }; + } + generateFields(fieldset: any[], data: any) { + fieldset.forEach((field) => { + if (field.group) { + this.generateFields(field.data, data); + return; + } + this.fields[field.id] = Form.getInitialField(field, data); + }); + } + generateMissingData(data: any) { + Object.keys(data).forEach((key) => { + if (this.fields[key]) { + return; + } + this.fields[key] = data[key]; + }); + } + handleSubmit() { + const { data, loading } = this.state; + const { onSubmit } = this.props; + const errors = Object.values(data).map((field: any) => field.error); + if (errors.indexOf(true) > -1) { + this.setState({ + error: true, + }); + return; + } + if (!loading) { + this.setState( + { + loading: true, + error: false, + }, + () => { + this.timer = setTimeout(() => { + this.setState({ + loading: false, + }); + }, 1000); + }, + ); + } + onSubmit(data); + } + + updateFieldData(fieldId: string, value: any, submitValue: any, error: any) { + const { onDataChanged } = this.props; + const { data: stateData } = this.state; + const data = { + ...stateData, + }; + data[fieldId] = { + value, + submitValue, + error, + }; + + onDataChanged && onDataChanged(data); + + this.setState({ + data, + }); + } + + render() { + return ( + + ); + } + }; + +export default withForm(FormBranch); diff --git a/src/Form/FormBranch.jsx b/src/Form/FormBranch.jsx deleted file mode 100644 index a865951..0000000 --- a/src/Form/FormBranch.jsx +++ /dev/null @@ -1,150 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { - Typography, - withStyles, -} from '@material-ui/core' - -import FormGroupWrapper from './FormGroupWrapper' -import FormField from './FormField' -import FormSubmitButton from './FormSubmitButton' - -const styles = theme => ({ - title: { - marginLeft: theme.spacing(), - marginRight: theme.spacing(), - }, - errorMessage: { - marginTop: theme.spacing(2), - marginBottom: theme.spacing(2), - color: theme.palette.error.main, - }, -}) - -const Element = ({ useFormElement, ...props }) => { - if (useFormElement) { - return ( -
- ) - } - - return ( -
- ) -} - -Element.propTypes = { - useFormElement: PropTypes.bool.isRequired, -} - -const FormBranch = ({ - form, - data, - loading, - useFormElement, - error, - errorMessage, - isFixedSubmitButton, - updateFieldData, - handleSubmit, - submitText, - children, - classes, -}) => { - const renderField = (field, index) => { - let valueName = data[field.id] && data[field.id].value - - if (!valueName) { - valueName = field.value - } - - return ( - - ) - } - - const generateFields = formData => formData.map((field, index) => { - if (field.group) { - return ( - - {field.title ? ( - - {field.title} - - ) : null} - - {generateFields(field.data, field)} - - ) - } - - return renderField(field, index) - }) - - const elements = generateFields(form) - - return ( - - {elements} - - {children} - - {error && ( - - {errorMessage} - - )} - - - {submitText} - - - ) -} - -FormBranch.propTypes = { - form: PropTypes.arrayOf(PropTypes.object).isRequired, - data: PropTypes.objectOf(PropTypes.object), - loading: PropTypes.bool.isRequired, - useFormElement: PropTypes.bool, - isFixedSubmitButton: PropTypes.bool, - error: PropTypes.bool, - errorMessage: PropTypes.string, - updateFieldData: PropTypes.func, - handleSubmit: PropTypes.func, - submitText: PropTypes.string, - children: PropTypes.node, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -FormBranch.defaultProps = { - data: {}, - useFormElement: true, - isFixedSubmitButton: false, - error: false, - errorMessage: 'An error occured. Please fill out all required fields correctly.', - updateFieldData: () => {}, - handleSubmit: () => {}, - submitText: 'Save', - children: null, -} - -export default withStyles(styles)(FormBranch) diff --git a/src/Form/FormBranch.tsx b/src/Form/FormBranch.tsx new file mode 100644 index 0000000..61dc896 --- /dev/null +++ b/src/Form/FormBranch.tsx @@ -0,0 +1,148 @@ +import React from "react"; +import { Typography, withStyles } from "@material-ui/core"; +import FormGroupWrapper from "./FormGroupWrapper"; +import FormField from "./FormField"; +import FormSubmitButton from "./FormSubmitButton"; + +const styles = (theme: any) => ({ + title: { + marginLeft: theme.spacing(), + marginRight: theme.spacing(), + }, + errorMessage: { + marginTop: theme.spacing(2), + marginBottom: theme.spacing(2), + color: theme.palette.error.main, + }, +}); + +type FormBranchProps = { + form: object[]; + data?: { + [key: string]: Record; + }; + loading: boolean; + useFormElement?: boolean; + isFixedSubmitButton?: boolean; + error?: boolean; + errorMessage?: string; + updateFieldData?: (...args: any[]) => any; + handleSubmit?: (...args: any[]) => any; + submitText?: string; + classes: { + [key: string]: string; + }; +}; + +type ElementProps = { + useFormElement?: boolean; + noValidate: boolean; + autoComplete: string; +}; + +const Element: React.SFC = ({ useFormElement, ...props }) => { + if (useFormElement) { + return ; + } + + return
; +}; + +const FormBranch: React.SFC = ({ + form, + data, + loading, + useFormElement, + error, + errorMessage, + isFixedSubmitButton, + updateFieldData, + handleSubmit, + submitText, + children, + classes, +}) => { + if (!data) { + return null; + } + + const renderField = (field: any, index: number) => { + let valueName = data[field.id] && data[field.id].value; + if (!valueName) { + valueName = field.value; + } + return ( + + ); + }; + + const generateFields = (formData: any) => + formData.map((field: any, index: number) => { + if (field.group) { + return ( + + {field.title ? ( + + {field.title} + + ) : null} + + {generateFields(field.data)} + + ); + } + return renderField(field, index); + }); + + const elements = generateFields(form); + + return ( + + {elements} + + {children} + + {error && ( + + {errorMessage} + + )} + + + {submitText} + + + ); +}; + +FormBranch.defaultProps = { + data: {}, + useFormElement: true, + isFixedSubmitButton: false, + error: false, + errorMessage: + "An error occured. Please fill out all required fields correctly.", + updateFieldData: () => {}, + handleSubmit: () => {}, + submitText: "Save", +}; + +export default withStyles(styles)(FormBranch); diff --git a/src/Form/FormField.jsx b/src/Form/FormField.jsx deleted file mode 100644 index a1d230e..0000000 --- a/src/Form/FormField.jsx +++ /dev/null @@ -1,222 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import { TYPES } from './constants' -import FormFieldBranch from './FormFieldBranch' -import isValid from './isValid' - -const withFormField = Component => class FormField extends React.Component { - static propTypes = { - id: PropTypes.string.isRequired, - type: PropTypes.string, - helperText: PropTypes.string, - validators: PropTypes.arrayOf(PropTypes.oneOfType([ - PropTypes.string, - PropTypes.func, - PropTypes.shape({ - validator: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.func, - ]), - message: PropTypes.string, - }), - ])), - isRequired: PropTypes.bool, - handleChange: PropTypes.func.isRequired, - getAdditionalValue: PropTypes.func, - beforeSubmit: PropTypes.func, - } - - static defaultProps = { - type: 'text', - validators: [], - isRequired: false, - helperText: '', - getAdditionalValue: data => data, - beforeSubmit: null, - } - - static getCompleteFrom(completeFrom = []) { - return completeFrom.map((option) => { - let { title } = option - - if (!option.title) { - title = option - } - - return { - title, - tooltip: option.tooltip, - } - }) - } - - constructor(props) { - super(props) - - this.handleChange = this.handleChange.bind(this) - this.initialize = this.initialize.bind(this) - this.handleAddListItem = this.handleAddListItem.bind(this) - this.handleRemoveListItem = this.handleRemoveListItem.bind(this) - } - - state = { - listItems: [], - value: '', - isDirty: false, - completeFrom: [], - messages: [], - } - - componentWillMount() { - this.initialize(this.props) - } - - componentWillReceiveProps(nextProps) { - this.initialize(nextProps) - } - - getAdditionalValue(value) { - if (typeof this.props.getAdditionalValue === 'function') { - return this.props.getAdditionalValue(value) - } - - return value - } - - getList({ value, completeFrom }) { - let listItems = this.state.listItems.map(item => item.title) - - if (this.state.isDirty === false) { - if (value && value.constructor === Array) { - listItems = [ - ...value, - ] - } else { - listItems = [] - } - } - - const allCompleteFrom = FormField.getCompleteFrom(completeFrom) - const transformedListItems = this.getAdditionalValue(listItems) - - listItems = transformedListItems.map((selectedValue) => { - if (allCompleteFrom.length > 0) { - return allCompleteFrom.filter(option => option.title === selectedValue)[0] - } - - return { - title: selectedValue, - } - }) - - return { - completeFrom: allCompleteFrom, - value: '', - isDirty: true, - listItems, - } - } - - initialize(props) { - const isList = props.type === 'list' - let state = {} - - if (isList) { - state = this.getList(props) - } else { - state = { - value: this.getAdditionalValue(props.value), - } - } - - this.setState(state) - } - - isValid(value) { - const { - type, - isRequired, - validators, - } = this.props - - return isValid(type, isRequired, validators, value) - } - - handleChange(fieldId) { - return ((event) => { - let newValue - - if (event.target) { - newValue = event.target.value - - // eslint-disable-next-line no-underscore-dangle - } else if (event._isAMomentObject) { - newValue = event.valueOf() - } - - let submitValue = newValue - - if (typeof this.props.beforeSubmit === 'function') { - submitValue = this.props.beforeSubmit(submitValue) - } - const isValidWithMessages = this.isValid(submitValue) - const error = !isValidWithMessages.isValid - - if (this.props.type === TYPES.NUMBER) { - newValue = parseFloat(newValue, 10) - } - - this.setState({ - value: newValue, - messages: isValidWithMessages.messages || [], - error, - }) - - this.props.handleChange(fieldId, newValue, submitValue, error) - }) - } - - handleAddListItem(option) { - this.setState({ - listItems: [...this.state.listItems, option], - }) - } - - handleRemoveListItem(option) { - const listItems = [...this.state.listItems] - const flatListItems = listItems.map(item => item.title) - const index = flatListItems.indexOf(option.title) - - if (index > -1) { - listItems.splice(index, 1) - } - - this.setState({ - listItems, - }) - } - - render() { - const { helperText, ...props } = this.props - const { messages, ...state } = this.state - let helperMessages = [] - - if (helperText) { - helperMessages = [helperText, ...messages] - } - - return ( - - ) - } -} - -export default withFormField(FormFieldBranch) diff --git a/src/Form/FormField.test.jsx b/src/Form/FormField.test.jsx deleted file mode 100644 index 8377768..0000000 --- a/src/Form/FormField.test.jsx +++ /dev/null @@ -1,249 +0,0 @@ -import React from 'react' -import Enzyme, { shallow } from 'enzyme' -import Adapter from 'enzyme-adapter-react-16' -import { ListItem, Chip } from '@material-ui/core' - -import FormField from './FormField' -import FormFieldInput from './FormFieldInput' - -Enzyme.configure({ adapter: new Adapter() }) - -describe('Form Field', () => { - it('handles change', () => { - const handleChange = jest.fn() - const field = shallow(( - - )) - - field.find('input').simulate('change', { value: 'a' }) - - expect(handleChange).toHaveBeenCalled() - }) - - it('renders non-visible', () => { - const field = shallow(( - { }} - isVisible={false} - width="small" - /> - )) - expect(field.find(FormFieldInput).props().classNames.join(' ')) - .toEqual(expect.stringMatching(/hidden/)) - }) - - it('renders calls beforeSubmit function, handles number', () => { - const beforeSubmit = jest.fn() - const field = shallow(( - { }} - beforeSubmit={beforeSubmit} - width="mid" - /> - )) - - field.find('input').simulate('change', { value: 1 }) - - expect(beforeSubmit).toHaveBeenCalled() - }) - - it('handles list', () => { - const field = shallow(( - { }} - /> - )) - - expect(field.state().listItems).toEqual([]) - - field.find('input').simulate('change', { target: { value: 'foo' } }) - field.find('input').simulate('keyPress', { which: 13 }) - - expect(field.state().listItems).toEqual([{ title: 'foo' }]) - }) - - it('handles list with allowed values', () => { - const field = shallow(( - { }} - completeFrom={['foo']} - /> - )) - - expect(field.state().listItems).toEqual([]) - - field.find('input').simulate('change', { target: { value: '_' } }) - field.find('input').simulate('keyPress', { which: 13 }) - - expect(field.state().listItems).toEqual([]) - - field.find('input').simulate('change', { target: { value: 'foo' } }) - field.find(ListItem).at(0).simulate('click') - - expect(field.state().listItems[0].title).toEqual('foo') - }) - - it('handles list delete', () => { - const field = shallow(( - { }} - completeFrom={[{ - title: 'foo', - tooltip: 'bar', - }]} - /> - )) - - field.find('input').simulate('change', { target: { value: 'foo' } }) - field.find(ListItem).at(0).simulate('click') - - expect(field.state().listItems[0].title).toEqual('foo') - field.find(Chip).at(0).find('svg').simulate('click') - }) - - it('renders list item via function', () => { - const renderElement = jest.fn() - const field = shallow(( - { }} - renderElement={renderElement} - /> - )) - - field.find('input').simulate('change', { target: { value: 'foo' } }) - field.find('input').simulate('keyPress', { which: 13 }) - - expect(renderElement).toHaveBeenCalled() - }) - - it('does not render if there is no option', () => { - const field = shallow(( - { }} - /> - )) - - field.setState({ - listItems: [null], - }) - - expect(field.find(Chip).length).toEqual(0) - }) - - it('handles datetime field', () => { - const field = shallow(( - { }} - /> - )) - const newDate = +new Date() - - field.instance().handleChange('6')({ - _isAMomentObject: true, - valueOf: () => newDate, - }) - - expect(field.state().value).toEqual(newDate) - }) - - it('handles date field', () => { - const field = shallow(( - { }} - /> - )) - const newDate = +new Date() - - field.instance().handleChange('6')({ - _isAMomentObject: true, - valueOf: () => newDate, - }) - - expect(field.state().value).toEqual(newDate) - }) - - it('handles time field', () => { - const field = shallow(( - { }} - /> - )) - const newDate = +new Date() - - field.instance().handleChange('6')({ - _isAMomentObject: true, - valueOf: () => newDate, - }) - - expect(field.state().value).toEqual(newDate) - }) - - it('handle validator with message', () => { - const handleChange = jest.fn() - const validatorWithMessage = { - validator: value => value === 'test', - message: 'Value should equal `test`', - } - - const field = shallow(( - - )) - - field.find('input').simulate('change', { value: 'a' }) - - const { messages } = field.instance().state - - expect(messages).toEqual([validatorWithMessage.message]) - }) -}) diff --git a/src/Form/FormField.test.tsx b/src/Form/FormField.test.tsx new file mode 100644 index 0000000..969b4f3 --- /dev/null +++ b/src/Form/FormField.test.tsx @@ -0,0 +1,242 @@ +import React from "react"; +import Enzyme, { shallow } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import { ListItem, Chip } from "@material-ui/core"; + +import FormField from "./FormField"; +import FormFieldInput from "./FormFieldInput"; + +Enzyme.configure({ adapter: new Adapter() }); + +describe("Form Field", () => { + it("handles change", () => { + const handleChange = jest.fn(); + const field = shallow( + , + ); + + field.find("input").simulate("change", { value: "a" }); + + expect(handleChange).toHaveBeenCalled(); + }); + + it("renders non-visible", () => { + const field = shallow( + {}} + isVisible={false} + width="small" + />, + ); + expect(field.find(FormFieldInput).props().classNames.join(" ")).toEqual( + expect.stringMatching(/hidden/), + ); + }); + + it("renders calls beforeSubmit function, handles number", () => { + const beforeSubmit = jest.fn(); + const field = shallow( + {}} + beforeSubmit={beforeSubmit} + width="mid" + />, + ); + + field.find("input").simulate("change", { value: 1 }); + + expect(beforeSubmit).toHaveBeenCalled(); + }); + + it("handles list", () => { + const field = shallow( + {}} />, + ); + + expect(field.state().listItems).toEqual([]); + + field.find("input").simulate("change", { target: { value: "foo" } }); + field.find("input").simulate("keyPress", { which: 13 }); + + expect(field.state().listItems).toEqual([{ title: "foo" }]); + }); + + it("handles list with allowed values", () => { + const field = shallow( + {}} + completeFrom={["foo"]} + />, + ); + + expect(field.state().listItems).toEqual([]); + + field.find("input").simulate("change", { target: { value: "_" } }); + field.find("input").simulate("keyPress", { which: 13 }); + + expect(field.state().listItems).toEqual([]); + + field.find("input").simulate("change", { target: { value: "foo" } }); + field.find(ListItem).at(0).simulate("click"); + + expect(field.state().listItems[0].title).toEqual("foo"); + }); + + it("handles list delete", () => { + const field = shallow( + {}} + completeFrom={[ + { + title: "foo", + tooltip: "bar", + }, + ]} + />, + ); + + field.find("input").simulate("change", { target: { value: "foo" } }); + field.find(ListItem).at(0).simulate("click"); + + expect(field.state().listItems[0].title).toEqual("foo"); + field.find(Chip).at(0).find("svg").simulate("click"); + }); + + it("renders list item via function", () => { + const renderElement = jest.fn(); + const field = shallow( + {}} + renderElement={renderElement} + />, + ); + + field.find("input").simulate("change", { target: { value: "foo" } }); + field.find("input").simulate("keyPress", { which: 13 }); + + expect(renderElement).toHaveBeenCalled(); + }); + + it("does not render if there is no option", () => { + const field = shallow( + {}} />, + ); + + field.setState({ + listItems: [null], + }); + + expect(field.find(Chip).length).toEqual(0); + }); + + it("handles datetime field", () => { + const field = shallow( + {}} + />, + ); + const newDate = +new Date(); + + field.instance().handleChange("6")({ + _isAMomentObject: true, + valueOf: () => newDate, + }); + + expect(field.state().value).toEqual(newDate); + }); + + it("handles date field", () => { + const field = shallow( + {}} + />, + ); + const newDate = +new Date(); + + field.instance().handleChange("6")({ + _isAMomentObject: true, + valueOf: () => newDate, + }); + + expect(field.state().value).toEqual(newDate); + }); + + it("handles time field", () => { + const field = shallow( + {}} + />, + ); + const newDate = +new Date(); + + field.instance().handleChange("6")({ + _isAMomentObject: true, + valueOf: () => newDate, + }); + + expect(field.state().value).toEqual(newDate); + }); + + it("handle validator with message", () => { + const handleChange = jest.fn(); + const validatorWithMessage = { + validator: (value) => value === "test", + message: "Value should equal `test`", + }; + + const field = shallow( + , + ); + + field.find("input").simulate("change", { value: "a" }); + + const { messages } = field.instance().state; + + expect(messages).toEqual([validatorWithMessage.message]); + }); +}); diff --git a/src/Form/FormField.tsx b/src/Form/FormField.tsx new file mode 100644 index 0000000..7944498 --- /dev/null +++ b/src/Form/FormField.tsx @@ -0,0 +1,197 @@ +import React from "react"; +import { TYPES } from "./constants"; +import FormFieldBranch from "./FormFieldBranch"; +import isValid from "./isValid"; + +type FormFieldProps = { + id: string; + type?: string; + helperText?: string; + validators?: ( + | string + | ((...args: any[]) => any) + | { + validator?: string | ((...args: any[]) => any); + message?: string; + } + )[]; + isRequired?: boolean; + handleChange: (...args: any[]) => any; + getAdditionalValue?: (...args: any[]) => any; + beforeSubmit?: (...args: any[]) => any; + value: any; +}; + +type FormFieldState = { + value: any; + messages: any; + error?: boolean; + listItems?: any[]; + isDirty: boolean; + completeFrom: any[]; +}; + +const withFormField = (Component: any) => + class FormField extends React.Component { + static getCompleteFrom(completeFrom = []) { + return completeFrom.map((option: any) => { + let { title } = option; + if (!option.title) { + title = option; + } + return { + title, + tooltip: option.tooltip, + }; + }); + } + + constructor(props: FormFieldProps) { + super(props); + this.handleChange = this.handleChange.bind(this); + this.initialize = this.initialize.bind(this); + this.handleAddListItem = this.handleAddListItem.bind(this); + this.handleRemoveListItem = this.handleRemoveListItem.bind(this); + } + + state = { + listItems: [], + value: "", + isDirty: false, + completeFrom: [], + messages: [], + }; + + UNSAFE_componentWillMount() { + this.initialize(this.props); + } + UNSAFE_componentWillReceiveProps(nextProps: FormFieldProps) { + this.initialize(nextProps); + } + getAdditionalValue(value: any) { + if (typeof this.props.getAdditionalValue === "function") { + return this.props.getAdditionalValue(value); + } + return value; + } + + getList({ value, completeFrom }: any) { + let listItems = this.state.listItems.map((item: any) => item.title); + if (this.state.isDirty === false) { + if (value && value.constructor === Array) { + listItems = [...value]; + } else { + listItems = []; + } + } + const allCompleteFrom = FormField.getCompleteFrom(completeFrom); + const transformedListItems = this.getAdditionalValue(listItems); + listItems = transformedListItems.map((selectedValue: any) => { + if (allCompleteFrom.length > 0) { + return allCompleteFrom.filter( + (option) => option.title === selectedValue, + )[0]; + } + return { + title: selectedValue, + }; + }); + return { + completeFrom: allCompleteFrom, + value: "", + isDirty: true, + listItems, + }; + } + + initialize(props: FormFieldProps) { + const isList = props.type === "list"; + let state = {}; + if (isList) { + state = this.getList(props); + } else { + state = { + value: this.getAdditionalValue(props.value), + }; + } + this.setState(state); + } + + isValid(value: any) { + const { type, isRequired, validators } = this.props; + return isValid(type, isRequired, validators, value); + } + + handleChange(fieldId: string) { + return (event: any) => { + let newValue; + + if (event.target) { + newValue = event.target.value; + // eslint-disable-next-line no-underscore-dangle + } else if (event._isAMomentObject) { + newValue = event.valueOf(); + } + + let submitValue = newValue; + + if (typeof this.props.beforeSubmit === "function") { + submitValue = this.props.beforeSubmit(submitValue); + } + + const isValidWithMessages = this.isValid(submitValue); + const error = !isValidWithMessages.isValid; + + if (this.props.type === TYPES.NUMBER) { + newValue = parseFloat(newValue); + } + + this.setState({ + value: newValue, + messages: isValidWithMessages.messages || [], + error, + }); + this.props.handleChange(fieldId, newValue, submitValue, error); + }; + } + + handleAddListItem(option: any) { + this.setState({ + listItems: [...this.state.listItems, option], + }); + } + + handleRemoveListItem(option: any) { + const listItems = [...this.state.listItems]; + const flatListItems = listItems.map((item: any) => item.title); + const index = flatListItems.indexOf(option.title); + if (index > -1) { + listItems.splice(index, 1); + } + this.setState({ + listItems, + }); + } + + render() { + const { helperText, ...props } = this.props; + const { messages, ...state } = this.state; + let helperMessages: any[] = []; + + if (helperText) { + helperMessages = [helperText, ...messages]; + } + return ( + + ); + } + }; + +export default withFormField(FormFieldBranch); diff --git a/src/Form/FormFieldBranch.jsx b/src/Form/FormFieldBranch.jsx deleted file mode 100644 index 912d7a8..0000000 --- a/src/Form/FormFieldBranch.jsx +++ /dev/null @@ -1,174 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import classnames from 'classnames' -import { withStyles } from '@material-ui/core' - -import { TYPES } from './constants' -import FormFieldInput from './FormFieldInput' -import FormFieldDate from './FormFieldDate' -import FormFieldList from './FormFieldList' -import FormFieldSwitch from './FormFieldSwitch' -import FormFieldHidden from './FormFieldHidden' - -const styles = theme => ({ - hidden: { - display: 'none', - }, - - headline: { - marginTop: theme.spacing(3), - }, - - field: { - marginLeft: theme.spacing(), - marginRight: theme.spacing(), - verticalAlign: 'top', - }, - - fieldInline: { - display: 'inline-block', - }, - - fieldDate: { - marginTop: theme.spacing(2), - marginBottom: theme.spacing(), - }, - - widthSmall: { - width: `calc(25% - ${theme.spacing(2)}px)`, - }, - - widthMid: { - width: `calc(50% - ${theme.spacing(2)}px)`, - }, - - widthFull: { - width: `calc(100% - ${theme.spacing(2)}px)`, - }, - - divider: { - marginTop: theme.spacing(3), - marginBottom: theme.spacing(3), - }, -}) - -const FormFieldBranch = ({ - type, - width, - classes, - isVisible, - ...props -}) => { - const getClasses = () => { - const classNames = [classes.field] - - if (width === 'small') { - classNames.push(classes.widthSmall) - } else if (width === 'mid') { - classNames.push(classes.widthMid) - } else { - classNames.push(classes.widthFull) - } - - if (!isVisible) { - classNames.push(classes.hidden) - } - - return classnames(...classNames) - } - - const classNames = getClasses() - - switch (type) { - case TYPES.SELECT: - return ( - - ) - case TYPES.LIST: - return ( - - ) - case TYPES.MULTILINE: - return ( - - ) - case TYPES.DATE: - case TYPES.TIME: - case TYPES.DATETIME: - return ( - - ) - case TYPES.SWITCH: - return ( - - ) - case TYPES.HIDDEN: - return ( - - ) - case TYPES.DIVIDER: - return ( -
- ) - case TYPES.EMPTY: - return ( -
- ) - case TYPES.CONTENT: - return ( -
- {props.content} -
- ) - default: - return ( - - ) - } -} - -FormFieldBranch.propTypes = { - type: PropTypes.string, - width: PropTypes.string, - listItems: PropTypes.arrayOf(PropTypes.object), - selectOptions: PropTypes.arrayOf(PropTypes.string), - content: PropTypes.node, - isVisible: PropTypes.bool, - classes: PropTypes.objectOf(PropTypes.string).isRequired, - handleChange: PropTypes.func, -} - -FormFieldBranch.defaultProps = { - type: 'text', - width: 'full', - listItems: [], - selectOptions: [], - content: null, - isVisible: true, - handleChange: () => { }, -} - -export default withStyles(styles)(FormFieldBranch) diff --git a/src/Form/FormFieldBranch.tsx b/src/Form/FormFieldBranch.tsx new file mode 100644 index 0000000..a2eba2f --- /dev/null +++ b/src/Form/FormFieldBranch.tsx @@ -0,0 +1,154 @@ +import React from "react"; +import classnames from "classnames"; +import { withStyles } from "@material-ui/core"; +import { TYPES } from "./constants"; +import FormFieldInput from "./FormFieldInput"; +import FormFieldDate from "./FormFieldDate"; +import FormFieldList from "./FormFieldList"; +import FormFieldSwitch from "./FormFieldSwitch"; +import FormFieldHidden from "./FormFieldHidden"; + +const styles = (theme: any) => ({ + hidden: { + display: "none", + }, + headline: { + marginTop: theme.spacing(3), + }, + field: { + marginLeft: theme.spacing(), + marginRight: theme.spacing(), + verticalAlign: "top", + }, + fieldInline: { + display: "inline-block", + }, + fieldDate: { + marginTop: theme.spacing(2), + marginBottom: theme.spacing(), + }, + widthSmall: { + width: `calc(25% - ${theme.spacing(2)}px)`, + }, + widthMid: { + width: `calc(50% - ${theme.spacing(2)}px)`, + }, + widthFull: { + width: `calc(100% - ${theme.spacing(2)}px)`, + }, + divider: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3), + }, +}); + +type FormFieldBranchProps = { + type?: string; + width?: string; + listItems?: object[]; + selectOptions?: string[]; + content?: React.ReactNode; + isVisible?: boolean; + classes: { + [key: string]: string; + }; + handleChange: (...args: any[]) => any; + id: string; + title: string; + onAddListItem: () => void; + onRemoveListItem: () => void; +}; + +const FormFieldBranch: React.SFC = ({ + type, + width, + classes, + isVisible, + ...props +}) => { + const getClasses = () => { + const classNames = [classes.field]; + + if (width === "small") { + classNames.push(classes.widthSmall); + } else if (width === "mid") { + classNames.push(classes.widthMid); + } else { + classNames.push(classes.widthFull); + } + if (!isVisible) { + classNames.push(classes.hidden); + } + return classnames(...classNames); + }; + + const classNames = getClasses(); + + switch (type) { + case TYPES.SELECT: + return ( + + ); + + case TYPES.LIST: + return ; + + case TYPES.MULTILINE: + return ; + + case TYPES.DATE: + case TYPES.TIME: + case TYPES.DATETIME: + return ( + + ); + + case TYPES.SWITCH: + return ( + + ); + + case TYPES.HIDDEN: + return ; + + case TYPES.DIVIDER: + return
; + + case TYPES.EMPTY: + return
; + + case TYPES.CONTENT: + return ( +
+ {props.content} +
+ ); + + default: + return ; + } +}; + +FormFieldBranch.defaultProps = { + type: "text", + width: "full", + listItems: [], + selectOptions: [], + content: null, + isVisible: true, + handleChange: () => {}, +}; + +export default withStyles(styles)(FormFieldBranch); diff --git a/src/Form/FormFieldDate.jsx b/src/Form/FormFieldDate.jsx deleted file mode 100644 index abc94a9..0000000 --- a/src/Form/FormFieldDate.jsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import KeyboardArrowLeftIcon from '@material-ui/icons/KeyboardArrowLeft' -import KeyboardArrowRightIcon from '@material-ui/icons/KeyboardArrowRight' -import DateRangeIcon from '@material-ui/icons/DateRange' -import AccessTimeIcon from '@material-ui/icons/AccessTime' -import MomentUtils from '@date-io/moment' -import { - MuiPickersUtilsProvider, - DateTimePicker, - DatePicker, - TimePicker, -} from '@material-ui/pickers' - -import { TYPES } from './constants' - -const FormFieldDate = ({ - type, - id, - title, - value, - format, - isRequired, - handleChange, - helperText, - className, -}) => { - let additionalAttributes = {} - let Component - - switch (type) { - case TYPES.TIME: - Component = TimePicker - break - case TYPES.DATE: - Component = DatePicker - additionalAttributes = { - leftArrowIcon: (), - rightArrowIcon: (), - } - break - default: - Component = DateTimePicker - additionalAttributes = { - leftArrowIcon: (), - rightArrowIcon: (), - timeIcon: (), - dateRangeIcon: (), - } - break - } - - return ( - - - - ) -} - -FormFieldDate.propTypes = { - type: PropTypes.oneOf([ - TYPES.TIME, - TYPES.DATE, - TYPES.DATETIME, - ]).isRequired, - id: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - value: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]).isRequired, - format: PropTypes.string, - helperText: PropTypes.string, - isRequired: PropTypes.bool.isRequired, - className: PropTypes.string.isRequired, - handleChange: PropTypes.func.isRequired, -} - -FormFieldDate.defaultProps = { - helperText: null, - format: null, -} - -export default FormFieldDate diff --git a/src/Form/FormFieldDate.tsx b/src/Form/FormFieldDate.tsx new file mode 100644 index 0000000..351effb --- /dev/null +++ b/src/Form/FormFieldDate.tsx @@ -0,0 +1,83 @@ +import React from "react"; +import KeyboardArrowLeftIcon from "@material-ui/icons/KeyboardArrowLeft"; +import KeyboardArrowRightIcon from "@material-ui/icons/KeyboardArrowRight"; +import DateRangeIcon from "@material-ui/icons/DateRange"; +import AccessTimeIcon from "@material-ui/icons/AccessTime"; +import MomentUtils from "@date-io/moment"; +import { + MuiPickersUtilsProvider, + DateTimePicker, + DatePicker, + TimePicker, +} from "@material-ui/pickers"; +import { TYPES } from "./constants"; + +type FormFieldDateProps = { + type: any; + id: string; + title: string; + value?: string | number; + format?: string; + helperText?: string; + isRequired?: boolean; + className: string; + handleChange: (...args: any[]) => any; +}; + +const FormFieldDate: React.SFC = ({ + type, + id, + title, + value, + format, + isRequired = false, + handleChange, + helperText, + className, +}) => { + let additionalAttributes = {}; + let Component; + switch (type) { + case TYPES.TIME: + Component = TimePicker; + break; + case TYPES.DATE: + Component = DatePicker; + additionalAttributes = { + leftArrowIcon: , + rightArrowIcon: , + }; + break; + default: + Component = DateTimePicker; + additionalAttributes = { + leftArrowIcon: , + rightArrowIcon: , + timeIcon: , + dateRangeIcon: , + }; + break; + } + return ( + + + + ); +}; + +FormFieldDate.defaultProps = { + helperText: undefined, + format: undefined, +}; +export default FormFieldDate; diff --git a/src/Form/FormFieldHidden.jsx b/src/Form/FormFieldHidden.jsx deleted file mode 100644 index fae6c97..0000000 --- a/src/Form/FormFieldHidden.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -const FormFieldHidden = ({ - id, - value, -}) => ( - -) - -FormFieldHidden.propTypes = { - id: PropTypes.string.isRequired, - value: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.array, - PropTypes.number, - ]), -} - -FormFieldHidden.defaultProps = { - value: '', -} - -export default FormFieldHidden diff --git a/src/Form/FormFieldHidden.tsx b/src/Form/FormFieldHidden.tsx new file mode 100644 index 0000000..5b8ca69 --- /dev/null +++ b/src/Form/FormFieldHidden.tsx @@ -0,0 +1,16 @@ +import React from "react"; + +type FormFieldHiddenProps = { + id: string; + value?: string | any[] | number; +}; + +const FormFieldHidden: React.SFC = ({ id, value }) => ( + +); + +FormFieldHidden.defaultProps = { + value: "", +}; + +export default FormFieldHidden; diff --git a/src/Form/FormFieldInput.jsx b/src/Form/FormFieldInput.jsx deleted file mode 100644 index 9f0dd36..0000000 --- a/src/Form/FormFieldInput.jsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, { Fragment } from 'react' -import PropTypes from 'prop-types' - -import { - TextField, - InputAdornment, - MenuItem, -} from '@material-ui/core' - -const InputEnd = ({ icon }) => ( - - {icon ? ( - - {icon} - - ) : null} - -) - -InputEnd.propTypes = { - icon: PropTypes.element, -} - -InputEnd.defaultProps = { - icon: null, -} - -const FormFieldInput = ({ - id, - type, - title, - value, - isMultiline, - handleChange, - helperText, - defaultValue, - select, - options, - rows, - isRequired, - error, - iconEnd, - isDisabled, - className, - onFocus, - onBlur, - onKeyPress, -}) => ( - ), - }} - className={className} - > - {options - ? options.map(option => ( - - {option} - - )) - : null - } - -) - -FormFieldInput.propTypes = { - id: PropTypes.string.isRequired, - type: PropTypes.string, - title: PropTypes.string, - value: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.array, - PropTypes.number, - ]), - defaultValue: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.array, - PropTypes.number, - ]), - rows: PropTypes.number, - isMultiline: PropTypes.bool, - helperText: PropTypes.string, - isRequired: PropTypes.bool, - error: PropTypes.bool, - className: PropTypes.string, - handleChange: PropTypes.func, - select: PropTypes.bool, - options: PropTypes.arrayOf(PropTypes.string), - isDisabled: PropTypes.bool, - iconEnd: PropTypes.element, - onFocus: PropTypes.func, - onBlur: PropTypes.func, - onKeyPress: PropTypes.func, -} - -FormFieldInput.defaultProps = { - type: 'text', - title: undefined, - value: '', - defaultValue: undefined, - isMultiline: false, - helperText: '', - className: '', - isRequired: false, - error: false, - rows: 1, - select: false, - options: [], - iconEnd: null, - isDisabled: false, - handleChange: () => { }, - onFocus: () => { }, - onBlur: () => { }, - onKeyPress: () => { }, -} - -export default FormFieldInput diff --git a/src/Form/FormFieldInput.test.jsx b/src/Form/FormFieldInput.test.jsx deleted file mode 100644 index 6541114..0000000 --- a/src/Form/FormFieldInput.test.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import FormFieldInput from './FormFieldInput' - -it('renders correctly', () => { - const tree = shallow(( - - )) - - expect(tree).toMatchSnapshot() -}) - - -it('renders disabled', () => { - const tree = shallow(( - - )) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Form/FormFieldInput.test.tsx b/src/Form/FormFieldInput.test.tsx new file mode 100644 index 0000000..5ab3103 --- /dev/null +++ b/src/Form/FormFieldInput.test.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import FormFieldInput from "./FormFieldInput"; + +it("renders correctly", () => { + const tree = shallow(); + + expect(tree).toMatchSnapshot(); +}); + +it("renders disabled", () => { + const tree = shallow(); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Form/FormFieldInput.tsx b/src/Form/FormFieldInput.tsx new file mode 100644 index 0000000..6e4fe01 --- /dev/null +++ b/src/Form/FormFieldInput.tsx @@ -0,0 +1,113 @@ +import React, { Fragment } from "react"; +import { TextField, InputAdornment, MenuItem } from "@material-ui/core"; + +type InputEndProps = { + icon?: JSX.Element; +}; + +const InputEnd: React.SFC = ({ icon }) => ( + + {icon && {icon}} + +); + +InputEnd.defaultProps = { + icon: undefined, +}; + +type FormFieldInputProps = { + id: string; + type?: string; + title?: string; + value?: string | any[] | number; + defaultValue?: string | any[] | number; + rows?: number; + isMultiline?: boolean; + helperText?: string; + isRequired?: boolean; + error?: boolean; + className?: string; + handleChange?: (...args: any[]) => any; + select?: boolean; + options?: string[]; + isDisabled?: boolean; + iconEnd?: JSX.Element; + onFocus?: (...args: any[]) => any; + onBlur?: (...args: any[]) => any; + onKeyPress?: (...args: any[]) => any; +}; + +const FormFieldInput: React.SFC = ({ + id, + type, + title, + value, + isMultiline, + handleChange, + helperText, + defaultValue, + select, + options, + rows, + isRequired, + error, + iconEnd, + isDisabled, + className, + onFocus, + onBlur, + onKeyPress, +}) => ( + , + }} + className={className} + > + {options + ? options.map((option) => ( + + {option} + + )) + : null} + +); + +FormFieldInput.defaultProps = { + type: "text", + value: "", + isMultiline: false, + helperText: "", + className: "", + isRequired: false, + error: false, + rows: 1, + select: false, + options: [], + isDisabled: false, + handleChange: () => {}, + onFocus: () => {}, + onBlur: () => {}, + onKeyPress: () => {}, +}; + +export default FormFieldInput; diff --git a/src/Form/FormFieldList.jsx b/src/Form/FormFieldList.jsx deleted file mode 100644 index 21822d5..0000000 --- a/src/Form/FormFieldList.jsx +++ /dev/null @@ -1,148 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import replace from '../utils/replace' - -import FormFieldListBranch from './FormFieldListBranch' - -const withFormFieldList = Component => class extends React.Component { - static propTypes = { - listItems: PropTypes.arrayOf(PropTypes.object), - completeFrom: PropTypes.arrayOf(PropTypes.object), - handleChange: PropTypes.func.isRequired, - onAddListItem: PropTypes.func.isRequired, - onRemoveListItem: PropTypes.func.isRequired, - } - - static defaultProps = { - listItems: [], - completeFrom: [], - } - - constructor(props) { - super(props) - - this.handleChange = this.handleChange.bind(this) - this.handleClick = this.handleClick.bind(this) - this.handleBlur = this.handleBlur.bind(this) - this.handleDelete = this.handleDelete.bind(this) - this.handleKeyPress = this.handleKeyPress.bind(this) - this.getAvailableOptions = this.getAvailableOptions.bind(this) - } - - state = { - availableOptions: [], - showMenu: false, - } - - componentWillUnmount() { - clearTimeout(this.timer) - } - - getAvailableOptions(value) { - const { completeFrom, listItems } = this.props - const lowerValue = value.toLowerCase() - const flatListItems = listItems.map(item => item.title) - - // Remove selected items - let availableOptions = completeFrom.filter(option => ( - flatListItems.indexOf(option.title) === -1 - )) - - // Overload data - availableOptions = availableOptions.map((option) => { - let { title } = option - - if (!option.title) { - title = option - } - - return { - title, - tooltip: option.tooltip, - text: replace(title, lowerValue, searchResult => `${searchResult}`), - } - }) - - // Filter for real - availableOptions = availableOptions.filter(option => ( - option.title.toLowerCase().indexOf(lowerValue) > -1 - )) - - return availableOptions - } - - timer = undefined - - handleChange(id) { - return (event) => { - const parentFunction = this.props.handleChange(id) - const { value } = event.target - - this.setState({ - value, - availableOptions: this.getAvailableOptions(value), - showMenu: value.length > 0, - }) - - return parentFunction(event) - } - } - - handleClick(option) { - return () => { - this.props.onAddListItem(option) - - this.setState({ - value: '', - }) - } - } - - handleBlur() { - this.timer = setTimeout(() => { - this.setState({ - showMenu: false, - }) - }, 100) - } - - handleDelete(option) { - return () => { - this.props.onRemoveListItem(option) - } - } - - handleKeyPress(event) { - const { value } = this.state - const { completeFrom } = this.props - - if (event.which === 13 && completeFrom.length === 0) { - this.props.onAddListItem({ - title: value, - }) - - this.setState({ - value: '', - }) - } - } - - render() { - return ( - - ) - } -} - -const FormFieldList = withFormFieldList(FormFieldListBranch) - -export default FormFieldList diff --git a/src/Form/FormFieldList.tsx b/src/Form/FormFieldList.tsx new file mode 100644 index 0000000..b275d54 --- /dev/null +++ b/src/Form/FormFieldList.tsx @@ -0,0 +1,152 @@ +import React from "react"; +import replace from "../utils/replace"; +import FormFieldListBranch from "./FormFieldListBranch"; + +type WithFormFieldListProps = { + listItems?: Record[]; + completeFrom?: Record[]; + handleChange: (...args: any[]) => any; + onAddListItem: (...args: any[]) => any; + onRemoveListItem: (...args: any[]) => any; + className: string; +}; + +type WithFormFieldListState = { + value?: string; + availableOptions: any[]; + showMenu: boolean; +}; + +const withFormFieldList = (Component: any) => + class WithFormFieldList extends React.Component< + WithFormFieldListProps, + WithFormFieldListState + > { + constructor(props: WithFormFieldListProps) { + super(props); + this.handleChange = this.handleChange.bind(this); + this.handleClick = this.handleClick.bind(this); + this.handleBlur = this.handleBlur.bind(this); + this.handleDelete = this.handleDelete.bind(this); + this.handleKeyPress = this.handleKeyPress.bind(this); + this.getAvailableOptions = this.getAvailableOptions.bind(this); + } + + state = { + availableOptions: [], + showMenu: false, + value: undefined, + }; + + componentWillUnmount() { + clearTimeout(this.timer); + } + + getAvailableOptions(value: any) { + const { completeFrom, listItems } = this.props; + const lowerValue = value.toLowerCase(); + const flatListItems = + listItems && listItems.map((item: any) => item.title); + // Remove selected items + let availableOptions = + completeFrom && + completeFrom.filter( + (option) => + flatListItems && flatListItems.indexOf(option.title) === -1, + ); + // Overload data + availableOptions = + availableOptions && + availableOptions.map((option) => { + let { title } = option; + if (!option.title) { + title = option; + } + return { + title, + tooltip: option.tooltip, + text: replace( + title, + lowerValue, + (searchResult: string) => `${searchResult}`, + ), + }; + }); + + // Filter for real + availableOptions = + availableOptions && + availableOptions.filter( + (option) => option.title.toLowerCase().indexOf(lowerValue) > -1, + ); + + return availableOptions as Record[]; + } + + timer: any = undefined; + + handleChange(id: string) { + return (event: any) => { + const parentFunction = this.props.handleChange(id); + const { value } = event.target; + this.setState({ + value, + availableOptions: this.getAvailableOptions(value), + showMenu: value.length > 0, + }); + return parentFunction(event); + }; + } + + handleClick(option: any) { + return () => { + this.props.onAddListItem(option); + this.setState({ + value: "", + }); + }; + } + + handleBlur() { + this.timer = setTimeout(() => { + this.setState({ + showMenu: false, + }); + }, 100); + } + + handleDelete(option: any) { + return () => { + this.props.onRemoveListItem(option); + }; + } + handleKeyPress(event: any) { + const { value } = this.state; + const { completeFrom } = this.props; + if (event.which === 13 && completeFrom && completeFrom.length === 0) { + this.props.onAddListItem({ + title: value, + }); + this.setState({ + value: "", + }); + } + } + + render() { + return ( + + ); + } + }; + +const FormFieldList = withFormFieldList(FormFieldListBranch); +export default FormFieldList; diff --git a/src/Form/FormFieldListBranch.jsx b/src/Form/FormFieldListBranch.jsx deleted file mode 100644 index 4a1c554..0000000 --- a/src/Form/FormFieldListBranch.jsx +++ /dev/null @@ -1,153 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import classnames from 'classnames' - -import { - Chip, - List, - ListItem, - ListItemText, - Paper, - Tooltip, - withStyles, -} from '@material-ui/core' - -import FormFieldInput from './FormFieldInput' - -const styles = theme => ({ - root: { - position: 'relative', - zIndex: 1, - display: 'inline-flex', - flexDirection: 'column', - }, - list: { - position: 'absolute', - top: theme.spacing(9), - width: '100%', - height: 0, - opacity: 0, - overflow: 'hidden', - transition: '0.25s', - }, - listActive: { - height: 'auto', - opacity: 1, - }, - selectedItem: { - display: 'inline-flex', - margin: '0.125rem', - }, -}) - -const FormFieldListBranch = ({ - availableOptions, - listItems, - showMenu, - renderElement, - className, - onClick, - onChange, - onBlur, - onDelete, - classes, - ...rest -}) => { - const renderListItem = (option, index) => { - if (renderElement) { - return renderElement(option, index) - } - - if (!option) { - return null - } - - const chipProp = { - label: option.title, - onDelete: onDelete(option), - } - - if (option.tooltip) { - return ( - - - - ) - } - - return ( - - ) - } - - return ( -
- - - 0, - })} - > - - {availableOptions.slice(0, 10).map((option) => { - const key = Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1) - - const title = ( - - ) - - return ( - - - - ) - })} - - - -
- {listItems ? listItems.map(renderListItem) : null} -
-
- ) -} - -FormFieldListBranch.propTypes = { - availableOptions: PropTypes.arrayOf(PropTypes.object).isRequired, - listItems: PropTypes.arrayOf(PropTypes.oneOfType([ - PropTypes.string, - PropTypes.object, - ])).isRequired, - showMenu: PropTypes.bool.isRequired, - renderElement: PropTypes.func, - className: PropTypes.string.isRequired, - onClick: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - onBlur: PropTypes.func.isRequired, - onDelete: PropTypes.func.isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -FormFieldListBranch.defaultProps = { - renderElement: null, -} - -export default withStyles(styles)(FormFieldListBranch) diff --git a/src/Form/FormFieldListBranch.tsx b/src/Form/FormFieldListBranch.tsx new file mode 100644 index 0000000..7b4ce0d --- /dev/null +++ b/src/Form/FormFieldListBranch.tsx @@ -0,0 +1,139 @@ +import React from "react"; +import classnames from "classnames"; +import { + Chip, + List, + ListItem, + ListItemText, + Paper, + Tooltip, + withStyles, +} from "@material-ui/core"; +import FormFieldInput from "./FormFieldInput"; + +const styles = (theme: any) => ({ + root: { + position: "relative", + zIndex: 1, + display: "inline-flex", + flexDirection: "column", + }, + list: { + position: "absolute", + top: theme.spacing(9), + width: "100%", + height: 0, + opacity: 0, + overflow: "hidden", + transition: "0.25s", + }, + listActive: { + height: "auto", + opacity: 1, + }, + selectedItem: { + display: "inline-flex", + margin: "0.125rem", + }, +}); + +type FormFieldListBranchProps = { + availableOptions: object[]; + listItems: (string | object)[]; + showMenu: boolean; + renderElement?: (...args: any[]) => any; + className: string; + onClick: (...args: any[]) => any; + onChange: (...args: any[]) => any; + onBlur: (...args: any[]) => any; + onDelete: (...args: any[]) => any; + classes: { + [key: string]: string; + }; + id: string; +}; + +const FormFieldListBranch: React.SFC = ({ + availableOptions, + listItems, + showMenu, + renderElement, + className, + onClick, + onChange, + onBlur, + onDelete, + classes, + ...rest +}) => { + const renderListItem = (option: any, index: number) => { + if (renderElement) { + return renderElement(option, index); + } + + if (!option) { + return null; + } + + const chipProp = { + label: option.title, + onDelete: onDelete(option), + }; + + if (option.tooltip) { + return ( + + + + ); + } + + return ( + + ); + }; + + return ( +
+ + + 0, + })} + > + + {availableOptions.slice(0, 10).map((option: any) => { + const key = Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + const title = ( + + ); + return ( + + + + ); + })} + + + +
{listItems ? listItems.map(renderListItem) : null}
+
+ ); +}; + +export default withStyles(styles as any)(FormFieldListBranch); diff --git a/src/Form/FormFieldSwitch.jsx b/src/Form/FormFieldSwitch.jsx deleted file mode 100644 index 2434c61..0000000 --- a/src/Form/FormFieldSwitch.jsx +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import { - FormControlLabel, - FormHelperText, - Switch, -} from '@material-ui/core' - -class FormFieldSwitch extends React.Component { - static propTypes = { - id: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - handleChange: PropTypes.func.isRequired, - helperText: PropTypes.string, - className: PropTypes.string, - isDisabled: PropTypes.bool, - } - - static defaultProps = { - helperText: null, - className: '', - isDisabled: false, - } - - state = { - value: false, - } - - handleChange(id) { - const { handleChange } = this.props - const { value } = this.state - const func = handleChange(id) - - return () => { - const newValue = !value - - this.setState({ - value: newValue, - }) - - return func({ - target: { - value: newValue, - }, - }) - } - } - - render() { - const { - id, - title, - helperText, - isDisabled, - className, - } = this.props - const { value } = this.state - - return ( -
- - )} - label={title} - /> - - {helperText ? ( - - {helperText} - - ) : null} -
- ) - } -} - -export default FormFieldSwitch diff --git a/src/Form/FormFieldSwitch.test.jsx b/src/Form/FormFieldSwitch.test.jsx deleted file mode 100644 index 1760c67..0000000 --- a/src/Form/FormFieldSwitch.test.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import FormFieldSwitch from './FormFieldSwitch' - -it('renders correctly', () => { - const tree = shallow(( - () => {}} - /> - )) - - expect(tree).toMatchSnapshot() -}) - -it('renders disabled', () => { - const tree = shallow(( - () => { }} - isDisabled - /> - )) - - expect(tree).toMatchSnapshot() -}) - -it('renders with helper text', () => { - const tree = shallow(( - () => { }} - helperText="test" - /> - )) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Form/FormFieldSwitch.test.tsx b/src/Form/FormFieldSwitch.test.tsx new file mode 100644 index 0000000..0d272db --- /dev/null +++ b/src/Form/FormFieldSwitch.test.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import FormFieldSwitch from "./FormFieldSwitch"; + +it("renders correctly", () => { + const tree = shallow( + () => {}} />, + ); + + expect(tree).toMatchSnapshot(); +}); + +it("renders disabled", () => { + const tree = shallow( + () => {}} + isDisabled + />, + ); + + expect(tree).toMatchSnapshot(); +}); + +it("renders with helper text", () => { + const tree = shallow( + () => {}} + helperText="test" + />, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Form/FormFieldSwitch.tsx b/src/Form/FormFieldSwitch.tsx new file mode 100644 index 0000000..dce7c91 --- /dev/null +++ b/src/Form/FormFieldSwitch.tsx @@ -0,0 +1,68 @@ +import React from "react"; +import { FormControlLabel, FormHelperText, Switch } from "@material-ui/core"; + +type FormFieldSwitchProps = { + id: string; + title: string; + handleChange: (...args: any[]) => any; + helperText?: string; + className?: string; + isDisabled?: boolean; +}; + +type FormFieldSwitchState = { + value: boolean; +}; + +class FormFieldSwitch extends React.Component< + FormFieldSwitchProps, + FormFieldSwitchState +> { + state = { + value: false, + }; + + handleChange(id: string) { + const { handleChange } = this.props; + const { value } = this.state; + const func = handleChange(id); + return () => { + const newValue = !value; + this.setState({ + value: newValue, + }); + return func({ + target: { + value: newValue, + }, + }); + }; + } + + render() { + const { id, title, helperText, isDisabled, className } = this.props; + const { value } = this.state; + return ( +
+ + } + label={title} + /> + + {helperText ? ( + {helperText} + ) : null} +
+ ); + } +} + +export default FormFieldSwitch; diff --git a/src/Form/FormGroupWrapper.jsx b/src/Form/FormGroupWrapper.tsx similarity index 56% rename from src/Form/FormGroupWrapper.jsx rename to src/Form/FormGroupWrapper.tsx index 723bb73..5565625 100644 --- a/src/Form/FormGroupWrapper.jsx +++ b/src/Form/FormGroupWrapper.tsx @@ -1,10 +1,8 @@ -import React from 'react' -import PropTypes from 'prop-types' -import classNames from 'classnames' +import React from "react"; +import classNames from "classnames"; +import { Paper, withStyles } from "@material-ui/core"; -import { Paper, withStyles } from '@material-ui/core' - -const styles = theme => ({ +const styles = (theme: any) => ({ group: { marginTop: theme.spacing(), marginBottom: theme.spacing(), @@ -15,11 +13,19 @@ const styles = theme => ({ paddingTop: theme.spacing(3), }, hidden: { - display: 'none', + display: "none", }, -}) +}); + +type FormGroupWrapperProps = { + isPaper?: boolean; + isVisible?: boolean; + classes: { + [key: string]: string; + }; +}; -const FormGroupWrapper = ({ +const FormGroupWrapper: React.SFC = ({ isPaper, isVisible, classes, @@ -30,38 +36,30 @@ const FormGroupWrapper = ({ return ( {children} - ) + ); } return (
{children}
- ) -} - -FormGroupWrapper.propTypes = { - isPaper: PropTypes.bool, - isVisible: PropTypes.bool, - children: PropTypes.node, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} + ); +}; FormGroupWrapper.defaultProps = { isPaper: true, isVisible: true, - children: null, -} +}; -export default withStyles(styles)(FormGroupWrapper) +export default withStyles(styles)(FormGroupWrapper); diff --git a/src/Form/FormSubmitButton.jsx b/src/Form/FormSubmitButton.tsx similarity index 55% rename from src/Form/FormSubmitButton.jsx rename to src/Form/FormSubmitButton.tsx index 6d7e80e..8ac2662 100644 --- a/src/Form/FormSubmitButton.jsx +++ b/src/Form/FormSubmitButton.tsx @@ -1,17 +1,15 @@ -import React from 'react' -import PropTypes from 'prop-types' -import classNames from 'classnames' +import React from "react"; +import classNames from "classnames"; +import { Button, CircularProgress, withStyles } from "@material-ui/core"; +import { green } from "@material-ui/core/colors"; -import { Button, CircularProgress, withStyles } from '@material-ui/core' -import { green } from '@material-ui/core/colors' - -const styles = theme => ({ +const styles = (theme: any) => ({ root: { - position: 'relative', - display: 'inline-block', + position: "relative", + display: "inline-block", }, fixed: { - position: 'fixed', + position: "fixed", right: theme.spacing(5), bottom: theme.spacing(3), marginBottom: theme.spacing(), @@ -22,15 +20,25 @@ const styles = theme => ({ }, progress: { color: green[500], - position: 'absolute', - top: '50%', - left: '50%', + position: "absolute", + top: "50%", + left: "50%", marginTop: -12, marginLeft: -12, }, -}) +}); + +type FormSubmitButtonProps = { + onSubmit: (...args: any[]) => any; + disabled?: boolean; + loading?: boolean; + fixed?: boolean; + classes: { + [key: string]: string; + }; +}; -const FormSubmitButton = ({ +const FormSubmitButton: React.SFC = ({ onSubmit, disabled, loading, @@ -40,8 +48,7 @@ const FormSubmitButton = ({ }) => { const wrapperClasses = classNames(classes.root, { [classes.fixed]: fixed, - }) - + }); return (
- ) -} - -FormSubmitButton.propTypes = { - onSubmit: PropTypes.func.isRequired, - disabled: PropTypes.bool, - loading: PropTypes.bool, - fixed: PropTypes.bool, - children: PropTypes.node, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - + ); +}; FormSubmitButton.defaultProps = { disabled: false, loading: false, fixed: false, - children: null, -} +}; -export default withStyles(styles)(FormSubmitButton) +export default withStyles(styles as any)(FormSubmitButton); diff --git a/src/Form/constants.js b/src/Form/constants.js deleted file mode 100644 index 6c55c7b..0000000 --- a/src/Form/constants.js +++ /dev/null @@ -1,20 +0,0 @@ -export const TYPES = { - SELECT: 'select', - LIST: 'list', - MULTILINE: 'multiline', - TEXT: 'text', - DATE: 'date', - TIME: 'time', - DATETIME: 'datetime', - NUMBER: 'number', - SWITCH: 'switch', - EMAIL: 'email', - PASSWORD: 'password', - URL: 'url', - CONTENT: 'content', - DIVIDER: 'divider', - EMPTY: 'empty', - HIDDEN: 'hidden', -} - -export default {} diff --git a/src/Form/constants.ts b/src/Form/constants.ts new file mode 100644 index 0000000..b4de2ce --- /dev/null +++ b/src/Form/constants.ts @@ -0,0 +1,20 @@ +export const TYPES = { + SELECT: "select", + LIST: "list", + MULTILINE: "multiline", + TEXT: "text", + DATE: "date", + TIME: "time", + DATETIME: "datetime", + NUMBER: "number", + SWITCH: "switch", + EMAIL: "email", + PASSWORD: "password", + URL: "url", + CONTENT: "content", + DIVIDER: "divider", + EMPTY: "empty", + HIDDEN: "hidden", +}; + +export default {}; diff --git a/src/Form/index.js b/src/Form/index.js deleted file mode 100644 index ce98af0..0000000 --- a/src/Form/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Form from './Form' - -export default Form diff --git a/src/Form/index.ts b/src/Form/index.ts new file mode 100644 index 0000000..5e2f96a --- /dev/null +++ b/src/Form/index.ts @@ -0,0 +1,3 @@ +import Form from "./Form"; + +export default Form; diff --git a/src/Form/isValid.js b/src/Form/isValid.js deleted file mode 100644 index 993354e..0000000 --- a/src/Form/isValid.js +++ /dev/null @@ -1,61 +0,0 @@ -import * as Validators from './validators' -import { TYPES } from './constants' - -export const getValidator = (validator) => { - if (typeof validator === 'function') { - return { - validator, - } - } - - if (typeof validator === 'string') { - return { - validator: Validators[validator], - } - } - - return validator -} - -/** - * Returns an object - */ -export default (type, isRequired, validators = [], value) => { - const allValidators = [...validators] - const messages = [] - - if (isRequired) { - allValidators.push('required') - } - - if (type === TYPES.EMAIL) { - allValidators.push('email') - } - - if (type === TYPES.URL) { - allValidators.push('url') - } - - if (allValidators.length === 0) { - return { - valid: true, - } - } - - const validatorFunctions = allValidators.map(getValidator) - - const validState = validatorFunctions.map((validator) => { - if (validator.message) { - messages.push(validator.message) - } - - return validator.validator(value) - }) - - const isValid = validState.indexOf(false) === -1 - - return { - isValid, - messages: !isValid ? messages : [], - } -} diff --git a/src/Form/isValid.ts b/src/Form/isValid.ts new file mode 100644 index 0000000..cdff56c --- /dev/null +++ b/src/Form/isValid.ts @@ -0,0 +1,74 @@ +import * as Validators from "./validators"; +import { TYPES } from "./constants"; + +type Validator = + | string + | ((...args: any[]) => any) + | { + validator?: string | ((...args: any[]) => any) | undefined; + message?: string | undefined; + }; + +export const getValidator = (validator: Validator): any => { + if (typeof validator === "function") { + return { + validator, + }; + } + + if (typeof validator === "string") { + return { + validator: Validators[validator], + }; + } + + return validator; +}; + +/** + * Returns an object + */ +export default ( + type: any, + isRequired?: boolean, + validators: Validator[] = [], + value?: any, +) => { + const allValidators = [...validators]; + const messages: Validator[] = []; + + if (isRequired) { + allValidators.push("required"); + } + + if (type === TYPES.EMAIL) { + allValidators.push("email"); + } + + if (type === TYPES.URL) { + allValidators.push("url"); + } + + if (allValidators.length === 0) { + return { + valid: true, + }; + } + + const validatorFunctions = allValidators.map(getValidator); + + const validState = validatorFunctions.map((validator) => { + if (validator.message) { + messages.push(validator.message); + } + + return validator.validator(value); + }); + + const isValid = validState.indexOf(false) === -1; + + return { + isValid, + messages: !isValid ? messages : [], + }; +}; diff --git a/src/Form/validators.js b/src/Form/validators.js deleted file mode 100644 index 916675d..0000000 --- a/src/Form/validators.js +++ /dev/null @@ -1,35 +0,0 @@ -import Isemail from 'isemail' -import isUrl from 'is-url' - -/** - * Check if a field has any value at all - */ -export const required = (value) => { - let checkValue = value - - if (typeof value === 'string') { - checkValue = value.trim() - } - - return !!(checkValue && checkValue.length > 0) -} - -/** - * Check if a value is readable for machines (e.g. as identifier) - */ -export const machinereadable = value => (value && value === encodeURIComponent(value)) || !value - -/** - * Check if value is a date - */ -export const date = value => (value && !Number.isNaN(Date.parse(value))) || !value - -/** - * Check if value is email - */ -export const email = value => (value && Isemail.validate(value)) || !value - -/** - * Check if value is url - */ -export const url = value => (value && isUrl(value)) || !value diff --git a/src/Form/validators.ts b/src/Form/validators.ts new file mode 100644 index 0000000..534d1d7 --- /dev/null +++ b/src/Form/validators.ts @@ -0,0 +1,38 @@ +import Isemail from "isemail"; +import isUrl from "is-url"; + +/** + * Check if a field has any value at all + */ +export const required = (value: any) => { + let checkValue = value; + + if (typeof value === "string") { + checkValue = value.trim(); + } + + return !!(checkValue && checkValue.length > 0); +}; + +/** + * Check if a value is readable for machines (e.g. as identifier) + */ +export const machinereadable = (value: any) => + (value && value === encodeURIComponent(value)) || !value; + +/** + * Check if value is a date + */ +export const date = (value: any) => + (value && !Number.isNaN(Date.parse(value))) || !value; + +/** + * Check if value is email + */ +export const email = (value: any) => + (value && Isemail.validate(value)) || !value; + +/** + * Check if value is url + */ +export const url = (value: any) => (value && isUrl(value)) || !value; diff --git a/src/Header/Header.test.jsx b/src/Header/Header.test.jsx deleted file mode 100644 index c68f46a..0000000 --- a/src/Header/Header.test.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import Header from '.' - -it('renders correctly', () => { - const tree = shallow(( -
{}} - onClick={() => { }} - /> - )) - - expect(tree).toMatchSnapshot() -}) - -it('renders correctly with closed', () => { - const tree = shallow(( -
{ }} - onClick={() => { }} - /> - )) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Header/Header.test.tsx b/src/Header/Header.test.tsx new file mode 100644 index 0000000..750f193 --- /dev/null +++ b/src/Header/Header.test.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import Header from "."; + +it("renders correctly", () => { + const tree = shallow( +
{}} + onClick={() => {}} + />, + ); + + expect(tree).toMatchSnapshot(); +}); + +it("renders correctly with closed", () => { + const tree = shallow( +
{}} + onClick={() => {}} + />, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Header/Header.jsx b/src/Header/Header.tsx similarity index 59% rename from src/Header/Header.jsx rename to src/Header/Header.tsx index 2fe5e7a..8606b4b 100644 --- a/src/Header/Header.jsx +++ b/src/Header/Header.tsx @@ -1,34 +1,33 @@ -import React from 'react' -import PropTypes from 'prop-types' -import classNames from 'classnames' +import React from "react"; +import classNames from "classnames"; import { AppBar, IconButton, Toolbar, Typography, withStyles, -} from '@material-ui/core' -import MenuIcon from '@material-ui/icons/Menu' +} from "@material-ui/core"; +import MenuIcon from "@material-ui/icons/Menu"; -const drawerWidth = 280 +const drawerWidth = 280; -const styles = theme => ({ +const styles = (theme: any) => ({ root: { - width: '100%', + width: "100%", }, appBar: { - transition: theme.transitions.create(['margin', 'width'], { + transition: theme.transitions.create(["margin", "width"], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), - transform: 'translate(0, 0)', + transform: "translate(0, 0)", }, appBarWithCookieInfo: { transform: `translate(0, ${theme.spacing(6)}px)`, }, appBarShift: { width: `calc(100% - ${drawerWidth}px)`, - transition: theme.transitions.create(['margin', 'width'], { + transition: theme.transitions.create(["margin", "width"], { easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen, }), @@ -39,25 +38,37 @@ const styles = theme => ({ marginRight: 20, }, hide: { - display: 'none', + display: "none", }, flex: { flex: 1, }, title: { - cursor: 'pointer', - '&:hover': { - textDecoration: 'underline', + cursor: "pointer", + "&:hover": { + textDecoration: "underline", }, }, -}) +}); -const Header = ({ +type HeaderProps = { + isOpen?: boolean; + title: string; + isFixed?: boolean; + isCookieInfoOpen?: boolean; + onDrawerOpen: (...args: any[]) => any; + onClick: (...args: any[]) => any; + classes: { + [key: string]: string; + }; +}; + +const Header: React.SFC = ({ title, isOpen, - isFixed, + isFixed = false, onDrawerOpen, - isCookieInfoOpen, + isCookieInfoOpen = false, onClick, children, classes, @@ -68,7 +79,7 @@ const Header = ({ [classes.appBarShift]: isOpen, [classes.appBarWithCookieInfo]: isCookieInfoOpen, })} - position={isFixed ? 'fixed' : 'static'} + position={isFixed ? "fixed" : "static"} >
-) - -Header.propTypes = { - isOpen: PropTypes.bool, - title: PropTypes.string.isRequired, - isFixed: PropTypes.bool.isRequired, - isCookieInfoOpen: PropTypes.bool.isRequired, - onDrawerOpen: PropTypes.func.isRequired, - onClick: PropTypes.func.isRequired, - children: PropTypes.node, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} +); Header.defaultProps = { isOpen: false, - children: null, -} +}; -export default withStyles(styles)(Header) +export default withStyles(styles)(Header); diff --git a/src/Header/index.js b/src/Header/index.js deleted file mode 100644 index 7cd29d7..0000000 --- a/src/Header/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Header from './Header' - -export default Header diff --git a/src/Header/index.ts b/src/Header/index.ts new file mode 100644 index 0000000..0d87fe1 --- /dev/null +++ b/src/Header/index.ts @@ -0,0 +1,3 @@ +import Header from "./Header"; + +export default Header; diff --git a/src/Listing/Listing.jsx b/src/Listing/Listing.jsx deleted file mode 100644 index ba40426..0000000 --- a/src/Listing/Listing.jsx +++ /dev/null @@ -1,401 +0,0 @@ -import React, { Fragment } from 'react' -import PropTypes from 'prop-types' -import keycode from 'keycode' -import Uuid from 'init-uuid' -import Store from 'vanilla-store' - -import easeInOutQuad from '../utils/easeInOutQuad' -import ListingBranch from './ListingBranch' - -const getStringContent = (content) => { - let matchContent = content - - if (content.constructor === Array) { - matchContent = content.join(' ') - } else if (typeof content === 'object') { - matchContent = Object.values(content).join(' ') - } - - return matchContent.toLowerCase() -} - -const tryToMatch = (value, content) => { - let initialContent = content - - if (!content) { - return false - } - - if (content.highlight) { - initialContent = content.value - } - - const contentToSearch = getStringContent(initialContent) - - if (contentToSearch.indexOf(value) > -1) { - return true - } - - return false -} - -const getSearchableHeaders = headers => headers - .filter(header => header.isSearchable) - .map(header => header.id) - - -const filterElement = (element, value, searchables) => { - const searchValue = value.toLowerCase() - let newElement - - Object.keys(element).forEach((key) => { - if (searchables.indexOf(key) > -1) { - const matched = tryToMatch(searchValue, element[key]) - - if (matched) { - newElement = element - } - } - }) - - if (newElement) { - Object.keys(newElement).forEach((key) => { - if (newElement[key]) { - newElement[key] = { - highlight: searchValue, - value: newElement[key].highlight ? newElement[key].value : newElement[key], - } - } - }) - } - - return newElement -} -const withListing = Component => class Listing extends React.Component { - static propTypes = { - orderBy: PropTypes.string.isRequired, - order: PropTypes.oneOf([ - 'asc', - 'desc', - ]), - hasLoader: PropTypes.bool, - data: PropTypes.arrayOf(PropTypes.object).isRequired, - headers: PropTypes.arrayOf(PropTypes.object).isRequired, - toolbarContent: PropTypes.node, - onUpdateSelection: PropTypes.func, - id: PropTypes.string, - } - - static defaultProps = { - id: new Uuid().get(), - order: 'asc', - hasLoader: false, - toolbarContent: (), - onUpdateSelection: () => { }, - } - - constructor(props, context) { - super(props, context) - - this.state = { - order: 'asc', - orderBy: 'id', - selected: [], - data: [], - page: 0, - rowsPerPage: 10, - searchable: [], - origData: null, - id: null, - } - - this.node = React.createRef() - - this.handleRequestSort = this.handleRequestSort.bind(this) - this.handleSelectAllClick = this.handleSelectAllClick.bind(this) - this.handleKeyDown = this.handleKeyDown.bind(this) - this.handleCheckClick = this.handleCheckClick.bind(this) - this.handleChangeRowsPerPage = this.handleChangeRowsPerPage.bind(this) - this.handleChangePage = this.handleChangePage.bind(this) - this.isSelected = this.isSelected.bind(this) - this.handleFilter = this.handleFilter.bind(this) - } - - componentDidMount() { - const { - id, - data, - headers, - order, - orderBy, - } = this.props - - const storedData = Store.get('Listing', id) - const newState = { - data: this.sortData(data, orderBy, order), - searchable: getSearchableHeaders(headers), - } - - if (storedData) { - if (storedData.page) { - newState.page = storedData.page - } - - if (storedData.rowsPerPage) { - newState.rowsPerPage = storedData.rowsPerPage - } - } - - this.setState({ - ...newState, - order, - orderBy, - }) - } - - componentWillReceiveProps({ - data, - headers, - orderBy, - order, - }) { - this.setState({ - data: this.sortData(data, orderBy, order), - orderBy, - order, - searchable: getSearchableHeaders(headers), - }) - } - - sortData(data, orderBy, order) { - const { headers } = this.props - const orderByHeader = headers.filter(header => header.id === orderBy)[0] - let { transformData } = orderByHeader - - if (!data) { - return data - } - - if (typeof transformData !== 'function') { - transformData = values => values - } - - const transformedData = data.map((element) => { - const newElement = element - newElement[orderBy] = transformData(element[orderBy]) - - return newElement - }) - const sortedData = transformedData.sort((a, b) => (a[orderBy] < b[orderBy] ? -1 : 1)) - - if (order === 'asc') { - return sortedData - } - - return sortedData.reverse() - } - - handleRequestSort(event, property) { - const { - orderBy: orderByState, - order: orderState, - data: dataState, - } = this.state - const orderBy = property - let order = 'desc' - - if (orderByState === property && orderState === 'desc') { - order = 'asc' - } - - let data - - if (order === 'desc') { - data = dataState.sort((a, b) => (b[orderBy] < a[orderBy] ? -1 : 1)) - } else { - data = dataState.sort((a, b) => (a[orderBy] < b[orderBy] ? -1 : 1)) - } - - this.setState({ - data, - order, - orderBy, - }) - } - - handleSelectAllClick(event, checked) { - const { data } = this.state - - if (checked) { - this.setState({ - selected: data.map(n => n.id), - }) - - return - } - - this.setState({ - selected: [], - }) - } - - handleKeyDown(event, id) { - if (keycode(event) === 'space') { - this.handleCheckClick(event, id) - } - } - - handleCheckClick(id) { - const { onUpdateSelection } = this.props - const { selected } = this.state - const selectedIndex = selected.indexOf(id) - let newSelected = [] - - if (selectedIndex === -1) { - newSelected = newSelected.concat(selected, id) - } else if (selectedIndex === 0) { - newSelected = newSelected.concat(selected.slice(1)) - } else if (selectedIndex === selected.length - 1) { - newSelected = newSelected.concat(selected.slice(0, -1)) - } else if (selectedIndex > 0) { - newSelected = newSelected.concat( - selected.slice(0, selectedIndex), - selected.slice(selectedIndex + 1), - ) - } - - onUpdateSelection(newSelected) - - this.setState({ - selected: newSelected, - }) - } - - scrollToElement() { - if (!this.node.current) { - return - } - - const { offsetTop } = this.node.current - const start = window.scrollY - const change = offsetTop - start - const increment = 20 - const duration = 250 - let currentTime = 0 - - const animateScroll = () => { - currentTime += increment - const val = easeInOutQuad(currentTime, start, change, duration) - window.scrollTo(0, val) - - if (currentTime < duration) { - setTimeout(animateScroll, increment) - } - } - - animateScroll() - } - - handleChangePage(event, page) { - const { id } = this.props - const { rowsPerPage } = this.state - - Store.create('Listing', { - id, - rowsPerPage, - page, - }) - - this.scrollToElement() - - this.setState({ - page, - }) - } - - handleChangeRowsPerPage(event) { - const rowsPerPage = event.target.value - const { id, page } = this.state - - Store.create('Listing', { - id, - page, - rowsPerPage, - }) - - this.setState({ - rowsPerPage, - }) - } - - handleFilter(value) { - const { searchable, origData, data } = this.state - let searchableData - - if (origData && origData.constructor === Array) { - searchableData = [...origData] - } else { - searchableData = [...data] - } - - if (!value) { - searchableData = searchableData.map((item) => { - const newItem = item - - Object.keys(item).forEach((key) => { - if (item[key]) { - if (item[key].value) { - newItem[key] = item[key].value - } else { - newItem[key] = item[key] - } - } - }) - - return newItem - }) - - this.setState({ - data: searchableData, - }) - - return - } - - const newData = searchableData - .map(element => filterElement(element, value, searchable)) - .filter(item => item !== undefined) - - this.setState({ - data: newData, - origData: searchableData, - }) - } - - isSelected(id) { - const { selected } = this.state - - return selected.indexOf(id) !== -1 - } - - render() { - return ( -
- -
- ) - } -} - -export default withListing(ListingBranch) diff --git a/src/Listing/Listing.test.jsx b/src/Listing/Listing.test.jsx deleted file mode 100644 index 346e98e..0000000 --- a/src/Listing/Listing.test.jsx +++ /dev/null @@ -1,243 +0,0 @@ -import React from 'react' -import Enzyme, { shallow } from 'enzyme' -import Adapter from 'enzyme-adapter-react-16' -import { - Checkbox, - TablePagination, - IconButton, -} from '@material-ui/core' - -import Listing from '.' - -import headers from '../tests/data/listing_headers' -import data from '../tests/data/listing_data' -import ListingHeader from './ListingHeader' - -Enzyme.configure({ adapter: new Adapter() }) - -describe('Listing', () => { - it('renders correctly', () => { - const tree = shallow(( - { }} - onUpdateSelection={() => { }} - /> - )) - - expect(tree).toMatchSnapshot() - }) - - it('renders correctly with Loader', () => { - const tree = shallow(( - { }} - hasLoader - onUpdateSelection={() => { }} - /> - )) - - expect(tree).toMatchSnapshot() - }) - - it('changes props', () => { - const origSortData = Listing.prototype.sortData - Listing.prototype.sortData = jest.fn() - - const listing = shallow(( - { }} - onUpdateSelection={() => { }} - /> - )) - - listing.setProps({ - orderBy: 'name', - data: null, - }) - - expect(Listing.prototype.sortData).toHaveBeenCalled() - Listing.prototype.sortData = origSortData - }) - - it('does a new sorting', () => { - const listing = shallow(( - { }} - onUpdateSelection={() => { }} - /> - )) - - expect(listing.state().data[0].username).toBe('Antonette') - - listing.instance().handleRequestSort({}, 'name') - expect(listing.state().data[0].username).toBe('Karianne') - - listing.instance().handleRequestSort({}, 'name') - expect(listing.state().data[0].username).toBe('Kamren') - }) - - it('handles click on a checkbox', () => { - const onUpdateSelection = jest.fn() - const listing = shallow(( - { }} - onUpdateSelection={onUpdateSelection} - /> - )) - - listing.instance().handleCheckClick('foo') - expect(listing.state().selected).toEqual(['foo']) - expect(onUpdateSelection).toHaveBeenCalled() - - listing.instance().handleCheckClick('bar') - expect(listing.state().selected).toEqual(['foo', 'bar']) - - listing.instance().handleCheckClick('baz') - expect(listing.state().selected).toEqual(['foo', 'bar', 'baz']) - - listing.instance().handleCheckClick('bar') - expect(listing.state().selected).toEqual(['foo', 'baz']) - - listing.instance().handleCheckClick('baz') - expect(listing.state().selected).toEqual(['foo']) - - listing.instance().handleCheckClick('foo') - expect(listing.state().selected).toEqual([]) - }) - - it('allows to filter', () => { - const listing = shallow(( - { }} - /> - )) - - const { length } = listing.state().data - - listing.instance().handleFilter('') - - expect(listing.state().data.length).toEqual(length) - - listing.instance().handleFilter('f0ooasdnajsbhdhuq2871dasd') - - expect(listing.state().data.length).toEqual(0) - }) - - it('allows to filter even if there are empty filters', () => { - data[0].name = null - - const listing = shallow(( - { }} - /> - )) - - const { length } = listing.state().data - - listing.instance().handleFilter('') - - expect(listing.state().data.length).toEqual(length) - - listing.instance().handleFilter('f0ooasdnajsbhdhuq2871dasd') - - expect(listing.state().data.length).toEqual(0) - }) - - it('selects all elements when checkbox is clicked', () => { - const listing = shallow(( - { }} - /> - )) - - listing.find(ListingHeader).find(Checkbox).props().onChange({}, true) - - expect(listing.state().selected.length).toBe(data.length) - }) - - it('deselects all elements when checkbox is unset', () => { - const listing = shallow(( - { }} - /> - )) - - listing.find(ListingHeader).find(Checkbox).props().onChange({}, false) - - expect(listing.state().selected.length).toBe(0) - }) - - it('handles changing the no of rows per age', () => { - const listing = shallow(( - { }} - /> - )) - - listing.find(TablePagination).props().onChangeRowsPerPage({ - target: { - value: 100, - }, - }) - - expect(listing.state().rowsPerPage).toBe(100) - }) - - it('handles changing the page', () => { - window.scrollTo = () => {} - - const listing = shallow(( - { }} - /> - )) - - listing.find(TablePagination).find(IconButton).last().simulate('click') - - expect(listing.state().page).toBe(1) - }) -}) diff --git a/src/Listing/Listing.test.tsx b/src/Listing/Listing.test.tsx new file mode 100644 index 0000000..7a7f429 --- /dev/null +++ b/src/Listing/Listing.test.tsx @@ -0,0 +1,244 @@ +import React from "react"; +import Enzyme, { shallow } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import { Checkbox, TablePagination, IconButton } from "@material-ui/core"; + +import Listing from "."; + +import headers from "../tests/data/listing_headers"; +import data from "../tests/data/listing_data"; +import ListingHeader from "./ListingHeader"; + +Enzyme.configure({ adapter: new Adapter() }); + +describe("Listing", () => { + it("renders correctly", () => { + const tree = shallow( + {}} + onUpdateSelection={() => {}} + />, + ); + + expect(tree).toMatchSnapshot(); + }); + + it("renders correctly with Loader", () => { + const tree = shallow( + {}} + hasLoader + onUpdateSelection={() => {}} + />, + ); + + expect(tree).toMatchSnapshot(); + }); + + it("changes props", () => { + const origSortData = Listing.prototype.sortData; + Listing.prototype.sortData = jest.fn(); + + const listing = shallow( + {}} + onUpdateSelection={() => {}} + />, + ); + + listing.setProps({ + orderBy: "name", + data: null, + }); + + expect(Listing.prototype.sortData).toHaveBeenCalled(); + Listing.prototype.sortData = origSortData; + }); + + it("does a new sorting", () => { + const listing = shallow( + {}} + onUpdateSelection={() => {}} + />, + ); + + expect(listing.state().data[0].username).toBe("Antonette"); + + listing.instance().handleRequestSort({}, "name"); + expect(listing.state().data[0].username).toBe("Karianne"); + + listing.instance().handleRequestSort({}, "name"); + expect(listing.state().data[0].username).toBe("Kamren"); + }); + + it("handles click on a checkbox", () => { + const onUpdateSelection = jest.fn(); + const listing = shallow( + {}} + onUpdateSelection={onUpdateSelection} + />, + ); + + listing.instance().handleCheckClick("foo"); + expect(listing.state().selected).toEqual(["foo"]); + expect(onUpdateSelection).toHaveBeenCalled(); + + listing.instance().handleCheckClick("bar"); + expect(listing.state().selected).toEqual(["foo", "bar"]); + + listing.instance().handleCheckClick("baz"); + expect(listing.state().selected).toEqual(["foo", "bar", "baz"]); + + listing.instance().handleCheckClick("bar"); + expect(listing.state().selected).toEqual(["foo", "baz"]); + + listing.instance().handleCheckClick("baz"); + expect(listing.state().selected).toEqual(["foo"]); + + listing.instance().handleCheckClick("foo"); + expect(listing.state().selected).toEqual([]); + }); + + it("allows to filter", () => { + const listing = shallow( + {}} + />, + ); + + const { length } = listing.state().data; + + listing.instance().handleFilter(""); + + expect(listing.state().data.length).toEqual(length); + + listing.instance().handleFilter("f0ooasdnajsbhdhuq2871dasd"); + + expect(listing.state().data.length).toEqual(0); + }); + + it("allows to filter even if there are empty filters", () => { + data[0].name = null; + + const listing = shallow( + {}} + />, + ); + + const { length } = (listing.state() as any).data; + + (listing.instance() as any).handleFilter(""); + + expect((listing.state() as any).data.length).toEqual(length); + + (listing.instance() as any).handleFilter("f0ooasdnajsbhdhuq2871dasd"); + + expect((listing.state() as any).data.length).toEqual(0); + }); + + it("selects all elements when checkbox is clicked", () => { + const listing = shallow( + {}} + />, + ); + + const { onChange } = listing.find(ListingHeader).find(Checkbox).props(); + onChange && onChange({}, true); + + expect((listing.state() as any).selected.length).toBe(data.length); + }); + + it("deselects all elements when checkbox is unset", () => { + const listing = shallow( + {}} + />, + ); + + const { onChange } = listing.find(ListingHeader).find(Checkbox).props(); + + onChange && onChange({}, false); + + expect((listing.state() as any).selected.length).toBe(0); + }); + + it("handles changing the no of rows per age", () => { + const listing = shallow( + {}} + />, + ); + + const { onChangeRowsPerPage } = listing.find(TablePagination).props(); + onChangeRowsPerPage && + onChangeRowsPerPage({ + target: { + value: 100, + }, + }); + + expect(listing.state().rowsPerPage).toBe(100); + }); + + it("handles changing the page", () => { + window.scrollTo = () => {}; + + const listing = shallow( + {}} + />, + ); + + listing.find(TablePagination).find(IconButton).last().simulate("click"); + + expect(listing.state().page).toBe(1); + }); +}); diff --git a/src/Listing/Listing.tsx b/src/Listing/Listing.tsx new file mode 100644 index 0000000..33bc178 --- /dev/null +++ b/src/Listing/Listing.tsx @@ -0,0 +1,372 @@ +import React from "react"; +import keycode from "keycode"; +import Store from "vanilla-store"; +import easeInOutQuad from "../utils/easeInOutQuad"; +import ListingBranch from "./ListingBranch"; + +const getStringContent = (content: any) => { + let matchContent = content; + if (content.constructor === Array) { + matchContent = content.join(" "); + } else if (typeof content === "object") { + matchContent = Object.values(content).join(" "); + } + return matchContent.toLowerCase(); +}; + +const tryToMatch = (value: any, content: any) => { + let initialContent = content; + if (!content) { + return false; + } + if (content.highlight) { + initialContent = content.value; + } + const contentToSearch = getStringContent(initialContent); + if (contentToSearch.indexOf(value) > -1) { + return true; + } + return false; +}; + +const getSearchableHeaders = (headers: Record[]) => + headers.filter((header) => header.isSearchable).map((header) => header.id); + +const filterElement = ( + element: Record, + value: any, + searchables: any, +) => { + const searchValue = value.toLowerCase(); + let newElement: Record | undefined = undefined; + + Object.keys(element).forEach((key) => { + if (searchables.indexOf(key) > -1) { + const matched = tryToMatch(searchValue, element[key]); + if (matched) { + newElement = element; + } + } + }); + + if (newElement) { + Object.keys(newElement).forEach((key: any) => { + if (newElement && newElement[key]) { + newElement[key] = { + highlight: searchValue, + value: newElement[key].highlight + ? newElement[key].value + : newElement[key], + }; + } + }); + } + + return newElement; +}; + +type ListingProps = { + orderBy: string; + order?: "asc" | "desc"; + hasLoader?: boolean; + data: object[]; + headers: object[]; + toolbarContent?: React.ReactNode; + onUpdateSelection?: (...args: any[]) => any; + id?: string; + onClick?: (...args: any[]) => any; + title?: string; + isIntegated?: boolean; +}; + +type ListingState = { + order?: string; + orderBy?: string; + data: any[]; + searchable: any[]; + page: number; + rowsPerPage?: number; + origData: any; + selected: any[]; + id: null; +}; + +const withListing = (Component: any) => + class Listing extends React.Component { + private node: any; + + constructor(props: ListingProps, context: any) { + super(props, context); + this.state = { + order: "asc", + orderBy: "id", + selected: [], + data: [], + page: 0, + rowsPerPage: 10, + searchable: [], + origData: null, + id: null, + }; + this.node = React.createRef(); + this.handleRequestSort = this.handleRequestSort.bind(this); + this.handleSelectAllClick = this.handleSelectAllClick.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleCheckClick = this.handleCheckClick.bind(this); + this.handleChangeRowsPerPage = this.handleChangeRowsPerPage.bind(this); + this.handleChangePage = this.handleChangePage.bind(this); + this.isSelected = this.isSelected.bind(this); + this.handleFilter = this.handleFilter.bind(this); + } + + componentDidMount() { + const { id, data, headers, order, orderBy } = this.props; + const storedData = Store.get("Listing", id); + const newState: any = { + data: this.sortData(data, orderBy, order), + searchable: getSearchableHeaders(headers), + }; + if (storedData) { + if (storedData.page) { + newState.page = storedData.page; + } + if (storedData.rowsPerPage) { + newState.rowsPerPage = storedData.rowsPerPage; + } + } + this.setState({ + ...newState, + order, + orderBy, + }); + } + + UNSAFE_componentWillReceiveProps({ + data, + headers, + orderBy, + order, + }: ListingProps) { + this.setState({ + data: this.sortData(data, orderBy, order), + orderBy, + order, + searchable: getSearchableHeaders(headers), + }); + } + + sortData(data: any, orderBy: any, order: any) { + const { headers } = this.props; + const orderByHeader: any = headers.filter( + (header: any) => header.id === orderBy, + )[0]; + + let { transformData } = orderByHeader; + + if (!data) { + return data; + } + + if (typeof transformData !== "function") { + transformData = (values: any) => values; + } + + const transformedData = data.map((element: any) => { + const newElement = element; + newElement[orderBy] = transformData(element[orderBy]); + return newElement; + }); + + const sortedData = transformedData.sort((a: any, b: any) => + a[orderBy] < b[orderBy] ? -1 : 1, + ); + + if (order === "asc") { + return sortedData; + } + + return sortedData.reverse(); + } + + handleRequestSort(event: any, property: any) { + const { + orderBy: orderByState, + order: orderState, + data: dataState, + } = this.state; + const orderBy = property; + let order = "desc"; + if (orderByState === property && orderState === "desc") { + order = "asc"; + } + let data; + if (order === "desc") { + data = dataState.sort((a, b) => (b[orderBy] < a[orderBy] ? -1 : 1)); + } else { + data = dataState.sort((a, b) => (a[orderBy] < b[orderBy] ? -1 : 1)); + } + this.setState({ + data, + order, + orderBy, + }); + } + + handleSelectAllClick(event: any, checked: any) { + const { data } = this.state; + + if (checked) { + this.setState({ + selected: data.map((n: any) => n.id), + }); + return; + } + + this.setState({ + selected: [], + }); + } + + handleKeyDown(event: any, id: string) { + if (keycode(event) === "space") { + this.handleCheckClick(id); + } + } + + handleCheckClick(id: string) { + const { onUpdateSelection } = this.props; + const { selected } = this.state; + const selectedIndex = selected.indexOf(id); + + let newSelected: string[] = []; + + if (selectedIndex === -1) { + newSelected = newSelected.concat(selected, id); + } else if (selectedIndex === 0) { + newSelected = newSelected.concat(selected.slice(1)); + } else if (selectedIndex === selected.length - 1) { + newSelected = newSelected.concat(selected.slice(0, -1)); + } else if (selectedIndex > 0) { + newSelected = newSelected.concat( + selected.slice(0, selectedIndex), + selected.slice(selectedIndex + 1), + ); + } + + onUpdateSelection && onUpdateSelection(newSelected); + + this.setState({ + selected: newSelected, + }); + } + + scrollToElement() { + if (!this.node.current) { + return; + } + const { offsetTop } = this.node.current; + const start = window.scrollY; + const change = offsetTop - start; + const increment = 20; + const duration = 250; + let currentTime = 0; + const animateScroll = () => { + currentTime += increment; + const val = easeInOutQuad(currentTime, start, change, duration); + window.scrollTo(0, val); + if (currentTime < duration) { + setTimeout(animateScroll, increment); + } + }; + animateScroll(); + } + + handleChangePage(event: any, page: any) { + const { id } = this.props; + const { rowsPerPage } = this.state; + Store.create("Listing", { + id, + rowsPerPage, + page, + }); + this.scrollToElement(); + this.setState({ + page, + }); + } + + handleChangeRowsPerPage(event: any) { + const rowsPerPage = event.target.value; + const { id, page } = this.state; + Store.create("Listing", { + id, + page, + rowsPerPage, + }); + this.setState({ + rowsPerPage, + }); + } + + handleFilter(value: any) { + const { searchable, origData, data } = this.state; + let searchableData; + if (origData && origData.constructor === Array) { + searchableData = [...origData]; + } else { + searchableData = [...data]; + } + if (!value) { + searchableData = searchableData.map((item) => { + const newItem = item; + Object.keys(item).forEach((key) => { + if (item[key]) { + if (item[key].value) { + newItem[key] = item[key].value; + } else { + newItem[key] = item[key]; + } + } + }); + return newItem; + }); + + this.setState({ + data: searchableData, + }); + + return; + } + const newData = searchableData + .map((element) => filterElement(element, value, searchable)) + .filter((item) => item !== undefined); + this.setState({ + data: newData, + origData: searchableData, + }); + } + isSelected(id: string) { + const { selected } = this.state; + return selected.indexOf(id) !== -1; + } + render() { + return ( +
+ +
+ ); + } + }; + +export default withListing(ListingBranch) as any; diff --git a/src/Listing/ListingBranch.jsx b/src/Listing/ListingBranch.jsx deleted file mode 100644 index f958ecb..0000000 --- a/src/Listing/ListingBranch.jsx +++ /dev/null @@ -1,153 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import { - Table, - TableBody, - TableFooter, - TablePagination, - TableRow, - Paper, - withStyles, -} from '@material-ui/core' - -import ListingHeader from './ListingHeader' -import ListingToolbar from './ListingToolbar' -import ListingLine from './ListingLine' -import ListingLoader from './ListingLoader' - -const styles = theme => ({ - root: { - width: '100%', - marginTop: theme.spacing(3), - }, - table: { - minWidth: 800, - }, - tableWrapper: { - overflowX: 'auto', - }, -}) - -const ListingBranch = ({ - title, - headers, - classes, - data, - order, - orderBy, - selected, - rowsPerPage, - rowsPerPageOptions, - page, - toolbarContent, - handleSelectAllClick, - handleRequestSort, - handleCheckClick, - onClick, - handleKeyDown, - handleChangePage, - handleChangeRowsPerPage, - isSelected, - onFilter, - hasLoader, - isIntegrated, -}) => ( - - - {toolbarContent} - - -
- - - - - - {hasLoader ? ( - - ) : null} - - {data.slice(page * rowsPerPage, (page * rowsPerPage) + rowsPerPage).map(n => ( - - ))} - - - - - - -
-
-
-) - -ListingBranch.propTypes = { - title: PropTypes.string, - data: PropTypes.arrayOf(PropTypes.object), - headers: PropTypes.arrayOf(PropTypes.object).isRequired, - order: PropTypes.string, - orderBy: PropTypes.string.isRequired, - selected: PropTypes.arrayOf(PropTypes.string), - rowsPerPage: PropTypes.number, - rowsPerPageOptions: PropTypes.arrayOf(PropTypes.number), - page: PropTypes.number, - hasLoader: PropTypes.bool.isRequired, - toolbarContent: PropTypes.node.isRequired, - handleSelectAllClick: PropTypes.func.isRequired, - handleRequestSort: PropTypes.func.isRequired, - handleCheckClick: PropTypes.func.isRequired, - onClick: PropTypes.func.isRequired, - handleKeyDown: PropTypes.func.isRequired, - handleChangePage: PropTypes.func.isRequired, - handleChangeRowsPerPage: PropTypes.func.isRequired, - isSelected: PropTypes.func, - onFilter: PropTypes.func, - isIntegrated: PropTypes.bool, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -ListingBranch.defaultProps = { - title: '', - data: [], - order: 'asc', - selected: [], - rowsPerPage: 10, - rowsPerPageOptions: [10, 25, 50, 100], - page: 0, - onFilter: () => {}, - isSelected: () => {}, - isIntegrated: false, -} - -export default withStyles(styles)(ListingBranch) diff --git a/src/Listing/ListingBranch.tsx b/src/Listing/ListingBranch.tsx new file mode 100644 index 0000000..267bbfa --- /dev/null +++ b/src/Listing/ListingBranch.tsx @@ -0,0 +1,146 @@ +import React from "react"; +import { + Table, + TableBody, + TableFooter, + TablePagination, + TableRow, + Paper, + withStyles, +} from "@material-ui/core"; +import ListingHeader from "./ListingHeader"; +import ListingToolbar from "./ListingToolbar"; +import ListingLine from "./ListingLine"; +import ListingLoader from "./ListingLoader"; +const styles = (theme: any) => ({ + root: { + width: "100%", + marginTop: theme.spacing(3), + }, + table: { + minWidth: 800, + }, + tableWrapper: { + overflowX: "auto", + }, +}); + +type ListingBranchProps = { + title?: string; + data?: object[]; + headers: object[]; + order?: "asc" | "desc"; + orderBy: string; + selected?: string[]; + rowsPerPage?: number; + rowsPerPageOptions?: number[]; + page?: number; + hasLoader: boolean; + toolbarContent: React.ReactNode; + handleSelectAllClick: (...args: any[]) => any; + handleRequestSort: (...args: any[]) => any; + handleCheckClick: (...args: any[]) => any; + onClick: (...args: any[]) => any; + handleKeyDown: (...args: any[]) => any; + handleChangePage: (...args: any[]) => any; + handleChangeRowsPerPage: (...args: any[]) => any; + isSelected?: (...args: any[]) => any; + onFilter?: (...args: any[]) => any; + isIntegrated?: boolean; + classes: { + [key: string]: string; + }; +}; + +const ListingBranch: React.SFC = ({ + title, + headers, + classes, + data, + order = "asc", + orderBy, + selected, + rowsPerPage = 10, + rowsPerPageOptions = [10, 25, 50, 100], + page = 0, + toolbarContent, + handleSelectAllClick, + handleRequestSort, + handleCheckClick, + onClick, + handleKeyDown, + handleChangePage, + handleChangeRowsPerPage, + isSelected, + onFilter, + hasLoader, + isIntegrated, +}) => ( + + + {toolbarContent} + + +
+ + + + + {hasLoader ? : null} + + {data + ?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((item: any) => ( + + ))} + + + + + + +
+
+
+); + +ListingBranch.defaultProps = { + title: "", + data: [], + selected: [], + onFilter: () => {}, + isSelected: () => {}, + isIntegrated: false, +}; + +export default withStyles(styles as any)(ListingBranch); diff --git a/src/Listing/ListingHeader.jsx b/src/Listing/ListingHeader.jsx deleted file mode 100644 index ff5a578..0000000 --- a/src/Listing/ListingHeader.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { - Checkbox, - TableCell, - TableHead, - TableRow, - TableSortLabel, - Tooltip, -} from '@material-ui/core' - -class ListingHeader extends React.Component { - createSortHandler(property) { - const { onRequestSort } = this.props - - return (event) => { - onRequestSort(event, property) - } - } - - render() { - const { - onSelectAllClick, - order, - orderBy, - numSelected, - rowCount, - headers, - } = this.props - - return ( - - - - 0 && numSelected < rowCount} - checked={numSelected === rowCount} - onChange={onSelectAllClick} - /> - - - {headers.map(column => ( - - - - {column.label} - - - - ), this)} - - - ) - } -} - -ListingHeader.propTypes = { - headers: PropTypes.arrayOf(PropTypes.object).isRequired, - numSelected: PropTypes.number.isRequired, - onRequestSort: PropTypes.func.isRequired, - onSelectAllClick: PropTypes.func.isRequired, - order: PropTypes.string.isRequired, - orderBy: PropTypes.string.isRequired, - rowCount: PropTypes.number.isRequired, -} - -export default ListingHeader diff --git a/src/Listing/ListingHeader.test.jsx b/src/Listing/ListingHeader.test.jsx deleted file mode 100644 index ca166fe..0000000 --- a/src/Listing/ListingHeader.test.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import { Table } from '@material-ui/core' - -import ListingHeader from './ListingHeader' - -it('renders correctly', () => { - const tree = shallow(( - - {}} - onSelectAllClick={() => { }} - order="asc" - orderBy="name" - rowCount={0} - /> -
- )) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Listing/ListingHeader.test.tsx b/src/Listing/ListingHeader.test.tsx new file mode 100644 index 0000000..a529549 --- /dev/null +++ b/src/Listing/ListingHeader.test.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import { Table } from "@material-ui/core"; + +import ListingHeader from "./ListingHeader"; + +it("renders correctly", () => { + const tree = shallow( + + {}} + onSelectAllClick={() => {}} + order="asc" + orderBy="name" + rowCount={0} + /> +
, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Listing/ListingHeader.tsx b/src/Listing/ListingHeader.tsx new file mode 100644 index 0000000..7a3e17a --- /dev/null +++ b/src/Listing/ListingHeader.tsx @@ -0,0 +1,89 @@ +import React from "react"; +import { + Checkbox, + TableCell, + TableHead, + TableRow, + TableSortLabel, + Tooltip, +} from "@material-ui/core"; + +type ListingHeaderProps = { + headers: object[]; + numSelected?: number; + onRequestSort: (...args: any[]) => any; + onSelectAllClick: (...args: any[]) => any; + order?: "asc" | "desc"; + orderBy: string; + rowCount?: number; +}; + +class ListingHeader extends React.Component { + static defaultProps = { + numSelected: 0, + rowCount: 0, + }; + + createSortHandler(property: any) { + const { onRequestSort } = this.props; + + return (event: any) => { + onRequestSort(event, property); + }; + } + + render() { + const { + onSelectAllClick, + order, + orderBy, + numSelected, + rowCount, + headers, + } = this.props; + + return ( + + + + 0 && (numSelected || 0) < (rowCount || 0) + } + checked={numSelected === rowCount} + onChange={onSelectAllClick} + /> + + + {headers.map( + (column: any) => ( + + + + {column.label} + + + + ), + this, + )} + + + ); + } +} + +export default ListingHeader; diff --git a/src/Listing/ListingLine.jsx b/src/Listing/ListingLine.jsx deleted file mode 100644 index 82a560e..0000000 --- a/src/Listing/ListingLine.jsx +++ /dev/null @@ -1,108 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { - Checkbox, - TableCell, - TableRow, -} from '@material-ui/core' - -import replace from '../utils/replace' - - -const getCellContent = (content, transformContent, data) => { - let printableContent = content - - if (content && content.highlight) { - printableContent = content.value - } - - if (typeof transformContent === 'function') { - printableContent = transformContent(printableContent, data) - } - - const props = {} - - if (typeof printableContent === 'string' && content.highlight) { - printableContent = replace( - printableContent, - content.highlight, - key => `${key}`, - ) - - props.dangerouslySetInnerHTML = { - __html: printableContent, - } - - printableContent = undefined - } - - return { - content: printableContent, - props, - } -} - -class ListingLine extends React.Component { - static propTypes = { - headers: PropTypes.arrayOf(PropTypes.object).isRequired, - data: PropTypes.objectOf(PropTypes.any).isRequired, - onClick: PropTypes.func.isRequired, - isSelected: PropTypes.bool.isRequired, - handleKeyDown: PropTypes.func.isRequired, - handleCheckClick: PropTypes.func.isRequired, - } - - renderCells() { - const { headers, data, onClick } = this.props - - return headers.map((header) => { - const { - content, - props, - } = getCellContent(data[header.id], header.transformContent, data) - - return ( - onClick(data.id)} - {...props} - > - {content} - - ) - }) - } - - render() { - const { - data, - isSelected, - handleKeyDown, - handleCheckClick, - } = this.props - - return ( - handleKeyDown(event, data.id)} - role="checkbox" - aria-checked={isSelected} - tabIndex={-1} - selected={isSelected} - > - handleCheckClick(data.id)} - > - - - - {this.renderCells()} - - ) - } -} - -export default ListingLine diff --git a/src/Listing/ListingLine.test.jsx b/src/Listing/ListingLine.test.jsx deleted file mode 100644 index be35ac4..0000000 --- a/src/Listing/ListingLine.test.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' -import { Table } from '@material-ui/core' - -import ListingLine from './ListingLine' - -import headers from '../tests/data/listing_headers' - -const twoHeaders = headers.slice(0, 2) - -it('renders correctly', () => { - const tree = shallow(( - - { }} - handleKeyDown={() => { }} - handleCheckClick={() => { }} - isSelected={false} - /> -
- )) - - expect(tree).toMatchSnapshot() -}) - -it('renders selected', () => { - const tree = shallow(( - - { }} - handleKeyDown={() => { }} - handleCheckClick={() => { }} - isSelected - /> -
- )) - - expect(tree).toMatchSnapshot() -}) - -it('renders highlighted', () => { - const tree = shallow(( - - { }} - handleKeyDown={() => { }} - handleCheckClick={() => { }} - isSelected - /> -
- )) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Listing/ListingLine.test.tsx b/src/Listing/ListingLine.test.tsx new file mode 100644 index 0000000..4352daa --- /dev/null +++ b/src/Listing/ListingLine.test.tsx @@ -0,0 +1,78 @@ +import React from "react"; +import { shallow } from "enzyme"; +import { Table } from "@material-ui/core"; + +import ListingLine from "./ListingLine"; + +import headers from "../tests/data/listing_headers"; + +const twoHeaders = headers.slice(0, 2); + +it("renders correctly", () => { + const tree = shallow( + + {}} + handleKeyDown={() => {}} + handleCheckClick={() => {}} + isSelected={false} + /> +
, + ); + + expect(tree).toMatchSnapshot(); +}); + +it("renders selected", () => { + const tree = shallow( + + {}} + handleKeyDown={() => {}} + handleCheckClick={() => {}} + isSelected + /> +
, + ); + + expect(tree).toMatchSnapshot(); +}); + +it("renders highlighted", () => { + const tree = shallow( + + {}} + handleKeyDown={() => {}} + handleCheckClick={() => {}} + isSelected + /> +
, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Listing/ListingLine.tsx b/src/Listing/ListingLine.tsx new file mode 100644 index 0000000..a7ee099 --- /dev/null +++ b/src/Listing/ListingLine.tsx @@ -0,0 +1,94 @@ +import React from "react"; +import { Checkbox, TableCell, TableRow } from "@material-ui/core"; +import replace from "../utils/replace"; + +const getCellContent = (content: any, transformContent: any, data: any) => { + let printableContent = content; + + if (content && content.highlight) { + printableContent = content.value; + } + + if (typeof transformContent === "function") { + printableContent = transformContent(printableContent, data); + } + + const props: Record = {}; + + if (typeof printableContent === "string" && content.highlight) { + printableContent = replace( + printableContent, + content.highlight, + (key: any) => `${key}`, + ); + + props.dangerouslySetInnerHTML = { + __html: printableContent, + }; + + printableContent = undefined; + } + + return { + content: printableContent, + props, + }; +}; + +type ListingLineProps = { + headers: object[]; + data: { + [key: string]: any; + }; + onClick: (...args: any[]) => any; + isSelected: boolean; + handleKeyDown: (...args: any[]) => any; + handleCheckClick: (...args: any[]) => any; +}; + +class ListingLine extends React.Component { + renderCells() { + const { headers, data, onClick } = this.props; + return headers.map((header: any) => { + const { content, props } = getCellContent( + data[header.id], + header.transformContent, + data, + ); + + return ( + onClick(data.id)} + {...props} + > + {content} + + ); + }); + } + + render() { + const { data, isSelected, handleKeyDown, handleCheckClick } = this.props; + return ( + handleKeyDown(event, data.id)} + role="checkbox" + aria-checked={isSelected} + tabIndex={-1} + selected={isSelected} + > + handleCheckClick(data.id)}> + + + + {this.renderCells()} + + ); + } +} + +export default ListingLine; diff --git a/src/Listing/ListingLoader.jsx b/src/Listing/ListingLoader.jsx deleted file mode 100644 index 778b771..0000000 --- a/src/Listing/ListingLoader.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import { - TableCell, - TableRow, - LinearProgress, - withStyles, -} from '@material-ui/core' - -const styles = { - row: { - height: 'auto', - }, - cell: { - border: 0, - padding: 0, - }, - progress: { - width: '100%', - }, -} - -const ListingLoader = ({ cols, classes }) => ( - - - - - -) - -ListingLoader.propTypes = { - cols: PropTypes.number, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -ListingLoader.defaultProps = { - cols: 1, -} - -export default withStyles(styles)(ListingLoader) diff --git a/src/Listing/ListingLoader.test.jsx b/src/Listing/ListingLoader.test.jsx deleted file mode 100644 index 897a694..0000000 --- a/src/Listing/ListingLoader.test.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' -import { Table } from '@material-ui/core' - -import ListingLoader from './ListingLoader' - -it('renders correctly', () => { - const tree = shallow(( - - -
- )) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Listing/ListingLoader.test.tsx b/src/Listing/ListingLoader.test.tsx new file mode 100644 index 0000000..1e6764f --- /dev/null +++ b/src/Listing/ListingLoader.test.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { shallow } from "enzyme"; +import { Table } from "@material-ui/core"; + +import ListingLoader from "./ListingLoader"; + +it("renders correctly", () => { + const tree = shallow( + + +
, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Listing/ListingLoader.tsx b/src/Listing/ListingLoader.tsx new file mode 100644 index 0000000..71ded4e --- /dev/null +++ b/src/Listing/ListingLoader.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { + TableCell, + TableRow, + LinearProgress, + withStyles, +} from "@material-ui/core"; + +const styles = { + row: { + height: "auto", + }, + cell: { + border: 0, + padding: 0, + }, + progress: { + width: "100%", + }, +}; + +type ListingLoaderProps = { + cols?: number; + classes: { + [key: string]: string; + }; +}; + +const ListingLoader: React.SFC = ({ cols, classes }) => ( + + + + + +); + +ListingLoader.defaultProps = { + cols: 1, +}; + +export default withStyles(styles)(ListingLoader); diff --git a/src/Listing/ListingSearch.jsx b/src/Listing/ListingSearch.jsx deleted file mode 100644 index c642f2e..0000000 --- a/src/Listing/ListingSearch.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import ListingSearchBranch from './ListingSearchBranch' - -const withListing = Component => class extends React.Component { - static propTypes = { - onFilter: PropTypes.func.isRequired, - } - - constructor() { - super() - - this.handleClick = this.handleClick.bind(this) - this.handleFilter = this.handleFilter.bind(this) - this.getSearchRef = this.getSearchRef.bind(this) - } - - state = { - open: false, - } - - getSearchRef(node) { - this.searchRef = node - } - - handleClick() { - const { open } = this.state - const { onFilter } = this.props - const newOpen = !open - - if (this.searchRef && newOpen) { - this.searchRef.focus() - } - - this.setState({ - open: newOpen, - }) - - onFilter(undefined) - } - - handleFilter(event) { - const { open } = this.state - const { onFilter } = this.props - const { value } = event.target - - if (open) { - onFilter(value) - } else { - onFilter(undefined) - } - } - - render() { - return ( - - ) - } -} - -export default withListing(ListingSearchBranch) diff --git a/src/Listing/ListingSearch.test.jsx b/src/Listing/ListingSearch.test.jsx deleted file mode 100644 index 1d277e0..0000000 --- a/src/Listing/ListingSearch.test.jsx +++ /dev/null @@ -1,103 +0,0 @@ -import React from 'react' -import Enzyme, { shallow } from 'enzyme' -import Adapter from 'enzyme-adapter-react-16' - -import { IconButton } from '@material-ui/core' -import ListingSearch from './ListingSearch' - -Enzyme.configure({ adapter: new Adapter() }) - -it('renders correctly', () => { - const tree = shallow(( - { }} - onFilter={() => { }} - /> - )) - - expect(tree).toMatchSnapshot() -}) - -it('renders correctly when open', () => { - const tree = shallow(( - { }} - onFilter={() => { }} - /> - )) - - expect(tree).toMatchSnapshot() -}) - -it('opens if click is triggered', () => { - const listingSearch = shallow(( - { }} - onFilter={() => { }} - /> - )) - - expect(listingSearch.instance().state.open).toEqual(false) - - listingSearch.find(IconButton).simulate('click') - - expect(listingSearch.instance().state.open).toEqual(true) -}) - -it('filters if open and value changes', () => { - const onFilter = jest.fn() - const listingSearch = shallow(( - { }} - onFilter={onFilter} - /> - )) - - listingSearch.find('input').simulate('change', { value: 'a' }) - - expect(onFilter).toHaveBeenCalled() -}) - -it('does not filter if not open and value changes', () => { - const onFilter = jest.fn() - const listingSearch = shallow(( - { }} - onFilter={onFilter} - /> - )) - - expect(listingSearch.instance().state.open).toEqual(false) - listingSearch.find(IconButton).simulate('click') - - expect(listingSearch.instance().state.open).toEqual(true) - - listingSearch.find('input').simulate('change', { value: 'a' }) - - expect(onFilter).toHaveBeenCalled() -}) - -it('focuses search field on click', () => { - const mockObject = { - focus: jest.fn(), - } - - const listingSearch = shallow(( - { }} - onFilter={() => {}} - /> - )) - - listingSearch.instance().searchRef = mockObject - - listingSearch.find(IconButton).simulate('click') - - expect(listingSearch.state().open).toBe(true) - - expect(mockObject.focus).toHaveBeenCalled() -}) diff --git a/src/Listing/ListingSearch.test.tsx b/src/Listing/ListingSearch.test.tsx new file mode 100644 index 0000000..4439081 --- /dev/null +++ b/src/Listing/ListingSearch.test.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import Enzyme, { shallow } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; + +import { IconButton } from "@material-ui/core"; +import ListingSearch from "./ListingSearch"; + +Enzyme.configure({ adapter: new Adapter() }); + +it("renders correctly", () => { + const tree = shallow( + {}} onFilter={() => {}} />, + ); + + expect(tree).toMatchSnapshot(); +}); + +it("renders correctly when open", () => { + const tree = shallow( + {}} onFilter={() => {}} />, + ); + + expect(tree).toMatchSnapshot(); +}); + +it("opens if click is triggered", () => { + const listingSearch = shallow( + {}} onFilter={() => {}} />, + ); + + expect(listingSearch.instance().state.open).toEqual(false); + + listingSearch.find(IconButton).simulate("click"); + + expect(listingSearch.instance().state.open).toEqual(true); +}); + +it("filters if open and value changes", () => { + const onFilter = jest.fn(); + const listingSearch = shallow( + {}} onFilter={onFilter} />, + ); + + listingSearch.find("input").simulate("change", { value: "a" }); + + expect(onFilter).toHaveBeenCalled(); +}); + +it("does not filter if not open and value changes", () => { + const onFilter = jest.fn(); + const listingSearch = shallow( + {}} onFilter={onFilter} />, + ); + + expect(listingSearch.instance().state.open).toEqual(false); + listingSearch.find(IconButton).simulate("click"); + + expect(listingSearch.instance().state.open).toEqual(true); + + listingSearch.find("input").simulate("change", { value: "a" }); + + expect(onFilter).toHaveBeenCalled(); +}); + +it("focuses search field on click", () => { + const mockObject = { + focus: jest.fn(), + }; + + const listingSearch = shallow( + {}} onFilter={() => {}} />, + ); + + (listingSearch.instance() as any).searchRef = mockObject; + + listingSearch.find(IconButton).simulate("click"); + + expect((listingSearch.state() as any).open).toBe(true); + + expect(mockObject.focus).toHaveBeenCalled(); +}); diff --git a/src/Listing/ListingSearch.tsx b/src/Listing/ListingSearch.tsx new file mode 100644 index 0000000..e61a8bf --- /dev/null +++ b/src/Listing/ListingSearch.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import ListingSearchBranch from "./ListingSearchBranch"; + +type WithListingSearchProps = { + onFilter: (...args: any[]) => any; +}; + +type WithListingSearchState = { + open: boolean; +}; + +const withListing = (Component: any) => + class WithListingSearch extends React.Component< + WithListingSearchProps, + WithListingSearchState + > { + private searchRef: any; + + constructor(props: WithListingSearchProps) { + super(props); + + this.handleClick = this.handleClick.bind(this); + this.handleFilter = this.handleFilter.bind(this); + this.getSearchRef = this.getSearchRef.bind(this); + } + state = { + open: false, + }; + + getSearchRef(node: any) { + this.searchRef = node; + } + + handleClick() { + const { open } = this.state; + const { onFilter } = this.props; + const newOpen = !open; + if (this.searchRef && newOpen) { + this.searchRef.focus(); + } + this.setState({ + open: newOpen, + }); + onFilter(undefined); + } + + handleFilter(event: any) { + const { open } = this.state; + const { onFilter } = this.props; + const { value } = event.target; + if (open) { + onFilter(value); + } else { + onFilter(undefined); + } + } + + render() { + return ( + + ); + } + }; + +export default withListing(ListingSearchBranch) as any; diff --git a/src/Listing/ListingSearchBranch.jsx b/src/Listing/ListingSearchBranch.jsx deleted file mode 100644 index 656ed1d..0000000 --- a/src/Listing/ListingSearchBranch.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import classNames from 'classnames' - -import { - TextField, - Tooltip, - IconButton, - withStyles, -} from '@material-ui/core' -import SearchIcon from '@material-ui/icons/Search' - -const styles = theme => ({ - root: { - position: 'relative', - marginRight: theme.spacing(), - }, - field: { - position: 'absolute', - right: '100%', - width: 0, - marginTop: theme.spacing(), - transition: 'width 0.25s', - }, - fieldActive: { - width: theme.spacing(30), - }, -}) - -const ListingSearchBranch = ({ - open, - placeholder, - onClick, - onFilter, - getSearchRef, - classes, -}) => ( -
- { getSearchRef(node) }, - }} - /> - - - - - -
-) - -ListingSearchBranch.propTypes = { - open: PropTypes.bool.isRequired, - placeholder: PropTypes.string, - onClick: PropTypes.func.isRequired, - onFilter: PropTypes.func.isRequired, - getSearchRef: PropTypes.func.isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -ListingSearchBranch.defaultProps = { - placeholder: 'Filter', -} - -export default withStyles(styles)(ListingSearchBranch) diff --git a/src/Listing/ListingSearchBranch.tsx b/src/Listing/ListingSearchBranch.tsx new file mode 100644 index 0000000..832f69d --- /dev/null +++ b/src/Listing/ListingSearchBranch.tsx @@ -0,0 +1,68 @@ +import React from "react"; +import classNames from "classnames"; +import { TextField, Tooltip, IconButton, withStyles } from "@material-ui/core"; +import SearchIcon from "@material-ui/icons/Search"; + +const styles = (theme: any) => ({ + root: { + position: "relative", + marginRight: theme.spacing(), + }, + field: { + position: "absolute", + right: "100%", + width: 0, + marginTop: theme.spacing(), + transition: "width 0.25s", + }, + fieldActive: { + width: theme.spacing(30), + }, +}); + +type ListingSearchBranchProps = { + open: boolean; + placeholder?: string; + onClick: (...args: any[]) => any; + onFilter: (...args: any[]) => any; + getSearchRef: (...args: any[]) => any; + classes: { + [key: string]: string; + }; +}; + +const ListingSearchBranch: React.SFC = ({ + open, + placeholder, + onClick, + onFilter, + getSearchRef, + classes, +}) => ( +
+ { + getSearchRef(node); + }, + }} + /> + + + + + +
+); + +ListingSearchBranch.defaultProps = { + placeholder: "Filter", +}; + +export default withStyles(styles as any)(ListingSearchBranch); diff --git a/src/Listing/ListingToolbar.jsx b/src/Listing/ListingToolbar.jsx deleted file mode 100644 index bb4976b..0000000 --- a/src/Listing/ListingToolbar.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, { Fragment } from 'react' -import PropTypes from 'prop-types' -import classNames from 'classnames' - -import { - Toolbar, - Typography, - withStyles, -} from '@material-ui/core' - -import ListingSearch from './ListingSearch' - -const toolbarStyles = theme => ({ - root: { - paddingRight: 2, - }, - highlight: - theme.palette.type === 'light' - ? { - color: theme.palette.secondary.dark, - backgroundColor: theme.palette.secondary.light, - } - : { - color: theme.palette.secondary.light, - backgroundColor: theme.palette.secondary.dark, - }, - spacer: { - flex: '1 1 100%', - }, - actions: { - color: theme.palette.text.secondary, - }, - title: { - flex: '0 0 auto', - }, -}) - -const ListingToolbar = ({ - title, - numSelected, - onFilter, - classes, - children, -}) => ( - 0, - })} - > -
- {numSelected > 0 ? ( - - {numSelected} -  selected - - ) : ( - {title} - )} -
- -
- -
- {numSelected > 0 ? children : ( - - )} -
- -) - -ListingToolbar.propTypes = { - title: PropTypes.string.isRequired, - numSelected: PropTypes.number.isRequired, - onFilter: PropTypes.func.isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, - children: PropTypes.node, -} - -ListingToolbar.defaultProps = { - children: (), -} - -export default withStyles(toolbarStyles)(ListingToolbar) diff --git a/src/Listing/ListingToolbar.test.jsx b/src/Listing/ListingToolbar.test.jsx deleted file mode 100644 index eb8923c..0000000 --- a/src/Listing/ListingToolbar.test.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import ListingToolbar from './ListingToolbar' - -it('renders correctly', () => { - const tree = shallow(( - { }} - /> - )) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Listing/ListingToolbar.test.tsx b/src/Listing/ListingToolbar.test.tsx new file mode 100644 index 0000000..290890b --- /dev/null +++ b/src/Listing/ListingToolbar.test.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import ListingToolbar from "./ListingToolbar"; + +it("renders correctly", () => { + const tree = shallow( + {}} />, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Listing/ListingToolbar.tsx b/src/Listing/ListingToolbar.tsx new file mode 100644 index 0000000..ab0b829 --- /dev/null +++ b/src/Listing/ListingToolbar.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import classNames from "classnames"; +import { Toolbar, Typography, withStyles } from "@material-ui/core"; +import ListingSearch from "./ListingSearch"; + +const toolbarStyles = (theme: any) => ({ + root: { + paddingRight: 2, + }, + highlight: + theme.palette.type === "light" + ? { + color: theme.palette.secondary.dark, + backgroundColor: theme.palette.secondary.light, + } + : { + color: theme.palette.secondary.light, + backgroundColor: theme.palette.secondary.dark, + }, + spacer: { + flex: "1 1 100%", + }, + actions: { + color: theme.palette.text.secondary, + }, + title: { + flex: "0 0 auto", + }, +}); + +type ListingToolbarProps = { + title: string; + numSelected: number; + onFilter: (...args: any[]) => any; + classes: { + [key: string]: string; + }; +}; + +const ListingToolbar: React.SFC = ({ + title, + numSelected, + onFilter, + classes, + children, +}) => ( + 0, + })} + > +
+ {numSelected > 0 ? ( + + {numSelected} +  selected + + ) : ( + {title} + )} +
+ +
+ +
+ {numSelected > 0 ? children : } +
+ +); + +export default withStyles(toolbarStyles as any)(ListingToolbar) as any; diff --git a/src/Listing/index.js b/src/Listing/index.js deleted file mode 100644 index 549251b..0000000 --- a/src/Listing/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Listing from './Listing' - -export default Listing diff --git a/src/Listing/index.ts b/src/Listing/index.ts new file mode 100644 index 0000000..07d8f28 --- /dev/null +++ b/src/Listing/index.ts @@ -0,0 +1,3 @@ +import Listing from "./Listing"; + +export default Listing; diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx deleted file mode 100644 index c332347..0000000 --- a/src/Menu/Menu.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { - Divider, - List, - withStyles, -} from '@material-ui/core' - -import MenuItem from './MenuItem' - -const styles = theme => ({ - root: { - width: '100%', - maxWidth: 360, - background: theme.palette.background.paper, - }, -}) - -const Menu = ({ data, redirectTo, classes }) => ( -
- - {data.map((item) => { - switch (item.type) { - case 'divider': - return - - default: - return ( - - ) - } - })} - -
-) - -Menu.propTypes = { - redirectTo: PropTypes.func.isRequired, - data: PropTypes.arrayOf(PropTypes.object).isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -export default withStyles(styles)(Menu) diff --git a/src/Menu/Menu.test.jsx b/src/Menu/Menu.test.jsx deleted file mode 100644 index dc52515..0000000 --- a/src/Menu/Menu.test.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react' -import { mount } from 'enzyme' - -import Icon from '@material-ui/icons/AccessAlarm' - -import Menu from './' - -it('renders correctly', () => { - const tree = mount(( - { }} - /> - )) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Menu/Menu.test.tsx b/src/Menu/Menu.test.tsx new file mode 100644 index 0000000..1c9c8a5 --- /dev/null +++ b/src/Menu/Menu.test.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { mount } from "enzyme"; + +import Icon from "@material-ui/icons/AccessAlarm"; + +import Menu from "."; + +it("renders correctly", () => { + const tree = mount( + {}} + />, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Menu/Menu.tsx b/src/Menu/Menu.tsx new file mode 100644 index 0000000..12ff73a --- /dev/null +++ b/src/Menu/Menu.tsx @@ -0,0 +1,59 @@ +import React, { FunctionComponent } from "react"; +import { Divider, List, withStyles } from "@material-ui/core"; +import MenuItem from "./MenuItem"; + +const styles = (theme: any) => ({ + root: { + width: "100%", + maxWidth: 360, + background: theme.palette.background.paper, + }, +}); + +type MenuItemLink = { + type: "link"; + url: string; + title: string; + isDisabled?: boolean; + icon?: FunctionComponent; +}; + +type MenuItemDivider = { + type: "divider"; +}; + +export type MenuDataItem = MenuItemLink | MenuItemDivider; + +type MenuProps = { + redirectTo: (...args: any[]) => any; + data: MenuDataItem[]; + classes: { + [key: string]: string; + }; +}; + +const Menu: React.SFC = ({ data, redirectTo, classes }) => ( +
+ + {data.map((item, index) => { + switch (item.type) { + case "divider": + return ; + default: + return ( + + ); + } + })} + +
+); + +export default withStyles(styles as any)(Menu); diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx deleted file mode 100644 index f6cbcbd..0000000 --- a/src/Menu/MenuItem.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react' -import PropTypes, { any } from 'prop-types' -import classNames from 'classnames' - -import { - ListItem, - ListItemIcon, - ListItemText, - withStyles, -} from '@material-ui/core' - -const styles = { - isDisabled: { - opacity: 0.5, - pointerEvents: 'none', - }, -} - -const MenuItem = ({ - redirectTo, - url, - title, - icon: Icon, - isDisabled, - classes, -}) => ( - redirectTo(url)} - className={classNames({ - [classes.isDisabled]: isDisabled, - })} - > - {Icon ? ( - - - - ) : null} - - -) - -MenuItem.propTypes = { - redirectTo: PropTypes.func.isRequired, - url: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - isDisabled: PropTypes.bool, - icon: PropTypes.objectOf(any), - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -MenuItem.defaultProps = { - isDisabled: false, - icon: null, -} - -export default withStyles(styles)(MenuItem) diff --git a/src/Menu/MenuItem.test.jsx b/src/Menu/MenuItem.test.jsx deleted file mode 100644 index c5c9111..0000000 --- a/src/Menu/MenuItem.test.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import Icon from '@material-ui/icons/AccessAlarm' - -import MenuItem from './MenuItem' - -it('renders correctly', () => { - const tree = shallow( {}} - />) - - expect(tree).toMatchSnapshot() -}) - -it('renders correctly with icon', () => { - const tree = shallow( { }} - icon={Icon} - />) - - expect(tree).toMatchSnapshot() -}) - -it('renders correctly disabled', () => { - const tree = shallow( {}} - isDisabled - />) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Menu/MenuItem.test.tsx b/src/Menu/MenuItem.test.tsx new file mode 100644 index 0000000..f9354d1 --- /dev/null +++ b/src/Menu/MenuItem.test.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import Icon from "@material-ui/icons/AccessAlarm"; + +import MenuItem from "./MenuItem"; + +it("renders correctly", () => { + const tree = shallow( + {}} />, + ); + + expect(tree).toMatchSnapshot(); +}); + +it("renders correctly with icon", () => { + const tree = shallow( + {}} icon={Icon} />, + ); + + expect(tree).toMatchSnapshot(); +}); + +it("renders correctly disabled", () => { + const tree = shallow( + {}} isDisabled />, + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Menu/MenuItem.tsx b/src/Menu/MenuItem.tsx new file mode 100644 index 0000000..6db99af --- /dev/null +++ b/src/Menu/MenuItem.tsx @@ -0,0 +1,57 @@ +import React, { FunctionComponent } from "react"; +import classNames from "classnames"; +import { + ListItem, + ListItemIcon, + ListItemText, + withStyles, +} from "@material-ui/core"; + +const styles = { + isDisabled: { + opacity: 0.5, + pointerEvents: "none", + }, +}; + +type MenuItemProps = { + redirectTo: (...args: any[]) => any; + url: string; + title: string; + isDisabled?: boolean; + icon?: FunctionComponent; + classes: { + [key: string]: string; + }; +}; + +const MenuItem: React.SFC = ({ + redirectTo, + url, + title, + icon: Icon, + isDisabled, + classes, +}) => ( + redirectTo(url)} + className={classNames({ + [classes.isDisabled]: isDisabled, + })} + > + {Icon && ( + + + + )} + + + +); + +MenuItem.defaultProps = { + isDisabled: false, +}; + +export default withStyles(styles as any)(MenuItem); diff --git a/src/Menu/index.jsx b/src/Menu/index.jsx deleted file mode 100644 index 7e045e4..0000000 --- a/src/Menu/index.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import Menu from './Menu' - -export default Menu diff --git a/src/Menu/index.ts b/src/Menu/index.ts new file mode 100644 index 0000000..6c3fc9d --- /dev/null +++ b/src/Menu/index.ts @@ -0,0 +1,3 @@ +import Menu from "./Menu"; + +export default Menu; diff --git a/src/NoMatch/NoMatch.jsx b/src/NoMatch/NoMatch.jsx deleted file mode 100644 index 97b0b0f..0000000 --- a/src/NoMatch/NoMatch.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { Fragment } from 'react' -import PropTypes from 'prop-types' -import { Link } from 'react-router-dom' - -import { - Typography, - withStyles, -} from '@material-ui/core' - -const styles = theme => ({ - root: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - maxWidth: '60rem', - paddingLeft: theme.spacing(3), - paddingRight: theme.spacing(3), - marginLeft: 'auto', - marginRight: 'auto', - height: 'calc(100vh - 104px)', - }, -}) - -const NoMatch = ({ title, description, classes }) => ( -
-
- - {title} - - - {description} - -
-
-) - -NoMatch.propTypes = { - title: PropTypes.string, - description: PropTypes.node, - classes: PropTypes.objectOf(PropTypes.string).isRequired, -} - -NoMatch.defaultProps = { - title: '404! Sorry, not found.', - description: ( - - This URL does not exist, sorry. Please start over from - the Dashboard. - - ), -} - -export default withStyles(styles)(NoMatch) diff --git a/src/NoMatch/NoMatch.test.jsx b/src/NoMatch/NoMatch.test.jsx deleted file mode 100644 index b062ee6..0000000 --- a/src/NoMatch/NoMatch.test.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import { BrowserRouter as Router } from 'react-router-dom' -import { shallow } from 'enzyme' - -import NoMatch from '.' - -it('renders correctly', () => { - const tree = shallow(( - - - - )) - - expect(tree).toMatchSnapshot() -}) - -it('renders correctly with description', () => { - const tree = shallow(Desc

- )} - />) - - expect(tree).toMatchSnapshot() -}) diff --git a/src/NoMatch/NoMatch.test.tsx b/src/NoMatch/NoMatch.test.tsx new file mode 100644 index 0000000..aede1ef --- /dev/null +++ b/src/NoMatch/NoMatch.test.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { BrowserRouter as Router } from "react-router-dom"; +import { shallow } from "enzyme"; + +import NoMatch from "."; + +it("renders correctly", () => { + const tree = shallow( + + + , + ); + + expect(tree).toMatchSnapshot(); +}); + +it("renders correctly with description", () => { + const tree = shallow(Desc

} />); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/NoMatch/NoMatch.tsx b/src/NoMatch/NoMatch.tsx new file mode 100644 index 0000000..bad22d5 --- /dev/null +++ b/src/NoMatch/NoMatch.tsx @@ -0,0 +1,51 @@ +import React, { Fragment } from "react"; +import { Link } from "react-router-dom"; +import { Typography, withStyles } from "@material-ui/core"; + +const styles = (theme: any) => ({ + root: { + display: "flex", + justifyContent: "center", + alignItems: "center", + maxWidth: "60rem", + paddingLeft: theme.spacing(3), + paddingRight: theme.spacing(3), + marginLeft: "auto", + marginRight: "auto", + height: "calc(100vh - 104px)", + }, +}); + +type NoMatchProps = { + title?: string; + description?: React.ReactNode; + classes: { + [key: string]: string; + }; +}; + +const NoMatch: React.SFC = ({ title, description, classes }) => ( +
+
+ {title} + + {description} + +
+
+); + +NoMatch.defaultProps = { + title: "404! Sorry, not found.", + description: ( + + This URL does not exist, sorry. Please start over from the{" "} + + Dashboard + + . + + ), +}; + +export default withStyles(styles)(NoMatch); diff --git a/src/NoMatch/index.js b/src/NoMatch/index.js deleted file mode 100644 index c8760ca..0000000 --- a/src/NoMatch/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import NoMatch from './NoMatch' - -export default NoMatch diff --git a/src/NoMatch/index.ts b/src/NoMatch/index.ts new file mode 100644 index 0000000..71fa195 --- /dev/null +++ b/src/NoMatch/index.ts @@ -0,0 +1,3 @@ +import NoMatch from "./NoMatch"; + +export default NoMatch; diff --git a/src/Snackbar/Snackbar.jsx b/src/Snackbar/Snackbar.jsx deleted file mode 100644 index 7757260..0000000 --- a/src/Snackbar/Snackbar.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import { Snackbar as MaterialSnackbar } from '@material-ui/core' - -const Snackbar = ({ - isOpen, - message, -}) => ( - {message}} - /> -) - -Snackbar.propTypes = { - isOpen: PropTypes.bool, - message: PropTypes.string.isRequired, -} - -Snackbar.defaultProps = { - isOpen: false, -} - -export default Snackbar diff --git a/src/Snackbar/Snackbar.test.jsx b/src/Snackbar/Snackbar.test.jsx deleted file mode 100644 index 389a7fe..0000000 --- a/src/Snackbar/Snackbar.test.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { shallow } from 'enzyme' - -import Snackbar from '.' - -it('renders correctly', () => { - const tree = shallow() - - expect(tree).toMatchSnapshot() -}) diff --git a/src/Snackbar/Snackbar.test.tsx b/src/Snackbar/Snackbar.test.tsx new file mode 100644 index 0000000..548fa83 --- /dev/null +++ b/src/Snackbar/Snackbar.test.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import Snackbar from "."; + +it("renders correctly", () => { + const tree = shallow(); + + expect(tree).toMatchSnapshot(); +}); diff --git a/src/Snackbar/Snackbar.tsx b/src/Snackbar/Snackbar.tsx new file mode 100644 index 0000000..3495f5c --- /dev/null +++ b/src/Snackbar/Snackbar.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { Snackbar as MaterialSnackbar } from "@material-ui/core"; + +type SnackbarProps = { + isOpen?: boolean; + message: string; +}; + +const Snackbar: React.SFC = ({ isOpen, message }) => ( + {message}} + /> +); + +Snackbar.defaultProps = { + isOpen: false, +}; + +export default Snackbar; diff --git a/src/Snackbar/index.d.ts b/src/Snackbar/index.d.ts deleted file mode 100644 index 426ec13..0000000 --- a/src/Snackbar/index.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from "react" -import { StandardProps } from ".." - -export interface ISnackbarProps - extends StandardProps< - React.HTMLAttributes, - SnackbarClassKey - > { - isOpen: boolean - message: React.ReactElement | string -} - -export type SnackbarClassKey = "root" - -declare const Snackbar: React.ComponentType - -export default Snackbar diff --git a/src/Snackbar/index.js b/src/Snackbar/index.js deleted file mode 100644 index 60953ff..0000000 --- a/src/Snackbar/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Snackbar from './Snackbar' - -export default Snackbar diff --git a/src/Snackbar/index.ts b/src/Snackbar/index.ts new file mode 100644 index 0000000..6422c63 --- /dev/null +++ b/src/Snackbar/index.ts @@ -0,0 +1,3 @@ +import Snackbar from "./Snackbar"; + +export default Snackbar; diff --git a/src/Tabs/Tabs.test.jsx b/src/Tabs/Tabs.test.jsx deleted file mode 100644 index d03d093..0000000 --- a/src/Tabs/Tabs.test.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react' -import MockRouter from 'react-mock-router' -import Enzyme, { shallow } from 'enzyme' -import Adapter from 'enzyme-adapter-react-16' -import { Tab } from '@material-ui/core' - -import Tabs from '.' -import tabsContent from '../tests/data/tabs' - -Enzyme.configure({ adapter: new Adapter() }) - -describe('Tabs', () => { - it('renders correctly', () => { - const tree = shallow(( - - Content

- ), - }]} - /> -
- )) - - expect(tree).toMatchSnapshot() - }) - - it('renders correctly if no data', () => { - const tree = shallow(( - - - - )) - - expect(tree).toMatchSnapshot() - }) - - it('click second tab', () => { - const tabAt = 1 - const tabs = shallow() - - expect(window.location.hash).toBe('') - tabs.find(Tab).at(tabAt).simulate('click') - expect(window.location.hash).toBe(`#/${tabsContent[tabAt].id}`) - }) - - it('test if correct element is active', () => { - const tabAt = 1 - window.location.hash = `#/${tabsContent[tabAt].id}` - - shallow() - - expect(window.location.hash).toBe(`#/${tabsContent[tabAt].id}`) - }) -}) diff --git a/src/Tabs/Tabs.test.tsx b/src/Tabs/Tabs.test.tsx new file mode 100644 index 0000000..3f8d382 --- /dev/null +++ b/src/Tabs/Tabs.test.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import MockRouter from "react-mock-router"; +import Enzyme, { shallow } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import { Tab } from "@material-ui/core"; + +import Tabs from "."; +import tabsContent from "../tests/data/tabs"; + +Enzyme.configure({ adapter: new Adapter() }); + +describe("Tabs", () => { + it("renders correctly", () => { + const tree = shallow( + + Content

, + }, + ]} + /> +
, + ); + + expect(tree).toMatchSnapshot(); + }); + + it("renders correctly if no data", () => { + const tree = shallow( + + + , + ); + + expect(tree).toMatchSnapshot(); + }); + + it("click second tab", () => { + const tabAt = 1; + const tabs = shallow( + + + , + ); + + expect(window.location.hash).toBe(""); + tabs.find(Tab).at(tabAt).simulate("click"); + expect(window.location.hash).toBe(`#/${tabsContent[tabAt].id}`); + }); + + it("test if correct element is active", () => { + const tabAt = 1; + window.location.hash = `#/${tabsContent[tabAt].id}`; + + shallow( + + + , + ); + + expect(window.location.hash).toBe(`#/${tabsContent[tabAt].id}`); + }); +}); diff --git a/src/Tabs/Tabs.jsx b/src/Tabs/Tabs.tsx similarity index 53% rename from src/Tabs/Tabs.jsx rename to src/Tabs/Tabs.tsx index 186606a..bff16c2 100644 --- a/src/Tabs/Tabs.jsx +++ b/src/Tabs/Tabs.tsx @@ -1,86 +1,80 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { withRouter } from 'react-router-dom' +import React from "react"; +import { withRouter } from "react-router-dom"; import { AppBar, withStyles, Tabs as MaterialTabs, Tab, Typography, -} from '@material-ui/core' +} from "@material-ui/core"; -function TabContainer(props) { +const TabContainer: React.SFC<{}> = (props) => { return ( {props.children} - ) -} - -TabContainer.propTypes = { - children: PropTypes.node.isRequired, -} + ); +}; -const styles = theme => ({ +const styles = (theme: any) => ({ root: { flexGrow: 1, - width: '100%', + width: "100%", marginTop: theme.spacing() * 3, backgroundColor: theme.palette.background.paper, }, -}) +}); -class Tabs extends React.Component { - static propTypes = { - isScrollable: PropTypes.bool, - data: PropTypes.arrayOf(PropTypes.object).isRequired, - classes: PropTypes.objectOf(PropTypes.string).isRequired, - } +type TabsProps = { + isScrollable?: boolean; + data: Record[]; + classes: { + [key: string]: string; + }; +}; - static defaultProps = { - isScrollable: false, - } +type TabsState = { + value?: number; +}; - constructor() { - super() +class Tabs extends React.Component { + constructor(props: TabsProps) { + super(props); - this.handleChange = this.handleChange.bind(this) + this.handleChange = this.handleChange.bind(this); } state = { value: 0, - } + }; componentDidMount() { - const { data } = this.props - const hash = window.location.hash.replace('#/', '') - let value = 0 + const { data } = this.props; + const hash = window.location.hash.replace("#/", ""); + let value = 0; if (data && data.constructor === Array) { data.forEach((item, index) => { if (item.id === hash) { - value = index + value = index; } - }) + }); } this.setState({ value, - }) + }); } - handleChange(event, value) { - const hash = this.props.data[value].id || value - - window.location.hash = `#/${hash}` - - this.setState({ value }) + handleChange(event: any, value: any) { + const hash = this.props.data[value].id || value; + window.location.hash = `#/${hash}`; + this.setState({ value }); } render() { - const { isScrollable, data, classes } = this.props - const { value } = this.state - + const { isScrollable, data, classes } = this.props; + const { value } = this.state; return (
@@ -91,7 +85,7 @@ class Tabs extends React.Component { variant={isScrollable ? "scrollable" : undefined} scrollButtons="auto" > - {data.map(item => ( + {data.map((item) => ( {item.content} - ) + ); } - - return null + return null; })}
- ) + ); } } +const tabsWithStyles = withStyles(styles)(Tabs); -const tabsWithStyles = withStyles(styles)(Tabs) -export default withRouter(tabsWithStyles) +export default withRouter(tabsWithStyles as any) as any; diff --git a/src/Tabs/index.js b/src/Tabs/index.js deleted file mode 100644 index d094ea3..0000000 --- a/src/Tabs/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Tabs from './Tabs' - -export default Tabs diff --git a/src/Tabs/index.ts b/src/Tabs/index.ts new file mode 100644 index 0000000..a5881f1 --- /dev/null +++ b/src/Tabs/index.ts @@ -0,0 +1,3 @@ +import Tabs from "./Tabs"; + +export default Tabs; diff --git a/src/app.js b/src/app.js deleted file mode 100644 index 09e2720..0000000 --- a/src/app.js +++ /dev/null @@ -1,16 +0,0 @@ -export { default as AddButton } from './AddButton' -export { default as AppContainer } from './AppContainer' -export { default as BackButton } from './BackButton' -export { default as Base } from './Base' -export { default as Confirm } from './Confirm' -export { default as CookieInfo } from './CookieInfo' -export { default as Drawer } from './Drawer' -export { default as Snackbar } from './Snackbar' -export { default as Form } from './Form' -export { default as Header } from './Header' -export { default as Dashboard } from './Dashboard' -export { default as Listing } from './Listing' -export { default as Menu } from './Menu' -export { default as NoMatch } from './NoMatch' -export { default as Tabs } from './Tabs' -export { TYPES } from './Form/constants' diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..3dd9a91 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,16 @@ +export { default as AddButton } from "./AddButton"; +export { default as AppContainer } from "./AppContainer"; +export { default as BackButton } from "./BackButton"; +export { default as Base } from "./Base"; +export { default as Confirm } from "./Confirm"; +export { default as CookieInfo } from "./CookieInfo"; +export { default as Drawer } from "./Drawer"; +export { default as Snackbar } from "./Snackbar"; +export { default as Form } from "./Form"; +export { default as Header } from "./Header"; +export { default as Dashboard } from "./Dashboard"; +export { default as Listing } from "./Listing"; +export { default as Menu } from "./Menu"; +export { default as NoMatch } from "./NoMatch"; +export { default as Tabs } from "./Tabs"; +export { TYPES } from "./Form/constants"; diff --git a/src/index.d.ts b/src/index.d.ts deleted file mode 100644 index 9f1618f..0000000 --- a/src/index.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from "react" - -/** - * All standard components exposed by `backoffice` are `StyledComponents` with - * certain `classes`, on which one can also set a top-level `className` and inline - * `style`. - */ -export type StandardProps = Omit< - C & { classes: any }, - "classes" | Removals - > & - IStyledComponentProps & { - className?: string - style?: Partial, - } - -export type ClassNameMap = Record - -export interface IStyledComponentProps { - classes?: Partial> - innerRef?: React.Ref -} - -/** @internal */ -type Diff = ({[P in T]: P } & - {[P in U]: never } & { [x: string]: never })[T] - -/** @internal */ -export type Omit = Pick> diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index 6431bc5..6d3309e 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1 +1,3 @@ /// + +declare module "vanilla-store"; diff --git a/src/tests/Container.jsx b/src/tests/Container.jsx deleted file mode 100644 index 14b43cf..0000000 --- a/src/tests/Container.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react' -import { - BrowserRouter as Router, - Route, - Switch, -} from 'react-router-dom' -import Typography from '@material-ui/core/Typography' - -import indigo from '@material-ui/core/colors/indigo' -import amber from '@material-ui/core/colors/amber' - -import menuData from './data/menu' - -import AppContainer from '../AppContainer' -import Base from '../Base' -import NoMatch from '../NoMatch' -import CookieInfo from '../CookieInfo' - -import Page from './Page' -import General from './General' - -const theme = { - palette: { - primary: { - light: indigo[300], - main: indigo[500], - dark: indigo[700], - }, - secondary: { - light: amber[300], - main: amber[500], - dark: amber[700], - }, - }, -} - -const Container = () => ( - - - - ( - - - - - )} - /> - - ( - - - - - - This is the cookie info - - - - )} - /> - - - -) - -export default Container diff --git a/src/tests/Container.tsx b/src/tests/Container.tsx new file mode 100644 index 0000000..2feccd2 --- /dev/null +++ b/src/tests/Container.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; +import Typography from "@material-ui/core/Typography"; +import indigo from "@material-ui/core/colors/indigo"; +import amber from "@material-ui/core/colors/amber"; +import menuData from "./data/menu"; +import AppContainer from "../AppContainer"; +import Base from "../Base"; +import NoMatch from "../NoMatch"; +import CookieInfo from "../CookieInfo"; +import Page from "./Page"; +import General from "./General"; + +const theme = { + palette: { + primary: { + light: indigo[300], + main: indigo[500], + dark: indigo[700], + }, + secondary: { + light: amber[300], + main: amber[500], + dark: amber[700], + }, + }, +}; + +const Container = () => ( + + + + ( + + + + + )} + /> + + ( + + + + + This is the cookie info + + + )} + /> + + + +); +export default Container; diff --git a/src/tests/General.jsx b/src/tests/General.jsx deleted file mode 100644 index 43db5d1..0000000 --- a/src/tests/General.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { Fragment } from 'react' -import Typography from '@material-ui/core/Typography' -import Button from '@material-ui/core/Button/Button' - -import CookieInfo from '../CookieInfo' -import Confirm from '../Confirm' - -class General extends React.Component { - constructor() { - super() - - this.state = { - dialogOpen: false, - } - - this.handleOpenDialoge = this.handleOpenDialoge.bind(this) - } - - handleOpenDialoge() { - this.setState({ - dialogOpen: true, - }) - } - - render() { - const { dialogOpen } = this.state - - return ( - - - - This is the cookie info - - - - - Confirm - - - {}} - /> - - ) - } -} - -export default General diff --git a/src/tests/General.tsx b/src/tests/General.tsx new file mode 100644 index 0000000..40d3878 --- /dev/null +++ b/src/tests/General.tsx @@ -0,0 +1,45 @@ +import React, { Fragment } from "react"; +import { Typography, Button } from "@material-ui/core"; +import CookieInfo from "../CookieInfo"; +import Confirm from "../Confirm"; + +type GeneralState = { + dialogOpen: boolean; +}; + +class General extends React.Component<{}, GeneralState> { + constructor(props: any) { + super(props); + + this.state = { + dialogOpen: false, + }; + this.handleOpenDialoge = this.handleOpenDialoge.bind(this); + } + handleOpenDialoge() { + this.setState({ + dialogOpen: true, + }); + } + + render() { + const { dialogOpen } = this.state; + return ( + + + This is the cookie info + + + Confirm + + {}} + /> + + ); + } +} + +export default General; diff --git a/src/tests/Page.jsx b/src/tests/Page.tsx similarity index 56% rename from src/tests/Page.jsx rename to src/tests/Page.tsx index 27fd8ee..fc7cb9a 100644 --- a/src/tests/Page.jsx +++ b/src/tests/Page.tsx @@ -1,82 +1,79 @@ -import React, { Fragment } from 'react' -import PropTypes from 'prop-types' -import Typography from '@material-ui/core/Typography' -import withStyles from '@material-ui/core/styles/withStyles' -import Tooltip from '@material-ui/core/Tooltip' -import IconButton from '@material-ui/core/IconButton' -import Button from '@material-ui/core/Button' -import DeleteIcon from '@material-ui/icons/Delete' +import React, { Fragment } from "react"; +import Typography from "@material-ui/core/Typography"; +import withStyles from "@material-ui/core/styles/withStyles"; +import Tooltip from "@material-ui/core/Tooltip"; +import IconButton from "@material-ui/core/IconButton"; +import Button from "@material-ui/core/Button"; +import DeleteIcon from "@material-ui/icons/Delete"; -import Dashboard from '../Dashboard' -import dashboardData from './data/dashboard' +import Dashboard from "../Dashboard"; +import dashboardData from "./data/dashboard"; -import Menu from '../Menu' -import menuData from './data/menu' +import Menu from "../Menu"; +import menuData from "./data/menu"; -import Listing from '../Listing' -import listingData from './data/listing_data' -import listingHeaders from './data/listing_headers' +import Listing from "../Listing"; +import listingData from "./data/listing_data"; +import listingHeaders from "./data/listing_headers"; -import Form from '../Form' -import formData from './data/form' +import Form from "../Form"; +import formData from "./data/form"; -import AddButton from '../AddButton' -import BackButton from '../BackButton' +import AddButton from "../AddButton"; +import BackButton from "../BackButton"; -import Tabs from '../Tabs' -import tabData from './data/tabs' +import Tabs from "../Tabs"; +import tabData from "./data/tabs"; -const noop = () => {} +const noop = () => {}; -const styles = theme => ({ +const styles = (theme: any) => ({ headline: { marginTop: theme.spacing(4), }, -}) +}); -class Page extends React.Component { - static propTypes = { - classes: PropTypes.objectOf(PropTypes.string).isRequired, - } +interface Props { + classes: { + [key: string]: string; + }; +} - constructor() { - super() +class Page extends React.Component { + constructor(props: Props) { + super(props); - this.handleFormButtonClick = this.handleFormButtonClick.bind(this) + this.handleFormButtonClick = this.handleFormButtonClick.bind(this); } state = { formData, - additionalValue: null, - } + additionalValue: undefined, + }; componentDidMount() { - const { formData: newFormData, additionalValue } = this.state + const { formData: newFormData, additionalValue } = this.state; - newFormData[0].data[0].getAdditionalValue = value => ( - additionalValue || value - ) + (newFormData[0].data[0] as any).getAdditionalValue = (value: any) => + additionalValue || value; this.setState({ formData: newFormData, - }) + }); } handleFormButtonClick() { this.setState({ - additionalValue: 'New value', - }) + additionalValue: "New value", + }); } render() { - const { classes, ...props } = this.props + const { classes, ...props } = this.props; return ( - + Listing @@ -89,14 +86,16 @@ class Page extends React.Component { orderBy="username" onClick={noop} hasLoader - onUpdateSelection={(selection) => { console.log(selection) }} - toolbarContent={( + onUpdateSelection={(selection: any) => { + console.log(selection); + }} + toolbarContent={ - )} + } /> @@ -119,10 +118,10 @@ class Page extends React.Component { - + Back Button + - ) + ); } } -export default withStyles(styles)(Page) +export default withStyles(styles)(Page); diff --git a/src/tests/data/dashboard.js b/src/tests/data/dashboard.js deleted file mode 100644 index f185a95..0000000 --- a/src/tests/data/dashboard.js +++ /dev/null @@ -1,22 +0,0 @@ -export default { - title: 'Backoffice', - description: 'Visual Testing for Backoffice Framework', - groups: [{ - id: 'list', - title: 'List', - cards: [{ - id: 'card-1', - title: 'Card 1', - description: 'Manage this card’s page', - link: '/some-link', - icon: null, - }, { - id: 'card-2', - title: 'Card 2', - description: 'Manage this card’s page', - link: '/some-link', - icon: null, - isDisabled: true, - }], - }], -} diff --git a/src/tests/data/dashboard.ts b/src/tests/data/dashboard.ts new file mode 100644 index 0000000..d22ac1a --- /dev/null +++ b/src/tests/data/dashboard.ts @@ -0,0 +1,27 @@ +export default { + title: "Backoffice", + description: "Visual Testing for Backoffice Framework", + groups: [ + { + id: "list", + title: "List", + cards: [ + { + id: "card-1", + title: "Card 1", + description: "Manage this card’s page", + link: "/some-link", + icon: null, + }, + { + id: "card-2", + title: "Card 2", + description: "Manage this card’s page", + link: "/some-link", + icon: null, + isDisabled: true, + }, + ], + }, + ], +}; diff --git a/src/tests/data/form.jsx b/src/tests/data/form.jsx deleted file mode 100644 index 3334bb8..0000000 --- a/src/tests/data/form.jsx +++ /dev/null @@ -1,196 +0,0 @@ -import React, { Fragment } from 'react' - -import Icon from '@material-ui/icons/Visibility' -import { - Divider, - Typography, -} from '@material-ui/core' - -import { TYPES } from '../../Form/constants' - -export default [{ - group: true, - title: 'Form', - id: 'base', - data: [{ - id: 'text', - title: 'Text Field', - type: TYPES.TEXT, - width: 'small', - isRequired: true, - }, { - id: 'select', - title: 'Select', - type: TYPES.SELECT, - options: ['Foo', 'Bar', 'Baz'], - width: 'small', - isRequired: true, - }, { - id: 'number', - title: 'Number', - type: TYPES.NUMBER, - value: 10, - width: 'small', - }, { - id: 'multiline', - title: 'Multiline', - type: 'multiline', - rows: 5, - }, { - id: 'list', - title: 'List', - type: TYPES.LIST, - width: 'mid', - value: ['Foo', 'Bar', 'Baz'], - completeFrom: [{ - title: 'Foo', - tooltip: 'Foo-Baz', - }, - 'Bar', - { - title: 'Baz', - tooltip: 'Foo-Baz', - }, - { - title: 'Froot', - }, - { - title: 'Foobar', - tooltip: 'Foo-Baz', - }, - { - title: 'Barbaz', - tooltip: 'Foo-Baz', - }, - { - title: 'Foobaz', - }], - }, { - id: 'free-list', - title: 'Free List', - type: TYPES.LIST, - width: 'mid', - }, { - id: 'nested', - group: true, - title: 'Nested Form', - integrated: true, - isVisible: true, - data: [{ - id: 'nested-text', - title: 'Nested Text', - type: TYPES.TEXT, - iconEnd: (), - width: 'mid', - }, { - id: 'empty', - type: TYPES.EMPTY, - width: 'mid', - }], - }, { - id: 'content', - type: TYPES.CONTENT, - content: ( - - Content - - - ), - }, { - id: 'email', - title: 'Email', - type: TYPES.EMAIL, - width: 'mid', - beforeSubmit: (url) => { - if (url) { - return `${url}#top` - } - - return url - }, - }, { - id: 'password', - title: 'Password', - type: TYPES.PASSWORD, - width: 'mid', - }, { - id: 'url', - title: 'URL', - type: TYPES.URL, - width: 'mid', - helperText: 'Should be URL with trailing slash', - validators: [{ - validator: value => value && value.substr(-1, 1) === '/', - message: 'Please add a trailing slash to your URL', - }], - }, { - id: 'empty', - type: TYPES.EMAIL, - width: 'mid', - }], -}, { - group: true, - title: 'Form - Date', - id: 'date', - data: [{ - id: 'date', - title: 'Date', - type: TYPES.DATE, - format: 'DD.MM.YYYY', - value: 1514989682669, - width: 'small', - validators: ['date'], - }, { - id: 'time', - title: 'Time', - type: TYPES.TIME, - format: 'hh:mm a', - value: 1514989682669, - width: 'small', - validators: ['date'], - }, { - id: 'datetime', - title: 'Datetime', - type: TYPES.DATETIME, - format: 'DD.MM.YYYY, hh:mm a', - value: 1514989682669, - width: 'small', - validators: ['date'], - }], -}, { - group: true, - title: 'Form - Disabled fields', - id: 'disabled', - data: [{ - id: 'disabled', - title: 'Disabled Text', - type: TYPES.TEXT, - width: 'mid', - isDisabled: true, - }, { - id: 'disabled-select', - title: 'Disabled Select', - type: TYPES.SELECT, - width: 'mid', - options: ['Foo', 'Bar', 'Baz'], - isDisabled: true, - }], -}, { - group: true, - title: 'Form - Switches, Radio Buttons and Checkboxes', - id: 'switches', - data: [{ - id: 'switch', - title: 'Switch', - type: TYPES.SWITCH, - helperText: 'Display Helper Text', - width: 'small', - }, { - id: 'switch-1', - title: 'Switch - Disabled', - type: TYPES.SWITCH, - helperText: 'Display Helper Text', - width: 'small', - isDisabled: true, - }], -}] diff --git a/src/tests/data/form.tsx b/src/tests/data/form.tsx new file mode 100644 index 0000000..fb6567b --- /dev/null +++ b/src/tests/data/form.tsx @@ -0,0 +1,228 @@ +import React, { Fragment } from "react"; + +import Icon from "@material-ui/icons/Visibility"; +import { Divider, Typography } from "@material-ui/core"; + +import { TYPES } from "../../Form/constants"; + +export default [ + { + group: true, + title: "Form", + id: "base", + data: [ + { + id: "text", + title: "Text Field", + type: TYPES.TEXT, + width: "small", + isRequired: true, + }, + { + id: "select", + title: "Select", + type: TYPES.SELECT, + options: ["Foo", "Bar", "Baz"], + width: "small", + isRequired: true, + }, + { + id: "number", + title: "Number", + type: TYPES.NUMBER, + value: 10, + width: "small", + }, + { + id: "multiline", + title: "Multiline", + type: "multiline", + rows: 5, + }, + { + id: "list", + title: "List", + type: TYPES.LIST, + width: "mid", + value: ["Foo", "Bar", "Baz"], + completeFrom: [ + { + title: "Foo", + tooltip: "Foo-Baz", + }, + "Bar", + { + title: "Baz", + tooltip: "Foo-Baz", + }, + { + title: "Froot", + }, + { + title: "Foobar", + tooltip: "Foo-Baz", + }, + { + title: "Barbaz", + tooltip: "Foo-Baz", + }, + { + title: "Foobaz", + }, + ], + }, + { + id: "free-list", + title: "Free List", + type: TYPES.LIST, + width: "mid", + }, + { + id: "nested", + group: true, + title: "Nested Form", + integrated: true, + isVisible: true, + data: [ + { + id: "nested-text", + title: "Nested Text", + type: TYPES.TEXT, + iconEnd: , + width: "mid", + }, + { + id: "empty", + type: TYPES.EMPTY, + width: "mid", + }, + ], + }, + { + id: "content", + type: TYPES.CONTENT, + content: ( + + Content + + + ), + }, + { + id: "email", + title: "Email", + type: TYPES.EMAIL, + width: "mid", + beforeSubmit: (url?: string) => { + if (url) { + return `${url}#top`; + } + + return url; + }, + }, + { + id: "password", + title: "Password", + type: TYPES.PASSWORD, + width: "mid", + }, + { + id: "url", + title: "URL", + type: TYPES.URL, + width: "mid", + helperText: "Should be URL with trailing slash", + validators: [ + { + validator: (value?: string) => value && value.substr(-1, 1) === "/", + message: "Please add a trailing slash to your URL", + }, + ], + }, + { + id: "empty", + type: TYPES.EMAIL, + width: "mid", + }, + ], + }, + { + group: true, + title: "Form - Date", + id: "date", + data: [ + { + id: "date", + title: "Date", + type: TYPES.DATE, + format: "DD.MM.YYYY", + value: 1514989682669, + width: "small", + validators: ["date"], + }, + { + id: "time", + title: "Time", + type: TYPES.TIME, + format: "hh:mm a", + value: 1514989682669, + width: "small", + validators: ["date"], + }, + { + id: "datetime", + title: "Datetime", + type: TYPES.DATETIME, + format: "DD.MM.YYYY, hh:mm a", + value: 1514989682669, + width: "small", + validators: ["date"], + }, + ], + }, + { + group: true, + title: "Form - Disabled fields", + id: "disabled", + data: [ + { + id: "disabled", + title: "Disabled Text", + type: TYPES.TEXT, + width: "mid", + isDisabled: true, + }, + { + id: "disabled-select", + title: "Disabled Select", + type: TYPES.SELECT, + width: "mid", + options: ["Foo", "Bar", "Baz"], + isDisabled: true, + }, + ], + }, + { + group: true, + title: "Form - Switches, Radio Buttons and Checkboxes", + id: "switches", + data: [ + { + id: "switch", + title: "Switch", + type: TYPES.SWITCH, + helperText: "Display Helper Text", + width: "small", + }, + { + id: "switch-1", + title: "Switch - Disabled", + type: TYPES.SWITCH, + helperText: "Display Helper Text", + width: "small", + isDisabled: true, + }, + ], + }, +]; diff --git a/src/tests/data/listing_data.js b/src/tests/data/listing_data.js deleted file mode 100644 index 0c45621..0000000 --- a/src/tests/data/listing_data.js +++ /dev/null @@ -1,253 +0,0 @@ -export default [{ - id: 1, - name: 'Leanne Graham', - username: 'Bret', - email: 'Sincere@april.biz', - address: { - street: 'Kulas Light', - suite: 'Apt. 556', - city: 'Gwenborough', - zipcode: '92998-3874', - geo: { - lat: '-37.3159', - lng: '81.1496', - }, - }, - phone: '1-770-736-8031 x56442', - website: 'hildegard.org', - company: { - name: 'Romaguera-Crona', - catchPhrase: 'Multi-layered client-server neural-net', - bs: 'harness real-time e-markets', - }, -}, -{ - id: 2, - name: 'Ervin Howell', - username: 'Antonette', - email: 'Shanna@melissa.tv', - address: { - street: 'Victor Plains', - suite: 'Suite 879', - city: 'Wisokyburgh', - zipcode: '90566-7771', - geo: { - lat: '-43.9509', - lng: '-34.4618', - }, - }, - phone: '010-692-6593 x09125', - website: 'anastasia.net', - company: { - name: 'Deckow-Crist', - catchPhrase: 'Proactive didactic contingency', - bs: 'synergize scalable supply-chains', - }, -}, -{ - id: 3, - name: 'Clementine Bauch', - username: 'Samantha', - email: 'Nathan@yesenia.net', - address: { - street: 'Douglas Extension', - suite: 'Suite 847', - city: 'McKenziehaven', - zipcode: '59590-4157', - geo: { - lat: '-68.6102', - lng: '-47.0653', - }, - }, - phone: '1-463-123-4447', - website: 'ramiro.info', - company: { - name: 'Romaguera-Jacobson', - catchPhrase: 'Face to face bifurcated interface', - bs: 'e-enable strategic applications', - }, -}, -{ - id: 4, - name: 'Patricia Lebsack', - username: 'Karianne', - email: 'Julianne.OConner@kory.org', - address: { - street: 'Hoeger Mall', - suite: 'Apt. 692', - city: 'South Elvis', - zipcode: '53919-4257', - geo: { - lat: '29.4572', - lng: '-164.2990', - }, - }, - phone: '493-170-9623 x156', - website: 'kale.biz', - company: { - name: 'Robel-Corkery', - catchPhrase: 'Multi-tiered zero tolerance productivity', - bs: 'transition cutting-edge web services', - }, -}, -{ - id: 5, - name: 'Chelsey Dietrich', - username: 'Kamren', - email: 'Lucio_Hettinger@annie.ca', - address: { - street: 'Skiles Walks', - suite: 'Suite 351', - city: 'Roscoeview', - zipcode: '33263', - geo: { - lat: '-31.8129', - lng: '62.5342', - }, - }, - phone: '(254)954-1289', - website: 'demarco.info', - company: { - name: 'Keebler LLC', - catchPhrase: 'User-centric fault-tolerant solution', - bs: 'revolutionize end-to-end systems', - }, -}, -{ - id: 6, - name: 'Mrs. Dennis Schulist', - username: 'Leopoldo_Corkery', - email: 'Karley_Dach@jasper.info', - address: { - street: 'Norberto Crossing', - suite: 'Apt. 950', - city: 'South Christy', - zipcode: '23505-1337', - geo: { - lat: '-71.4197', - lng: '71.7478', - }, - }, - phone: '1-477-935-8478 x6430', - website: 'ola.org', - company: { - name: 'Considine-Lockman', - catchPhrase: 'Synchronised bottom-line interface', - bs: 'e-enable innovative applications', - }, -}, -{ - id: 7, - name: 'Kurtis Weissnat', - username: 'Elwyn.Skiles', - email: 'Telly.Hoeger@billy.biz', - address: { - street: 'Rex Trail', - suite: 'Suite 280', - city: 'Howemouth', - zipcode: '58804-1099', - geo: { - lat: '24.8918', - lng: '21.8984', - }, - }, - phone: '210.067.6132', - website: 'elvis.io', - company: { - name: 'Johns Group', - catchPhrase: 'Configurable multimedia task-force', - bs: 'generate enterprise e-tailers', - }, -}, -{ - id: 8, - name: 'Nicholas Runolfsdottir V', - username: 'Maxime_Nienow', - email: 'Sherwood@rosamond.me', - address: { - street: 'Ellsworth Summit', - suite: 'Suite 729', - city: 'Aliyaview', - zipcode: '45169', - geo: { - lat: '-14.3990', - lng: '-120.7677', - }, - }, - phone: '586.493.6943 x140', - website: 'jacynthe.com', - company: { - name: 'Abernathy Group', - catchPhrase: 'Implemented secondary concept', - bs: 'e-enable extensible e-tailers', - }, -}, -{ - id: 9, - name: 'Glenna Reichert', - username: 'Delphine', - email: 'Chaim_McDermott@dana.io', - address: { - street: 'Dayna Park', - suite: 'Suite 449', - city: 'Bartholomebury', - zipcode: '76495-3109', - geo: { - lat: '24.6463', - lng: '-168.8889', - }, - }, - phone: '(775)976-6794 x41206', - website: 'conrad.com', - company: { - name: 'Yost and Sons', - catchPhrase: 'Switchable contextually-based project', - bs: 'aggregate real-time technologies', - }, -}, -{ - id: 10, - name: 'Clementina DuBuque', - username: 'Moriah.Stanton', - email: 'Rey.Padberg@karina.biz', - address: { - street: 'Kattie Turnpike', - suite: 'Suite 198', - city: 'Lebsackbury', - zipcode: '31428-2261', - geo: { - lat: '-38.2386', - lng: '57.2232', - }, - }, - phone: '024-648-3804', - website: 'ambrose.net', - company: { - name: 'Hoeger LLC', - catchPhrase: 'Centralized empowering task-force', - bs: 'target end-to-end models', - }, -}, -{ - id: 11, - name: 'Mr. Dan Schulist', - username: 'Dan.Schulist', - email: 'Dan.Schulist@karina.biz', - address: { - street: 'Kattie Turnpike', - suite: 'Suite 198', - city: 'Lebsackbury', - zipcode: '31428-2261', - geo: { - lat: '-38.2386', - lng: '57.2232', - }, - }, - phone: '024-648-3804', - website: 'ambrose.net', - company: { - name: 'Hoeger LLC', - catchPhrase: 'Centralized empowering task-force', - bs: 'target end-to-end models', - }, -}] diff --git a/src/tests/data/listing_data.ts b/src/tests/data/listing_data.ts new file mode 100644 index 0000000..89d8ffa --- /dev/null +++ b/src/tests/data/listing_data.ts @@ -0,0 +1,255 @@ +export default [ + { + id: 1, + name: "Leanne Graham", + username: "Bret", + email: "Sincere@april.biz", + address: { + street: "Kulas Light", + suite: "Apt. 556", + city: "Gwenborough", + zipcode: "92998-3874", + geo: { + lat: "-37.3159", + lng: "81.1496", + }, + }, + phone: "1-770-736-8031 x56442", + website: "hildegard.org", + company: { + name: "Romaguera-Crona", + catchPhrase: "Multi-layered client-server neural-net", + bs: "harness real-time e-markets", + }, + }, + { + id: 2, + name: "Ervin Howell", + username: "Antonette", + email: "Shanna@melissa.tv", + address: { + street: "Victor Plains", + suite: "Suite 879", + city: "Wisokyburgh", + zipcode: "90566-7771", + geo: { + lat: "-43.9509", + lng: "-34.4618", + }, + }, + phone: "010-692-6593 x09125", + website: "anastasia.net", + company: { + name: "Deckow-Crist", + catchPhrase: "Proactive didactic contingency", + bs: "synergize scalable supply-chains", + }, + }, + { + id: 3, + name: "Clementine Bauch", + username: "Samantha", + email: "Nathan@yesenia.net", + address: { + street: "Douglas Extension", + suite: "Suite 847", + city: "McKenziehaven", + zipcode: "59590-4157", + geo: { + lat: "-68.6102", + lng: "-47.0653", + }, + }, + phone: "1-463-123-4447", + website: "ramiro.info", + company: { + name: "Romaguera-Jacobson", + catchPhrase: "Face to face bifurcated interface", + bs: "e-enable strategic applications", + }, + }, + { + id: 4, + name: "Patricia Lebsack", + username: "Karianne", + email: "Julianne.OConner@kory.org", + address: { + street: "Hoeger Mall", + suite: "Apt. 692", + city: "South Elvis", + zipcode: "53919-4257", + geo: { + lat: "29.4572", + lng: "-164.2990", + }, + }, + phone: "493-170-9623 x156", + website: "kale.biz", + company: { + name: "Robel-Corkery", + catchPhrase: "Multi-tiered zero tolerance productivity", + bs: "transition cutting-edge web services", + }, + }, + { + id: 5, + name: "Chelsey Dietrich", + username: "Kamren", + email: "Lucio_Hettinger@annie.ca", + address: { + street: "Skiles Walks", + suite: "Suite 351", + city: "Roscoeview", + zipcode: "33263", + geo: { + lat: "-31.8129", + lng: "62.5342", + }, + }, + phone: "(254)954-1289", + website: "demarco.info", + company: { + name: "Keebler LLC", + catchPhrase: "User-centric fault-tolerant solution", + bs: "revolutionize end-to-end systems", + }, + }, + { + id: 6, + name: "Mrs. Dennis Schulist", + username: "Leopoldo_Corkery", + email: "Karley_Dach@jasper.info", + address: { + street: "Norberto Crossing", + suite: "Apt. 950", + city: "South Christy", + zipcode: "23505-1337", + geo: { + lat: "-71.4197", + lng: "71.7478", + }, + }, + phone: "1-477-935-8478 x6430", + website: "ola.org", + company: { + name: "Considine-Lockman", + catchPhrase: "Synchronised bottom-line interface", + bs: "e-enable innovative applications", + }, + }, + { + id: 7, + name: "Kurtis Weissnat", + username: "Elwyn.Skiles", + email: "Telly.Hoeger@billy.biz", + address: { + street: "Rex Trail", + suite: "Suite 280", + city: "Howemouth", + zipcode: "58804-1099", + geo: { + lat: "24.8918", + lng: "21.8984", + }, + }, + phone: "210.067.6132", + website: "elvis.io", + company: { + name: "Johns Group", + catchPhrase: "Configurable multimedia task-force", + bs: "generate enterprise e-tailers", + }, + }, + { + id: 8, + name: "Nicholas Runolfsdottir V", + username: "Maxime_Nienow", + email: "Sherwood@rosamond.me", + address: { + street: "Ellsworth Summit", + suite: "Suite 729", + city: "Aliyaview", + zipcode: "45169", + geo: { + lat: "-14.3990", + lng: "-120.7677", + }, + }, + phone: "586.493.6943 x140", + website: "jacynthe.com", + company: { + name: "Abernathy Group", + catchPhrase: "Implemented secondary concept", + bs: "e-enable extensible e-tailers", + }, + }, + { + id: 9, + name: "Glenna Reichert", + username: "Delphine", + email: "Chaim_McDermott@dana.io", + address: { + street: "Dayna Park", + suite: "Suite 449", + city: "Bartholomebury", + zipcode: "76495-3109", + geo: { + lat: "24.6463", + lng: "-168.8889", + }, + }, + phone: "(775)976-6794 x41206", + website: "conrad.com", + company: { + name: "Yost and Sons", + catchPhrase: "Switchable contextually-based project", + bs: "aggregate real-time technologies", + }, + }, + { + id: 10, + name: "Clementina DuBuque", + username: "Moriah.Stanton", + email: "Rey.Padberg@karina.biz", + address: { + street: "Kattie Turnpike", + suite: "Suite 198", + city: "Lebsackbury", + zipcode: "31428-2261", + geo: { + lat: "-38.2386", + lng: "57.2232", + }, + }, + phone: "024-648-3804", + website: "ambrose.net", + company: { + name: "Hoeger LLC", + catchPhrase: "Centralized empowering task-force", + bs: "target end-to-end models", + }, + }, + { + id: 11, + name: "Mr. Dan Schulist", + username: "Dan.Schulist", + email: "Dan.Schulist@karina.biz", + address: { + street: "Kattie Turnpike", + suite: "Suite 198", + city: "Lebsackbury", + zipcode: "31428-2261", + geo: { + lat: "-38.2386", + lng: "57.2232", + }, + }, + phone: "024-648-3804", + website: "ambrose.net", + company: { + name: "Hoeger LLC", + catchPhrase: "Centralized empowering task-force", + bs: "target end-to-end models", + }, + }, +]; diff --git a/src/tests/data/listing_headers.jsx b/src/tests/data/listing_headers.jsx deleted file mode 100644 index ba805f2..0000000 --- a/src/tests/data/listing_headers.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' - -export default [{ - id: 'name', - isPaddingDisabled: true, - label: 'Name', - isSearchable: true, -}, { - id: 'username', - isPaddingDisabled: true, - label: 'Username', - isSearchable: true, -}, { - id: 'phone', - isPaddingDisabled: false, - label: 'Phone', - isSearchable: true, - isNumeric: true, -}, { - id: 'website', - isPaddingDisabled: true, - label: 'Website', - transformContent: (website) => { - if (website) { - return ( - {website} - ) - } - - return website - }, -}, { - id: 'company', - isPaddingDisabled: true, - label: 'Company', - transformContent: (company) => { - if (company.name) { - return company.name - } - - return '' - }, - isSearchable: true, -}] diff --git a/src/tests/data/listing_headers.tsx b/src/tests/data/listing_headers.tsx new file mode 100644 index 0000000..4ff7c65 --- /dev/null +++ b/src/tests/data/listing_headers.tsx @@ -0,0 +1,48 @@ +import React from "react"; + +export default [ + { + id: "name", + isPaddingDisabled: true, + label: "Name", + isSearchable: true, + }, + { + id: "username", + isPaddingDisabled: true, + label: "Username", + isSearchable: true, + }, + { + id: "phone", + isPaddingDisabled: false, + label: "Phone", + isSearchable: true, + isNumeric: true, + }, + { + id: "website", + isPaddingDisabled: true, + label: "Website", + transformContent: (website?: string) => { + if (website) { + return {website}; + } + + return website; + }, + }, + { + id: "company", + isPaddingDisabled: true, + label: "Company", + transformContent: (company: Record) => { + if (company.name) { + return company.name; + } + + return ""; + }, + isSearchable: true, + }, +]; diff --git a/src/tests/data/menu.js b/src/tests/data/menu.js deleted file mode 100644 index 80ecddf..0000000 --- a/src/tests/data/menu.js +++ /dev/null @@ -1,20 +0,0 @@ -import BugIcon from '@material-ui/icons/BugReport' - -export default [{ - type: 'link', - url: '/', - title: 'Dashboard', - icon: null, -}, { - type: 'link', - url: '/portfolio', - title: 'New portfolio', - isDisabled: true, -}, { - type: 'divider', -}, { - type: 'link', - url: '/bug', - title: 'Report a bug', - icon: BugIcon, -}] diff --git a/src/tests/data/menu.ts b/src/tests/data/menu.ts new file mode 100644 index 0000000..0f773cd --- /dev/null +++ b/src/tests/data/menu.ts @@ -0,0 +1,25 @@ +import BugIcon from "@material-ui/icons/BugReport"; + +export default [ + { + type: "link", + url: "/", + title: "Dashboard", + icon: null, + }, + { + type: "link", + url: "/portfolio", + title: "New portfolio", + isDisabled: true, + }, + { + type: "divider", + }, + { + type: "link", + url: "/bug", + title: "Report a bug", + icon: BugIcon, + }, +]; diff --git a/src/tests/data/tabs.jsx b/src/tests/data/tabs.jsx deleted file mode 100644 index 332f2fe..0000000 --- a/src/tests/data/tabs.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react' - -export default [{ - id: 'item-1', - title: 'Item One', - content: ( -

Tab One

- ), -}, { - id: 'item-2', - title: 'Item Two', - content: ( -

Tab Two

- ), -}, { - id: 'item-3', - title: 'Item Three', - content: ( -

Tab Three

- ), -}, { - id: 'item-4', - title: 'Item Four', - content: ( -

Tab Four

- ), -}, { - id: 'item-5', - title: 'Item Five', - content: ( -

Tab Five

- ), -}, { - id: 'item-6', - title: 'Item Six', - content: ( -

Tab Six

- ), -}, { - id: 'item-7', - title: 'Item Seven', - content: ( -

Tab Seven

- ), -}] diff --git a/src/tests/data/tabs.tsx b/src/tests/data/tabs.tsx new file mode 100644 index 0000000..ea32898 --- /dev/null +++ b/src/tests/data/tabs.tsx @@ -0,0 +1,39 @@ +import React from "react"; + +export default [ + { + id: "item-1", + title: "Item One", + content:

Tab One

, + }, + { + id: "item-2", + title: "Item Two", + content:

Tab Two

, + }, + { + id: "item-3", + title: "Item Three", + content:

Tab Three

, + }, + { + id: "item-4", + title: "Item Four", + content:

Tab Four

, + }, + { + id: "item-5", + title: "Item Five", + content:

Tab Five

, + }, + { + id: "item-6", + title: "Item Six", + content:

Tab Six

, + }, + { + id: "item-7", + title: "Item Seven", + content:

Tab Seven

, + }, +]; diff --git a/src/utils/easeInOutQuad.js b/src/utils/easeInOutQuad.ts similarity index 58% rename from src/utils/easeInOutQuad.js rename to src/utils/easeInOutQuad.ts index aa2bbfa..7753c14 100644 --- a/src/utils/easeInOutQuad.js +++ b/src/utils/easeInOutQuad.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -const easeInOutQuad = (t, b, c, d) => { +const easeInOutQuad = (t: number, b: number, c: number, d: number) => { t /= d / 2 if (t < 1) return c / 2 * t * t + b t-- @@ -7,4 +7,4 @@ const easeInOutQuad = (t, b, c, d) => { }; /* eslint-enable */ -export default easeInOutQuad +export default easeInOutQuad; diff --git a/src/utils/replace.js b/src/utils/replace.js deleted file mode 100644 index 0c199c9..0000000 --- a/src/utils/replace.js +++ /dev/null @@ -1,18 +0,0 @@ -export default (string, searchValue, replaceValue) => { - if (!searchValue) { - return string - } - - const search = searchValue.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') - - if (typeof replaceValue === 'function') { - const index = string.search(new RegExp(search, 'gi')) - const replaceString = string - .substring(index) - .substring(0, searchValue.length) - - return string.replace(new RegExp(search, 'gi'), replaceValue(replaceString)) - } - - return string.replace(new RegExp(search, 'gi'), replaceValue) -} diff --git a/src/utils/replace.ts b/src/utils/replace.ts new file mode 100644 index 0000000..3989b2a --- /dev/null +++ b/src/utils/replace.ts @@ -0,0 +1,25 @@ +export default ( + string: string, + searchValue: string, + replaceValue: string | ((replace: string) => string), +) => { + if (!searchValue) { + return string; + } + + const search = searchValue.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); + + if (typeof replaceValue === "function") { + const index = string.search(new RegExp(search, "gi")); + const replaceString = string + .substring(index) + .substring(0, searchValue.length); + + return string.replace( + new RegExp(search, "gi"), + replaceValue(replaceString), + ); + } + + return string.replace(new RegExp(search, "gi"), replaceValue); +}; diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..c8a4b5d --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "jsx": "react", + "module": "commonjs", + "moduleResolution": "node", + "resolveJsonModule": true, + "sourceMap": true, + "strict": true, + "target": "es5", + "declaration": true, + "noImplicitAny": false, + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "skipLibCheck": true, + "outDir": "./dist/" + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "build", + "dist", + "src/**/*test*" + ] +} diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 3aa73bd..0000000 --- a/tslint.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": [ - "tslint:latest", - "tslint-react" - ], - "rules": { - "semicolon": [ - true, - "never" - ] - } -} diff --git a/yarn.lock b/yarn.lock index 823f7bb..ef26724 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,22 +2,6 @@ # yarn lockfile v1 -"@babel/cli@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.8.4.tgz#505fb053721a98777b2b175323ea4f090b7d3c1c" - integrity sha512-XXLgAm6LBbaNxaGhMAznXXaxtCWfuv6PIDJ9Alsy9JYTOh+j2jJz+L/162kkfU1j/pTSxK1xGmlwI4pdIMkoag== - dependencies: - commander "^4.0.1" - convert-source-map "^1.1.0" - fs-readdir-recursive "^1.1.0" - glob "^7.0.0" - lodash "^4.17.13" - make-dir "^2.1.0" - slash "^2.0.0" - source-map "^0.5.0" - optionalDependencies: - chokidar "^2.1.8" - "@babel/code-frame@7.8.3", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" @@ -56,28 +40,6 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.6.tgz#d9aa1f580abf3b2286ef40b6904d390904c63376" - integrity sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.6" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helpers" "^7.9.6" - "@babel/parser" "^7.9.6" - "@babel/template" "^7.8.6" - "@babel/traverse" "^7.9.6" - "@babel/types" "^7.9.6" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - "@babel/generator@^7.4.0", "@babel/generator@^7.9.0", "@babel/generator@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" @@ -88,16 +50,6 @@ lodash "^4.17.13" source-map "^0.5.0" -"@babel/generator@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43" - integrity sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ== - dependencies: - "@babel/types" "^7.9.6" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - "@babel/helper-annotate-as-pure@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" @@ -308,15 +260,6 @@ "@babel/traverse" "^7.9.0" "@babel/types" "^7.9.0" -"@babel/helpers@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.6.tgz#092c774743471d0bb6c7de3ad465ab3d3486d580" - integrity sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw== - dependencies: - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.9.6" - "@babel/types" "^7.9.6" - "@babel/highlight@^7.8.3": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" @@ -331,11 +274,6 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== -"@babel/parser@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.6.tgz#3b1bbb30dabe600cd72db58720998376ff653bc7" - integrity sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q== - "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" @@ -345,7 +283,7 @@ "@babel/helper-remap-async-to-generator" "^7.8.3" "@babel/plugin-syntax-async-generators" "^7.8.0" -"@babel/plugin-proposal-class-properties@7.8.3", "@babel/plugin-proposal-class-properties@^7.8.3": +"@babel/plugin-proposal-class-properties@7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz#5e06654af5cd04b608915aada9b2a6788004464e" integrity sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA== @@ -993,7 +931,7 @@ "@babel/plugin-transform-react-jsx-self" "^7.9.0" "@babel/plugin-transform-react-jsx-source" "^7.9.0" -"@babel/preset-react@^7.0.0", "@babel/preset-react@^7.9.4": +"@babel/preset-react@^7.0.0": version "7.9.4" resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.9.4.tgz#c6c97693ac65b6b9c0b4f25b948a8f665463014d" integrity sha512-AxylVB3FXeOTQXNXyiuAQJSvss62FEotbX2Pzx3K/7c+MKJMdSg6Ose6QYllkdCFA8EInCJVw7M/o5QbLuA4ZQ== @@ -1059,21 +997,6 @@ globals "^11.1.0" lodash "^4.17.13" -"@babel/traverse@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.6.tgz#5540d7577697bf619cc57b92aa0f1c231a94f442" - integrity sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.6" - "@babel/helper-function-name" "^7.9.5" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.6" - "@babel/types" "^7.9.6" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - "@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" @@ -1083,15 +1006,6 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@babel/types@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7" - integrity sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA== - dependencies: - "@babel/helper-validator-identifier" "^7.9.5" - lodash "^4.17.13" - to-fast-properties "^2.0.0" - "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1324,6 +1238,16 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" +"@jest/types@^25.5.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" + integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + "@material-ui/core@^4.9.13": version "4.9.13" resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.9.13.tgz#024962bcdda05139e1bad17a1815bf4088702b15" @@ -1580,11 +1504,38 @@ dependencies: "@babel/types" "^7.3.0" +"@types/cheerio@*": + version "0.22.18" + resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.18.tgz#19018dceae691509901e339d63edf1e935978fe6" + integrity sha512-Fq7R3fINAPSdUEhOyjG4iVxgHrOnqDJbY0/BUuiN0pvD/rfmZWekVZnv+vcs8TtpA2XF50uv50LaE4EnpEL/Hw== + dependencies: + "@types/node" "*" + +"@types/classnames@^2.2.10": + version "2.2.10" + resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999" + integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ== + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/enzyme-adapter-react-16@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.6.tgz#8aca7ae2fd6c7137d869b6616e696d21bb8b0cec" + integrity sha512-VonDkZ15jzqDWL8mPFIQnnLtjwebuL9YnDkqeCDYnB4IVgwUm0mwKkqhrxLL6mb05xm7qqa3IE95m8CZE9imCg== + dependencies: + "@types/enzyme" "*" + +"@types/enzyme@*", "@types/enzyme@^3.10.5": + version "3.10.5" + resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.10.5.tgz#fe7eeba3550369eed20e7fb565bfb74eec44f1f0" + integrity sha512-R+phe509UuUYy9Tk0YlSbipRpfVtIzb/9BHn5pTEtjJTF5LXvUjrIQcZvNyANNEyFrd2YGs196PniNT1fgvOQA== + dependencies: + "@types/cheerio" "*" + "@types/react" "*" + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -1604,6 +1555,16 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/history@*": + version "4.7.5" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" + integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== + +"@types/is-url@^1.2.28": + version "1.2.28" + resolved "https://registry.yarnpkg.com/@types/is-url/-/is-url-1.2.28.tgz#914dabd50546d9b0142806e42c72bc7c2b7e0787" + integrity sha1-kU2r1QVG2bAUKAbkLHK8fCt+B4c= + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -1624,6 +1585,14 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/jest@^25.2.1": + version "25.2.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.1.tgz#9544cd438607955381c1bdbdb97767a249297db5" + integrity sha512-msra1bCaAeEdkSyA0CZ6gW1ukMIvZ5YoJkdXw/qhQdsuuDlFTcEUrUw8CLCPt2rVRUfXlClVvK2gvPs9IokZaA== + dependencies: + jest-diff "^25.2.1" + pretty-format "^25.2.1" + "@types/json-schema@^7.0.3": version "7.0.4" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" @@ -1654,6 +1623,23 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== +"@types/react-router-dom@^5.1.5": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090" + integrity sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.7" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.7.tgz#e9d12ed7dcfc79187e4d36667745b69a5aa11556" + integrity sha512-2ouP76VQafKjtuc0ShpwUebhHwJo0G6rhahW9Pb8au3tQTjYXd2jta4wv6U2tGLR/I42yuG00+UXjNYY0dTzbg== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-transition-group@^4.2.0": version "4.2.4" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.2.4.tgz#c7416225987ccdb719262766c1483da8f826838d" @@ -1693,6 +1679,21 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^15.0.0": + version "15.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.4.tgz#7e5d0f8ca25e9d5849f2ea443cf7c402decd8299" + integrity sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin-tslint@^2.30.0": + version "2.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin-tslint/-/eslint-plugin-tslint-2.30.0.tgz#408e356066add27af23dbeebdb67459a67d9799a" + integrity sha512-phARGRY1SyAkG9uVhF7o0yjK1eqmGYCwM7JpNkOo/50d68ZG0V/P9VyYMSKAj+IbRlZ/k2rKFibKZvfLJPcFGw== + dependencies: + "@typescript-eslint/experimental-utils" "2.30.0" + lodash "^4.17.15" + "@typescript-eslint/eslint-plugin@^2.10.0": version "2.27.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.27.0.tgz#e479cdc4c9cf46f96b4c287755733311b0d0ba4b" @@ -1703,6 +1704,16 @@ regexpp "^3.0.0" tsutils "^3.17.1" +"@typescript-eslint/eslint-plugin@^2.30.0": + version "2.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.30.0.tgz#312a37e80542a764d96e8ad88a105316cdcd7b05" + integrity sha512-PGejii0qIZ9Q40RB2jIHyUpRWs1GJuHP1pkoCiaeicfwO9z7Fx03NQzupuyzAmv+q9/gFNHu7lo1ByMXe8PNyg== + dependencies: + "@typescript-eslint/experimental-utils" "2.30.0" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + tsutils "^3.17.1" + "@typescript-eslint/experimental-utils@2.27.0": version "2.27.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz#801a952c10b58e486c9a0b36cf21e2aab1e9e01a" @@ -1713,6 +1724,16 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" +"@typescript-eslint/experimental-utils@2.30.0": + version "2.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.30.0.tgz#9845e868c01f3aed66472c561d4b6bac44809dd0" + integrity sha512-L3/tS9t+hAHksy8xuorhOzhdefN0ERPDWmR9CclsIGOUqGKy6tqc/P+SoXeJRye5gazkuPO0cK9MQRnolykzkA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.30.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + "@typescript-eslint/parser@^2.10.0": version "2.27.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.27.0.tgz#d91664335b2c46584294e42eb4ff35838c427287" @@ -1723,6 +1744,16 @@ "@typescript-eslint/typescript-estree" "2.27.0" eslint-visitor-keys "^1.1.0" +"@typescript-eslint/parser@^2.30.0": + version "2.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.30.0.tgz#7681c305a6f4341ae2579f5e3a75846c29eee9ce" + integrity sha512-9kDOxzp0K85UnpmPJqUzdWaCNorYYgk1yZmf4IKzpeTlSAclnFsrLjfwD9mQExctLoLoGAUXq1co+fbr+3HeFw== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "2.30.0" + "@typescript-eslint/typescript-estree" "2.30.0" + eslint-visitor-keys "^1.1.0" + "@typescript-eslint/typescript-estree@2.27.0": version "2.27.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8" @@ -1736,6 +1767,19 @@ semver "^6.3.0" tsutils "^3.17.1" +"@typescript-eslint/typescript-estree@2.30.0": + version "2.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.30.0.tgz#1b8e848b55144270255ffbfe4c63291f8f766615" + integrity sha512-nI5WOechrA0qAhnr+DzqwmqHsx7Ulr/+0H7bWCcClDhhWkSyZR5BmTvnBEyONwJCTWHfc5PAQExX24VD26IAVw== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^6.3.0" + tsutils "^3.17.1" + "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" @@ -2069,7 +2113,7 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== @@ -2205,7 +2249,7 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -asap@~2.0.3, asap@~2.0.6: +asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -2733,11 +2777,6 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -2892,7 +2931,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2920,11 +2959,6 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -change-emitter@^0.1.2: - version "0.1.6" - resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" - integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU= - chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -3173,12 +3207,12 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.12.1, commander@^2.19.0, commander@^2.20.0: +commander@^2.11.0, commander@^2.19.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^4.0.1, commander@^4.1.1: +commander@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== @@ -3292,7 +3326,7 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@1.7.0, convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.7.0: +convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== @@ -3344,11 +3378,6 @@ core-js-pure@^3.0.0: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= - core-js@^2.4.0: version "2.6.11" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" @@ -3886,10 +3915,10 @@ diff-sequences@^24.9.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diff-sequences@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" + integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== diffie-hellman@^5.0.0: version "5.0.3" @@ -4127,13 +4156,6 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= - dependencies: - iconv-lite "~0.4.13" - end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -4332,6 +4354,31 @@ escodegen@^1.11.0, escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" +eslint-config-airbnb-base@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.1.0.tgz#2ba4592dd6843258221d9bff2b6831bd77c874e4" + integrity sha512-+XCcfGyCnbzOnktDVhwsCAx+9DmrzEmuwxyHUJpw+kqBVT744OUBrB09khgFKlK1lshVww6qXGsYPZpavoNjJw== + dependencies: + confusing-browser-globals "^1.0.9" + object.assign "^4.1.0" + object.entries "^1.1.1" + +eslint-config-airbnb@^18.1.0: + version "18.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-18.1.0.tgz#724d7e93dadd2169492ff5363c5aaa779e01257d" + integrity sha512-kZFuQC/MPnH7KJp6v95xsLBf63G/w7YqdPfQ0MUanxQ7zcKUNG8j+sSY860g3NwCBOa62apw16J6pRN+AOgXzw== + dependencies: + eslint-config-airbnb-base "^14.1.0" + object.assign "^4.1.0" + object.entries "^1.1.1" + +eslint-config-prettier@^6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz#f6d2238c1290d01c859a8b5c1f7d352a0b0da8b1" + integrity sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA== + dependencies: + get-stdin "^6.0.0" + eslint-config-react-app@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz#698bf7aeee27f0cea0139eaef261c7bf7dd623df" @@ -4339,6 +4386,11 @@ eslint-config-react-app@^5.2.1: dependencies: confusing-browser-globals "^1.0.9" +eslint-config-react@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/eslint-config-react/-/eslint-config-react-1.1.7.tgz#a0918d0fc47d0e9bd161a47308021da85d2585b3" + integrity sha1-oJGND8R9DpvRYaRzCAIdqF0lhbM= + eslint-import-resolver-node@^0.3.2: version "0.3.3" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" @@ -4406,12 +4458,19 @@ eslint-plugin-jsx-a11y@6.2.3: has "^1.0.3" jsx-ast-utils "^2.2.1" +eslint-plugin-prettier@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz#ae116a0fc0e598fdae48743a4430903de5b4e6ca" + integrity sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ== + dependencies: + prettier-linter-helpers "^1.0.0" + eslint-plugin-react-hooks@^1.6.1: version "1.7.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04" integrity sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA== -eslint-plugin-react@7.19.0: +eslint-plugin-react@7.19.0, eslint-plugin-react@^7.19.0: version "7.19.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz#6d08f9673628aa69c5559d33489e855d83551666" integrity sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ== @@ -4464,7 +4523,7 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== -eslint@^6.6.0: +eslint@^6.6.0, eslint@^6.8.0: version "6.8.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== @@ -4729,6 +4788,11 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + fast-glob@^2.0.2: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -4772,19 +4836,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fbjs@^0.8.1: - version "0.8.17" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" - integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= - dependencies: - core-js "^1.0.0" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.18" - figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -5047,11 +5098,6 @@ fs-minipass@^2.0.0: dependencies: minipass "^3.0.0" -fs-readdir-recursive@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" - integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== - fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -5124,6 +5170,11 @@ get-own-enumerable-property-symbols@^3.0.0: resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== +get-stdin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" + integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== + get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -5163,7 +5214,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -5380,11 +5431,6 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^2.3.1: - version "2.5.5" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" - integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== - hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.2.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -5569,7 +5615,7 @@ hyphenate-style-name@^1.0.3: resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -6077,7 +6123,7 @@ is-root@2.1.0: resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== -is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -6165,14 +6211,6 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -6284,6 +6322,16 @@ jest-diff@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" +jest-diff@^25.2.1: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" + integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== + dependencies: + chalk "^3.0.0" + diff-sequences "^25.2.6" + jest-get-type "^25.2.6" + pretty-format "^25.5.0" + jest-docblock@^24.3.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" @@ -6358,6 +6406,11 @@ jest-get-type@^24.9.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== +jest-get-type@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" + integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== + jest-haste-map@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" @@ -7656,14 +7709,6 @@ no-case@^3.0.3: lower-case "^2.0.1" tslib "^1.10.0" -node-fetch@^1.0.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - node-fetch@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5" @@ -9072,6 +9117,18 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" + integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== + pretty-bytes@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" @@ -9095,6 +9152,16 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" +pretty-format@^25.2.1, pretty-format@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" + integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ== + dependencies: + "@jest/types" "^25.5.0" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^16.12.0" + private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -9120,13 +9187,6 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - promise@^8.0.3: version "8.1.0" resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" @@ -9402,11 +9462,6 @@ react-jss@^10.1.1: theming "3.2.0" tiny-warning "^1.0.2" -react-lifecycles-compat@^3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - react-mock-router@^1.0.15: version "1.0.15" resolved "https://registry.yarnpkg.com/react-mock-router/-/react-mock-router-1.0.15.tgz#01c823a7ed366e7e834565e879bc72443131b4e1" @@ -9618,18 +9673,6 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -recompose@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.30.0.tgz#82773641b3927e8c7d24a0d87d65aeeba18aabd0" - integrity sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w== - dependencies: - "@babel/runtime" "^7.0.0" - change-emitter "^0.1.2" - fbjs "^0.8.1" - hoist-non-react-statics "^2.3.1" - react-lifecycles-compat "^3.0.2" - symbol-observable "^1.0.4" - recursive-readdir@2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" @@ -10093,7 +10136,7 @@ selfsigned@^1.10.7: dependencies: node-forge "0.9.0" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -10170,7 +10213,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4, setimmediate@^1.0.5: +setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -10814,7 +10857,7 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.0.4, symbol-observable@^1.2.0: +symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== @@ -11053,39 +11096,6 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== -tslint-react@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-5.0.0.tgz#d0ae644e8163bdd3e134012e9353094904e8dd44" - integrity sha512-/IbcSmoBPlFic8kQaRfQ4knTY4mivwo5LVzvozvX6Dyu2ynEnrh1dIcR2ujjyp/IodXqY/H5GbxFxSMo/Kf2Hg== - dependencies: - tsutils "^3.17.1" - -tslint@^6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.2.tgz#2433c248512cc5a7b2ab88ad44a6b1b34c6911cf" - integrity sha512-UyNrLdK3E0fQG/xWNqAFAC5ugtFyPO4JJR1KyyfQAyzR8W0fTRrC91A8Wej4BntFzcvETdCSDa/4PnNYJQLYiA== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.3" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.10.0" - tsutils "^2.29.0" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -11155,11 +11165,6 @@ typescript@^3.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== -ua-parser-js@^0.7.18: - version "0.7.19" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" - integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ== - unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -11568,7 +11573,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: +whatwg-fetch@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==