From eb30848354fa476b97416deda9055acfdbd6df8b Mon Sep 17 00:00:00 2001 From: fritz-c Date: Sun, 3 Dec 2017 11:34:54 +0900 Subject: [PATCH] feat: change theme to full-node drag theme --- CHANGELOG.md | 31 ---- README.md | 12 +- demo/app.css | 1 + demo/app.js | 99 ++----------- index.js | 5 - node-content-renderer.js | 223 +++++++++++++--------------- node-content-renderer.scss | 294 +++++++++++++++++-------------------- package.json | 12 +- tree-node-renderer.js | 80 ---------- tree-node-renderer.scss | 4 - 10 files changed, 259 insertions(+), 502 deletions(-) delete mode 100644 tree-node-renderer.js delete mode 100644 tree-node-renderer.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 795b7a7..e69de29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,31 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. - - -## [1.1.2](https://github.com/fritz-c/react-sortable-tree-theme-file-explorer/compare/v1.1.1...v1.1.2) (2017-11-28) - - -### Bug Fixes - -* silence warning on latest react-sortable-tree ([7c81d55](https://github.com/fritz-c/react-sortable-tree-theme-file-explorer/commit/7c81d55)) - - - - -## [1.1.1](https://github.com/fritz-c/react-sortable-tree-theme-file-explorer/compare/v1.1.0...v1.1.1) (2017-11-01) - - -### Bug Fixes - -* make canDrag work. Fixes [#5](https://github.com/fritz-c/react-sortable-tree-theme-file-explorer/issues/5) ([f82d6c1](https://github.com/fritz-c/react-sortable-tree-theme-file-explorer/commit/f82d6c1)) - - - - -# 1.1.0 (2017-10-29) - - -### Features - -* Complete basic appearance ([98a8d09](https://github.com/fritz-c/react-sortable-tree/commit/98a8d09)) diff --git a/README.md b/README.md index 76d67df..dfc150f 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -# React Sortable Tree File Explorer Theme -![theme appearance](https://user-images.githubusercontent.com/4413963/32144463-a7de23e0-bcfc-11e7-8054-1a83d561261e.png) +# React Sortable Tree Full Node Drag Theme +theme appearance + ## Features -* You can click anywhere on a node to drag it. -* More compact design, with indentation alone used to represent tree depth. +* No drag handles. You can click anywhere on a node to drag it. ## Usage ```sh -npm install --save react-sortable-tree-theme-file-explorer +npm install --save react-sortable-tree-theme-full-node-drag ``` ```jsx import React, { Component } from 'react'; import SortableTree from 'react-sortable-tree'; -import FileExplorerTheme from 'react-sortable-tree-theme-file-explorer'; +import FileExplorerTheme from 'react-sortable-tree-theme-full-node-drag'; export default class Tree extends Component { constructor(props) { diff --git a/demo/app.css b/demo/app.css index eacb6f8..6168799 100644 --- a/demo/app.css +++ b/demo/app.css @@ -1,4 +1,5 @@ body { padding: 0; margin: 0; + background-color: #f1f1f1; } diff --git a/demo/app.js b/demo/app.js index d0ca424..99ce35a 100644 --- a/demo/app.js +++ b/demo/app.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import SortableTree, { toggleExpandedForAll } from 'react-sortable-tree'; -import FileExplorerTheme from '../index'; +import CustomTheme from '../index'; import './app.css'; class App extends Component { @@ -12,44 +12,16 @@ class App extends Component { searchFocusIndex: 0, searchFoundCount: null, treeData: [ - { title: '.gitignore' }, - { title: 'package.json' }, + { title: 'This is the Full Node Drag theme' }, + { title: 'You can click anywhere on the node to drag it' }, { - title: 'src', - isDirectory: true, - expanded: true, - children: [ - { title: 'styles.css' }, - { title: 'index.js' }, - { title: 'reducers.js' }, - { title: 'actions.js' }, - { title: 'utils.js' }, - ], - }, - { - title: 'tmp', - isDirectory: true, - children: [ - { title: '12214124-log' }, - { title: 'drag-disabled-file', dragDisabled: true }, - ], - }, - { - title: 'build', - isDirectory: true, - children: [{ title: 'react-sortable-tree.js' }], - }, - { - title: 'public', - isDirectory: true, - }, - { - title: 'node_modules', - isDirectory: true, + title: 'This node has dragging disabled', + subtitle: 'Note how the hover behavior is different', + dragDisabled: true, }, + { title: 'Chicken', children: [{ title: 'Egg' }] }, ], }; - this.updateTreeData = this.updateTreeData.bind(this); this.expandAll = this.expandAll.bind(this); this.collapseAll = this.collapseAll.bind(this); @@ -118,7 +90,7 @@ class App extends Component { style={{ display: 'flex', flexDirection: 'column', height: '100vh' }} >
-

