From 2bddeee53e70c058255a7a93b672052a66e75ef9 Mon Sep 17 00:00:00 2001 From: Duong Pham Date: Wed, 18 Dec 2019 15:29:53 +0700 Subject: [PATCH 01/14] [CLD-552] Upgrade webpack due to security issue --- package.json | 3 ++- yarn.lock | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0615624cd6..411e634667 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,8 @@ "tslint-config-prettier": "^1.18.0", "tslint-config-standard": "^8.0.1", "tslint-plugin-prettier": "^2.0.1", - "typescript": "3.7.2" + "typescript": "3.7.2", + "webpack": "4.41.3" }, "dependencies": { "bulma": "^0.7.5", diff --git a/yarn.lock b/yarn.lock index 84c4071e18..d3b86d09f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12018,6 +12018,11 @@ serialize-javascript@^1.6.1, serialize-javascript@^1.7.0: resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb" integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A== +serialize-javascript@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" + integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== + serve-favicon@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.5.0.tgz#935d240cdfe0f5805307fdfe967d88942a2cbcf0" @@ -12794,6 +12799,21 @@ terser-webpack-plugin@^1.2.4, terser-webpack-plugin@^1.4.1: webpack-sources "^1.4.0" worker-farm "^1.7.0" +terser-webpack-plugin@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" + integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== + dependencies: + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" + schema-utils "^1.0.0" + serialize-javascript "^2.1.2" + source-map "^0.6.1" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" + terser@^3.14.1: version "3.17.0" resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" @@ -13731,6 +13751,35 @@ webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1: source-list-map "^2.0.0" source-map "~0.6.1" +webpack@4.41.3: + version "4.41.3" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.3.tgz#cb7592c43080337dbc9be9e98fc6478eb3981026" + integrity sha512-EcNzP9jGoxpQAXq1VOoTet0ik7/VVU1MovIfcUSAjLowc7GhcQku/sOXALvq5nPpSei2HF6VRhibeJSC3i/Law== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/wasm-edit" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + acorn "^6.2.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" + enhanced-resolve "^4.1.0" + eslint-scope "^4.0.3" + json-parse-better-errors "^1.0.2" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.1" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" + schema-utils "^1.0.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.3" + watchpack "^1.6.0" + webpack-sources "^1.4.1" + webpack@^4.28.4, webpack@^4.33.0, webpack@^4.38.0: version "4.41.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.0.tgz#db6a254bde671769f7c14e90a1a55e73602fc70b" From eb98150fec0d41d1eb32e581e9c6b96759cd3b68 Mon Sep 17 00:00:00 2001 From: Duong Pham Date: Wed, 18 Dec 2019 15:51:37 +0700 Subject: [PATCH 02/14] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 411e634667..7b1aa9f2f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@reapit/elements", - "version": "0.5.17", + "version": "0.5.18", "description": "A collection of React components and utilities for building apps for Reapit Marketplace", "main": "dist/index.js", "umd:main": "dist/elements.umd.production.js", From 6a7f3f9664912e967e4e5cc9dadc4f46d02a90fc Mon Sep 17 00:00:00 2001 From: Duong Pham Date: Wed, 18 Dec 2019 22:02:10 +0700 Subject: [PATCH 03/14] [CLD-552] Upgrade webpack due to security issue --- package.json | 2 +- yarn.lock | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 7b1aa9f2f2..8be22a3457 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "react-router": "^5.1.2", "react-router-dom": "^5.1.2", "rimraf": "^2.7.0", - "rollup": "^1.27.9", + "rollup": "1.27.13", "rollup-plugin-scss": "^1.0.2", "sass-loader": "^7.2.0", "style-loader": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index d3b86d09f5..74727a7eb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5584,11 +5584,16 @@ fast-glob@^3.0.3: merge2 "^1.3.0" micromatch "^4.0.2" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -11609,9 +11614,9 @@ resolve@1.x, resolve@^1.1.6, resolve@^1.11.0, resolve@^1.11.1, resolve@^1.3.2, r path-parse "^1.0.6" resolve@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16" - integrity sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w== + version "1.14.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.0.tgz#6d14c6f9db9f8002071332b600039abf82053f64" + integrity sha512-uviWSi5N67j3t3UKFxej1loCH0VZn5XuqdNxoLShPcYPw6cUZn74K1VRj+9myynRX03bxIBEkwlkob/ujLsJVw== dependencies: path-parse "^1.0.6" @@ -11784,19 +11789,19 @@ rollup-pluginutils@2.6.0: estree-walker "^0.6.0" micromatch "^3.1.10" -rollup@^1.12.0: - version "1.23.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.23.1.tgz#0315a0f5d0dfb056e6363e1dff05b89ac2da6b8e" - integrity sha512-95C1GZQpr/NIA0kMUQmSjuMDQ45oZfPgDBcN0yZwBG7Kee//m7H68vgIyg+SPuyrTZ5PrXfyLK80OzXeKG5dAA== +rollup@1.27.13: + version "1.27.13" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.27.13.tgz#d6d3500512daacbf8de54d2800de62d893085b90" + integrity sha512-hDi7M07MpmNSDE8YVwGVFA8L7n8jTLJ4lG65nMAijAyqBe//rtu4JdxjUBE7JqXfdpqxqDTbCDys9WcqdpsQvw== dependencies: "@types/estree" "*" "@types/node" "*" acorn "^7.1.0" -rollup@^1.27.9: - version "1.27.9" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.27.9.tgz#742f1234c1fa935f35149a433807da675b10f9a6" - integrity sha512-8AfW4cJTPZfG6EXWwT/ujL4owUsDI1Xl8J1t+hvK4wDX81F5I4IbwP9gvGbHzxnV19fnU4rRABZQwZSX9J402Q== +rollup@^1.12.0: + version "1.23.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.23.1.tgz#0315a0f5d0dfb056e6363e1dff05b89ac2da6b8e" + integrity sha512-95C1GZQpr/NIA0kMUQmSjuMDQ45oZfPgDBcN0yZwBG7Kee//m7H68vgIyg+SPuyrTZ5PrXfyLK80OzXeKG5dAA== dependencies: "@types/estree" "*" "@types/node" "*" From 1400327d28772a1d89d2108c16b0d0fdf840218f Mon Sep 17 00:00:00 2001 From: vuhuucuong Date: Mon, 16 Dec 2019 16:24:52 +0700 Subject: [PATCH 04/14] [CLD-586] Create Helper component --- package.json | 1 + src/components/Spreadsheet/index.tsx | 39 +++++++++++++++++++ .../Spreadsheet/spreadsheet.stories.tsx | 5 +++ src/index.tsx | 1 + src/styles/components/spreadsheet.scss | 8 ++++ src/styles/index.scss | 1 + yarn.lock | 5 +++ 7 files changed, 60 insertions(+) create mode 100644 src/components/Spreadsheet/index.tsx create mode 100644 src/components/Spreadsheet/spreadsheet.stories.tsx create mode 100644 src/styles/components/spreadsheet.scss diff --git a/package.json b/package.json index 8be22a3457..0393f5465a 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "jsonwebtoken": "^8.5.1", "pell": "^1.0.6", "prop-types": "^15.7.2", + "react-datasheet": "^1.4.0", "react-datepicker": "^2.9.6", "react-google-map": "^3.1.1", "react-google-maps-loader": "^4.2.5", diff --git a/src/components/Spreadsheet/index.tsx b/src/components/Spreadsheet/index.tsx new file mode 100644 index 0000000000..ec6f9c9b0d --- /dev/null +++ b/src/components/Spreadsheet/index.tsx @@ -0,0 +1,39 @@ +import * as React from 'react' +import ReactDataSheet from 'react-datasheet' + +export type valueType = string + +export interface DataType { + value: valueType + component?: React.ReactNode +} + +export interface GridElement extends ReactDataSheet.Cell { + value: valueType +} +export interface SpreadSheetProps {} + +const data = [ + [{ value: '1', component: }, { value: '3' }], + [{ value: '2' }, { value: '4' }] +] + +class MyReactDataSheet extends ReactDataSheet {} + +export const Spreadsheet: React.FC = () => { + return ( +
+ cell.value} + cellRenderer={({ cell, children, className, style, ...rest }) => { + return ( + + {children} + + ) + }} + /> +
+ ) +} diff --git a/src/components/Spreadsheet/spreadsheet.stories.tsx b/src/components/Spreadsheet/spreadsheet.stories.tsx new file mode 100644 index 0000000000..4815b01ecd --- /dev/null +++ b/src/components/Spreadsheet/spreadsheet.stories.tsx @@ -0,0 +1,5 @@ +import React from 'react' +import { storiesOf } from '@storybook/react' +import { Spreadsheet } from './index' + +storiesOf('Spreadsheet', module).add('Spreadsheet', () => ) diff --git a/src/index.tsx b/src/index.tsx index 276a7e317f..e9873f5713 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -40,6 +40,7 @@ export * from './components/Form' export * from './components/ProgressBar' export * from './components/ToastMessage' export * from './components/Helper' +export * from './components/Spreadsheet' // Utils export * from './utils/validators' diff --git a/src/styles/components/spreadsheet.scss b/src/styles/components/spreadsheet.scss new file mode 100644 index 0000000000..a1cc4b1249 --- /dev/null +++ b/src/styles/components/spreadsheet.scss @@ -0,0 +1,8 @@ +@import 'react-datasheet/lib/react-datasheet'; +@import '../base/colors.scss'; + +.spreadsheet { + & .root-datasheet { + background-color: $white; + } +} diff --git a/src/styles/index.scss b/src/styles/index.scss index 0dae764265..f0558990c2 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -27,3 +27,4 @@ @import './components/pagination.scss'; @import './components/input.scss'; @import './components/helper.scss'; +@import './components/spreadsheet.scss'; diff --git a/yarn.lock b/yarn.lock index 74727a7eb1..10f779e9fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10811,6 +10811,11 @@ react-clientside-effect@^1.2.0: dependencies: "@babel/runtime" "^7.0.0" +react-datasheet@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/react-datasheet/-/react-datasheet-1.4.0.tgz#da251307137a12de3b113c0a1ef62baf114bc3fc" + integrity sha512-MiBYQtvZYAEWN/2gJS84SEL4jNAOIJjdFI5i7AZ7BIx06p2PbjUMN7wgT9QTlhrfgFHAfM5tfgBA6ibDLMyVvA== + react-datepicker@^2.9.6: version "2.9.6" resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-2.9.6.tgz#26190c9f71692149d0d163398aa19e08626444b1" From 0aa37e00b88be85278faaeaddd6e960f13f290e6 Mon Sep 17 00:00:00 2001 From: vuhuucuong Date: Tue, 17 Dec 2019 18:07:05 +0700 Subject: [PATCH 05/14] Double click to select row --- src/components/Spreadsheet/index.tsx | 104 ++++++++++++++---- .../Spreadsheet/spreadsheet.stories.tsx | 36 +++++- src/styles/components/spreadsheet.scss | 10 +- 3 files changed, 126 insertions(+), 24 deletions(-) diff --git a/src/components/Spreadsheet/index.tsx b/src/components/Spreadsheet/index.tsx index ec6f9c9b0d..4b9e5ab35a 100644 --- a/src/components/Spreadsheet/index.tsx +++ b/src/components/Spreadsheet/index.tsx @@ -1,38 +1,102 @@ import * as React from 'react' import ReactDataSheet from 'react-datasheet' -export type valueType = string +export class MyReactDataSheet extends ReactDataSheet {} -export interface DataType { - value: valueType - component?: React.ReactNode +/* CellProps type contain several type like row, col, Cell, ... */ +export interface Cell extends ReactDataSheet.Cell { + /** The value of the cell, always a string */ + value: string + /** The validate function, receive Cell as param, must return boolean */ + validate?: (cell: Cell) => boolean + /* Additional className of cell */ + className?: string } -export interface GridElement extends ReactDataSheet.Cell { - value: valueType +export type CellProps = ReactDataSheet.CellRendererProps + +export interface DoubleClickPayLoad { + row: number + col: number + maxRowIndex: number + maxColIndex: number + isReadOnly?: boolean +} + +export const onDoubleClickCell = (payload: DoubleClickPayLoad, setSelected, onDoubleClickDefault) => ( + e: React.MouseEvent +) => { + onDoubleClickDefault(e) + const { row, col, maxRowIndex, maxColIndex, isReadOnly } = payload + const isFirstRow = row === 0 + const isFirstCol = col === 0 + if (isFirstCol && isFirstRow) { + return + } + if (isFirstCol && isReadOnly) { + setSelected({ + start: { i: row, j: 0 }, + end: { i: row, j: maxColIndex } + }) + return + } + if (isFirstRow && isReadOnly) { + setSelected({ + start: { i: 0, j: col }, + end: { i: maxRowIndex, j: col } + }) + return + } } -export interface SpreadSheetProps {} -const data = [ - [{ value: '1', component: }, { value: '3' }], - [{ value: '2' }, { value: '4' }] -] +export interface SpreadSheetProps extends ReactDataSheet.DataSheetProps {} + +export const customCellRenderer = (data: Cell[][], setSelected) => (props: ReactDataSheet.CellRendererProps) => { + const { style, cell, ...restProps } = props + const { validate = () => true, className = '', readOnly, ...restCell } = cell + /* const isValid = validate(cell) */ + const payload = { + row: props.row, + col: props.col, + maxRowIndex: data.length, + maxColIndex: data[0].length, + isReadOnly: readOnly + } + return ( + + {props.children} + + ) +} + +export const onSelect = setSelected => ({ start, end }) => { + setSelected({ start, end }) +} -class MyReactDataSheet extends ReactDataSheet {} +export const Spreadsheet: React.FC = ({ data, valueRenderer, ...rest }) => { + const [selected, setSelected] = React.useState<{ + start: ReactDataSheet.Location + end: ReactDataSheet.Location + } | null>(null) -export const Spreadsheet: React.FC = () => { + const cellRenderer = React.useCallback(customCellRenderer(data, setSelected), []) return (
cell.value} - cellRenderer={({ cell, children, className, style, ...rest }) => { - return ( - - {children} - - ) + valueRenderer={valueRenderer} + selected={selected} + onSelect={({ start, end }) => { + setSelected({ start, end }) }} + {...rest} + cellRenderer={cellRenderer} />
) diff --git a/src/components/Spreadsheet/spreadsheet.stories.tsx b/src/components/Spreadsheet/spreadsheet.stories.tsx index 4815b01ecd..6225b6a619 100644 --- a/src/components/Spreadsheet/spreadsheet.stories.tsx +++ b/src/components/Spreadsheet/spreadsheet.stories.tsx @@ -1,5 +1,37 @@ -import React from 'react' +import React, { useState } from 'react' import { storiesOf } from '@storybook/react' import { Spreadsheet } from './index' -storiesOf('Spreadsheet', module).add('Spreadsheet', () => ) +const SpreadSheetBasic = () => { + const [data, setData] = useState([ + [ + { readOnly: true, value: '' }, + { value: 'A', readOnly: true }, + { value: 'B', readOnly: true }, + { value: 'C', readOnly: true }, + { value: 'D', readOnly: true } + ], + [{ readOnly: true, value: '1' }, { value: '1' }, { value: '3' }, { value: '3' }, { value: '3' }], + [{ readOnly: true, value: '2' }, { value: '2' }, { value: '4' }, { value: '4' }, { value: '4' }], + [{ readOnly: true, value: '3' }, { value: '1' }, { value: '3' }, { value: '3' }, { value: '3' }], + [{ readOnly: true, value: '4' }, { value: '2' }, { value: '4' }, { value: '4' }, { value: '4' }] + ]) + return ( + cell.value} + overflow="wrap" + onCellsChanged={changes => { + setData(prev => { + const newData = prev.map(row => [...row]) + changes.forEach(({ row, col, value }) => { + newData[row][col] = { ...newData[row][col], value: value as string } + }) + return newData + }) + }} + /> + ) +} + +storiesOf('Spreadsheet', module).add('Spreadsheet', SpreadSheetBasic) diff --git a/src/styles/components/spreadsheet.scss b/src/styles/components/spreadsheet.scss index a1cc4b1249..c0d989154e 100644 --- a/src/styles/components/spreadsheet.scss +++ b/src/styles/components/spreadsheet.scss @@ -2,7 +2,13 @@ @import '../base/colors.scss'; .spreadsheet { - & .root-datasheet { - background-color: $white; + background-color: $white; + .data-grid-container .data-grid { + width: 100%; + margin: auto; + } + & input.data-editor { + width: 100% !important; + height: 100% !important; } } From 60cbca8c27d4dda3abfea80247bf2ae5e5f6560a Mon Sep 17 00:00:00 2001 From: vuhuucuong Date: Wed, 18 Dec 2019 18:28:50 +0700 Subject: [PATCH 06/14] WIP Select option support --- src/components/Spreadsheet/handlers.tsx | 91 +++++++++++ src/components/Spreadsheet/index.tsx | 147 +++++++----------- .../Spreadsheet/spreadsheet.stories.tsx | 64 +++++--- src/components/Spreadsheet/types.tsx | 39 +++++ src/components/Spreadsheet/utils.tsx | 19 +++ src/styles/components/spreadsheet.scss | 46 ++++++ 6 files changed, 293 insertions(+), 113 deletions(-) create mode 100644 src/components/Spreadsheet/handlers.tsx create mode 100644 src/components/Spreadsheet/types.tsx create mode 100644 src/components/Spreadsheet/utils.tsx diff --git a/src/components/Spreadsheet/handlers.tsx b/src/components/Spreadsheet/handlers.tsx new file mode 100644 index 0000000000..a31c5085e6 --- /dev/null +++ b/src/components/Spreadsheet/handlers.tsx @@ -0,0 +1,91 @@ +import * as React from 'react' +import ReactDataSheet from 'react-datasheet' +import { Cell, DoubleClickPayLoad, SelectedMatrix } from './types' +import { getMaxRowAndCol } from './utils' + +export const valueRenderer = (cell: Cell): string => cell.value + +export const onDoubleClickCell = (payload: DoubleClickPayLoad, setSelected, onDoubleClickDefault) => ( + ...args +): boolean => { + onDoubleClickDefault(...args) + const { row, col, maxRowIndex, maxColIndex, isReadOnly } = payload + const isFirstRow = row === 0 + const isFirstCol = col === 0 + if (isFirstCol && isFirstRow) { + return false + } + if (isFirstCol && isReadOnly) { + setSelected({ + start: { i: row, j: 0 }, + end: { i: row, j: maxColIndex } + }) + return true + } + if (isFirstRow && isReadOnly) { + setSelected({ + start: { i: 0, j: col }, + end: { i: maxRowIndex, j: col } + }) + return true + } + return false +} + +export const onSelectCells = setSelected => ({ start, end }: SelectedMatrix) => { + setSelected({ start, end }) +} + +/* export const handleContextMenu: ReactDataSheet.ContextMenuHandler = (e, cell, i, j) => { + console.log('sad') +} */ + +/** all the customization of cell go here */ +export const customCellRenderer = (data: Cell[][], setSelected) => (props: ReactDataSheet.CellRendererProps) => { + const { style, cell, onDoubleClick, ...restProps } = props + const { validate = () => true, className = '', readOnly, selectComponent, ...restCell } = cell + /* const isValid = validate(cell) */ + const [maxRowIndex, maxColIndex] = getMaxRowAndCol(data) + const payload = { + row: props.row, + col: props.col, + maxRowIndex, + maxColIndex, + isReadOnly: readOnly + } + return ( + + {props.children} + + ) +} + +export const handleAddNewRow = (data: Cell[][], setData) => () => { + const [maxRow] = getMaxRowAndCol(data) + const lastRow = data[maxRow - 1] + /* [ + { readOnly: true, value: '' }, + { value: 'A', readOnly: true }, + { value: 'B', readOnly: true }, + { value: 'C', readOnly: true }, + { value: 'D', readOnly: true } + ] */ + /* return new row with same type of last row */ + const newEmptyRow = lastRow.map(e => ({ ...e, value: '' })) + const newData = [...data, newEmptyRow] + setData(newData) +} + +export const handleCellsChanged = (prevData: Cell[][], setData /* setData from useState*/) => changes => { + const newData = prevData.map(row => [...row]) + changes.forEach(({ row, col, value }) => { + newData[row][col] = { ...newData[row][col], value } + }) + setData(newData) +} diff --git a/src/components/Spreadsheet/index.tsx b/src/components/Spreadsheet/index.tsx index 4b9e5ab35a..0f427df3a4 100644 --- a/src/components/Spreadsheet/index.tsx +++ b/src/components/Spreadsheet/index.tsx @@ -1,103 +1,74 @@ import * as React from 'react' -import ReactDataSheet from 'react-datasheet' +import { MyReactDataSheet, Cell, SpreadsheetProps, SelectedMatrix } from './types' +import { + valueRenderer, + onSelectCells, + customCellRenderer, + handleAddNewRow, + handleCellsChanged + /* handleContextMenu */ +} from './handlers' +import { Button } from '../Button' -export class MyReactDataSheet extends ReactDataSheet {} +export const Spreadsheet: React.FC = ({ + data: initialData, + description = '', + hasUploadButton = true, + hasDownloadButton = true, + hasAddButton = true +}) => { + const [selected, setSelected] = React.useState(null) -/* CellProps type contain several type like row, col, Cell, ... */ -export interface Cell extends ReactDataSheet.Cell { - /** The value of the cell, always a string */ - value: string - /** The validate function, receive Cell as param, must return boolean */ - validate?: (cell: Cell) => boolean - /* Additional className of cell */ - className?: string -} - -export type CellProps = ReactDataSheet.CellRendererProps - -export interface DoubleClickPayLoad { - row: number - col: number - maxRowIndex: number - maxColIndex: number - isReadOnly?: boolean -} - -export const onDoubleClickCell = (payload: DoubleClickPayLoad, setSelected, onDoubleClickDefault) => ( - e: React.MouseEvent -) => { - onDoubleClickDefault(e) - const { row, col, maxRowIndex, maxColIndex, isReadOnly } = payload - const isFirstRow = row === 0 - const isFirstCol = col === 0 - if (isFirstCol && isFirstRow) { - return - } - if (isFirstCol && isReadOnly) { - setSelected({ - start: { i: row, j: 0 }, - end: { i: row, j: maxColIndex } - }) - return - } - if (isFirstRow && isReadOnly) { - setSelected({ - start: { i: 0, j: col }, - end: { i: maxRowIndex, j: col } - }) - return - } -} - -export interface SpreadSheetProps extends ReactDataSheet.DataSheetProps {} - -export const customCellRenderer = (data: Cell[][], setSelected) => (props: ReactDataSheet.CellRendererProps) => { - const { style, cell, ...restProps } = props - const { validate = () => true, className = '', readOnly, ...restCell } = cell - /* const isValid = validate(cell) */ - const payload = { - row: props.row, - col: props.col, - maxRowIndex: data.length, - maxColIndex: data[0].length, - isReadOnly: readOnly - } - return ( - - {props.children} - - ) -} - -export const onSelect = setSelected => ({ start, end }) => { - setSelected({ start, end }) -} + const [data, setData] = React.useState(initialData) -export const Spreadsheet: React.FC = ({ data, valueRenderer, ...rest }) => { - const [selected, setSelected] = React.useState<{ - start: ReactDataSheet.Location - end: ReactDataSheet.Location - } | null>(null) + const cellRenderer = React.useCallback(customCellRenderer(data, setSelected), [data]) + const onSelect = React.useCallback(onSelectCells(setSelected), []) + const onCellsChanged = React.useCallback(handleCellsChanged(data, setData), [data]) + const addNewRow = React.useCallback(handleAddNewRow(data, setData), [data]) - const cellRenderer = React.useCallback(customCellRenderer(data, setSelected), []) return (
+
+
{description}
+
+ {hasUploadButton && ( +
+ +
+ )} + {hasDownloadButton && ( +
+ +
+ )} +
+
{ - setSelected({ start, end }) - }} - {...rest} + valueRenderer={valueRenderer} + onCellsChanged={onCellsChanged} + onSelect={onSelect} + /* onContextMenu={handleContextMenu} */ cellRenderer={cellRenderer} /> +
+ {hasAddButton && ( +
+ +
+ )} +
) } + +export * from './types' +export * from './utils' diff --git a/src/components/Spreadsheet/spreadsheet.stories.tsx b/src/components/Spreadsheet/spreadsheet.stories.tsx index 6225b6a619..a11c8a7b78 100644 --- a/src/components/Spreadsheet/spreadsheet.stories.tsx +++ b/src/components/Spreadsheet/spreadsheet.stories.tsx @@ -1,35 +1,49 @@ -import React, { useState } from 'react' +import React from 'react' import { storiesOf } from '@storybook/react' import { Spreadsheet } from './index' +const data = [ + [ + { + readOnly: true, + component:
adad
, + forceComponent: true, + value: 'Office Name' + }, + { readOnly: true, value: 'Building Name' }, + { readOnly: true, value: 'Building No.' }, + { readOnly: true, value: 'Address 1' }, + { readOnly: true, value: 'Address 2' }, + { readOnly: true, value: 'Address 3' }, + { readOnly: true, value: 'Address 4' }, + { readOnly: true, value: 'Post Code' }, + { readOnly: true, value: 'Telephone' }, + { readOnly: true, value: 'Fax' }, + { readOnly: true, value: 'Email' } + ], + [ + { + value: '', + component:
adad
, + forceComponent: true + }, + { value: 'Building Name' }, + { value: 'Building No.' }, + { value: 'Address 1' }, + { value: 'Address 2' }, + { value: 'Address 3' }, + { value: 'Address 4' }, + { value: 'Post Code' }, + { value: 'Telephone' }, + { value: 'Fax' }, + { value: 'Email' } + ] +] const SpreadSheetBasic = () => { - const [data, setData] = useState([ - [ - { readOnly: true, value: '' }, - { value: 'A', readOnly: true }, - { value: 'B', readOnly: true }, - { value: 'C', readOnly: true }, - { value: 'D', readOnly: true } - ], - [{ readOnly: true, value: '1' }, { value: '1' }, { value: '3' }, { value: '3' }, { value: '3' }], - [{ readOnly: true, value: '2' }, { value: '2' }, { value: '4' }, { value: '4' }, { value: '4' }], - [{ readOnly: true, value: '3' }, { value: '1' }, { value: '3' }, { value: '3' }, { value: '3' }], - [{ readOnly: true, value: '4' }, { value: '2' }, { value: '4' }, { value: '4' }, { value: '4' }] - ]) return ( cell.value} - overflow="wrap" - onCellsChanged={changes => { - setData(prev => { - const newData = prev.map(row => [...row]) - changes.forEach(({ row, col, value }) => { - newData[row][col] = { ...newData[row][col], value: value as string } - }) - return newData - }) - }} + description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s.Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s.Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s." /> ) } diff --git a/src/components/Spreadsheet/types.tsx b/src/components/Spreadsheet/types.tsx new file mode 100644 index 0000000000..5eb1ee5f7e --- /dev/null +++ b/src/components/Spreadsheet/types.tsx @@ -0,0 +1,39 @@ +import * as React from 'react' +import ReactDataSheet from 'react-datasheet' + +export class MyReactDataSheet extends ReactDataSheet {} + +/** Cell contain predefined value + * https://github.com/nadbm/react-datasheet/blob/master/types/react-datasheet.d.ts + * plus some properties below + */ +export interface Cell extends ReactDataSheet.Cell { + /** The value of the cell, always a string */ + value: string + /** The validate function, receive Cell as param, must return boolean */ + validate?: (cell: Cell) => boolean + /** Additional className for styling cell */ + className?: string + selectComponent?: React.FC +} + +export interface DoubleClickPayLoad { + row: number + col: number + maxRowIndex: number + maxColIndex: number + isReadOnly?: boolean +} + +export interface SpreadsheetProps { + data: Cell[][] + description?: React.ReactNode + hasUploadButton?: boolean + hasDownloadButton?: boolean + hasAddButton?: boolean +} + +export interface SelectedMatrix { + start: ReactDataSheet.Location + end: ReactDataSheet.Location +} diff --git a/src/components/Spreadsheet/utils.tsx b/src/components/Spreadsheet/utils.tsx new file mode 100644 index 0000000000..3a11af13ec --- /dev/null +++ b/src/components/Spreadsheet/utils.tsx @@ -0,0 +1,19 @@ +import { Cell } from './types' + +/** Get max row and col index */ +export const getMaxRowAndCol = (data: Cell[][]) => + data.reduce( + (acc, rowData) => { + const lastMaxRow = acc[0] + const lastMaxCol = acc[1] + const newAcc = [lastMaxRow, lastMaxCol] + if (lastMaxRow < data.length) { + newAcc[0] = data.length + } + if (lastMaxCol < rowData.length) { + newAcc[1] = rowData.length + } + return newAcc + }, + [0 /* row */, 0 /* col */] + ) diff --git a/src/styles/components/spreadsheet.scss b/src/styles/components/spreadsheet.scss index c0d989154e..70866f8e93 100644 --- a/src/styles/components/spreadsheet.scss +++ b/src/styles/components/spreadsheet.scss @@ -2,7 +2,32 @@ @import '../base/colors.scss'; .spreadsheet { + width: 100%; background-color: $white; + color: $black; + /* custom style */ + .wrap-top { + margin-bottom: 1rem; + display: flex; + align-items: flex-end; + justify-content: space-between; + .description { + flex: 7 1 70%; + } + .button-group { + flex: 3 0 30%; + display: flex; + justify-content: flex-end; + .download-button { + margin-left: 1rem; + } + } + } + .wrap-bottom { + margin-top: 1rem; + text-align: right; + } + /* lib style modify */ .data-grid-container .data-grid { width: 100%; margin: auto; @@ -11,4 +36,25 @@ width: 100% !important; height: 100% !important; } + .data-grid-container .data-grid .cell { + height: 30px; + line-height: 30px; + vertical-align: middle; + padding: 0 0.5rem 0 0.5rem; + text-align: left; + &.read-only { + color: $black; + font-weight: bold; + background-color: inherit; + } + & > input { + text-align: left; + } + } + tr:nth-child(odd) { + background-color: $white; + } + tr:nth-child(even) { + background-color: $grey-lighter; + } } From e479873ab30bdb8c3717c675eb10f4c6d1314d16 Mon Sep 17 00:00:00 2001 From: vuhuucuong Date: Fri, 20 Dec 2019 13:15:20 +0700 Subject: [PATCH 07/14] Finish tests --- .../Spreadsheet/__stubs__/index.tsx | 84 ++++ .../__tests__/__snapshots__/handers.tsx.snap | 260 +++++++++++ .../Spreadsheet/__tests__/handers.tsx | 147 ++++++ .../Spreadsheet/__tests__/index.tsx | 24 + src/components/Spreadsheet/__tests__/utils.ts | 30 ++ src/components/Spreadsheet/handlers.tsx | 66 ++- src/components/Spreadsheet/index.tsx | 2 +- .../Spreadsheet/spreadsheet.stories.tsx | 434 ++++++++++++++++-- src/components/Spreadsheet/types.tsx | 8 +- src/components/Spreadsheet/utils.tsx | 13 + src/styles/components/spreadsheet.scss | 14 +- 11 files changed, 1006 insertions(+), 76 deletions(-) create mode 100644 src/components/Spreadsheet/__stubs__/index.tsx create mode 100644 src/components/Spreadsheet/__tests__/__snapshots__/handers.tsx.snap create mode 100644 src/components/Spreadsheet/__tests__/handers.tsx create mode 100644 src/components/Spreadsheet/__tests__/index.tsx create mode 100644 src/components/Spreadsheet/__tests__/utils.ts diff --git a/src/components/Spreadsheet/__stubs__/index.tsx b/src/components/Spreadsheet/__stubs__/index.tsx new file mode 100644 index 0000000000..9b1e2da1e3 --- /dev/null +++ b/src/components/Spreadsheet/__stubs__/index.tsx @@ -0,0 +1,84 @@ +import * as React from 'react' +import { Cell, SelectedMatrix } from '../types' +import ReactDataSheet from 'react-datasheet' + +export const data: Cell[][] = [ + [ + { readOnly: true, value: 'Office Name' }, + { readOnly: true, value: 'Building Name' }, + { readOnly: true, value: 'Building No.' }, + { readOnly: true, value: 'Address 1' }, + { readOnly: true, value: 'Address 2' }, + { readOnly: true, value: 'Address 3' }, + { readOnly: true, value: 'Address 4' }, + { readOnly: true, value: 'Post Code' }, + { readOnly: true, value: 'Telephone' }, + { readOnly: true, value: 'Fax' }, + { readOnly: true, value: 'Email' } + ], + [ + { value: 'London' }, + { value: 'The White House' }, + { value: '15' }, + { value: 'London 1' }, + { value: '' }, + { value: 'Londom 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '0845 0000' }, + { value: '' }, + { value: 'row1@gmail.com' } + ], + [ + { value: 'London2' }, + { value: 'The Black House' }, + { value: '11' }, + { value: '' }, + { value: '' }, + { value: 'Adress 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '087 471 929' }, + { value: '' }, + { value: 'row2@gmail.com' } + ], + [ + { value: 'New York' }, + { value: 'Building A' }, + { value: '11' }, + { value: '' }, + { value: '' }, + { value: 'City Z' }, + { value: '' }, + { value: 'AL7187' }, + { value: '017 7162 9121' }, + { value: '' }, + { value: 'row3@gmail.com' } + ] +] + +export const cellRenderProps: ReactDataSheet.CellRendererProps = { + row: 3, + col: 10, + cell: { value: 'row3@gmail.com' }, + selected: false, + editing: false, + updated: false, + attributesRenderer: jest.fn() as ReactDataSheet.AttributesRenderer, + className: 'cell', + style: { background: 'red' }, + onMouseDown: jest.fn(), + onMouseOver: jest.fn(), + onDoubleClick: jest.fn(), + onContextMenu: jest.fn(), + children:
hi
+} + +export const setData: React.Dispatch = jest.fn() + +export const setSelected: React.Dispatch = jest.fn() + +export const selectedMatrix = { + start: { i: 0, j: 1 }, + end: { i: 2, j: 3 } +} diff --git a/src/components/Spreadsheet/__tests__/__snapshots__/handers.tsx.snap b/src/components/Spreadsheet/__tests__/__snapshots__/handers.tsx.snap new file mode 100644 index 0000000000..32c06e75d1 --- /dev/null +++ b/src/components/Spreadsheet/__tests__/__snapshots__/handers.tsx.snap @@ -0,0 +1,260 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`onDoubleClickCell customCellRenderer should match snapshot with CustomComponent 1`] = ` + + + hi + , + "className": "cell", + "col": 10, + "editing": false, + "onContextMenu": [MockFunction], + "onDoubleClick": [MockFunction], + "onMouseDown": [MockFunction], + "onMouseOver": [MockFunction], + "row": 3, + "selected": false, + "style": Object { + "background": "red", + }, + "updated": false, + } + } + data={ + Array [ + Array [ + Object { + "readOnly": true, + "value": "Office Name", + }, + Object { + "readOnly": true, + "value": "Building Name", + }, + Object { + "readOnly": true, + "value": "Building No.", + }, + Object { + "readOnly": true, + "value": "Address 1", + }, + Object { + "readOnly": true, + "value": "Address 2", + }, + Object { + "readOnly": true, + "value": "Address 3", + }, + Object { + "readOnly": true, + "value": "Address 4", + }, + Object { + "readOnly": true, + "value": "Post Code", + }, + Object { + "readOnly": true, + "value": "Telephone", + }, + Object { + "readOnly": true, + "value": "Fax", + }, + Object { + "readOnly": true, + "value": "Email", + }, + ], + Array [ + Object { + "value": "London", + }, + Object { + "value": "The White House", + }, + Object { + "value": "15", + }, + Object { + "value": "London 1", + }, + Object { + "value": "", + }, + Object { + "value": "Londom 3", + }, + Object { + "value": "", + }, + Object { + "value": "EC12NH", + }, + Object { + "value": "0845 0000", + }, + Object { + "value": "", + }, + Object { + "value": "row1@gmail.com", + }, + ], + Array [ + Object { + "value": "London2", + }, + Object { + "value": "The Black House", + }, + Object { + "value": "11", + }, + Object { + "value": "", + }, + Object { + "value": "", + }, + Object { + "value": "Adress 3", + }, + Object { + "value": "", + }, + Object { + "value": "EC12NH", + }, + Object { + "value": "087 471 929", + }, + Object { + "value": "", + }, + Object { + "value": "row2@gmail.com", + }, + ], + Array [ + Object { + "value": "New York", + }, + Object { + "value": "Building A", + }, + Object { + "value": "11", + }, + Object { + "value": "", + }, + Object { + "value": "", + }, + Object { + "value": "City Z", + }, + Object { + "value": "", + }, + Object { + "value": "AL7187", + }, + Object { + "value": "017 7162 9121", + }, + Object { + "value": "", + }, + Object { + "value": "row3@gmail.com", + }, + ], + ] + } + setData={[MockFunction]} + setSelected={[MockFunction]} + /> + +`; + +exports[`onDoubleClickCell customCellRenderer should match snapshot with invalid cell 1`] = ` + +
+ hi +
+ +`; + +exports[`onDoubleClickCell customCellRenderer should match snapshot without CustomComponent 1`] = ` + +
+ hi +
+ +`; diff --git a/src/components/Spreadsheet/__tests__/handers.tsx b/src/components/Spreadsheet/__tests__/handers.tsx new file mode 100644 index 0000000000..96567db3e7 --- /dev/null +++ b/src/components/Spreadsheet/__tests__/handers.tsx @@ -0,0 +1,147 @@ +import * as React from 'react' +import ReactDataSheet from 'react-datasheet' +import { shallow } from 'enzyme' +import { + onDoubleClickCell, + valueRenderer, + onSelectCells, + customCellRenderer, + handleAddNewRow, + handleCellsChanged +} from '../handlers' +import { data, cellRenderProps, selectedMatrix, setData, setSelected } from '../__stubs__' + +const onDoubleClickDefault = jest.fn() + +afterEach(() => { + jest.resetAllMocks() +}) + +describe('valueRenderer', () => { + it('should return value', () => { + const result = valueRenderer(cellRenderProps.cell) + expect(result).toBe(cellRenderProps.cell.value) + }) +}) + +describe('onDoubleClickCell', () => { + it('should call setSelected if row = 0 and isReadOnly, and return true', () => { + const payload = { + row: 0, + col: 1, + maxRowIndex: 3, + maxColIndex: 4, + isReadOnly: true + } + const fn = onDoubleClickCell(payload, setSelected, onDoubleClickDefault) + const result = fn(1, 2) + expect(onDoubleClickDefault).toHaveBeenCalledWith(1, 2) + expect(setSelected).toHaveBeenCalledWith({ + start: { i: 0, j: payload.col }, + end: { + i: payload.maxRowIndex, + j: payload.col + } + }) + expect(result).toBe(true) + }) + it('should not call setSelected when row !== 0, and return false', () => { + const payload = { + row: 2, + col: 1, + maxRowIndex: 3, + maxColIndex: 4, + isReadOnly: true + } + const fn = onDoubleClickCell(payload, setSelected, onDoubleClickDefault) + const result = fn(1, 2) + expect(onDoubleClickDefault).toHaveBeenCalledWith(1, 2) + expect(setSelected).not.toHaveBeenCalled() + expect(result).toBe(false) + }) + it('should not call setSelected when isReadOnly = false, and return false', () => { + const payload = { + row: 2, + col: 1, + maxRowIndex: 3, + maxColIndex: 4, + isReadOnly: false + } + const fn = onDoubleClickCell(payload, setSelected, onDoubleClickDefault) + const result = fn(1, 2) + expect(onDoubleClickDefault).toHaveBeenCalledWith(1, 2) + expect(setSelected).not.toHaveBeenCalled() + expect(result).toBe(false) + }) + + describe('onSelectCell', () => { + it('should call setSelected with right arg', () => { + const fn = onSelectCells(setSelected) + fn(selectedMatrix) + expect(setSelected).toHaveBeenCalledWith(selectedMatrix) + }) + }) + + describe('customCellRenderer', () => { + it('should match snapshot without CustomComponent', () => { + const CellComponent = customCellRenderer(data, setData, setSelected) + expect(shallow()).toMatchSnapshot() + }) + it('should match snapshot with CustomComponent', () => { + const cellRenderPropsCustomComponent = { + ...cellRenderProps, + cell: { + ...cellRenderProps.cell, + CustomComponent: () =>
Custom Component
+ } + } + const CellComponent = customCellRenderer(data, setData, setSelected) + expect(shallow()).toMatchSnapshot() + }) + + it('should match snapshot with invalid cell', () => { + const cellRenderPropsInvalid = { + ...cellRenderProps, + cell: { value: '11aa', validate: cell => Number.isInteger(Number(cell.value)) } + } + + const CellComponent = customCellRenderer(data, setData, setSelected) + expect(shallow()).toMatchSnapshot() + }) + }) +}) + +describe('handleAddNewRow', () => { + it('should call setData with correct arg', () => { + const getMaxRowAndCol = jest.fn(data => [data.length, data[0].length]) + const fn = handleAddNewRow(data, setData) + fn() + const expectedResult = [...data] + expectedResult.push([ + { value: '' }, + { value: '' }, + { value: '' }, + { value: '' }, + { value: '' }, + { value: '' }, + { value: '' }, + { value: '' }, + { value: '' }, + { value: '' }, + { value: '' } + ]) + expect(setData).toHaveBeenCalledWith(expectedResult) + }) +}) + +describe('handleCellsChanged', () => { + it('should call setData with correct arg', () => { + const fn = handleCellsChanged(data, setData) + + const changes = [{ row: 1, col: 2, value: 'new' }] + fn(changes) + const expectResult = data.map(row => [...row]) + expectResult[1][2] = { ...expectResult[1][2], value: 'new' } + expect(setData).toHaveBeenCalledWith(expectResult) + }) +}) diff --git a/src/components/Spreadsheet/__tests__/index.tsx b/src/components/Spreadsheet/__tests__/index.tsx new file mode 100644 index 0000000000..061cbd5770 --- /dev/null +++ b/src/components/Spreadsheet/__tests__/index.tsx @@ -0,0 +1,24 @@ +import * as React from 'react' +import ReactDataSheet from 'react-datasheet' +import { shallow } from 'enzyme' +import { Spreadsheet } from '../index' +import { data } from '../__stubs__' + +describe('Spreadsheet', () => { + it('should match snapshot with default props', () => { + expect(shallow()) + }) + it('should match snapshot with full props', () => { + expect( + shallow( + + ) + ) + }) +}) diff --git a/src/components/Spreadsheet/__tests__/utils.ts b/src/components/Spreadsheet/__tests__/utils.ts new file mode 100644 index 0000000000..c9bf020d6b --- /dev/null +++ b/src/components/Spreadsheet/__tests__/utils.ts @@ -0,0 +1,30 @@ +import { data, setData } from '../__stubs__' +import { getMaxRowAndCol, setCurrentCellValue } from '../utils' +import { Cell } from '../types' + +afterEach(() => { + jest.resetAllMocks() +}) + +describe('getMaxRowAndCol', () => { + it('should return correct value with same-length rows and columns', () => { + const result = getMaxRowAndCol(data) + expect(result).toEqual([data.length, data[0].length]) + }) + it('should return correct value with different-length col', () => { + const newData = [...data] + newData.push([{ value: 'val' }]) + + const result = getMaxRowAndCol(newData) + expect(result).toEqual([newData.length, data[0].length]) + }) +}) + +describe('setCurrentCellValue', () => { + it('should call setData with correct arg', () => { + setCurrentCellValue('cell value', data, 2, 3, setData) + const newData = [...data] + newData[2][3].value = 'cell value' + expect(setData).toHaveBeenCalledWith(newData) + }) +}) diff --git a/src/components/Spreadsheet/handlers.tsx b/src/components/Spreadsheet/handlers.tsx index a31c5085e6..84b20bb5fb 100644 --- a/src/components/Spreadsheet/handlers.tsx +++ b/src/components/Spreadsheet/handlers.tsx @@ -5,24 +5,18 @@ import { getMaxRowAndCol } from './utils' export const valueRenderer = (cell: Cell): string => cell.value -export const onDoubleClickCell = (payload: DoubleClickPayLoad, setSelected, onDoubleClickDefault) => ( - ...args -): boolean => { +/** Double click on first read-only cell */ +export const onDoubleClickCell = ( + payload: DoubleClickPayLoad, + setSelected: React.Dispatch, + onDoubleClickDefault +) => (...args): boolean => { + /* trigger default handler from lib */ onDoubleClickDefault(...args) - const { row, col, maxRowIndex, maxColIndex, isReadOnly } = payload + const { row, col, maxRowIndex, isReadOnly } = payload const isFirstRow = row === 0 - const isFirstCol = col === 0 - if (isFirstCol && isFirstRow) { - return false - } - if (isFirstCol && isReadOnly) { - setSelected({ - start: { i: row, j: 0 }, - end: { i: row, j: maxColIndex } - }) - return true - } if (isFirstRow && isReadOnly) { + /* select all row's cells */ setSelected({ start: { i: 0, j: col }, end: { i: maxRowIndex, j: col } @@ -32,7 +26,7 @@ export const onDoubleClickCell = (payload: DoubleClickPayLoad, setSelected, onDo return false } -export const onSelectCells = setSelected => ({ start, end }: SelectedMatrix) => { +export const onSelectCells = (setSelected: React.Dispatch) => ({ start, end }: SelectedMatrix) => { setSelected({ start, end }) } @@ -41,10 +35,21 @@ export const onSelectCells = setSelected => ({ start, end }: SelectedMatrix) => } */ /** all the customization of cell go here */ -export const customCellRenderer = (data: Cell[][], setSelected) => (props: ReactDataSheet.CellRendererProps) => { - const { style, cell, onDoubleClick, ...restProps } = props - const { validate = () => true, className = '', readOnly, selectComponent, ...restCell } = cell - /* const isValid = validate(cell) */ +export const customCellRenderer = ( + data: Cell[][], + setData: React.Dispatch, + setSelected: React.Dispatch +) => (props: ReactDataSheet.CellRendererProps) => { + const { style: defaultStyle, cell, onDoubleClick, ...restProps } = props + const { + CustomComponent = false, + validate = () => true, + className = '', + readOnly, + style: customStyle, + ...restCell + } = cell + const isValid = validate(cell) const [maxRowIndex, maxColIndex] = getMaxRowAndCol(data) const payload = { row: props.row, @@ -53,20 +58,28 @@ export const customCellRenderer = (data: Cell[][], setSelected) => (props: React maxColIndex, isReadOnly: readOnly } + const style = { + ...defaultStyle, + ...customStyle + } return ( - {props.children} + {CustomComponent ? ( + + ) : ( + props.children + )} ) } -export const handleAddNewRow = (data: Cell[][], setData) => () => { +export const handleAddNewRow = (data: Cell[][], setData: React.Dispatch) => () => { const [maxRow] = getMaxRowAndCol(data) const lastRow = data[maxRow - 1] /* [ @@ -82,7 +95,10 @@ export const handleAddNewRow = (data: Cell[][], setData) => () => { setData(newData) } -export const handleCellsChanged = (prevData: Cell[][], setData /* setData from useState*/) => changes => { +export const handleCellsChanged = ( + prevData: Cell[][], + setData: React.Dispatch /* setData from useState*/ +) => changes => { const newData = prevData.map(row => [...row]) changes.forEach(({ row, col, value }) => { newData[row][col] = { ...newData[row][col], value } diff --git a/src/components/Spreadsheet/index.tsx b/src/components/Spreadsheet/index.tsx index 0f427df3a4..a2edc0c660 100644 --- a/src/components/Spreadsheet/index.tsx +++ b/src/components/Spreadsheet/index.tsx @@ -21,7 +21,7 @@ export const Spreadsheet: React.FC = ({ const [data, setData] = React.useState(initialData) - const cellRenderer = React.useCallback(customCellRenderer(data, setSelected), [data]) + const cellRenderer = React.useCallback(customCellRenderer(data, setData, setSelected), [data]) const onSelect = React.useCallback(onSelectCells(setSelected), []) const onCellsChanged = React.useCallback(handleCellsChanged(data, setData), [data]) const addNewRow = React.useCallback(handleAddNewRow(data, setData), [data]) diff --git a/src/components/Spreadsheet/spreadsheet.stories.tsx b/src/components/Spreadsheet/spreadsheet.stories.tsx index a11c8a7b78..18ae09f4ff 100644 --- a/src/components/Spreadsheet/spreadsheet.stories.tsx +++ b/src/components/Spreadsheet/spreadsheet.stories.tsx @@ -1,51 +1,391 @@ import React from 'react' import { storiesOf } from '@storybook/react' -import { Spreadsheet } from './index' +import { Spreadsheet, setCurrentCellValue, Cell } from './index' -const data = [ - [ - { - readOnly: true, - component:
adad
, - forceComponent: true, - value: 'Office Name' - }, - { readOnly: true, value: 'Building Name' }, - { readOnly: true, value: 'Building No.' }, - { readOnly: true, value: 'Address 1' }, - { readOnly: true, value: 'Address 2' }, - { readOnly: true, value: 'Address 3' }, - { readOnly: true, value: 'Address 4' }, - { readOnly: true, value: 'Post Code' }, - { readOnly: true, value: 'Telephone' }, - { readOnly: true, value: 'Fax' }, - { readOnly: true, value: 'Email' } - ], - [ - { - value: '', - component:
adad
, - forceComponent: true - }, - { value: 'Building Name' }, - { value: 'Building No.' }, - { value: 'Address 1' }, - { value: 'Address 2' }, - { value: 'Address 3' }, - { value: 'Address 4' }, - { value: 'Post Code' }, - { value: 'Telephone' }, - { value: 'Fax' }, - { value: 'Email' } - ] -] -const SpreadSheetBasic = () => { - return ( - - ) -} +storiesOf('Spreadsheet', module) + .add('Basic', () => { + const dataBasic = [ + [ + { readOnly: true, value: 'Office Name' }, + { readOnly: true, value: 'Building Name' }, + { readOnly: true, value: 'Building No.' }, + { readOnly: true, value: 'Address 1' }, + { readOnly: true, value: 'Address 2' }, + { readOnly: true, value: 'Address 3' }, + { readOnly: true, value: 'Address 4' }, + { readOnly: true, value: 'Post Code' }, + { readOnly: true, value: 'Telephone' }, + { readOnly: true, value: 'Fax' }, + { readOnly: true, value: 'Email' } + ], + [ + { value: 'London' }, + { value: 'The White House' }, + { value: '15' }, + { value: 'London 1' }, + { value: '' }, + { value: 'Londom 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '0845 0000' }, + { value: '' }, + { value: 'row1@gmail.com' } + ], + [ + { value: 'London2' }, + { value: 'The Black House' }, + { value: '11' }, + { value: '' }, + { value: '' }, + { value: 'Adress 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '087 471 929' }, + { value: '' }, + { value: 'row2@gmail.com' } + ], + [ + { value: 'New York' }, + { value: 'Building A' }, + { value: '11' }, + { value: '' }, + { value: '' }, + { value: 'City Z' }, + { value: '' }, + { value: 'AL7187' }, + { value: '017 7162 9121' }, + { value: '' }, + { value: 'row3@gmail.com' } + ] + ] -storiesOf('Spreadsheet', module).add('Spreadsheet', SpreadSheetBasic) + return ( + + Basic DataSheet +
+ You can double click a column header to select the entire column's cells. +
+ Select one or multiple cells and press Delete/Backspace to delete it value. +

+ } + /> + ) + }) + .add('Validate', () => { + const dataValidate = [ + [ + { readOnly: true, value: 'Office Name' }, + { readOnly: true, value: 'Building Name' }, + { readOnly: true, value: 'Building No.' }, + { readOnly: true, value: 'Address 1' }, + { readOnly: true, value: 'Address 2' }, + { readOnly: true, value: 'Address 3' }, + { readOnly: true, value: 'Address 4' }, + { readOnly: true, value: 'Post Code' }, + { readOnly: true, value: 'Telephone' }, + { readOnly: true, value: 'Fax' }, + { readOnly: true, value: 'Email' } + ], + [ + { value: 'London' }, + { value: 'The White House' }, + { value: '15' }, + { value: 'London 1' }, + { value: '' }, + { value: 'Londom 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '0845 0000' }, + { value: '' }, + { value: 'row1@gmail.com' } + ], + [ + { value: 'London2' }, + { value: 'The Black House' }, + { value: '11aa', validate: cell => Number.isInteger(Number(cell.value)) }, + { value: '' }, + { value: '' }, + { value: 'Adress 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '087 471 929' }, + { value: '' }, + { value: 'row2@gmail.com' } + ], + [ + { value: 'New York' }, + { value: 'Building A' }, + { value: '11' }, + { value: '' }, + { value: '' }, + { value: 'City Z' }, + { value: '' }, + { value: 'AL7187' }, + { value: '017 7162 9121' }, + { value: '' }, + { + value: 'row3@com', + validate: cell => { + const emailRegex = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i + return emailRegex.test(cell.value) + } + } + ] + ] + return ( + + DataSheet with validate +
+ Errors are marked with red background +

+ } + /> + ) + }) + .add('Custom Style', () => { + const dataBasic = [ + [ + { readOnly: true, value: 'Office Name' }, + { readOnly: true, value: 'Building Name' }, + { readOnly: true, value: 'Building No.' }, + { readOnly: true, value: 'Address 1' }, + { readOnly: true, value: 'Address 2' }, + { readOnly: true, value: 'Address 3' }, + { readOnly: true, value: 'Address 4' }, + { readOnly: true, value: 'Post Code' }, + { readOnly: true, value: 'Telephone' }, + { readOnly: true, value: 'Fax' }, + { readOnly: true, value: 'Email' } + ], + [ + { value: 'London' }, + { value: 'The White House' }, + { value: '15' }, + { value: 'London 1' }, + { value: '' }, + { value: 'Londom 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '0845 0000' }, + { value: '' }, + { value: 'row1@gmail.com' } + ], + [ + { value: 'London2' }, + { value: 'The Black House' }, + { value: '11' }, + { value: '' }, + { value: '' }, + { value: 'Adress 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '087 471 929' }, + { value: '' }, + { value: 'row2@gmail.com' } + ], + [ + { value: 'New York' }, + { value: 'Building A' }, + { value: '11' }, + { value: '' }, + { value: '' }, + { value: 'City Z' }, + { value: '' }, + { value: 'AL7187' }, + { value: '017 7162 9121' }, + { value: '' }, + { value: 'row3@gmail.com' } + ] + ] + const dataCustomStyle = (dataBasic as Cell[][]).map((e, i) => { + if (i % 2 !== 0) { + /* customize by style */ + return e.map(c => ({ + ...c, + style: { + background: '#6A5ACD', + color: '#fff' + } + })) + } + /* customize by className */ + return e.map(c => ({ + ...c, + className: 'custom-classname-style' + })) + }) + + return ( + + DataSheet with validate +
+ Add custom style to cell by using className property of cell +
+ Or if you want to override default style, use style proprty +

+ } + /> + ) + }) + .add('Custom Component', () => { + /* follow this pattern to create custom eleemnt */ + const CustomComponent = ({ cellRenderProps, data, setData }) => { + return ( + + ) + } + + const dataCustomComponent = [ + [ + { readOnly: true, value: 'Office Name' }, + { readOnly: true, value: 'Building Name' }, + { readOnly: true, value: 'Building No.' }, + { readOnly: true, value: 'Address 1' }, + { readOnly: true, value: 'Address 2' }, + { readOnly: true, value: 'Address 3' }, + { readOnly: true, value: 'Address 4' }, + { readOnly: true, value: 'Post Code' }, + { readOnly: true, value: 'Telephone' }, + { readOnly: true, value: 'Fax' }, + { readOnly: true, value: 'Email' } + ], + [ + { value: 'London' }, + { + value: 'The White House', + CustomComponent + }, + { value: '15' }, + { value: 'London 1' }, + { value: '' }, + { value: 'Londom 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '0845 0000' }, + { value: '' }, + { value: 'row1@gmail.com' } + ], + [ + { value: 'London2' }, + { value: 'The Black House' }, + { value: '11' }, + { value: '' }, + { value: '' }, + { value: 'Adress 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '087 471 929' }, + { value: '' }, + { value: 'row2@gmail.com' } + ], + [ + { value: 'New York' }, + { value: 'Building A' }, + { value: '11' }, + { value: '' }, + { value: '' }, + { value: 'City Z' }, + { value: '' }, + { value: 'AL7187' }, + { value: '017 7162 9121' }, + { value: '' }, + { value: 'row3@gmail.com' } + ] + ] + + return ( + + + DataSheet with <select> + +
+ You can create a cell which include custom component by using CustomComponent property, here we use{' '} + <select> as an example. Follow this pattern to create various types of custom + components +

+ } + /> + ) + }) + .add('Spreadsheet only', () => { + const dataBasic = [ + [ + { readOnly: true, value: 'Office Name' }, + { readOnly: true, value: 'Building Name' }, + { readOnly: true, value: 'Building No.' }, + { readOnly: true, value: 'Address 1' }, + { readOnly: true, value: 'Address 2' }, + { readOnly: true, value: 'Address 3' }, + { readOnly: true, value: 'Address 4' }, + { readOnly: true, value: 'Post Code' }, + { readOnly: true, value: 'Telephone' }, + { readOnly: true, value: 'Fax' }, + { readOnly: true, value: 'Email' } + ], + [ + { value: 'London' }, + { value: 'The White House' }, + { value: '15' }, + { value: 'London 1' }, + { value: '' }, + { value: 'Londom 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '0845 0000' }, + { value: '' }, + { value: 'row1@gmail.com' } + ], + [ + { value: 'London2' }, + { value: 'The Black House' }, + { value: '11' }, + { value: '' }, + { value: '' }, + { value: 'Adress 3' }, + { value: '' }, + { value: 'EC12NH' }, + { value: '087 471 929' }, + { value: '' }, + { value: 'row2@gmail.com' } + ], + [ + { value: 'New York' }, + { value: 'Building A' }, + { value: '11' }, + { value: '' }, + { value: '' }, + { value: 'City Z' }, + { value: '' }, + { value: 'AL7187' }, + { value: '017 7162 9121' }, + { value: '' }, + { value: 'row3@gmail.com' } + ] + ] + + return + }) diff --git a/src/components/Spreadsheet/types.tsx b/src/components/Spreadsheet/types.tsx index 5eb1ee5f7e..1fb36c1da3 100644 --- a/src/components/Spreadsheet/types.tsx +++ b/src/components/Spreadsheet/types.tsx @@ -14,7 +14,13 @@ export interface Cell extends ReactDataSheet.Cell { validate?: (cell: Cell) => boolean /** Additional className for styling cell */ className?: string - selectComponent?: React.FC + style?: React.CSSProperties + CustomComponent?: React.FC<{ + data: Cell[][] + cellRenderProps: ReactDataSheet.CellRendererProps + setData: React.Dispatch + setSelected: React.Dispatch + }> } export interface DoubleClickPayLoad { diff --git a/src/components/Spreadsheet/utils.tsx b/src/components/Spreadsheet/utils.tsx index 3a11af13ec..4f1a701ba4 100644 --- a/src/components/Spreadsheet/utils.tsx +++ b/src/components/Spreadsheet/utils.tsx @@ -1,3 +1,4 @@ +import * as React from 'react' import { Cell } from './types' /** Get max row and col index */ @@ -17,3 +18,15 @@ export const getMaxRowAndCol = (data: Cell[][]) => }, [0 /* row */, 0 /* col */] ) + +export const setCurrentCellValue = ( + cellData: string, + data: Cell[][], + row: number, + col: number, + setData: React.Dispatch +): void => { + const newData = [...data] + newData[row][col].value = cellData + setData(newData) +} diff --git a/src/styles/components/spreadsheet.scss b/src/styles/components/spreadsheet.scss index 70866f8e93..6004087363 100644 --- a/src/styles/components/spreadsheet.scss +++ b/src/styles/components/spreadsheet.scss @@ -37,8 +37,8 @@ height: 100% !important; } .data-grid-container .data-grid .cell { - height: 30px; - line-height: 30px; + height: 2rem; + line-height: 2rem; vertical-align: middle; padding: 0 0.5rem 0 0.5rem; text-align: left; @@ -47,6 +47,10 @@ font-weight: bold; background-color: inherit; } + &.error-cell { + background: $reapit-red !important; + color: $white !important; + } & > input { text-align: left; } @@ -57,4 +61,10 @@ tr:nth-child(even) { background-color: $grey-lighter; } + + /* custom style use for demo in storybook */ + + .custom-classname-style { + color: $reapit-light-blue; + } } From c74db2d8f9cd382026d3850a3d0779ae0c322f8e Mon Sep 17 00:00:00 2001 From: vuhuucuong Date: Fri, 20 Dec 2019 13:21:46 +0700 Subject: [PATCH 08/14] Correct filename --- .../__snapshots__/{handers.tsx.snap => handlers.tsx.snap} | 0 .../Spreadsheet/__tests__/{handers.tsx => handlers.tsx} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/components/Spreadsheet/__tests__/__snapshots__/{handers.tsx.snap => handlers.tsx.snap} (100%) rename src/components/Spreadsheet/__tests__/{handers.tsx => handlers.tsx} (100%) diff --git a/src/components/Spreadsheet/__tests__/__snapshots__/handers.tsx.snap b/src/components/Spreadsheet/__tests__/__snapshots__/handlers.tsx.snap similarity index 100% rename from src/components/Spreadsheet/__tests__/__snapshots__/handers.tsx.snap rename to src/components/Spreadsheet/__tests__/__snapshots__/handlers.tsx.snap diff --git a/src/components/Spreadsheet/__tests__/handers.tsx b/src/components/Spreadsheet/__tests__/handlers.tsx similarity index 100% rename from src/components/Spreadsheet/__tests__/handers.tsx rename to src/components/Spreadsheet/__tests__/handlers.tsx From 3d4d49675e21d404bfec1a60c104fdb825d19784 Mon Sep 17 00:00:00 2001 From: vuhuucuong Date: Fri, 20 Dec 2019 13:25:33 +0700 Subject: [PATCH 09/14] Correct story header --- src/components/Spreadsheet/spreadsheet.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Spreadsheet/spreadsheet.stories.tsx b/src/components/Spreadsheet/spreadsheet.stories.tsx index 18ae09f4ff..29d2ecbc2b 100644 --- a/src/components/Spreadsheet/spreadsheet.stories.tsx +++ b/src/components/Spreadsheet/spreadsheet.stories.tsx @@ -226,7 +226,7 @@ storiesOf('Spreadsheet', module) data={dataCustomStyle} description={

- DataSheet with validate + DataSheet with custom styles
Add custom style to cell by using className property of cell
From d8b73cb36b20b838d5adab210879ecd6f1dc66b9 Mon Sep 17 00:00:00 2001 From: vuhuucuong Date: Fri, 20 Dec 2019 17:17:26 +0700 Subject: [PATCH 10/14] Change util for readable reason --- src/components/Spreadsheet/utils.tsx | 30 ++++++++++++---------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/components/Spreadsheet/utils.tsx b/src/components/Spreadsheet/utils.tsx index 4f1a701ba4..661be1184a 100644 --- a/src/components/Spreadsheet/utils.tsx +++ b/src/components/Spreadsheet/utils.tsx @@ -1,23 +1,19 @@ import * as React from 'react' import { Cell } from './types' -/** Get max row and col index */ -export const getMaxRowAndCol = (data: Cell[][]) => - data.reduce( - (acc, rowData) => { - const lastMaxRow = acc[0] - const lastMaxCol = acc[1] - const newAcc = [lastMaxRow, lastMaxCol] - if (lastMaxRow < data.length) { - newAcc[0] = data.length - } - if (lastMaxCol < rowData.length) { - newAcc[1] = rowData.length - } - return newAcc - }, - [0 /* row */, 0 /* col */] - ) +/** Get max row and col of data */ +export const getMaxRowAndCol = (data: Cell[][]) => { + const maxRow = data.length + /* default to 0 */ + let maxCol = 0 + /* check every row to find max length of column */ + data.forEach(row => { + if (maxCol < row.length) { + maxCol = row.length + } + }) + return [maxRow, maxCol] +} export const setCurrentCellValue = ( cellData: string, From 14ab5e45332b1cb3de9fe977c84ba20d395ce1e2 Mon Sep 17 00:00:00 2001 From: vuhuucuong Date: Fri, 20 Dec 2019 17:41:32 +0700 Subject: [PATCH 11/14] Change util for readable reason --- src/components/Spreadsheet/utils.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Spreadsheet/utils.tsx b/src/components/Spreadsheet/utils.tsx index 661be1184a..66fda133d6 100644 --- a/src/components/Spreadsheet/utils.tsx +++ b/src/components/Spreadsheet/utils.tsx @@ -8,8 +8,9 @@ export const getMaxRowAndCol = (data: Cell[][]) => { let maxCol = 0 /* check every row to find max length of column */ data.forEach(row => { - if (maxCol < row.length) { - maxCol = row.length + const numberOfCurrentRowColumn = row.length + if (maxCol < numberOfCurrentRowColumn) { + maxCol = numberOfCurrentRowColumn } }) return [maxRow, maxCol] From 5538feb61d6e22a47dfc2a2f822401dba757585a Mon Sep 17 00:00:00 2001 From: Thanh Pham Date: Mon, 23 Dec 2019 09:56:41 +0700 Subject: [PATCH 12/14] [CLD-602][CLD-628] general update and radio fix (#183) * [CLD-602][CLD-628] general update and radio fix * fix wizard invisible * bump v0.5.19 --- package.json | 2 +- .../ac-dynamic-links.stories.tsx | 2 +- src/components/Form/form.stories.tsx | 22 +- .../RadioSelect/__tests__/index.tsx | 10 +- src/components/RadioSelect/index.stories.tsx | 11 +- src/components/RadioSelect/index.tsx | 23 +- src/components/Wizard/index.tsx | 18 +- src/components/Wizard/wizard.stories.tsx | 245 +++++++++++------- 8 files changed, 211 insertions(+), 122 deletions(-) diff --git a/package.json b/package.json index 0393f5465a..7c1e7d546a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@reapit/elements", - "version": "0.5.18", + "version": "0.5.19", "description": "A collection of React components and utilities for building apps for Reapit Marketplace", "main": "dist/index.js", "umd:main": "dist/elements.umd.production.js", diff --git a/src/components/AcDynamicLinks/ac-dynamic-links.stories.tsx b/src/components/AcDynamicLinks/ac-dynamic-links.stories.tsx index c9e77da48b..7852954524 100644 --- a/src/components/AcDynamicLinks/ac-dynamic-links.stories.tsx +++ b/src/components/AcDynamicLinks/ac-dynamic-links.stories.tsx @@ -144,7 +144,7 @@ export const dynamicLinkScenarios: DynamicLinkScenario[] = [ } ] -storiesOf('AcDynamicLinks', module).add('AcButtonsAndLinks', () => ( +storiesOf('DynamicLinks', module).add('DynamicButtonsAndLinks', () => ( {dynamicLinkScenarios.map((scenario: DynamicLinkScenario, index: number) => (

diff --git a/src/components/Form/form.stories.tsx b/src/components/Form/form.stories.tsx index dfb94c7f68..780e1bed9a 100644 --- a/src/components/Form/form.stories.tsx +++ b/src/components/Form/form.stories.tsx @@ -24,7 +24,13 @@ export const FormExample: React.SFC = () => ( Section One Information about this section to help the user - + @@ -36,7 +42,7 @@ export const FormExample: React.SFC = () => ( id="passwordConfirm" type="password" placeholder="********" - name="password" + name="passwordConfirm" labelText="Password Confirm" /> @@ -46,7 +52,13 @@ export const FormExample: React.SFC = () => ( Section Three Information about this section to help the user - +
(
- Section Three + Section Four Information about this section to help the user