From 6fd60e3cd59c52ca390717894acc43d845c9cc1c Mon Sep 17 00:00:00 2001 From: Alexandru Munteanu <alexandru.munt@gmail.com> Date: Mon, 20 Aug 2018 16:14:53 +0300 Subject: [PATCH] feat(styleguide): add react dnd to filesection - WIP --- packages/component-faraday-ui/src/File.js | 6 +- packages/component-faraday-ui/src/File.md | 6 +- .../component-faraday-ui/src/FileSection.js | 57 ++----- .../component-faraday-ui/src/FileSection.md | 33 +++- .../component-faraday-ui/src/SortableList.js | 151 +++++++++++++++++- .../component-faraday-ui/src/SortableList.md | 54 ++++++- packages/component-faraday-ui/src/Tag.js | 3 +- packages/component-faraday-ui/src/Text.js | 1 + .../src/gridItems/Item.js | 1 + .../component-faraday-ui/src/gridItems/Row.js | 1 + packages/component-faraday-ui/src/index.js | 1 + .../component-faraday-ui/src/styledHelpers.js | 22 +++ .../src/components/Files/FileSection.js | 12 +- packages/styleguide/src/Wrapper.js | 2 +- 14 files changed, 289 insertions(+), 61 deletions(-) diff --git a/packages/component-faraday-ui/src/File.js b/packages/component-faraday-ui/src/File.js index a0f04d2d9..a9c5252ef 100644 --- a/packages/component-faraday-ui/src/File.js +++ b/packages/component-faraday-ui/src/File.js @@ -31,7 +31,7 @@ const hasPreview = (name = '') => { } const FileItem = ({ - file, + item: file, fileSize, onPreview, removeFile, @@ -69,7 +69,7 @@ const FileItem = ({ FileItem.propTypes = { /** The file. */ - file: PropTypes.shape({ + item: PropTypes.shape({ id: PropTypes.string, name: PropTypes.string, size: PropTypes.number, @@ -83,7 +83,7 @@ FileItem.propTypes = { } export default compose( - withProps(({ file: { name, size } }) => ({ + withProps(({ name, size }) => ({ hasPreview: hasPreview(name), fileSize: parseFileSize(size), })), diff --git a/packages/component-faraday-ui/src/File.md b/packages/component-faraday-ui/src/File.md index 7fc933709..615a0badc 100644 --- a/packages/component-faraday-ui/src/File.md +++ b/packages/component-faraday-ui/src/File.md @@ -2,7 +2,7 @@ A pdf file. ```js <FileItem - file={{ + item={{ id: 'file1', name: 'myfile.pdf', size: 1231312, @@ -16,7 +16,7 @@ A Word document (no preview available). ```js <FileItem - file={{ + item={{ id: 'file1', name: 'myfile.docx', size: 51312, @@ -30,7 +30,7 @@ With a drag handle. ```js <FileItem - file={{ + item={{ id: 'file1', name: 'myfile.pdf', size: 1231312, diff --git a/packages/component-faraday-ui/src/FileSection.js b/packages/component-faraday-ui/src/FileSection.js index b5aaa0421..2590b3a1c 100644 --- a/packages/component-faraday-ui/src/FileSection.js +++ b/packages/component-faraday-ui/src/FileSection.js @@ -1,10 +1,20 @@ import React from 'react' +import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' import { FilePicker } from '@pubsweet/ui' -import styled, { css } from 'styled-components' import { compose, withProps } from 'recompose' -import { Label, ActionLink, Text, Row, Item, FileItem, DragHandle } from './' +import { radiusHelpers } from './styledHelpers' +import { + Row, + Item, + Text, + Label, + FileItem, + ActionLink, + DragHandle, + SortableList, +} from './' const EXTENSIONS = { pdf: 'PDF', @@ -18,6 +28,7 @@ const FileSection = ({ isLast, isFirst, required, + files = [], supportedFormats, allowedFileExtensions, }) => ( @@ -27,7 +38,7 @@ const FileSection = ({ <Label required={required}>{title}</Label> <FilePicker allowedFileExtensions={allowedFileExtensions} - onUpload={file => console.log('uploaded ', file)} + onUpload={file => {}} > <ActionLink icon="plus" size="small"> UPLOAD FILE @@ -42,22 +53,10 @@ const FileSection = ({ </Item> )} </Row> - <FileItem + <SortableList dragHandle={DragHandle} - file={{ - id: 'file1', - name: 'myfile.docx', - size: 51312, - }} - mb={1} - /> - <FileItem - dragHandle={DragHandle} - file={{ - id: 'file1', - name: 'myfile.docx', - size: 51312, - }} + items={files} + listItem={FileItem} mb={1} /> </Root> @@ -72,28 +71,6 @@ export default compose( )(FileSection) // #region styles -const radiusHelpers = props => { - const marginTop = props.isFirst - ? css` - border-top-left-radius: ${th('borderRadius')}; - border-top-right-radius: ${th('borderRadius')}; - ` - : css`` - - const marginBottom = props.isLast - ? css` - border-bottom-left-radius: ${th('borderRadius')}; - border-bottom-right-radius: ${th('borderRadius')}; - ` - : css`` - - return css` - border-radius: none; - ${marginTop}; - ${marginBottom}; - ` -} - const Root = styled.div` background: ${th('colorBackground')}; min-height: calc(${th('gridUnit')} * 22); diff --git a/packages/component-faraday-ui/src/FileSection.md b/packages/component-faraday-ui/src/FileSection.md index d9dd000a9..e67f554e0 100644 --- a/packages/component-faraday-ui/src/FileSection.md +++ b/packages/component-faraday-ui/src/FileSection.md @@ -1,7 +1,21 @@ A section that shows FileItems. Drag and drop support. ```js +const files = [ + { + id: 'file1', + name: 'myfile.docx', + size: 51312, + }, + { + id: 'file2', + name: 'myfile.docx', + size: 133127, + }, +]; + <FileSection + files={files} title="Main Manuscript" required allowedFileExtensions={['pdf', 'doc', 'docx']} @@ -11,16 +25,31 @@ A section that shows FileItems. Drag and drop support. Multiple sections on top of each other. ```js +const files = [ + { + id: 'file1', + name: 'myfile.docx', + size: 51312, + }, + { + id: 'file2', + name: 'myfile.docx', + size: 133127, + }, +]; + <div style={{ display: 'flex', flexDirection: 'column' }}> <FileSection - title="Main Manuscript" isFirst required + files={files} + title="Main Manuscript" allowedFileExtensions={['pdf', 'doc', 'docx']} /> <FileSection - title="Cover Letter" required + title="Cover Letter" + files={files} allowedFileExtensions={['pdf', 'doc', 'docx']} /> <FileSection title="Supplimental Files" required isLast /> diff --git a/packages/component-faraday-ui/src/SortableList.js b/packages/component-faraday-ui/src/SortableList.js index 294df130b..1c6595d65 100644 --- a/packages/component-faraday-ui/src/SortableList.js +++ b/packages/component-faraday-ui/src/SortableList.js @@ -1,5 +1,152 @@ -import React from 'react' +/* eslint-disable react/forbid-prop-types */ +/* eslint-disable react/require-default-props */ +import React, { Fragment } from 'react' +import PropTypes from 'prop-types' +import { pick } from 'lodash' +import { findDOMNode } from 'react-dom' +import { compose, toClass } from 'recompose' +import { DragSource, DropTarget } from 'react-dnd' -const SortableList = () => <div>sortable items inside</div> +const itemSource = { + beginDrag(props) { + return pick(props, props.beginDragProps) + }, +} + +const itemTarget = { + hover({ moveItem, index, listId }, monitor, component) { + const { index: dragIndex, listId: toListId } = monitor.getItem() + const hoverIndex = index + + if (listId !== toListId) { + return + } + + if (dragIndex === hoverIndex) { + return + } + + const hoverBoundingRect = findDOMNode(component).getBoundingClientRect() // eslint-disable-line + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2 + const clientOffset = monitor.getClientOffset() + const hoverClientY = clientOffset.y - hoverBoundingRect.top + + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { + return + } + + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { + return + } + if (typeof moveItem === 'function') { + moveItem(dragIndex, hoverIndex, monitor.getItem()) + } + monitor.getItem().index = hoverIndex + }, + drop({ dropItem, ...restProps }, monitor) { + if (dropItem && typeof dropItem === 'function') + dropItem(monitor.getItem(), restProps) + }, +} + +const Item = ({ + listItem, + dragHandle, + connectDragSource, + connectDropTarget, + connectDragPreview, + ...rest +}) => + dragHandle + ? connectDragPreview( + connectDropTarget( + <div style={{ flex: 1 }}> + {React.createElement(listItem, { + ...rest, + dragHandle: connectDragSource( + <div style={{ display: 'flex' }}> + {React.createElement(dragHandle)} + </div>, + ), + })} + </div>, + ), + ) + : connectDropTarget( + connectDragSource( + <div style={{ flex: 1 }}>{React.createElement(listItem, rest)}</div>, + ), + ) + +const DecoratedItem = compose( + DropTarget('item', itemTarget, (connect, monitor) => ({ + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + })), + DragSource('item', itemSource, (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + connectDragPreview: connect.dragPreview(), + isDragging: monitor.isDragging(), + })), + toClass, +)(Item) + +const SortableList = ({ + listItem, + dragHandle, + items = [], + itemKey = 'id', + ...rest +}) => ( + <Fragment> + {items.map((item, i) => ( + <DecoratedItem + dragHandle={dragHandle} + index={i} + item={item} + key={item[itemKey]} + listItem={listItem} + {...item} + {...rest} + /> + ))} + </Fragment> +) + +SortableList.propTypes = { + /** List items. */ + items: PropTypes.array, + /** Render prop for list's item. */ + listItem: PropTypes.oneOfType([PropTypes.element, PropTypes.func]).isRequired, + /** Key used to map through items. */ + itemKey: PropTypes.string, + /** Function invoked to change the order of the list's items. */ + moveItem: PropTypes.func, + /** Function invoked when the currently dragged item is dropped. */ + dropItem: PropTypes.func, + /** + * What props to pick from the dragged item. E.g.: if a specific property is needed + * in the move function. + * */ + beginDragProps: PropTypes.array, +} + +SortableList.moveItem = (items, dragIndex, hoverIndex) => { + if (!dragIndex) return items + if (dragIndex <= hoverIndex) { + return [ + ...items.slice(0, dragIndex), + items[hoverIndex], + items[dragIndex], + ...items.slice(hoverIndex + 1), + ] + } + return [ + ...items.slice(0, hoverIndex), + items[dragIndex], + items[hoverIndex], + ...items.slice(dragIndex + 1), + ] +} export default SortableList diff --git a/packages/component-faraday-ui/src/SortableList.md b/packages/component-faraday-ui/src/SortableList.md index 3302a88e0..4db4cfa6c 100644 --- a/packages/component-faraday-ui/src/SortableList.md +++ b/packages/component-faraday-ui/src/SortableList.md @@ -1,5 +1,55 @@ A list with drag and drop support. ```js -<SortableList /> -``` \ No newline at end of file +const items = [ + { firstName: 'John', lastName: 'Doe' }, + { firstName: 'Michael', lastName: 'Jackson' }, + { firstName: 'David', lastName: 'Blaine' }, +] + +const Item = ({ dragHandle, isOver, isDragging, ...rest }) => ( + <div style={{ display: 'flex' }}> + {dragHandle} + <span> + {rest.firstName} {rest.lastName} + </span> + </div> +) + +class Example extends React.Component { + constructor(props) { + super(props) + + this.state = { + items: [ + { firstName: 'John', lastName: 'Doe' }, + { firstName: 'Michael', lastName: 'Jackson' }, + { firstName: 'David', lastName: 'Blaine' }, + ], + } + } + + render() { + return ( + <SortableList + items={this.state.items} + listItem={Item} + itemKey="firstName" + dragHandle={DragHandle} + moveItem={(dragIndex, hoverIndex) => { + this.setState({ + items: SortableList.moveItem( + this.state.items, + dragIndex, + hoverIndex, + ), + }) + }} + dropItem={(item, props) => {}} + /> + ) + } +} + +<Example /> +``` diff --git a/packages/component-faraday-ui/src/Tag.js b/packages/component-faraday-ui/src/Tag.js index 2093a3fc8..b1fae817f 100644 --- a/packages/component-faraday-ui/src/Tag.js +++ b/packages/component-faraday-ui/src/Tag.js @@ -1,7 +1,7 @@ import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' -// #region styles +/** @component */ export default styled.div` background-color: ${props => props.status ? th('tag.statusBackgroundColor') : th('tag.backgroundColor')}; @@ -15,4 +15,3 @@ export default styled.div` text-transform: uppercase; width: fit-content; ` -// #endregion diff --git a/packages/component-faraday-ui/src/Text.js b/packages/component-faraday-ui/src/Text.js index f3663495b..48d1330b0 100644 --- a/packages/component-faraday-ui/src/Text.js +++ b/packages/component-faraday-ui/src/Text.js @@ -36,6 +36,7 @@ const fontSize = css` props.small ? th('lineHeightBaseSmall') : th('lineHeightBase')}; ` +/** @component */ export default styled.span` display: inline-block; font-style: ${props => props.fontStyle || 'normal'}; diff --git a/packages/component-faraday-ui/src/gridItems/Item.js b/packages/component-faraday-ui/src/gridItems/Item.js index 500bd409f..780a1125f 100644 --- a/packages/component-faraday-ui/src/gridItems/Item.js +++ b/packages/component-faraday-ui/src/gridItems/Item.js @@ -2,6 +2,7 @@ import { isNumber } from 'lodash' import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' +/** @component */ export default styled.div.attrs({ 'data-test-id': props => props['data-test-id'] || 'item', })` diff --git a/packages/component-faraday-ui/src/gridItems/Row.js b/packages/component-faraday-ui/src/gridItems/Row.js index 60fedfa89..06ab4eff0 100644 --- a/packages/component-faraday-ui/src/gridItems/Row.js +++ b/packages/component-faraday-ui/src/gridItems/Row.js @@ -3,6 +3,7 @@ import styled from 'styled-components' import { marginHelper } from '../styledHelpers' +/** @component */ export default styled.div.attrs({ 'data-test-id': props => props['data-test-id'] || 'row', })` diff --git a/packages/component-faraday-ui/src/index.js b/packages/component-faraday-ui/src/index.js index 22efa382a..0dd450ad6 100644 --- a/packages/component-faraday-ui/src/index.js +++ b/packages/component-faraday-ui/src/index.js @@ -13,6 +13,7 @@ export { default as Label } from './Label' export { default as Logo } from './Logo' export { default as ManuscriptCard } from './ManuscriptCard' export { default as PersonInfo } from './PersonInfo' +export { default as SortableList } from './SortableList' export { default as Tag } from './Tag' export { default as Text } from './Text' diff --git a/packages/component-faraday-ui/src/styledHelpers.js b/packages/component-faraday-ui/src/styledHelpers.js index 7e64825c5..2bdd97529 100644 --- a/packages/component-faraday-ui/src/styledHelpers.js +++ b/packages/component-faraday-ui/src/styledHelpers.js @@ -60,3 +60,25 @@ export const positionHelper = css` right: ${props => has(props, 'right') ? `${get(props, 'right')}px` : 'unset'}; ` + +export const radiusHelpers = props => { + const borderTop = props.isFirst + ? css` + border-top-left-radius: ${th('borderRadius')}; + border-top-right-radius: ${th('borderRadius')}; + ` + : css`` + + const borderBottom = props.isLast + ? css` + border-bottom-left-radius: ${th('borderRadius')}; + border-bottom-right-radius: ${th('borderRadius')}; + ` + : css`` + + return css` + border-radius: none; + ${borderTop}; + ${borderBottom}; + ` +} diff --git a/packages/components-faraday/src/components/Files/FileSection.js b/packages/components-faraday/src/components/Files/FileSection.js index 5811b4207..148c51a92 100644 --- a/packages/components-faraday/src/components/Files/FileSection.js +++ b/packages/components-faraday/src/components/Files/FileSection.js @@ -132,11 +132,11 @@ export default compose( { drop( { - changeList, - listId: toListId, - maxFiles, files, + maxFiles, setError, + changeList, + listId: toListId, allowedFileExtensions, }, monitor, @@ -176,11 +176,11 @@ export default compose( drop( { files, - maxFiles, addFile, - allowedFileExtensions, + maxFiles, setError, requestPending, + allowedFileExtensions, }, monitor, ) { @@ -203,9 +203,9 @@ export default compose( }, }, (connect, monitor) => ({ - connectFileDrop: connect.dropTarget(), isFileOver: monitor.isOver(), canDropFile: monitor.canDrop(), + connectFileDrop: connect.dropTarget(), }), ), )(FileSection) diff --git a/packages/styleguide/src/Wrapper.js b/packages/styleguide/src/Wrapper.js index 7b3bc02dd..b4b841f14 100644 --- a/packages/styleguide/src/Wrapper.js +++ b/packages/styleguide/src/Wrapper.js @@ -12,7 +12,7 @@ const store = createStore( }), ) -export default class ThemeWrapper extends Component { +export default class Wrapper extends Component { render() { return ( <Provider store={store}> -- GitLab