From 3657e65eed5f9bf99deb4e823451c75d783b6afa Mon Sep 17 00:00:00 2001 From: animesh1987 Date: Wed, 16 Dec 2020 11:39:44 +1100 Subject: [PATCH 1/6] add react dnd library --- packages/big-design/package.json | 4 +- .../big-design/src/components/Table/Table.tsx | 89 ++++++++++++++----- .../big-design/src/components/Table/types.ts | 1 + packages/docs/pages/Table/TablePage.tsx | 1 + packages/docs/pages/_document.tsx | 2 + yarn.lock | 67 +++++++++++++- 6 files changed, 138 insertions(+), 26 deletions(-) diff --git a/packages/big-design/package.json b/packages/big-design/package.json index bf0278115..0d3294fe8 100644 --- a/packages/big-design/package.json +++ b/packages/big-design/package.json @@ -36,10 +36,10 @@ "@bigcommerce/big-design-icons": "^0.14.1", "@bigcommerce/big-design-theme": "^0.11.0", "@popperjs/core": "^2.2.1", - "@types/react-datepicker": "^2.11.0", "downshift": "6.0.6", "focus-trap": "^5.1.0", "polished": "^3.0.3", + "react-beautiful-dnd": "^13.0.0", "react-datepicker": "^2.16.0", "react-popper": "^2.2.3", "react-uid": "^2.2.0" @@ -68,6 +68,8 @@ "@types/node": "^13.1.8", "@types/react": "^16.8.8", "@types/react-dom": "^16.8.5", + "@types/react-beautiful-dnd": "^13.0.0", + "@types/react-datepicker": "^2.11.0", "@types/react-test-renderer": "^16.8.3", "@types/styled-components": "^4.1.12", "babel-jest": "^25.4.0", diff --git a/packages/big-design/src/components/Table/Table.tsx b/packages/big-design/src/components/Table/Table.tsx index 761a88e57..567932d07 100644 --- a/packages/big-design/src/components/Table/Table.tsx +++ b/packages/big-design/src/components/Table/Table.tsx @@ -1,4 +1,5 @@ import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; +import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'; import { useEventCallback, useUniqueId } from '../../hooks'; import { MarginProps } from '../../mixins'; @@ -18,6 +19,7 @@ const InternalTable = (props: TableProps): React.ReactEl className, columns, actions, + draggable = false, emptyComponent, headerless = false, id, @@ -118,25 +120,58 @@ const InternalTable = (props: TableProps): React.ReactEl ); - const renderItems = () => ( - - {items.map((item: T, index) => { - const key = getItemKey(item, index); - const isSelected = selectedItems.has(item); - - return ( - - ); - })} - - ); + const renderItems = () => { + console.log('draggable items', draggable); + + return draggable ? ( + + {items.map((item: T, index) => { + const key = getItemKey(item, index); + const isSelected = selectedItems.has(item); + + return ( + + ); + })} + + ) : + ( + + {(provided) => ( + + {items.map((item: T, index) => { + const key = getItemKey(item, index); + const isSelected = selectedItems.has(item); + + return ( + + {(provided) => ( + + )} + + ); + })} + + )} + + ) + }; const renderEmptyState = () => { if (items.length === 0 && emptyComponent) { @@ -145,7 +180,8 @@ const InternalTable = (props: TableProps): React.ReactEl return null; }; - + + console.log('draggable is', draggable); return ( <> {shouldRenderActions() && ( @@ -162,8 +198,17 @@ const InternalTable = (props: TableProps): React.ReactEl /> )} - {renderHeaders()} - {renderItems()} + { + draggable ? + console.log(a)}> + {renderHeaders()} + {renderItems()} + + : <> + {renderHeaders()} + {renderItems()} + + } {renderEmptyState()} diff --git a/packages/big-design/src/components/Table/types.ts b/packages/big-design/src/components/Table/types.ts index 58452e201..f918c3973 100644 --- a/packages/big-design/src/components/Table/types.ts +++ b/packages/big-design/src/components/Table/types.ts @@ -38,6 +38,7 @@ export type TablePaginationProps = Omit; export interface TableProps extends React.TableHTMLAttributes { actions?: React.ReactNode; columns: Array>; + draggable?: boolean; emptyComponent?: React.ReactElement; headerless?: boolean; itemName?: string; diff --git a/packages/docs/pages/Table/TablePage.tsx b/packages/docs/pages/Table/TablePage.tsx index 3ef67f830..c9542e43c 100644 --- a/packages/docs/pages/Table/TablePage.tsx +++ b/packages/docs/pages/Table/TablePage.tsx @@ -47,6 +47,7 @@ const TablePage = () => { {/* jsx-to-string:start */} sku }, { header: 'Name', hash: 'name', render: ({ name }) => name }, diff --git a/packages/docs/pages/_document.tsx b/packages/docs/pages/_document.tsx index 8b6c27872..ca9536121 100644 --- a/packages/docs/pages/_document.tsx +++ b/packages/docs/pages/_document.tsx @@ -1,5 +1,6 @@ import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document'; import React from 'react'; +import { resetServerContext } from 'react-beautiful-dnd'; import { ServerStyleSheet } from 'styled-components'; import { GTM_ID, GTM_URL } from '../utils/analytics/gtm'; @@ -8,6 +9,7 @@ const isProd = process.env.NODE_ENV === 'production'; export default class AppDocument extends Document { static async getInitialProps(ctx: DocumentContext) { + resetServerContext(); const sheet = new ServerStyleSheet(); const originalRenderPage = ctx.renderPage; diff --git a/yarn.lock b/yarn.lock index 44128558a..7167b73d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3633,6 +3633,13 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== +"@types/react-beautiful-dnd@^13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#e60d3d965312fcf1516894af92dc3e9249587db4" + integrity sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg== + dependencies: + "@types/react" "*" + "@types/react-datepicker@^2.11.0": version "2.11.1" resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-2.11.1.tgz#f7ad70cf935c75ced3d769347cc2212c6234fcd9" @@ -5784,6 +5791,13 @@ crypto-browserify@3.12.0, crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +css-box-model@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" + integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw== + dependencies: + tiny-invariant "^1.0.6" + css-color-keywords@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" @@ -7960,7 +7974,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.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" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -9895,7 +9909,7 @@ mdn-data@2.0.6: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== -memoize-one@^5.0.0: +memoize-one@^5.0.0, memoize-one@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== @@ -12011,6 +12025,11 @@ quote-stream@^1.0.1, quote-stream@~1.0.2: minimist "^1.1.3" through2 "^2.0.0" +raf-schd@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.2.tgz#bd44c708188f2e84c810bf55fcea9231bcaed8a0" + integrity sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -12031,6 +12050,19 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== +react-beautiful-dnd@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#f70cc8ff82b84bc718f8af157c9f95757a6c3b40" + integrity sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg== + dependencies: + "@babel/runtime" "^7.8.4" + css-box-model "^1.2.0" + memoize-one "^5.1.1" + raf-schd "^4.0.2" + react-redux "^7.1.1" + redux "^4.0.4" + use-memo-one "^1.1.1" + react-datepicker@^2.16.0: version "2.16.0" resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-2.16.0.tgz#6bd68de94f5fb38c8f6a4370f3c612837c700e4e" @@ -12107,6 +12139,17 @@ react-popper@^2.2.3: react-fast-compare "^3.0.1" warning "^4.0.2" +react-redux@^7.1.1: + version "7.2.2" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736" + integrity sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA== + dependencies: + "@babel/runtime" "^7.12.1" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.13.1" + react-refresh@0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -12361,6 +12404,14 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +redux@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + regenerate-unicode-properties@^8.1.0, regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -13758,7 +13809,7 @@ svgo@^1.0.0, svgo@^1.2.2, svgo@^1.3.2: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.1.0: +symbol-observable@^1.1.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== @@ -13961,6 +14012,11 @@ tiny-inflate@^1.0.0: resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.2.tgz#93d9decffc8805bd57eae4310f0b745e9b6fb3a7" integrity sha1-k9nez/yIBb1X6uQxDwt0Xptvs6c= +tiny-invariant@^1.0.6: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + tiny-warning@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" @@ -14389,6 +14445,11 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use-memo-one@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c" + integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ== + use-subscription@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.4.1.tgz#edcbcc220f1adb2dd4fa0b2f61b6cc308e620069" From b136456510c085cdb7f155da6da2f78b5a44434f Mon Sep 17 00:00:00 2001 From: animesh1987 Date: Wed, 16 Dec 2020 13:33:22 +1100 Subject: [PATCH 2/6] feat(component): add support for drag and drop in table --- .../src/components/Table/Body/Body.tsx | 12 +++- .../src/components/Table/Row/Row.tsx | 18 +++-- .../src/components/Table/Row/styled.tsx | 2 + .../big-design/src/components/Table/Table.tsx | 69 +++++++++---------- 4 files changed, 60 insertions(+), 41 deletions(-) diff --git a/packages/big-design/src/components/Table/Body/Body.tsx b/packages/big-design/src/components/Table/Body/Body.tsx index 79afa63a7..c3c671d82 100644 --- a/packages/big-design/src/components/Table/Body/Body.tsx +++ b/packages/big-design/src/components/Table/Body/Body.tsx @@ -1,4 +1,4 @@ -import React, { memo, TableHTMLAttributes } from 'react'; +import React, { forwardRef, memo, TableHTMLAttributes } from 'react'; import { StyledTableBody } from './styled'; @@ -6,4 +6,12 @@ export interface BodyProps extends TableHTMLAttributes withFirstRowBorder?: boolean; } -export const Body: React.FC = memo(({ className, style, ...props }) => ); +interface PrivateProps { + forwardedRef?: React.Ref; +} + +const RawBody: React.FC = (props) => ; + +export const Body = memo( + forwardRef((props, ref) => ), +); diff --git a/packages/big-design/src/components/Table/Row/Row.tsx b/packages/big-design/src/components/Table/Row/Row.tsx index 64a01f4d4..89642fe68 100644 --- a/packages/big-design/src/components/Table/Row/Row.tsx +++ b/packages/big-design/src/components/Table/Row/Row.tsx @@ -1,4 +1,4 @@ -import React, { TableHTMLAttributes } from 'react'; +import React, { forwardRef, TableHTMLAttributes } from 'react'; import { typedMemo } from '../../../utils'; import { Checkbox } from '../../Checkbox'; @@ -8,6 +8,7 @@ import { TableColumn, TableItem } from '../types'; import { StyledTableRow } from './styled'; export interface RowProps extends TableHTMLAttributes { + isDragging?: boolean; isSelected?: boolean; isSelectable?: boolean; item: T; @@ -15,13 +16,20 @@ export interface RowProps extends TableHTMLAttributes { onItemSelect?(item: T): void; } +interface PrivateProps { + forwardedRef?: React.Ref; +} + const InternalRow = ({ columns, + forwardedRef, + isDragging = false, isSelectable = false, isSelected = false, item, onItemSelect, -}: RowProps) => { + ...rest +}: RowProps & PrivateProps) => { const onChange = () => { if (onItemSelect) { onItemSelect(item); @@ -31,7 +39,7 @@ const InternalRow = ({ const label = isSelected ? `Selected` : `Unselected`; return ( - + {isSelectable && ( @@ -50,4 +58,6 @@ const InternalRow = ({ ); }; -export const Row = typedMemo(InternalRow); +export const Row = typedMemo( + forwardRef>((props, ref) => ), +); diff --git a/packages/big-design/src/components/Table/Row/styled.tsx b/packages/big-design/src/components/Table/Row/styled.tsx index 6138ff1ba..52ca8b57c 100644 --- a/packages/big-design/src/components/Table/Row/styled.tsx +++ b/packages/big-design/src/components/Table/Row/styled.tsx @@ -4,11 +4,13 @@ import styled from 'styled-components'; import { withTransition } from '../../../mixins/transitions'; interface StyledTableRowProps { + isDragging: boolean; isSelected: boolean; } export const StyledTableRow = styled.tr` ${withTransition(['background-color'])} + display: ${({ isDragging }) => (isDragging ? 'table' : 'table-row')}; background-color: ${({ isSelected, theme }) => (isSelected ? theme.colors.primary10 : 'transparent')}; diff --git a/packages/big-design/src/components/Table/Table.tsx b/packages/big-design/src/components/Table/Table.tsx index 567932d07..bcdc496f3 100644 --- a/packages/big-design/src/components/Table/Table.tsx +++ b/packages/big-design/src/components/Table/Table.tsx @@ -123,7 +123,35 @@ const InternalTable = (props: TableProps): React.ReactEl const renderItems = () => { console.log('draggable items', draggable); - return draggable ? ( + return draggable ? + {provided => ( + + {items.map((item: T, index) => { + const key = getItemKey(item, index); + const isSelected = selectedItems.has(item); + + return ( + + {(provided, snapshot) => ( + + )} + + ); + })} + {provided.placeholder} + + )} + : ( {items.map((item: T, index) => { const key = getItemKey(item, index); @@ -141,36 +169,7 @@ const InternalTable = (props: TableProps): React.ReactEl ); })} - ) : - ( - - {(provided) => ( - - {items.map((item: T, index) => { - const key = getItemKey(item, index); - const isSelected = selectedItems.has(item); - - return ( - - {(provided) => ( - - )} - - ); - })} - - )} - - ) + ); }; const renderEmptyState = () => { @@ -200,10 +199,10 @@ const InternalTable = (props: TableProps): React.ReactEl { draggable ? - console.log(a)}> - {renderHeaders()} - {renderItems()} - + console.log(a)}> + {renderHeaders()} + {renderItems()} + : <> {renderHeaders()} {renderItems()} From 6cee6eb535792bc5fc0e675871283bccb7726976 Mon Sep 17 00:00:00 2001 From: animesh1987 Date: Wed, 16 Dec 2020 20:40:05 +1100 Subject: [PATCH 3/6] feat(component): update interface and add table section --- .../big-design/src/components/Table/Table.tsx | 104 ++++++++++-------- .../big-design/src/components/Table/types.ts | 6 +- packages/docs/pages/Table/TablePage.tsx | 35 +++++- 3 files changed, 99 insertions(+), 46 deletions(-) diff --git a/packages/big-design/src/components/Table/Table.tsx b/packages/big-design/src/components/Table/Table.tsx index bcdc496f3..bfbd0d1e5 100644 --- a/packages/big-design/src/components/Table/Table.tsx +++ b/packages/big-design/src/components/Table/Table.tsx @@ -1,5 +1,5 @@ import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; -import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'; +import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd'; import { useEventCallback, useUniqueId } from '../../hooks'; import { MarginProps } from '../../mixins'; @@ -19,7 +19,7 @@ const InternalTable = (props: TableProps): React.ReactEl className, columns, actions, - draggable = false, + dragAndDrop, emptyComponent, headerless = false, id, @@ -33,6 +33,7 @@ const InternalTable = (props: TableProps): React.ReactEl style, ...rest } = props; + const actionsRef = useRef(null); const uniqueTableId = useUniqueId('table'); const tableIdRef = useRef(id || uniqueTableId); @@ -79,6 +80,23 @@ const InternalTable = (props: TableProps): React.ReactEl [sortable], ); + const onDragEnd = useCallback( + (result: DropResult) => { + const { destination, source } = result; + + if (!destination) { + return; + } + + if (destination.droppableId === source.droppableId && destination.index === source.index) { + return; + } + + dragAndDrop && dragAndDrop.onDragEnd(source.index, destination.index); + }, + [dragAndDrop], + ); + const shouldRenderActions = () => { return Boolean(actions) || Boolean(pagination) || Boolean(selectable) || Boolean(itemName); }; @@ -120,43 +138,43 @@ const InternalTable = (props: TableProps): React.ReactEl ); - const renderItems = () => { - console.log('draggable items', draggable); - - return draggable ? - {provided => ( - + const renderItems = () => + dragAndDrop ? ( + + {(provided) => ( + {items.map((item: T, index) => { - const key = getItemKey(item, index); - const isSelected = selectedItems.has(item); - - return ( - - {(provided, snapshot) => ( - - )} - - ); + const key = getItemKey(item, index); + const isSelected = selectedItems.has(item); + + return ( + + {(provided, snapshot) => ( + + )} + + ); })} {provided.placeholder} - - )} - : ( + + )} + + ) : ( {items.map((item: T, index) => { const key = getItemKey(item, index); const isSelected = selectedItems.has(item); - + return ( (props: TableProps): React.ReactEl })} ); - }; const renderEmptyState = () => { if (items.length === 0 && emptyComponent) { @@ -179,8 +196,7 @@ const InternalTable = (props: TableProps): React.ReactEl return null; }; - - console.log('draggable is', draggable); + return ( <> {shouldRenderActions() && ( @@ -197,17 +213,17 @@ const InternalTable = (props: TableProps): React.ReactEl /> )} - { - draggable ? - console.log(a)}> - {renderHeaders()} - {renderItems()} - - : <> + {dragAndDrop ? ( + + {renderHeaders()} + {renderItems()} + + ) : ( + <> {renderHeaders()} {renderItems()} - - } + + )} {renderEmptyState()} diff --git a/packages/big-design/src/components/Table/types.ts b/packages/big-design/src/components/Table/types.ts index f918c3973..251e9e6dd 100644 --- a/packages/big-design/src/components/Table/types.ts +++ b/packages/big-design/src/components/Table/types.ts @@ -8,6 +8,10 @@ export interface TableSelectable { onSelectionChange(selectedItems: T[]): void; } +export interface TableDraggable { + onDragEnd(from: number, to: number): void; +} + export type TableSortDirection = 'ASC' | 'DESC'; export interface TableSortable { @@ -38,7 +42,7 @@ export type TablePaginationProps = Omit; export interface TableProps extends React.TableHTMLAttributes { actions?: React.ReactNode; columns: Array>; - draggable?: boolean; + dragAndDrop?: TableDraggable; emptyComponent?: React.ReactElement; headerless?: boolean; itemName?: string; diff --git a/packages/docs/pages/Table/TablePage.tsx b/packages/docs/pages/Table/TablePage.tsx index c9542e43c..824cae4e0 100644 --- a/packages/docs/pages/Table/TablePage.tsx +++ b/packages/docs/pages/Table/TablePage.tsx @@ -39,6 +39,12 @@ const sort = (items, columnHash, direction) => { ); }; +const dragEnd = (items, from, to) => { + const item = items.splice(from, 1); + items.splice(to, 0, ...item); + return items; +}; + const TablePage = () => { return ( <> @@ -47,7 +53,6 @@ const TablePage = () => { {/* jsx-to-string:start */}
sku }, { header: 'Name', hash: 'name', render: ({ name }) => name }, @@ -248,6 +253,34 @@ const TablePage = () => { /> {/* jsx-to-string:end */} + +

Usage with drag and drop

+ + + {/* jsx-to-string:start */} + {function Example() { + const [items, setItems] = useState(data); + + const onDragEnd = (from: number, to: number) => setItems((currentItems) => dragEnd(currentItems, from, to)); + + return ( +
sku, isSortable: true }, + { header: 'Name', hash: 'name', render: ({ name }) => name, isSortable: true }, + { header: 'Stock', hash: 'stock', render: ({ stock }) => stock, isSortable: true }, + ]} + items={items} + itemName="Products" + dragAndDrop={{ + onDragEnd, + }} + /> + ); + }} + {/* jsx-to-string:end */} + ); }; From d256e485a701c462c172d6e3a3848244499d3437 Mon Sep 17 00:00:00 2001 From: animesh1987 Date: Thu, 17 Dec 2020 13:39:05 +1100 Subject: [PATCH 4/6] feat(component): refactor to show drag icon --- .../Table/HeaderCell/HeaderCell.tsx | 10 +++ .../src/components/Table/Row/Row.tsx | 8 ++ .../big-design/src/components/Table/Table.tsx | 90 +++++++++++-------- .../big-design/src/components/Table/types.ts | 6 +- packages/docs/pages/Table/TablePage.tsx | 4 +- 5 files changed, 71 insertions(+), 47 deletions(-) diff --git a/packages/big-design/src/components/Table/HeaderCell/HeaderCell.tsx b/packages/big-design/src/components/Table/HeaderCell/HeaderCell.tsx index a322cee11..bee68303a 100644 --- a/packages/big-design/src/components/Table/HeaderCell/HeaderCell.tsx +++ b/packages/big-design/src/components/Table/HeaderCell/HeaderCell.tsx @@ -22,6 +22,10 @@ export interface HeaderCheckboxCellProps { stickyHeader?: boolean; } +export interface DragIconCellProps { + actionsRef: RefObject; +} + const InternalHeaderCell = ({ children, column, @@ -78,4 +82,10 @@ export const HeaderCheckboxCell: React.FC = memo(({ sti return ; }); +export const DragIconHeaderCell: React.FC = memo(({ actionsRef }) => { + const actionsSize = useComponentSize(actionsRef); + + return ; +}); + export const HeaderCell = typedMemo(InternalHeaderCell); diff --git a/packages/big-design/src/components/Table/Row/Row.tsx b/packages/big-design/src/components/Table/Row/Row.tsx index 89642fe68..7e9150c7c 100644 --- a/packages/big-design/src/components/Table/Row/Row.tsx +++ b/packages/big-design/src/components/Table/Row/Row.tsx @@ -1,3 +1,4 @@ +import { DragIndicatorIcon } from '@bigcommerce/big-design-icons'; import React, { forwardRef, TableHTMLAttributes } from 'react'; import { typedMemo } from '../../../utils'; @@ -13,6 +14,7 @@ export interface RowProps extends TableHTMLAttributes { isSelectable?: boolean; item: T; columns: Array>; + showDragIcon?: boolean; onItemSelect?(item: T): void; } @@ -27,6 +29,7 @@ const InternalRow = ({ isSelectable = false, isSelected = false, item, + showDragIcon = false, onItemSelect, ...rest }: RowProps & PrivateProps) => { @@ -40,6 +43,11 @@ const InternalRow = ({ return ( + {showDragIcon && ( + + + + )} {isSelectable && ( diff --git a/packages/big-design/src/components/Table/Table.tsx b/packages/big-design/src/components/Table/Table.tsx index bfbd0d1e5..29a272686 100644 --- a/packages/big-design/src/components/Table/Table.tsx +++ b/packages/big-design/src/components/Table/Table.tsx @@ -9,7 +9,7 @@ import { Actions } from './Actions'; import { Body } from './Body'; import { Head } from './Head'; import { HeaderCell } from './HeaderCell'; -import { HeaderCheckboxCell } from './HeaderCell/HeaderCell'; +import { DragIconHeaderCell, HeaderCheckboxCell } from './HeaderCell/HeaderCell'; import { Row } from './Row'; import { StyledTable, StyledTableFigure } from './styled'; import { TableColumn, TableItem, TableProps } from './types'; @@ -19,13 +19,13 @@ const InternalTable = (props: TableProps): React.ReactEl className, columns, actions, - dragAndDrop, emptyComponent, headerless = false, id, itemName, items, keyField = 'id', + onRowDrop, pagination, selectable, sortable, @@ -92,9 +92,11 @@ const InternalTable = (props: TableProps): React.ReactEl return; } - dragAndDrop && dragAndDrop.onDragEnd(source.index, destination.index); + if (typeof onRowDrop === 'function') { + onRowDrop(source.index, destination.index); + } }, - [dragAndDrop], + [onRowDrop], ); const shouldRenderActions = () => { @@ -112,6 +114,7 @@ const InternalTable = (props: TableProps): React.ReactEl const renderHeaders = () => ( + {typeof onRowDrop === 'function' && } {isSelectable && } {columns.map((column, index) => { @@ -138,37 +141,42 @@ const InternalTable = (props: TableProps): React.ReactEl ); + const renderDroppableItems = () => ( + + {(provided) => ( + + {items.map((item: T, index) => { + const key = getItemKey(item, index); + const isSelected = selectedItems.has(item); + + return ( + + {(provided, snapshot) => ( + + )} + + ); + })} + {provided.placeholder} + + )} + + ); + const renderItems = () => - dragAndDrop ? ( - - {(provided) => ( - - {items.map((item: T, index) => { - const key = getItemKey(item, index); - const isSelected = selectedItems.has(item); - - return ( - - {(provided, snapshot) => ( - - )} - - ); - })} - {provided.placeholder} - - )} - + onRowDrop ? ( + renderDroppableItems() ) : ( {items.map((item: T, index) => { @@ -197,6 +205,13 @@ const InternalTable = (props: TableProps): React.ReactEl return null; }; + const renderWithDragDropContext = () => ( + + {renderHeaders()} + {renderItems()} + + ); + return ( <> {shouldRenderActions() && ( @@ -213,11 +228,8 @@ const InternalTable = (props: TableProps): React.ReactEl /> )} - {dragAndDrop ? ( - - {renderHeaders()} - {renderItems()} - + {onRowDrop ? ( + renderWithDragDropContext() ) : ( <> {renderHeaders()} diff --git a/packages/big-design/src/components/Table/types.ts b/packages/big-design/src/components/Table/types.ts index 251e9e6dd..4d49c9e4f 100644 --- a/packages/big-design/src/components/Table/types.ts +++ b/packages/big-design/src/components/Table/types.ts @@ -8,10 +8,6 @@ export interface TableSelectable { onSelectionChange(selectedItems: T[]): void; } -export interface TableDraggable { - onDragEnd(from: number, to: number): void; -} - export type TableSortDirection = 'ASC' | 'DESC'; export interface TableSortable { @@ -42,12 +38,12 @@ export type TablePaginationProps = Omit; export interface TableProps extends React.TableHTMLAttributes { actions?: React.ReactNode; columns: Array>; - dragAndDrop?: TableDraggable; emptyComponent?: React.ReactElement; headerless?: boolean; itemName?: string; items: T[]; keyField?: string; + onRowDrop?(from: number, to: number): void; pagination?: TablePaginationProps; selectable?: TableSelectable; sortable?: TableSortable; diff --git a/packages/docs/pages/Table/TablePage.tsx b/packages/docs/pages/Table/TablePage.tsx index 824cae4e0..da49a7b92 100644 --- a/packages/docs/pages/Table/TablePage.tsx +++ b/packages/docs/pages/Table/TablePage.tsx @@ -273,9 +273,7 @@ const TablePage = () => { ]} items={items} itemName="Products" - dragAndDrop={{ - onDragEnd, - }} + onRowDrop={onDragEnd} /> ); }} From 3e740b2c502cecb30bdce79733a2c9f4a4dbe5c1 Mon Sep 17 00:00:00 2001 From: animesh1987 Date: Tue, 5 Jan 2021 16:49:43 +1100 Subject: [PATCH 5/6] feat(component): add test for drag and drop snapshot --- .../Table/__snapshots__/spec.tsx.snap | 385 ++++++++++++++++++ .../big-design/src/components/Table/spec.tsx | 30 ++ 2 files changed, 415 insertions(+) diff --git a/packages/big-design/src/components/Table/__snapshots__/spec.tsx.snap b/packages/big-design/src/components/Table/__snapshots__/spec.tsx.snap index 4618c8cef..2aab00160 100644 --- a/packages/big-design/src/components/Table/__snapshots__/spec.tsx.snap +++ b/packages/big-design/src/components/Table/__snapshots__/spec.tsx.snap @@ -1,5 +1,389 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`draggable renders drag and drop icon 1`] = ` +.c8 { + vertical-align: middle; + height: 1.5rem; + width: 1.5rem; +} + +.c3 { + box-sizing: border-box; +} + +.c4 { + -webkit-align-content: stretch; + -ms-flex-line-pack: stretch; + align-content: stretch; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-flex-wrap: nowrap; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.c1 { + background-color: #F6F7FC; + border-bottom: 1px solid #D9DCE9; + border-top: 1px solid #D9DCE9; + box-sizing: border-box; + color: #5E637A; + font-size: 1rem; + padding: 0.75rem; + white-space: nowrap; +} + +.c2 { + background-color: #F6F7FC; + border-bottom: 1px solid #D9DCE9; + border-top: 1px solid #D9DCE9; + box-sizing: border-box; + color: #5E637A; + font-size: 1rem; + padding: 0.75rem; + white-space: nowrap; + cursor: pointer; +} + +.c5 { + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c7 { + background-color: #FFFFFF; + box-sizing: border-box; + color: #313440; + font-size: 1rem; + padding: 0.75rem; + border-bottom: 1px solid #D9DCE9; +} + +.c6 { + -webkit-transition: all 150ms ease-out; + transition: all 150ms ease-out; + -webkit-transition-property: background-color; + transition-property: background-color; + display: table-row; + background-color: transparent; +} + +.c6:hover { + background-color: #F6F7FC; +} + +.c0 { + border-color: transparent; + border-spacing: 0; + color: #313440; + text-align: left; + width: 100%; +} + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ Sku +
+
+
+ Name +
+
+
+ Stock +
+
+ + + + + + SM13 + + [Sample] Smith Journal 13 + + 25 +
+ + + + + + DPB + + [Sample] Dustpan & Brush + + 34 +
+ + + + + + OFSUC + + [Sample] Utility Caddy + + 45 +
+ + + + + + CLC + + [Sample] Canvas Laundry Cart + + 2 +
+ + + + + + CGLD + + [Sample] Laundry Detergent + + 29 +
+`; + exports[`renders a pagination component 1`] = ` .c10 { vertical-align: middle; @@ -535,6 +919,7 @@ exports[`renders a simple table 1`] = ` transition: all 150ms ease-out; -webkit-transition-property: background-color; transition-property: background-color; + display: table-row; background-color: transparent; } diff --git a/packages/big-design/src/components/Table/spec.tsx b/packages/big-design/src/components/Table/spec.tsx index 9829e2581..29eed7163 100644 --- a/packages/big-design/src/components/Table/spec.tsx +++ b/packages/big-design/src/components/Table/spec.tsx @@ -469,3 +469,33 @@ describe('sortable', () => { expect(screen.queryByText(/no items/i)).not.toBeInTheDocument(); }); }); + +describe('draggable', () => { + let columns: any; + let items: any; + let onRowDrop: jest.Mock; + + beforeEach(() => { + onRowDrop = jest.fn(); + items = [ + { sku: 'SM13', name: '[Sample] Smith Journal 13', stock: 25 }, + { sku: 'DPB', name: '[Sample] Dustpan & Brush', stock: 34 }, + { sku: 'OFSUC', name: '[Sample] Utility Caddy', stock: 45 }, + { sku: 'CLC', name: '[Sample] Canvas Laundry Cart', stock: 2 }, + { sku: 'CGLD', name: '[Sample] Laundry Detergent', stock: 29 }, + ]; + columns = [ + { header: 'Sku', hash: 'sku', render: ({ sku }: any) => sku, isSortable: true }, + { header: 'Name', hash: 'name', render: ({ name }: any) => name }, + { header: 'Stock', hash: 'stock', render: ({ stock }: any) => stock }, + ]; + }); + + test('renders drag and drop icon', () => { + const { container } = render(); + const dragIcons = container.querySelectorAll('svg'); + + expect(dragIcons?.length).toBe(items.length); + expect(container.firstChild).toMatchSnapshot(); + }); +}); From 9dbe14399f0f40c3ea883083f315f3ba772064a2 Mon Sep 17 00:00:00 2001 From: animesh1987 Date: Tue, 12 Jan 2021 16:10:37 +1100 Subject: [PATCH 6/6] feat(component): add test to check if onRowDrop is called --- .../big-design/src/components/Table/Table.tsx | 14 +- .../Table/__snapshots__/spec.tsx.snap | 384 ------------------ .../big-design/src/components/Table/spec.tsx | 16 +- packages/docs/pages/Table/TablePage.tsx | 1 + 4 files changed, 21 insertions(+), 394 deletions(-) diff --git a/packages/big-design/src/components/Table/Table.tsx b/packages/big-design/src/components/Table/Table.tsx index 29a272686..dddf6b5b8 100644 --- a/packages/big-design/src/components/Table/Table.tsx +++ b/packages/big-design/src/components/Table/Table.tsx @@ -142,7 +142,7 @@ const InternalTable = (props: TableProps): React.ReactEl ); const renderDroppableItems = () => ( - + {(provided) => ( {items.map((item: T, index) => { @@ -205,13 +205,6 @@ const InternalTable = (props: TableProps): React.ReactEl return null; }; - const renderWithDragDropContext = () => ( - - {renderHeaders()} - {renderItems()} - - ); - return ( <> {shouldRenderActions() && ( @@ -229,7 +222,10 @@ const InternalTable = (props: TableProps): React.ReactEl )} {onRowDrop ? ( - renderWithDragDropContext() + + {renderHeaders()} + {renderItems()} + ) : ( <> {renderHeaders()} diff --git a/packages/big-design/src/components/Table/__snapshots__/spec.tsx.snap b/packages/big-design/src/components/Table/__snapshots__/spec.tsx.snap index 2aab00160..c2ab05f44 100644 --- a/packages/big-design/src/components/Table/__snapshots__/spec.tsx.snap +++ b/packages/big-design/src/components/Table/__snapshots__/spec.tsx.snap @@ -1,389 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`draggable renders drag and drop icon 1`] = ` -.c8 { - vertical-align: middle; - height: 1.5rem; - width: 1.5rem; -} - -.c3 { - box-sizing: border-box; -} - -.c4 { - -webkit-align-content: stretch; - -ms-flex-line-pack: stretch; - align-content: stretch; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; - -webkit-flex-wrap: nowrap; - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; - -webkit-box-pack: start; - -webkit-justify-content: flex-start; - -ms-flex-pack: start; - justify-content: flex-start; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - -.c1 { - background-color: #F6F7FC; - border-bottom: 1px solid #D9DCE9; - border-top: 1px solid #D9DCE9; - box-sizing: border-box; - color: #5E637A; - font-size: 1rem; - padding: 0.75rem; - white-space: nowrap; -} - -.c2 { - background-color: #F6F7FC; - border-bottom: 1px solid #D9DCE9; - border-top: 1px solid #D9DCE9; - box-sizing: border-box; - color: #5E637A; - font-size: 1rem; - padding: 0.75rem; - white-space: nowrap; - cursor: pointer; -} - -.c5 { - -webkit-box-pack: start; - -webkit-justify-content: flex-start; - -ms-flex-pack: start; - justify-content: flex-start; -} - -.c7 { - background-color: #FFFFFF; - box-sizing: border-box; - color: #313440; - font-size: 1rem; - padding: 0.75rem; - border-bottom: 1px solid #D9DCE9; -} - -.c6 { - -webkit-transition: all 150ms ease-out; - transition: all 150ms ease-out; - -webkit-transition-property: background-color; - transition-property: background-color; - display: table-row; - background-color: transparent; -} - -.c6:hover { - background-color: #F6F7FC; -} - -.c0 { - border-color: transparent; - border-spacing: 0; - color: #313440; - text-align: left; - width: 100%; -} - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- Sku -
-
-
- Name -
-
-
- Stock -
-
- - - - - - SM13 - - [Sample] Smith Journal 13 - - 25 -
- - - - - - DPB - - [Sample] Dustpan & Brush - - 34 -
- - - - - - OFSUC - - [Sample] Utility Caddy - - 45 -
- - - - - - CLC - - [Sample] Canvas Laundry Cart - - 2 -
- - - - - - CGLD - - [Sample] Laundry Detergent - - 29 -
-`; - exports[`renders a pagination component 1`] = ` .c10 { vertical-align: middle; diff --git a/packages/big-design/src/components/Table/spec.tsx b/packages/big-design/src/components/Table/spec.tsx index 29eed7163..7142032d4 100644 --- a/packages/big-design/src/components/Table/spec.tsx +++ b/packages/big-design/src/components/Table/spec.tsx @@ -496,6 +496,20 @@ describe('draggable', () => { const dragIcons = container.querySelectorAll('svg'); expect(dragIcons?.length).toBe(items.length); - expect(container.firstChild).toMatchSnapshot(); + }); + + test('onRowDrop called with expected args when a row is dropped', () => { + const spaceKey = { keyCode: 32 }; + const downKey = { keyCode: 40 }; + const { container } = render(); + const dragEl = container.querySelector('[data-rbd-draggable-id]') as HTMLElement; + dragEl.focus(); + expect(dragEl).toHaveFocus(); + + fireEvent.keyDown(dragEl, spaceKey); + fireEvent.keyDown(dragEl, downKey); + fireEvent.keyDown(dragEl, spaceKey); + + expect(onRowDrop).toHaveBeenCalledWith(0, 1); }); }); diff --git a/packages/docs/pages/Table/TablePage.tsx b/packages/docs/pages/Table/TablePage.tsx index da49a7b92..a1aeba389 100644 --- a/packages/docs/pages/Table/TablePage.tsx +++ b/packages/docs/pages/Table/TablePage.tsx @@ -42,6 +42,7 @@ const sort = (items, columnHash, direction) => { const dragEnd = (items, from, to) => { const item = items.splice(from, 1); items.splice(to, 0, ...item); + return items; };