UNPKG

11.6 kBMarkdownView Raw
1# react-range
2
3[![npm version](https://img.shields.io/npm/v/react-range.svg?style=flat-square)](https://www.npmjs.com/package/react-range)
4[![npm downloads](https://img.shields.io/npm/dm/react-range.svg?style=flat-square)](https://www.npmjs.com/package/react-range)
5[![Build Status](https://travis-ci.org/tajo/react-range.svg?branch=master)](https://travis-ci.org/tajo/react-range)
6[![size](https://img.shields.io/bundlephobia/minzip/react-range.svg?style=flat)](https://bundlephobia.com/result?p=react-range)
7
8![Labeled Range](https://raw.githubusercontent.com/tajo/react-range/master/assets/react-range.gif?raw=true)
9
10[![Edit react-range](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/rlp1j1183n)
11
12[See all the other examples](https://react-range.netlify.com) and [their source code](https://github.com/tajo/react-range/tree/master/examples)!
13
14## Installation
15
16```
17yarn add react-range
18```
19
20## Usage
21
22```jsx
23import * as React from 'react';
24import { Range } from 'react-range';
25
26class SuperSimple extends React.Component {
27 state = { values: [50] };
28 render() {
29 return (
30 <Range
31 step={0.1}
32 min={0}
33 max={100}
34 values={this.state.values}
35 onChange={(values) => this.setState({ values })}
36 renderTrack={({ props, children }) => (
37 <div
38 {...props}
39 style={{
40 ...props.style,
41 height: '6px',
42 width: '100%',
43 backgroundColor: '#ccc'
44 }}
45 >
46 {children}
47 </div>
48 )}
49 renderThumb={({ props }) => (
50 <div
51 {...props}
52 style={{
53 ...props.style,
54 height: '42px',
55 width: '42px',
56 backgroundColor: '#999'
57 }}
58 />
59 )}
60 />
61 );
62 }
63}
64```
65
66## Features
67
68- Range input supporting **vertical and horizontal sliding**
69- Unopinionated styling, great for **CSS in JS** too
70- No wrapping divs or additional markup, bring your own!
71- **Accessible**, made for keyboards and screen readers
72- **Touchable**, works on mobile devices
73- Can handle negative and decimal values
74- Stateless and controlled single component
75- Typescript and Flow type definitions
76- **No dependencies, less than 5kB (gzipped)**
77- Coverage by [e2e puppeteer tests](#end-to-end-testing)
78- RTL support
79
80## Keyboard support
81
82- `tab` and `shift+tab` to focus thumbs
83- `arrow up` or `arrow right` or `k` to increase the thumb value by one step
84- `arrow down` or `arrow left` or `j` to decrease the thumb value by one step
85- `page up` to increase the thumb value by ten steps
86- `page down` to decrease the thumb value by ten steps
87
88## `<Range />` props
89
90### renderTrack
91
92```ts
93renderTrack: (params: {
94 props: {
95 style: React.CSSProperties;
96 ref: React.RefObject<any>;
97 onMouseDown: (e: React.MouseEvent) => void;
98 onTouchStart: (e: React.TouchEvent) => void;
99 };
100 children: React.ReactNode;
101 isDragged: boolean;
102 disabled: boolean;
103}) => React.ReactNode;
104```
105
106`renderTrack` prop to define your track (root) element. **Your function gets four parameters and should return a React component**:
107
108- `props` - this needs to be spread over the root track element, it connects mouse and touch events, adds a ref and some necessary styling
109- `children` - the rendered thumbs, thumb structure should be specified in a different prop - `renderThumb`
110- `isDragged` - `true` if any thumb is being dragged
111- `disabled` - `true` if `<Range disabled={true} />` is set
112
113The track can be a single narrow `div` as in the [Super simple example](https://github.com/tajo/react-range/blob/master/examples/SuperSimple.tsx); however, it might be better to use at least two nested `div`s where the outer `div` is much thicker and has a transparent background and the inner `div` is narrow, has visible background and is centered. `props` should be then spread over the outer bigger `div`. **Why to do this? It's nice to keep the `onMouseDown` and `onTouchStart` targets bigger** since the thumb can be moved also by clicking on the track (in a single thumb scenario).
114
115### renderThumb
116
117```ts
118renderThumb: (params: {
119 props: {
120 key: number;
121 style: React.CSSProperties;
122 tabIndex?: number;
123 'aria-valuemax': number;
124 'aria-valuemin': number;
125 'aria-valuenow': number;
126 draggable: boolean;
127 role: string;
128 onKeyDown: (e: React.KeyboardEvent) => void;
129 onKeyUp: (e: React.KeyboardEvent) => void;
130 };
131 value: number;
132 index: number;
133 isDragged: boolean;
134}) => React.ReactNode;
135```
136
137`renderThumb` prop to define your thumb. **Your function gets four parameters and should return a React component**:
138
139- `props` - it has multiple props that you need to spread over your thumb element
140- `value` - a number, relative value based on `min`, `max`, `step` and the thumb's position
141- `index` - the thumb index (order)
142- `isDragged` - `true` if the thumb is dragged, great for styling purposes
143
144### renderMark (optional)
145
146```ts
147renderMark?: (params: {
148 props: {
149 key: string;
150 style: React.CSSProperties;
151 ref: React.RefObject<any>;
152 };
153 index: number;
154}) => React.ReactNode;
155```
156
157`renderMark` is an optional prop so you can render an element at each step. See this [example](https://react-range.netlify.app/?story=range--marks). **Your function gets 2 parameters and should return a React component**:
158
159- `props` - this needs to be spread over the root track element, it adds a ref, key and some necessary styling
160- `index` - index of the mark, might be useful if you want to use different styles for even/odd marks
161
162You can use any dimensions for your marks and react-range will automatically position them at the correct place.
163
164### values
165
166```ts
167values: number[];
168```
169
170An array of numbers. It controls the position of thumbs on the track. `values.length` equals to the number of rendered thumbs.
171
172### onChange
173
174```ts
175onChange: (values: number[]) => void;
176```
177
178Called when a thumb is moved, provides new `values`.
179
180### onFinalChange
181
182```ts
183onFinalChange: (values: number[]) => void;
184```
185
186Called when a change is finished (mouse/touch up, or keyup), provides current `values`. Use this event when you have to make for example ajax request with new values.
187
188### min (optional)
189
190```ts
191min: number;
192```
193
194The range start. Can be decimal or negative. Default is `0`.
195
196### max (optional)
197
198```ts
199max: number;
200```
201
202The range end. Can be decimal or negative. Default is `100`.
203
204### step (optional)
205
206```ts
207step: number;
208```
209
210The minimal distance between two `values`. Can be decimal. Default is `1`.
211
212### allowOverlap (optional)
213
214```ts
215allowOverlap: boolean;
216```
217
218When there are multiple thumbs on a single track, should they be allowed to overlap? Default is `false`.
219
220### draggableTrack (optional)
221
222```ts
223draggableTrack: boolean;
224```
225
226When there are multiple thumbs on a single track, should it be possible to drag all thumbs at once? Default is `false`.
227
228### direction (optional)
229
230```ts
231direction: Direction;
232
233enum Direction {
234 Right = 'to right',
235 Left = 'to left',
236 Down = 'to bottom',
237 Up = 'to top'
238}
239```
240
241It sets the orientation (vertical vs horizontal) and the direction in which the value increases. You can get this enum by:
242
243```js
244import { Direction } from 'react-range';
245```
246
247Default value is `Direction.Right`.
248
249### disabled (optional)
250
251```ts
252disabled: boolean;
253```
254
255If `true`, it ignores all touch and mouse events and makes the component not focusable. Default is `false`.
256
257### rtl (optional)
258
259```ts
260rtl: boolean;
261```
262
263If `true`, the slider will be optimized for RTL layouts. Default is `false`.
264
265## getTrackBackground
266
267There is an additional helper function being exported from `react-range`. Your track is most likely a `div` with some background. What if you want to achieve a nice "progress bar" effect where the part before the thumb has different color than the part after? What if you want to have the same thing even with multiple thumbs (aka differently colored segments)? **You don't need to glue together multiple divs in order to do that!** You can use a single `div` and set `background: linear-gradient(...)`. `getTrackBackground` function builds this verbose `linear-gradient(...)` for you!
268
269```ts
270getTrackBackground: (params: {
271 min: number;
272 max: number;
273 values: number[];
274 colors: string[];
275 direction?: Direction;
276 rtl?: boolean;
277}) => string;
278```
279
280`min`, `max`, `values` and `direction` should be same as for the `<Range />` component. `colors` is a list of colors. This needs to be true:
281
282```js
283values.length + 1 === colors.length;
284```
285
286That's because **one thumb** (one value) splits the track into **two segments**, so you need **two colors**.
287
288## Motivation
289
290There is a native [input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range) solution:
291
292```html
293<input type="range" />
294```
295
296However, it has some serious shortcomings:
297
298- vertical-oriented slider is [not supported in all browsers](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range#Browser_compatibility)
299- supports only a single direction
300- very limited styling options
301- no support for multiple thumbs
302
303There are also many `React` based solutions but most of them are too bloated, don't support styling through CSS in JS or have lacking performance.
304
305`react-range` has two main goals:
306
307- **Small footprint** - less then 4kB gzipped, single component.
308- **Bring your own styles and HTML markup** - `react-range` is a more low-level approach than other libraries. It doesn't come with any styling (except some positioning) or markup. It's up to the user to specify both! Think about `react-range` as a foundation for other styled input ranges.
309
310## End to end testing
311
312**This library is tightly coupled to many DOM APIs**. It would be very hard to ensure 100% test coverage just with 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.
313
314Instead of that, `react-range` adds thorough end to end tests powered by [puppeteer](https://github.com/GoogleChrome/puppeteer).
315
316All 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.
317
318Do you want to run them in the `dev` mode (slows down operations, opens the browser)?
319
320```bash
321yarn ladle serve #start the ladle server
322yarn test:e2e:dev #run the e2e tests
323```
324
325`CI` mode (ladle started on the background, quick, headless)
326
327```bash
328yarn test:e2e
329```
330
331## Browser support
332
333- **Chrome** (latest, mac, windows, iOS, Android)
334- **Firefox** (latest, mac, windows)
335- **Safari** (latest, mac, iOS)
336- **Edge** (latest, windows)
337
338## Contributing
339
340This is how you can spin up the dev environment:
341
342```
343git clone https://github.com/tajo/react-range
344cd react-range
345yarn
346yarn ladle serve
347```
348
349## Shoutouts 🙏
350
351Big big shoutout to **[Tom MacWright](https://macwright.org/)** for donating the `react-range` npm handle! ❤️
352
353<img src="https://raw.githubusercontent.com/tajo/react-range/master/assets/browserstack-logo.png?raw=true" height="80" title="BrowserStack Logo" alt="BrowserStack Logo" />
354
355Big thanks to [BrowserStack](https://www.browserstack.com) for letting the maintainers use their service to debug browser issues.
356
357And [Netlify](https://www.netlify.com/) for free hosting.
358
359## Author
360
361Vojtech Miksu 2019, [miksu.cz](https://miksu.cz), [@vmiksu](https://twitter.com/vmiksu)