From c7c486c5fa99693588c11b8f7c080f3a0c5f2512 Mon Sep 17 00:00:00 2001 From: Franco Correa Date: Tue, 18 Dec 2018 16:55:23 -0300 Subject: [PATCH] Sortable Resource List (#1098) Now, the `sortable` prop can be used to make the list sortable by dragging and dropping items. You will need to implement the `onSortEnd` method in order to reorder the items using the provided `arrayMove` ![kapture 2018-12-04 at 18 22 31](https://user-images.githubusercontent.com/4152942/49473729-9bed4f80-f7f1-11e8-87c3-5638d8d95bac.gif) --- .../molecules/resource-list/index.js | 3 +- .../molecules/resource-list/item/item.js | 134 ++++++++++++------ .../molecules/resource-list/resource-list.js | 124 ++++++++++++++-- .../molecules/resource-list/resource-list.md | 40 ++++++ .../resource-list/resource-list.story.js | 81 ++++++++--- .../resource-list/sortable-handle.js | 55 +++++++ core/components/package.json | 1 + yarn.lock | 11 +- 8 files changed, 365 insertions(+), 84 deletions(-) create mode 100644 core/components/molecules/resource-list/sortable-handle.js diff --git a/core/components/molecules/resource-list/index.js b/core/components/molecules/resource-list/index.js index 6ffa60240..405004362 100644 --- a/core/components/molecules/resource-list/index.js +++ b/core/components/molecules/resource-list/index.js @@ -1,3 +1,4 @@ -import ResourceList from './resource-list' +import ResourceList, { arrayMove } from './resource-list' export default ResourceList +export { arrayMove } diff --git a/core/components/molecules/resource-list/item/item.js b/core/components/molecules/resource-list/item/item.js index ec1cc38b0..7178ab928 100644 --- a/core/components/molecules/resource-list/item/item.js +++ b/core/components/molecules/resource-list/item/item.js @@ -1,6 +1,6 @@ import React from 'react' import PropTypes from 'prop-types' -import styled from 'styled-components' +import styled, { css } from '@auth0/cosmos/styled' import { Button, ButtonGroup, Link } from '@auth0/cosmos' import Avatar, { StyledAvatar } from '@auth0/cosmos/atoms/avatar' import { StyledTextAllCaps } from '@auth0/cosmos/atoms/text' @@ -9,7 +9,8 @@ import { __ICONNAMES__ } from '@auth0/cosmos/atoms/icon' import { colors, spacing } from '@auth0/cosmos-tokens' import Automation from '../../../_helpers/automation-attribute' import { actionToButtonProps, buttonBuilder } from '../action-builder' -import { deprecate } from '../../../_helpers/custom-validations' + +const itemFocusOutline = '2px' /** * Builds the button from the action or @@ -37,54 +38,79 @@ const resolveAction = (item, action, key) => { */ const resolveActions = (actions, item) => actions.map(resolveAction.bind(this, item)) -const ListItem = props => { - let image - let title - let subtitle - let actions +class ListItem extends React.Component { + constructor(props) { + super(props) + this.dragHandler = React.createRef() + } + + renderSortingHandler() { + if (this.props.reorderHandle) { + const SortableHandler = this.props.reorderHandle - const callHandler = handler => evt => handler(evt, props.item) + return + } - if (props.image) { - // TODO: We might want a way to control the type of the avatar, but we don't - // want to leak every prop from Avatar into the ListItem... - image = - } else if (props.icon) { - image = + return null } - if (props.title) { - if (props.href) { - title = {props.title} - } else { - title = props.title + renderImage() { + if (this.props.image) { + // TODO: We might want a way to control the type of the avatar, but we don't + // want to leak every prop from Avatar into the ListItem... + return + } else if (this.props.icon) { + return } + + return null } - if (props.subtitle) { - subtitle = {props.subtitle} + renderTitle() { + if (this.props.title) { + if (this.props.href) { + return {this.props.title} + } else { + return this.props.title + } + } + + return null } - if (props.actions) { - actions = {resolveActions(props.actions, props.item)} + renderSubtitle() { + return this.props.subtitle ? {this.props.subtitle} : null } - return ( - - - {image} -
- {title} - {subtitle} -
-
- {props.children && {props.children}} - {props.actions && {actions}} -
- ) + renderActions() { + return this.props.actions ? ( + {resolveActions(this.props.actions, this.props.item)} + ) : null + } + + render() { + const props = this.props + const callHandler = handler => evt => handler(evt, props.item) + + return ( + + {this.renderSortingHandler()} + + {this.renderImage()} +
+ {this.renderTitle()} + {this.renderSubtitle()} +
+
+ {props.children && {props.children}} + {props.actions && {this.renderActions()}} +
+ ) + } } ListItem.Element = styled.li` @@ -95,12 +121,31 @@ ListItem.Element = styled.li` border-top: 1px solid ${colors.list.borderColor}; padding: ${spacing.small} ${spacing.xsmall}; cursor: ${props => (props.onClick ? 'pointer' : 'inherit')}; + &:hover { background: ${colors.list.backgroundHover}; } + + &.cosmos-dragging { + background-color: ${colors.base.white}; + opacity: 0.9; + box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.2); + } + > *:not(:last-child) { margin-right: ${spacing.small}; } + + /* Disable pointer events on non-dragging elements */ + /* to avoid unexpected hover behaviors. */ + ${props => + props.draggingMode + ? css` + &:not(.cosmos-dragging) { + pointer-events: none; + } + ` + : ''}; ` ListItem.Header = styled.div` @@ -158,11 +203,12 @@ ListItem.propTypes = { const firstAction = props.actions[0] if (!React.isValidElement(firstAction)) { - return deprecate(props, { - name: 'actions', - oldAPI: 'passing objects in actions', - replacement: '