├── .travis.yml ├── .codesandbox └── ci.json ├── src ├── .stories │ ├── grouping-items │ │ ├── Item │ │ │ ├── index.js │ │ │ ├── Item.js │ │ │ └── Item.scss │ │ ├── List │ │ │ ├── index.js │ │ │ ├── List.scss │ │ │ └── List.js │ │ ├── utils.js │ │ └── index.js │ ├── interactive-elements-stress-test │ │ ├── Item │ │ │ ├── index.js │ │ │ ├── Item.js │ │ │ └── Item.scss │ │ ├── List.js │ │ └── index.js │ ├── Storybook.scss │ └── index.js ├── SortableContainer │ ├── defaultGetHelperDimensions.js │ ├── defaultShouldCancelStart.js │ ├── props.js │ └── index.js ├── index.js ├── Manager │ └── index.js ├── SortableHandle │ └── index.js ├── AutoScroller │ └── index.js ├── SortableElement │ └── index.js └── utils.js ├── .npmignore ├── .gitignore ├── .prettierrc ├── .github └── assets │ └── react-sortable-hoc-logo.png ├── examples ├── .eslintrc.json ├── basic.js ├── drag-handle.js ├── react-infinite.js ├── collections.js ├── react-virtualized-table-columns.js └── react-virtualized.js ├── .storybook ├── config.js ├── webpack.config.js ├── theme.js └── manager-head.html ├── .editorconfig ├── .eslintrc.json ├── LICENSE ├── rollup.config.js ├── package.json ├── types └── index.d.ts ├── CHANGELOG.md └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | -------------------------------------------------------------------------------- /.codesandbox/ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "sandboxes": ["react", "o104x95y86"] 3 | } 4 | -------------------------------------------------------------------------------- /src/.stories/grouping-items/Item/index.js: -------------------------------------------------------------------------------- 1 | import Item from './Item'; 2 | 3 | export default Item; 4 | -------------------------------------------------------------------------------- /src/.stories/grouping-items/List/index.js: -------------------------------------------------------------------------------- 1 | import List from './List'; 2 | 3 | export default List; 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .babelrc 3 | coverage 4 | src 5 | test 6 | .* 7 | *.md 8 | codecov.yml 9 | .travis.yml 10 | -------------------------------------------------------------------------------- /src/.stories/interactive-elements-stress-test/Item/index.js: -------------------------------------------------------------------------------- 1 | import Item from './Item'; 2 | 3 | export default Item; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | node_modules 3 | dist 4 | styles.min.css 5 | styles.min.css.map 6 | coverage 7 | npm-debug.log 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": false, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } 7 | -------------------------------------------------------------------------------- /.github/assets/react-sortable-hoc-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clauderic/react-sortable-hoc/HEAD/.github/assets/react-sortable-hoc-logo.png -------------------------------------------------------------------------------- /src/.stories/grouping-items/utils.js: -------------------------------------------------------------------------------- 1 | export function generateItems(length) { 2 | return Array.from(Array(length), (_, index) => index.toString()); 3 | } 4 | -------------------------------------------------------------------------------- /examples/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "import/no-unresolved": "off", 4 | "react/prop-types": "off", 5 | "react/no-array-index-key": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/SortableContainer/defaultGetHelperDimensions.js: -------------------------------------------------------------------------------- 1 | export default function defaultGetHelperDimensions({node}) { 2 | return { 3 | height: node.offsetHeight, 4 | width: node.offsetWidth, 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import {addParameters, configure} from '@storybook/react'; 2 | import theme from './theme'; 3 | 4 | addParameters({ 5 | options: { 6 | showAddonPanel: false, 7 | theme, 8 | }, 9 | }); 10 | 11 | function loadStories() { 12 | require('../src/.stories/index.js'); 13 | } 14 | 15 | configure(loadStories, module); 16 | -------------------------------------------------------------------------------- /src/.stories/interactive-elements-stress-test/Item/Item.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {sortableElement} from '../../../../src'; 3 | 4 | import styles from './Item.scss'; 5 | 6 | function Item(props) { 7 | const {children} = props; 8 | 9 | return ( 10 |
11 | {children} 12 |
13 | ); 14 | } 15 | 16 | export default sortableElement(Item); 17 | -------------------------------------------------------------------------------- /src/.stories/grouping-items/List/List.scss: -------------------------------------------------------------------------------- 1 | $backgroundColor: #f3f3f3; 2 | $borderColor: #efefef; 3 | $borderWidth: 1px; 4 | 5 | .List { 6 | position: relative; 7 | width: 400px; 8 | height: 600px; 9 | overflow: auto; 10 | -webkit-overflow-scrolling: touch; 11 | z-index: 0; 12 | background-color: $backgroundColor; 13 | border: $borderWidth solid $borderColor; 14 | border-radius: 3px; 15 | outline: none; 16 | } 17 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export {default as SortableContainer} from './SortableContainer'; 2 | export {default as SortableElement} from './SortableElement'; 3 | export {default as SortableHandle} from './SortableHandle'; 4 | 5 | export {default as sortableContainer} from './SortableContainer'; 6 | export {default as sortableElement} from './SortableElement'; 7 | export {default as sortableHandle} from './SortableHandle'; 8 | 9 | export {arrayMove} from './utils'; 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | # Markdown syntax specifies that trailing whitespaces can be meaningful, 12 | # so let’s not trim those. e.g. 2 trailing spaces = linebreak (
) 13 | # See https://daringfireball.net/projects/markdown/syntax#p 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /src/.stories/interactive-elements-stress-test/List.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {sortableContainer} from '../../../src'; 3 | 4 | import Item from './Item'; 5 | 6 | function List({items}) { 7 | return ( 8 |
9 | {items.map(([key, children], index) => { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | })} 16 |
17 | ); 18 | } 19 | 20 | export default sortableContainer(List); 21 | -------------------------------------------------------------------------------- /src/SortableContainer/defaultShouldCancelStart.js: -------------------------------------------------------------------------------- 1 | import {NodeType, closest} from '../utils'; 2 | 3 | export default function defaultShouldCancelStart(event) { 4 | // Cancel sorting if the event target is an `input`, `textarea`, `select` or `option` 5 | const interactiveElements = [ 6 | NodeType.Input, 7 | NodeType.Textarea, 8 | NodeType.Select, 9 | NodeType.Option, 10 | NodeType.Button, 11 | ]; 12 | 13 | if (interactiveElements.indexOf(event.target.tagName) !== -1) { 14 | // Return true to cancel sorting 15 | return true; 16 | } 17 | 18 | if (closest(event.target, (el) => el.contentEditable === 'true')) { 19 | return true; 20 | } 21 | 22 | return false; 23 | } 24 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /(\.scss)$/, 6 | use: [ 7 | 'style-loader', 8 | { 9 | loader: 'css-loader', 10 | options: { 11 | modules: true, 12 | localIdentName: '[name]__[local]', 13 | }, 14 | }, 15 | { 16 | loader: 'postcss-loader', 17 | options: { 18 | plugins: [require('autoprefixer')], 19 | }, 20 | }, 21 | 'sass-loader', 22 | ], 23 | }, 24 | { 25 | test: /(\.css)$/, 26 | use: ['style-loader', 'css-loader'], 27 | }, 28 | ], 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:shopify/react", "plugin:prettier/recommended"], 3 | "rules": { 4 | "no-process-env": "off", 5 | "no-lonely-if": "off", 6 | "no-undefined": "off", 7 | "no-param-reassign": "off", 8 | "no-mixed-operators": "off", 9 | "no-misleading-character-class": "off", 10 | "require-atomic-updates": "off", 11 | "prefer-object-spread": "off", 12 | "lines-around-comment": "off", 13 | "function-paren-newline": "off", 14 | "promise/catch-or-return": "off", 15 | "react/forbid-prop-types": "off", 16 | "react/jsx-filename-extension": "off", 17 | "react/no-unused-prop-types": "off", 18 | "react/no-string-refs": 1, 19 | "react/no-deprecated": 1, 20 | "shopify/binary-assignment-parens": "off", 21 | "sort-class-members/sort-class-members": "off" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/.stories/grouping-items/List/List.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {sortableContainer} from '../../../../src'; 3 | 4 | import Item from '../Item'; 5 | 6 | import styles from './List.scss'; 7 | 8 | function List({items, isSorting, selectedItems, sortingItemKey, onItemSelect}) { 9 | return ( 10 |
11 | {items.map((value, index) => { 12 | const isSelected = selectedItems.includes(value); 13 | const itemIsBeingDragged = sortingItemKey === value; 14 | 15 | return ( 16 | 26 | ); 27 | })} 28 |
29 | ); 30 | } 31 | 32 | export default sortableContainer(List); 33 | -------------------------------------------------------------------------------- /.storybook/theme.js: -------------------------------------------------------------------------------- 1 | import {create} from '@storybook/theming'; 2 | 3 | export default create({ 4 | base: 'light', 5 | 6 | colorSecondary: '#9276ff', 7 | 8 | // UI 9 | appBg: 'white', 10 | appContentBg: '#f9f9f9', 11 | appBorderColor: 'grey', 12 | appBorderRadius: 4, 13 | 14 | // Typography 15 | fontBase: '"Roboto", Helvetica Neue, Helvetica, Arial, sans-serif', 16 | fontCode: 'monospace', 17 | 18 | // Text colors 19 | textColor: '#364149', 20 | textInverseColor: 'rgba(255,255,255,0.9)', 21 | 22 | // Toolbar default and active colors 23 | barTextColor: '#FFF', 24 | barSelectedColor: '#FFF', 25 | barBg: '#9276ff', 26 | 27 | // Form colors 28 | inputBg: 'white', 29 | inputBorder: '#efefef', 30 | inputTextColor: '#364149', 31 | inputBorderRadius: 0, 32 | 33 | brandTitle: 'React Sortable HOC', 34 | brandUrl: 'https://github.com/clauderic/react-sortable-hoc', 35 | brandImage: 36 | 'https://user-images.githubusercontent.com/1416436/54170652-dfd59d80-444d-11e9-9c51-658638c0454b.png', 37 | }); 38 | -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {render} from 'react-dom'; 3 | import {sortableContainer, sortableElement} from 'react-sortable-hoc'; 4 | import arrayMove from 'array-move'; 5 | 6 | const SortableItem = sortableElement(({value}) =>
  • {value}
  • ); 7 | 8 | const SortableContainer = sortableContainer(({children}) => { 9 | return ; 10 | }); 11 | 12 | class App extends Component { 13 | state = { 14 | items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'], 15 | }; 16 | 17 | onSortEnd = ({oldIndex, newIndex}) => { 18 | this.setState(({items}) => ({ 19 | items: arrayMove(items, oldIndex, newIndex), 20 | })); 21 | }; 22 | 23 | render() { 24 | const {items} = this.state; 25 | 26 | return ( 27 | 28 | {items.map((value, index) => ( 29 | 30 | ))} 31 | 32 | ); 33 | } 34 | } 35 | 36 | render(, document.getElementById('root')); 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016, Claudéric Demers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Manager/index.js: -------------------------------------------------------------------------------- 1 | export default class Manager { 2 | refs = {}; 3 | 4 | add(collection, ref) { 5 | if (!this.refs[collection]) { 6 | this.refs[collection] = []; 7 | } 8 | 9 | this.refs[collection].push(ref); 10 | } 11 | 12 | remove(collection, ref) { 13 | const index = this.getIndex(collection, ref); 14 | 15 | if (index !== -1) { 16 | this.refs[collection].splice(index, 1); 17 | } 18 | } 19 | 20 | isActive() { 21 | return this.active; 22 | } 23 | 24 | getActive() { 25 | return this.refs[this.active.collection].find( 26 | // eslint-disable-next-line eqeqeq 27 | ({node}) => node.sortableInfo.index == this.active.index, 28 | ); 29 | } 30 | 31 | getIndex(collection, ref) { 32 | return this.refs[collection].indexOf(ref); 33 | } 34 | 35 | getOrderedRefs(collection = this.active.collection) { 36 | return this.refs[collection].sort(sortByIndex); 37 | } 38 | } 39 | 40 | function sortByIndex( 41 | { 42 | node: { 43 | sortableInfo: {index: index1}, 44 | }, 45 | }, 46 | { 47 | node: { 48 | sortableInfo: {index: index2}, 49 | }, 50 | }, 51 | ) { 52 | return index1 - index2; 53 | } 54 | -------------------------------------------------------------------------------- /src/SortableHandle/index.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {findDOMNode} from 'react-dom'; 3 | import invariant from 'invariant'; 4 | 5 | import {provideDisplayName} from '../utils'; 6 | 7 | export default function sortableHandle( 8 | WrappedComponent, 9 | config = {withRef: false}, 10 | ) { 11 | return class WithSortableHandle extends React.Component { 12 | static displayName = provideDisplayName('sortableHandle', WrappedComponent); 13 | 14 | componentDidMount() { 15 | const node = findDOMNode(this); 16 | node.sortableHandle = true; 17 | } 18 | 19 | getWrappedInstance() { 20 | invariant( 21 | config.withRef, 22 | 'To access the wrapped instance, you need to pass in {withRef: true} as the second argument of the SortableHandle() call', 23 | ); 24 | return this.wrappedInstance.current; 25 | } 26 | 27 | wrappedInstance = React.createRef(); 28 | 29 | render() { 30 | const ref = config.withRef ? this.wrappedInstance : null; 31 | 32 | return ; 33 | } 34 | }; 35 | } 36 | 37 | export function isSortableHandle(node) { 38 | return node.sortableHandle != null; 39 | } 40 | -------------------------------------------------------------------------------- /src/.stories/grouping-items/Item/Item.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import {sortableElement} from '../../../../src'; 4 | 5 | import styles from './Item.scss'; 6 | 7 | const ENTER_KEY = 13; 8 | 9 | function Item(props) { 10 | const { 11 | dragging, 12 | sorting, 13 | onClick, 14 | selected, 15 | selectedItemsCount, 16 | value, 17 | } = props; 18 | const shouldRenderItemCountBadge = dragging && selectedItemsCount > 1; 19 | 20 | return ( 21 |
    onClick(value)} 29 | onKeyPress={(event) => { 30 | if (event.which === ENTER_KEY) { 31 | onClick(value); 32 | } 33 | }} 34 | tabIndex={0} 35 | > 36 | Item {value} 37 | {shouldRenderItemCountBadge ? : null} 38 |
    39 | ); 40 | } 41 | 42 | function Badge(props) { 43 | return
    {props.count}
    ; 44 | } 45 | 46 | export default sortableElement(Item); 47 | -------------------------------------------------------------------------------- /examples/drag-handle.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {render} from 'react-dom'; 3 | import { 4 | sortableContainer, 5 | sortableElement, 6 | sortableHandle, 7 | } from 'react-sortable-hoc'; 8 | import arrayMove from 'array-move'; 9 | 10 | const DragHandle = sortableHandle(() => ::); 11 | 12 | const SortableItem = sortableElement(({value}) => ( 13 |
  • 14 | 15 | {value} 16 |
  • 17 | )); 18 | 19 | const SortableContainer = sortableContainer(({children}) => { 20 | return ; 21 | }); 22 | 23 | class App extends Component { 24 | state = { 25 | items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'], 26 | }; 27 | 28 | onSortEnd = ({oldIndex, newIndex}) => { 29 | this.setState(({items}) => ({ 30 | items: arrayMove(items, oldIndex, newIndex), 31 | })); 32 | }; 33 | 34 | render() { 35 | const {items} = this.state; 36 | 37 | return ( 38 | 39 | {items.map((value, index) => ( 40 | 41 | ))} 42 | 43 | ); 44 | } 45 | } 46 | 47 | render(, document.getElementById('root')); 48 | -------------------------------------------------------------------------------- /src/.stories/interactive-elements-stress-test/Item/Item.scss: -------------------------------------------------------------------------------- 1 | $color: #333; 2 | $white: #fff; 3 | $backgroundColor: $white; 4 | 5 | $padding: 20px; 6 | 7 | $boxShadow: 0 5px 5px -5px rgba(0, 0, 0, 0.2); 8 | 9 | $fontWeight-regular: 400; 10 | $fontWeight-bold: 600; 11 | 12 | $borderRadius: 3px; 13 | $borderWidth: 1px; 14 | $borderColor: #efefef; 15 | 16 | $focusedOutlineColor: #4c9ffe; 17 | 18 | .root { 19 | display: block; 20 | width: 250px; 21 | padding: $padding; 22 | background-color: $backgroundColor; 23 | border-bottom: $borderWidth solid #efefef; 24 | box-sizing: border-box; 25 | user-select: none; 26 | 27 | color: $color; 28 | font-family: sans-serif; 29 | font-weight: $fontWeight-regular; 30 | 31 | > * { 32 | display: block; 33 | width: 100%; 34 | font-size: 14px; 35 | } 36 | 37 | > input, 38 | > textarea { 39 | padding: 5px; 40 | border: 1px solid #e0e0e0; 41 | box-sizing: border-box; 42 | } 43 | 44 | label { 45 | input { 46 | margin-right: 0.5em; 47 | } 48 | } 49 | 50 | &:focus { 51 | outline: none; 52 | padding: $padding - 2px; 53 | padding-bottom: $padding - 1px; 54 | border: 2px solid $focusedOutlineColor; 55 | } 56 | 57 | &.dragging { 58 | &:focus { 59 | box-shadow: 0 0px 5px 1px $focusedOutlineColor; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/react-infinite.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {render} from 'react-dom'; 3 | import {sortableContainer, sortableElement} from 'react-sortable-hoc'; 4 | import arrayMove from 'array-move'; 5 | import Infinite from 'react-infinite'; 6 | 7 | const SortableItem = sortableElement(({height, value}) => { 8 | return
  • {value}
  • ; 9 | }); 10 | 11 | const SortableInfiniteList = sortableContainer(({items}) => { 12 | return ( 13 | height)} 16 | > 17 | {items.map(({value, height}, index) => ( 18 | 24 | ))} 25 | 26 | ); 27 | }); 28 | 29 | class App extends Component { 30 | state = { 31 | items: [ 32 | {value: 'Item 1', height: 89}, 33 | {value: 'Item 2', height: 59}, 34 | {value: 'Item 3', height: 130}, 35 | {value: 'Item 4', height: 59}, 36 | {value: 'Item 5', height: 200}, 37 | {value: 'Item 6', height: 150}, 38 | ], 39 | }; 40 | 41 | onSortEnd = ({oldIndex, newIndex}) => { 42 | this.setState(({items}) => ({ 43 | items: arrayMove(items, oldIndex, newIndex), 44 | })); 45 | }; 46 | 47 | render() { 48 | const {items} = this.state; 49 | 50 | return ; 51 | } 52 | } 53 | 54 | render(, document.getElementById('root')); 55 | -------------------------------------------------------------------------------- /examples/collections.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {render} from 'react-dom'; 3 | import {sortableContainer, sortableElement} from 'react-sortable-hoc'; 4 | import arrayMove from 'array-move'; 5 | 6 | const SortableItem = sortableElement(({value}) =>
  • {value}
  • ); 7 | 8 | const SortableContainer = sortableContainer(({children}) => { 9 | return
    {children}
    ; 10 | }); 11 | 12 | class App extends Component { 13 | state = { 14 | collections: [[0, 1, 2], [0, 1, 2, 3, 4], [0, 1, 2]], 15 | }; 16 | 17 | onSortEnd = ({oldIndex, newIndex, collection}) => { 18 | this.setState(({collections}) => { 19 | const newCollections = [...collections]; 20 | 21 | newCollections[collection] = arrayMove( 22 | collections[collection], 23 | oldIndex, 24 | newIndex, 25 | ); 26 | 27 | return {collections: newCollections}; 28 | }); 29 | }; 30 | 31 | render() { 32 | const {collections} = this.state; 33 | 34 | return ( 35 | 36 | {collections.map((items, index) => ( 37 | 38 | LIST {index} 39 |
      40 | {items.map((item, i) => ( 41 | 47 | ))} 48 |
    49 |
    50 | ))} 51 |
    52 | ); 53 | } 54 | } 55 | 56 | render(, document.getElementById('root')); 57 | -------------------------------------------------------------------------------- /src/.stories/grouping-items/Item/Item.scss: -------------------------------------------------------------------------------- 1 | $color: #333; 2 | $white: #fff; 3 | $backgroundColor: $white; 4 | 5 | $boxShadow: 0 5px 5px -5px rgba(0, 0, 0, 0.2); 6 | 7 | $fontWeight-regular: 400; 8 | $fontWeight-bold: 600; 9 | 10 | $borderRadius: 3px; 11 | $borderWidth: 1px; 12 | $borderColor: #efefef; 13 | 14 | $selectedColor: $white; 15 | $selectedBackgroundColor: rgba(216, 232, 251, 0.9); 16 | $selectedBorderColor: #bbcee8; 17 | 18 | $badgeColor: $white; 19 | $badgeBackgroundColor: #f75959; 20 | $badgeBorderColor: #da4553; 21 | 22 | $focusedOutlineColor: #4c9ffe; 23 | 24 | .Item { 25 | display: flex; 26 | align-items: center; 27 | width: 100%; 28 | height: 59px; 29 | padding: 0 20px; 30 | background-color: $backgroundColor; 31 | border-bottom: $borderWidth solid #efefef; 32 | box-sizing: border-box; 33 | user-select: none; 34 | outline: none; 35 | 36 | color: $color; 37 | font-weight: $fontWeight-regular; 38 | 39 | cursor: grab; 40 | 41 | &:last-child { 42 | border-bottom: none; 43 | } 44 | 45 | &.selected { 46 | background: $selectedBackgroundColor; 47 | border-bottom-color: $selectedBorderColor; 48 | 49 | &:focus { 50 | border-bottom-color: $focusedOutlineColor; 51 | } 52 | } 53 | 54 | &.sorting { 55 | pointer-events: none; 56 | } 57 | 58 | &.dragging { 59 | border-radius: $borderRadius; 60 | border: $borderWidth solid #efefef; 61 | box-shadow: $boxShadow; 62 | 63 | &:focus { 64 | box-shadow: 0 0px 5px 1px $focusedOutlineColor; 65 | } 66 | } 67 | 68 | &:focus { 69 | text-indent: -2px; 70 | border: 2px solid $focusedOutlineColor; 71 | } 72 | } 73 | 74 | .Badge { 75 | position: absolute; 76 | top: -8px; 77 | right: -8px; 78 | padding: 0.35em 0.5em; 79 | border-radius: 0.3rem; 80 | 81 | color: $badgeColor; 82 | font-size: 0.8em; 83 | font-weight: $fontWeight-bold; 84 | background-color: $badgeBackgroundColor; 85 | border: $borderWidth solid $badgeBorderColor; 86 | } 87 | -------------------------------------------------------------------------------- /src/.stories/interactive-elements-stress-test/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import arrayMove from 'array-move'; 3 | 4 | import SortableList from './List'; 5 | import ItemStyles from './Item/Item.scss'; 6 | 7 | const items = { 8 | input: , 9 | textarea: