UNPKG

13.1 kBMarkdownView Raw
1# react-movable
2
3[![npm version](https://img.shields.io/npm/v/react-movable.svg?style=flat-square)](https://www.npmjs.com/package/react-movable)
4[![npm downloads](https://img.shields.io/npm/dm/react-movable.svg?style=flat-square)](https://www.npmjs.com/package/react-movable)
5[![Build Status](https://travis-ci.org/tajo/react-movable.svg?branch=master)](https://travis-ci.org/tajo/react-movable)
6[![size](https://img.shields.io/bundlephobia/minzip/react-movable.svg?style=flat)](https://bundlephobia.com/result?p=react-movable)
7
8![Basic list](https://raw.githubusercontent.com/tajo/react-movable/master/assets/react-movable.gif?raw=true)
9
10[![Edit Basic react-movable](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/k1mrwyr9l3)
11
12[See all the other examples](https://react-movable.netlify.com) and [their source code](https://github.com/tajo/react-movable/tree/master/examples)!
13
14## Installation
15
16```
17yarn add react-movable
18```
19
20## Usage
21
22```tsx
23import * as React from 'react';
24import { List, arrayMove } from 'react-movable';
25
26const SuperSimple: React.FC = () => {
27 const [items, setItems] = React.useState(['Item 1', 'Item 2', 'Item 3']);
28 return (
29 <List
30 values={items}
31 onChange={({ oldIndex, newIndex }) =>
32 setItems(arrayMove(items, oldIndex, newIndex))
33 }
34 renderList={({ children, props }) => <ul {...props}>{children}</ul>}
35 renderItem={({ value, props }) => <li {...props}>{value}</li>}
36 />
37 );
38};
39```
40
41## Features
42
43- **Vertical drag and drop for your lists and tables**
44- No wrapping divs or additional markup
45- Simple single component, no providers or HoCs
46- Unopinionated styling, great for **CSS in JS** too
47- **Accessible**, made for keyboards and screen readers
48- **Touchable**, works on mobile devices
49- Full control over the dragged item, it's a portaled React component
50- **Autoscrolling** when dragging (both for containers and the window)
51- Scrolling with the mousewheel / trackpad when dragging
52- Works with semantic table rows too
53- **Smooth animations**, can be disabled
54- Varying heights of items supported
55- Optional lock of the horizontal axis when dragging
56- Typescript and Flow type definitions
57- **No dependencies, less than 4kB (gzipped)**
58- Coverage by [e2e puppeteer tests](#end-to-end-testing)
59
60## Keyboard support
61
62- `tab` and `shift+tab` to focus items
63- `space` to lift or drop the item
64- `j` or `arrow down` to move the lifted item down
65- `k` or `arrow up` to move the lifted item up
66- `escape` to cancel the lift and return the item to its initial position
67
68## `<List />` props
69
70### renderList
71
72```ts
73renderList: (props: {
74 children: React.ReactNode;
75 isDragged: boolean;
76 props: {
77 ref: React.RefObject<any>;
78 };
79}) => React.ReactNode;
80```
81
82`renderList` prop to define your list (root) element. **Your function gets three parameters and should return a React component**:
83
84- `props` containing `ref` - this needs to be spread over the root list element, note that items need to be direct children of the DOM element that's being set with this `ref`
85- `children` - the content of the list
86- `isDragged` - `true` if any item is being dragged
87
88### renderItem
89
90```ts
91renderItem: (params: {
92 value: Value;
93 index?: number;
94 isDragged: boolean;
95 isSelected: boolean;
96 isOutOfBounds: boolean;
97 props: {
98 key?: number;
99 tabIndex?: number;
100 'aria-roledescription'?: string;
101 onKeyDown?: (e: React.KeyboardEvent) => void;
102 onWheel?: (e: React.WheelEvent) => void;
103 style?: React.CSSProperties;
104 ref?: React.RefObject<any>;
105 };
106}) => React.ReactNode;
107```
108
109`renderItem` prop to define your item element. **Your function gets 5 parameters and should return a React component**:
110
111- `value` - an item of the array you passed into the `values` prop
112- `index` - the item index (order)
113- `isDragged` - `true` if the item is dragged, great for styling purposes
114- `isSelected` - `true` if the item is lifted with the `space`
115- `isOutOfBounds` - `true` if the item is dragged far left or right
116- `props` - it has multiple props that you need to spread over your item element. Since one of these is `ref`, if you're spreading over a custom component, it must be wrapped in `React.forwardRef` like in the "Custom component" example.
117
118### values
119
120```ts
121values: Value[]
122```
123
124An array of values. The value can be a string or any more complex object. The length of the `values` array equals the number of rendered items.
125
126### onChange
127
128```ts
129onChange: (meta: { oldIndex: number; newIndex: number, targetRect: ClientRect }) => void
130```
131
132Called when the item is dropped to a new location:
133
134- `oldIndex` - the initial position of the element (0 indexed)
135- `newIndex` - the new position of the element (0 indexed), -1 when `removableByMove` is set and item dropped out of bounds
136- `targetRect` - [getBoundingClientRect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) of dropped item
137
138The List component is `stateless` and `controlled` so you need to implement this function to change the order of input `values`. Check the initial example.
139
140### beforeDrag
141
142```ts
143beforeDrag?: (params: { elements: Element[]; index: number }) => void;
144```
145
146Called when a valid drag is initiated. It provides a direct access to all list DOM elements and the index of dragged item. This can be useful when you need to do some upfront measurements like when building a [table with variable column widths](https://react-movable.netlify.app/?story=list--table-auto-cell-widths).
147
148### removableByMove
149
150```ts
151removableByMove: boolean;
152```
153
154Default is `false`. When set to `true` and an item is dragged far left or far right (out of bounds), the original gap disappears (animated) and following item drop will cause `onChange` being called with `newIndex = -1`. You can use that to remove the item from your `values` state. [Example](https://react-movable.netlify.com/?story=list--removable-by-move).
155
156### transitionDuration
157
158```ts
159transitionDuration: number;
160```
161
162The duration of CSS transitions. By default it's **300ms**. You can set it to 0 to disable all animations.
163
164### lockVertically
165
166```ts
167lockVertically: boolean;
168```
169
170If `true`, the dragged element can move only vertically when being dragged.
171
172### voiceover
173
174```ts
175voiceover: {
176 item: (position: number) => string;
177 lifted: (position: number) => string;
178 dropped: (from: number, to: number) => string;
179 moved: (position: number, up: boolean) => string;
180 canceled: (position: number) => string;
181}
182```
183
184In order to support screen reader users, `react-movable` is triggering different messages when user is interacting with the list. There is already a set of [English messages](https://github.com/tajo/react-movable/blob/master/src/List.tsx#L77-L89) included but you can override it with this prop.
185
186## container
187
188```ts
189container?: Element;
190```
191
192Provide custom DOM element where moved item will be rendered.
193
194## `arrayMove` and `arrayRemove`
195
196There are also additional two helper functions being exported:
197
198```ts
199arrayMove: <T>(array: T[], from: number, to: number) => T[];
200arrayRemove: <T>(array: T[], index: number) => T[];
201```
202
203They are useful when you need to manipulate the state of `values` when `onChange` is triggered.
204
205## Motivation
206
207There are two main ways how you can implement drag and drop today:
208
209- **[HTML5 drag and drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API)**. However, it has some [severe limitations](https://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html).
210- Mouse and touch events. It's very low level. You have the full control but it has no concept of DnD.
211
212There are multiple great libraries in React's ecosystem already. DnD can get pretty complicated so each one of them covers different use-cases and has different goals:
213
214[react-dnd](https://github.com/react-dnd/react-dnd) is a general purpose DnD library that makes amazing job abstracting quirky HTML5 API. It provides well thought out lower-level DnD primitives and let you build anything you want.
215
216[react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd) is a really beautiful DnD library for lists. It comes with a great support for accessibility and it's packed with awesome features. It doesn't use HTML5 API so it doesn't impose any of its limitations.
217
218[react-sortable-hoc](https://github.com/clauderic/react-sortable-hoc) provides a set of higher order components to make your lists dnd-able. It has many features and approaches similar to `react-beautiful-dnd` but it's more minimalistic and lacks some features as accessibility or unopinionated styling.
219
220So why `react-movable` was created? There are two main goals:
221
222- **Small footprint**. It's about 10 times smaller than `react-dnd` or `react-beautiful-dnd` (~3kB vs ~30kB) and half of the size of `react-sortable-hoc` (~7kB). That's especially important when you intend to use `react-movable` as a dependency in your own library. However, that also means that some features are left out - for example, the horizontal DnD is not supported.
223- **Simple but not compromised**. - Every byte counts but not if it comes down to the support for accessibility, screen readers, keyboards and touch devices. The goal is to support a limited set of use cases but without compromises.
224
225### Features that are not supported (and never will be)
226
227- Horizontal sorting.
228- DnD between multiple list.
229- Combining items / multi drag support.
230- Supporting older versions of React. The minimum required version is `16.3` since the new `createRef` and `createPortal` APIs are used.
231
232If you need the features above, please give a try to `react-beautiful-dnd`. It's a really well-designed library with all those features and gives you a lot of power to customize! If you are building an application heavy on DnD interactions, it might be your best bet! `react-movable`'s goal is not to be feature complete with `react-beautiful-dnd`.
233
234### Planned features
235
236- Built-in virtualization / windowing.
237
238Other feature requests will be thoroughly vetted. Again, the primary goal is to keep the size down while supporting main use-cases!
239
240## End to end testing
241
242**This library is tightly coupled to many DOM APIs**. It would be very hard to write unit tests that would not involve a lot of mocking. Or we could re-architect the library to better abstract all DOM interfaces but that would mean more code and bigger footprint.
243
244Instead of that, `react-movable` is thoroughly tested by end to end tests powered by [puppeteer](https://github.com/GoogleChrome/puppeteer). It tests all user interactions:
245
246- [drag and drop](https://github.com/tajo/react-movable/blob/master/e2e/basic.test.ts) of items by mouse (same and different heights)
247- [keyboard controls](https://github.com/tajo/react-movable/blob/master/e2e/basic.a11y.test.ts) (moving items around)
248- [auto scrolling for containers](https://github.com/tajo/react-movable/blob/master/e2e/scrolling.container.test.ts)
249- [auto scrolling for the window](https://github.com/tajo/react-movable/blob/master/e2e/scrolling.window.test.ts)
250- [visual regression testing](https://github.com/americanexpress/jest-image-snapshot)
251
252All tests are automatically ran in Travis CI with headless chromium. This way, the public API is well tested, including pixel-perfect positioning. Also, the tests are pretty fast, reliable and very descriptive.
253
254Do you want to run them in the `dev` mode (slows down operations, opens the browser)?
255
256```bash
257yarn ladle serve #start the ladle server
258yarn test:e2e:dev #run the e2e tests
259```
260
261```bash
262yarn test:e2e
263```
264
265## Browser support
266
267- **Chrome** (latest, mac, windows, iOS, Android)
268- **Firefox** (latest, mac, windows)
269- **Safari** (latest, mac, iOS)
270- **Edge** (latest, windows)
271
272## Users
273
274- [Uber Base UI](https://baseui.design/components/dnd-list/)
275
276> If you are using react-movable, please open a PR and add yourself to this list!
277
278## Contributing
279
280This is how you can spin up the dev environment:
281
282```
283git clone https://github.com/tajo/react-movable
284cd react-movable
285yarn
286yarn ladle serve
287```
288
289## Learning more
290
291I wrote an article about [Building a Drag and Drop List](https://baseweb.design/blog/drag-and-drop-list/).
292
293Also, gave a talk at React Advanced London: What a Drag (2019):
294
295[![React Advanced London: What a Drag](https://img.youtube.com/vi/y_XkQ2qMTSA/0.jpg)](https://www.youtube.com/watch?v=y_XkQ2qMTSA)
296
297## Shoutouts 🙏
298
299The popular React DnD libraries were already mentioned in the motivation part. Big shoutout to `react-beautiful-dnd` ❤️ ️ for supporting multiple great features and adding first-class support for accessibility! It was strongly used as an inspiration for `react-movable`!
300
301<img src="https://raw.githubusercontent.com/tajo/react-movable/master/assets/browserstack-logo.png?raw=true" height="80" title="BrowserStack Logo" alt="BrowserStack Logo" />
302
303Big thanks to [BrowserStack](https://www.browserstack.com) for letting the maintainers use their service to debug browser issues.
304
305And [Netlify](https://www.netlify.com/) for free hosting.
306
307## Author
308
309Vojtech Miksu 2019, [miksu.cz](https://miksu.cz), [@vmiksu](https://twitter.com/vmiksu)