File Explorer Theme

+

Full Node Drag Theme

         @@ -135,7 +107,8 @@ class App extends Component { type="text" value={searchString} onChange={event => - this.setState({ searchString: event.target.value })} + this.setState({ searchString: event.target.value }) + } /> @@ -166,7 +139,7 @@ class App extends Component {
0 ? searchFocusIndex % matches.length : 0, - })} + }) + } canDrag={({ node }) => !node.dragDisabled} - canDrop={({ nextParent }) => !nextParent || nextParent.isDirectory} generateNodeProps={rowInfo => ({ - icons: rowInfo.node.isDirectory - ? [ -
, - ] - : [ -
- F -
, - ], buttons: [ - , + , ], })} /> diff --git a/index.js b/index.js index d036448..e8d1b44 100644 --- a/index.js +++ b/index.js @@ -11,12 +11,7 @@ // placeholderRenderer: PropTypes.func, import nodeContentRenderer from './node-content-renderer'; -import treeNodeRenderer from './tree-node-renderer'; module.exports = { nodeContentRenderer, - treeNodeRenderer, - scaffoldBlockPxWidth: 25, - rowHeight: 25, - slideRegionSize: 50, }; diff --git a/node-content-renderer.js b/node-content-renderer.js index 5a1ce96..e46f71d 100644 --- a/node-content-renderer.js +++ b/node-content-renderer.js @@ -13,7 +13,7 @@ function isDescendant(older, younger) { } // eslint-disable-next-line react/prefer-stateless-function -class FileThemeNodeContentRenderer extends Component { +class CustomThemeNodeContentRenderer extends Component { render() { const { scaffoldBlockPxWidth, @@ -25,6 +25,7 @@ class FileThemeNodeContentRenderer extends Component { canDrag, node, title, + subtitle, draggedNode, path, treeIndex, @@ -46,72 +47,105 @@ class FileThemeNodeContentRenderer extends Component { ...otherProps } = this.props; const nodeTitle = title || node.title; + const nodeSubtitle = subtitle || node.subtitle; const isDraggedDescendant = draggedNode && isDescendant(draggedNode, node); const isLandingPadActive = !didDrop && isDragging; - // Construct the scaffold representing the structure of the tree - const scaffold = []; - lowerSiblingCounts.forEach((lowerSiblingCount, i) => { - scaffold.push( + const nodeContent = connectDragPreview( +
- ); - - if (treeIndex !== listIndex && i === swapDepth) { - // This row has been shifted, and is at the depth of - // the line pointing to the new destination - let highlightLineClass = ''; + className={ + styles.rowContents + + (!canDrag ? ` ${styles.rowContentsDragDisabled}` : '') + } + > +
+ + {typeof nodeTitle === 'function' + ? nodeTitle({ + node, + path, + treeIndex, + }) + : nodeTitle} + - if (listIndex === swapFrom + swapLength - 1) { - // This block is on the bottom (target) line - // This block points at the target block (where the row will go when released) - highlightLineClass = styles.highlightBottomLeftCorner; - } else if (treeIndex === swapFrom) { - // This block is on the top (source) line - highlightLineClass = styles.highlightTopLeftCorner; - } else { - // This block is between the bottom and top - highlightLineClass = styles.highlightLineVertical; - } + {nodeSubtitle && ( + + {typeof nodeSubtitle === 'function' + ? nodeSubtitle({ + node, + path, + treeIndex, + }) + : nodeSubtitle} + + )} +
- scaffold.push( -
- ); - } - }); +
+ {buttons.map((btn, index) => ( +
+ {btn} +
+ ))} +
+
+
+ ); - const nodeContent = ( + return (
{toggleChildrenVisibility && node.children && - node.children.length > 0 && ( -
); - - return canDrag - ? connectDragSource(nodeContent, { dropEffect: 'copy' }) - : nodeContent; } } -FileThemeNodeContentRenderer.defaultProps = { +CustomThemeNodeContentRenderer.defaultProps = { buttons: [], canDrag: false, canDrop: false, @@ -203,6 +174,7 @@ FileThemeNodeContentRenderer.defaultProps = { isSearchMatch: false, parentNode: null, style: {}, + subtitle: null, swapDepth: null, swapFrom: null, swapLength: null, @@ -210,7 +182,7 @@ FileThemeNodeContentRenderer.defaultProps = { toggleChildrenVisibility: null, }; -FileThemeNodeContentRenderer.propTypes = { +CustomThemeNodeContentRenderer.propTypes = { buttons: PropTypes.arrayOf(PropTypes.node), canDrag: PropTypes.bool, className: PropTypes.string, @@ -225,6 +197,7 @@ FileThemeNodeContentRenderer.propTypes = { ).isRequired, scaffoldBlockPxWidth: PropTypes.number.isRequired, style: PropTypes.shape({}), + subtitle: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), swapDepth: PropTypes.number, swapFrom: PropTypes.number, swapLength: PropTypes.number, @@ -246,4 +219,4 @@ FileThemeNodeContentRenderer.propTypes = { isOver: PropTypes.bool.isRequired, }; -export default FileThemeNodeContentRenderer; +export default CustomThemeNodeContentRenderer; diff --git a/node-content-renderer.scss b/node-content-renderer.scss index a18c416..420777f 100644 --- a/node-content-renderer.scss +++ b/node-content-renderer.scss @@ -1,4 +1,7 @@ +$row-padding: 10px; + .rowWrapper { + padding: $row-padding $row-padding $row-padding 0; height: 100%; box-sizing: border-box; cursor: move; @@ -14,13 +17,15 @@ .rowWrapperDragDisabled { cursor: default; + &:hover { + opacity: 1; + } } .row { height: 100%; white-space: nowrap; display: flex; - position: relative; & > * { box-sizing: border-box; @@ -31,9 +36,9 @@ * The outline of where the element will go if dropped, displayed while dragging */ .rowLandingPad { - border: none; - box-shadow: none; - outline: none; + border: none !important; + box-shadow: none !important; + outline: none !important; * { opacity: 0 !important; @@ -41,7 +46,7 @@ &::before { background-color: lightblue; - border: 2px dotted black; + border: 3px dashed white; content: ''; position: absolute; top: 0; @@ -67,14 +72,14 @@ * Nodes matching the search conditions are highlighted */ .rowSearchMatch { - box-shadow: inset 0 -7px 7px -3px #0080ff; + outline: solid 3px #0080ff; } /** * The node that matches the search conditions and is currently focused */ .rowSearchFocus { - box-shadow: inset 0 -7px 7px -3px #fc6421; + outline: solid 3px #fc6421; } %rowItem { @@ -86,10 +91,16 @@ @extend %rowItem; position: relative; height: 100%; + border: solid #bbb 1px; + box-shadow: 0 2px 2px -2px; + padding: 0 5px 0 10px; + border-radius: 2px; + min-width: 230px; flex: 1 0 auto; display: flex; align-items: center; justify-content: space-between; + background-color: white; } .rowLabel { @@ -104,195 +115,158 @@ display: flex; } - -.toolbarButton { +.moveHandle { @extend %rowItem; + + height: 100%; + width: 44px; + background: #d9d9d9 + url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MiIgaGVpZ2h0PSI0MiI+PGcgc3Ryb2tlPSIjRkZGIiBzdHJva2Utd2lkdGg9IjIuOSIgPjxwYXRoIGQ9Ik0xNCAxNS43aDE0LjQiLz48cGF0aCBkPSJNMTQgMjEuNGgxNC40Ii8+PHBhdGggZD0iTTE0IDI3LjFoMTQuNCIvPjwvZz4KPC9zdmc+') + no-repeat center; + border: solid #aaa 1px; + box-shadow: 0 2px 2px -2px; + cursor: move; + border-radius: 1px; + z-index: 1; } -.collapseButton, -.expandButton { - appearance: none; - border: none; - background: transparent; - padding: 0; - z-index: 2; +.loadingHandle { + @extend .moveHandle; + + cursor: default; + background: #d9d9d9; +} + +@keyframes pointFade { + 0%, + 19.999%, + 100% { + opacity: 0; + } + 20% { + opacity: 1; + } +} + +.loadingCircle { + width: 80%; + height: 80%; + margin: 10%; + position: relative; +} + +.loadingCirclePoint { + width: 100%; + height: 100%; position: absolute; - top: 45%; - width: 30px; - height: 30px; - transform: translate3d(-50%, -50%, 0); - cursor: pointer; + left: 0; + top: 0; - &::after { + $point-count: 12; + $spin-animation-time: 800ms; + + &:before { content: ''; - position: absolute; - transform-origin: 7px 4px; - transform: translate3d(-50%, -20%, 0); - border: solid transparent 10px; - border-left-width: 7px; - border-right-width: 7px; - border-top-color: gray; + display: block; + margin: 0 auto; + width: 11%; + height: 30%; + background-color: #fff; + border-radius: 30%; + animation: pointFade $spin-animation-time infinite ease-in-out both; } - &:hover::after { - border-top-color: black; - } + @for $i from 1 through (($point-count + 1) / 2) { + &:nth-of-type(#{$i}) { + transform: rotate(360deg / $point-count * ($i - 1)); + } - &:focus { - outline: none; + &:nth-of-type(#{$i + $point-count / 2}) { + transform: rotate(180deg + 360deg / $point-count * ($i - 1)); + } - &::after { - filter: drop-shadow(0 0 1px #83bef9) drop-shadow(0 0 1px #83bef9) - drop-shadow(0 0 1px #83bef9); + &:nth-of-type(#{$i}), + &:nth-of-type(#{$i + $point-count / 2}) { + &:before { + animation-delay: - $spin-animation-time + ($spin-animation-time / + $point-count * 2 * ($i - 1)); + } } } } -.expandButton::after { - transform: translate3d(-50%, -20%, 0) rotateZ(-90deg); +.toolbarButton { + @extend %rowItem; } -/** - * Line for under a node with children - */ -.lineChildren { - height: 100%; - display: inline-block; +.rowTitle { + font-weight: bold; } -/* ========================================================================== - Scaffold - - Line-overlaid blocks used for showing the tree structure - ========================================================================== */ -.lineBlock { - height: 100%; - position: relative; - display: inline-block; - flex: 0 0 auto; +.rowTitleWithSubtitle { + font-size: 85%; + display: block; + height: 0.8rem; } -.absoluteLineBlock { - @extend .lineBlock; - position: absolute; - top: 0; +.rowSubtitle { + font-size: 70%; + line-height: 1; } -/* Highlight line for pointing to dragged row destination - ========================================================================== */ -$highlight-color: #36c2f6; -$highlight-line-size: 6px; // Make it an even number for clean rendering - -/** - * +--+--+ - * | | | - * | | | - * | | | - * +--+--+ - */ -.highlightLineVertical { - z-index: 3; +.collapseButton, +.expandButton { + appearance: none; + border: none; + position: absolute; + border-radius: 100%; + box-shadow: 0 0 0 1px #000; + width: 16px; + height: 16px; + padding: 0; + top: 50%; + transform: translate(-50%, -50%); + cursor: pointer; - &::before { - position: absolute; - content: ''; - background-color: $highlight-color; - width: $highlight-line-size; - margin-left: $highlight-line-size / -2; - left: 50%; - top: 0; - height: 100%; + &:focus { + outline: none; + box-shadow: 0 0 0 1px #000, 0 0 1px 3px #83bef9; } - @keyframes arrow-pulse { - $base-multiplier: 10; - 0% { - transform: translate(0, 0); - opacity: 0; - } - 30% { - transform: translate(0, 30% * $base-multiplier); - opacity: 1; - } - 70% { - transform: translate(0, 70% * $base-multiplier); - opacity: 1; - } - 100% { - transform: translate(0, 100% * $base-multiplier); - opacity: 0; - } + &:hover:not(:active) { + background-size: 24px; + height: 20px; + width: 20px; } +} - &::after { - content: ''; - position: absolute; - height: 0; - margin-left: -1 * $highlight-line-size / 2; - left: 50%; - top: 0; - border-left: $highlight-line-size / 2 solid transparent; - border-right: $highlight-line-size / 2 solid transparent; - border-top: $highlight-line-size / 2 solid white; - animation: arrow-pulse 1s infinite linear both; - } +.collapseButton { + background: #fff + url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCI+PGNpcmNsZSBjeD0iOSIgY3k9IjkiIHI9IjgiIGZpbGw9IiNGRkYiLz48ZyBzdHJva2U9IiM5ODk4OTgiIHN0cm9rZS13aWR0aD0iMS45IiA+PHBhdGggZD0iTTQuNSA5aDkiLz48L2c+Cjwvc3ZnPg==') + no-repeat center; } -/** - * +-----+ - * | | - * | +--+ - * | | | - * +--+--+ - */ -.highlightTopLeftCorner { - &::before { - z-index: 3; - content: ''; - position: absolute; - border-top: solid $highlight-line-size $highlight-color; - border-left: solid $highlight-line-size $highlight-color; - box-sizing: border-box; - height: calc(50% + #{$highlight-line-size / 2}); - top: 50%; - margin-top: $highlight-line-size / -2; - right: 0; - width: calc(50% + #{$highlight-line-size / 2}); - } +.expandButton { + background: #fff + url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCI+PGNpcmNsZSBjeD0iOSIgY3k9IjkiIHI9IjgiIGZpbGw9IiNGRkYiLz48ZyBzdHJva2U9IiM5ODk4OTgiIHN0cm9rZS13aWR0aD0iMS45IiA+PHBhdGggZD0iTTQuNSA5aDkiLz48cGF0aCBkPSJNOSA0LjV2OSIvPjwvZz4KPC9zdmc+') + no-repeat center; } /** - * +--+--+ - * | | | - * | | | - * | +->| - * +-----+ + * Line for under a node with children */ -.highlightBottomLeftCorner { - $arrow-size: 7px; - z-index: 3; - - &::before { - content: ''; - position: absolute; - border-bottom: solid $highlight-line-size $highlight-color; - border-left: solid $highlight-line-size $highlight-color; - box-sizing: border-box; - height: calc(100% + #{$highlight-line-size / 2}); - top: 0; - right: $arrow-size; - width: calc(50% - #{$arrow-size - ($highlight-line-size / 2)}); - } +.lineChildren { + height: 100%; + display: inline-block; + position: absolute; &::after { content: ''; position: absolute; - height: 0; - right: 0; - top: 100%; - margin-top: -1 * $arrow-size; - border-top: $arrow-size solid transparent; - border-bottom: $arrow-size solid transparent; - border-left: $arrow-size solid $highlight-color; + background-color: black; + width: 1px; + left: 50%; + bottom: 0; + height: $row-padding; } } diff --git a/package.json b/package.json index 3d9dc14..dc74176 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "react-sortable-tree-theme-file-explorer", - "version": "1.1.2", - "description": "File explorer theme for react-sortable-tree", + "name": "react-sortable-tree-theme-full-node-drag", + "version": "1.0.0", + "description": "Full node drag theme for react-sortable-tree", "scripts": { "build": "npm run clean && cross-env NODE_ENV=production TARGET=umd webpack --bail", "build:demo": "npm run clean:demo && cross-env NODE_ENV=production TARGET=demo webpack --bail", @@ -19,10 +19,10 @@ ], "repository": { "type": "git", - "url": "https://github.com/fritz-c/react-sortable-tree-theme-file-explorer" + "url": "https://github.com/fritz-c/react-sortable-tree-theme-full-node-drag" }, - "homepage": "https://github.com/fritz-c/react-sortable-tree-theme-file-explorer", - "bugs": "https://github.com/fritz-c/react-sortable-tree-theme-file-explorer/issues", + "homepage": "https://github.com/fritz-c/react-sortable-tree-theme-full-node-drag", + "bugs": "https://github.com/fritz-c/react-sortable-tree-theme-full-node-drag/issues", "authors": [ "Chris Fritz" ], diff --git a/tree-node-renderer.js b/tree-node-renderer.js deleted file mode 100644 index 0871712..0000000 --- a/tree-node-renderer.js +++ /dev/null @@ -1,80 +0,0 @@ -import React, { Component, Children, cloneElement } from 'react'; -import PropTypes from 'prop-types'; -import styles from './tree-node-renderer.scss'; - -class FileThemeTreeNodeRenderer extends Component { - render() { - const { - children, - listIndex, - swapFrom, - swapLength, - swapDepth, - scaffoldBlockPxWidth, - lowerSiblingCounts, - connectDropTarget, - isOver, - draggedNode, - canDrop, - treeIndex, - treeId, // Delete from otherProps - getPrevRow, // Delete from otherProps - node, // Delete from otherProps - path, // Delete from otherProps - ...otherProps - } = this.props; - - return connectDropTarget( -
- {Children.map(children, child => - cloneElement(child, { - isOver, - canDrop, - draggedNode, - lowerSiblingCounts, - listIndex, - swapFrom, - swapLength, - swapDepth, - }) - )} -
- ); - } -} - -FileThemeTreeNodeRenderer.defaultProps = { - swapFrom: null, - swapDepth: null, - swapLength: null, - canDrop: false, - draggedNode: null, -}; - -FileThemeTreeNodeRenderer.propTypes = { - treeIndex: PropTypes.number.isRequired, - treeId: PropTypes.string.isRequired, - swapFrom: PropTypes.number, - swapDepth: PropTypes.number, - swapLength: PropTypes.number, - scaffoldBlockPxWidth: PropTypes.number.isRequired, - lowerSiblingCounts: PropTypes.arrayOf(PropTypes.number).isRequired, - - listIndex: PropTypes.number.isRequired, - children: PropTypes.node.isRequired, - - // Drop target - connectDropTarget: PropTypes.func.isRequired, - isOver: PropTypes.bool.isRequired, - canDrop: PropTypes.bool, - draggedNode: PropTypes.shape({}), - - // used in dndManager - getPrevRow: PropTypes.func.isRequired, - node: PropTypes.shape({}).isRequired, - path: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.string, PropTypes.number]) - ).isRequired, -}; - -export default FileThemeTreeNodeRenderer; diff --git a/tree-node-renderer.scss b/tree-node-renderer.scss deleted file mode 100644 index 7e33a50..0000000 --- a/tree-node-renderer.scss +++ /dev/null @@ -1,4 +0,0 @@ -.node { - min-width: 100%; - position: relative; -}