├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── docs
├── app.js
├── app.less
├── components
│ ├── Codeblock.js
│ ├── Demo.js
│ ├── Examples.js
│ ├── Features.js
│ ├── Install.js
│ ├── Usage.js
│ ├── footer.js
│ ├── header.js
│ └── sliders
│ │ ├── float.js
│ │ ├── horizontal.js
│ │ ├── index.js
│ │ ├── labels.js
│ │ ├── negative.js
│ │ └── orientation.js
├── favicon.ico
├── images
│ ├── bg.png
│ ├── code.svg
│ ├── play.svg
│ ├── rangeslider.png
│ ├── rangeslider_dark.png
│ └── slider.png
├── index.ejs
├── index.html
├── index.js
├── server.js
└── webpack.config.js
├── package.json
├── src
├── Rangeslider.js
├── __tests__
│ ├── Rangeslider.spec.js
│ ├── Sanity.spec.js
│ └── __snapshots__
│ │ └── Rangeslider.spec.js.snap
├── index.js
├── rangeslider.less
└── utils.js
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 |
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": ["standard", "standard-jsx"],
4 | "env": {
5 | "es6": true,
6 | "browser": true,
7 | "node": true,
8 | "jest": true
9 | },
10 | "plugins": [
11 | "react",
12 | "prettier"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Directories to be ignored
2 | .DS_Store
3 | npm-debug.log
4 | favicon.png
5 | node_modules
6 | coverage
7 | public
8 | lib
9 | umd
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | notifications:
4 | email: false
5 | node_js:
6 | - '6'
7 | - '4'
8 | script:
9 | - npm test
10 | before_script:
11 | - npm prune
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Bhargav Anand
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 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | A fast & lightweight react component as a drop in replacement for HTML5 input range slider element.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | ## Installation
30 | Using `npm` (use `--save` to include it in your package.json)
31 |
32 | ```bash
33 | $ npm install react-rangeslider --save
34 | ```
35 |
36 | Using `yarn` (this command also adds react-rangeslider to your package.json dependencies)
37 |
38 | ```bash
39 | $ yarn add react-rangeslider
40 | ```
41 |
42 |
43 | ## Getting Started
44 | React-Rangeslider is bundled with a slider component & default styles which can be overridden depending on your design requirements.
45 |
46 | With a module bundler like webpack that supports either CommonJS or ES2015 modules, use as you would anything else:
47 |
48 | ```js
49 | // Using an ES6 transpiler like Babel
50 | import Slider from 'react-rangeslider'
51 |
52 | // To include the default styles
53 | import 'react-rangeslider/lib/index.css'
54 |
55 | // Not using an ES6 transpiler
56 | var Slider = require('react-rangeslider')
57 | ```
58 |
59 | The UMD build is also available on [unpkg][unpkg]:
60 |
61 | ```html
62 |
63 | ```
64 |
65 | You can find the library on `window.ReactRangeslider`. Optionally you can drop in the default styles by adding the stylesheet.
66 | ```html
67 |
68 | ```
69 | Check out [docs & examples](https://whoisandy.github.io/react-rangeslider).
70 |
71 | ## Basic Example
72 |
73 | ```jsx
74 | import React, { Component } from 'react'
75 | import Slider from 'react-rangeslider'
76 |
77 | class VolumeSlider extends Component {
78 | constructor(props, context) {
79 | super(props, context)
80 | this.state = {
81 | volume: 0
82 | }
83 | }
84 |
85 | handleOnChange = (value) => {
86 | this.setState({
87 | volume: value
88 | })
89 | }
90 |
91 | render() {
92 | let { volume } = this.state
93 | return (
94 |
99 | )
100 | }
101 | }
102 | ```
103 |
104 |
105 | ## API
106 | Rangeslider is bundled as a single component, that accepts data and callbacks only as `props`.
107 |
108 | ### Component
109 | ```jsx
110 | import Slider from 'react-rangeslider'
111 |
112 | // inside render
113 |
128 | ```
129 |
130 | ### Props
131 | Prop | Type | Default | Description
132 | --------- | ------- | ------- | -----------
133 | `min` | number | 0 | minimum value the slider can hold
134 | `max` | number | 100 | maximum value the slider can hold
135 | `step` | number | 1 | step in which increments/decrements have to be made
136 | `value` | number | | current value of the slider
137 | `orientation` | string | horizontal | orientation of the slider
138 | `tooltip` | boolean | true | show or hide tooltip
139 | `reverse` | boolean | false | reverse direction of vertical slider (top-bottom)
140 | `labels` | object | {} | object containing key-value pairs. `{ 0: 'Low', 50: 'Medium', 100: 'High'}`
141 | `handleLabel` | string | '' | string label to appear inside slider handles
142 | `format` | function | | function to format and display the value in label or tooltip
143 | `onChangeStart` | function | | function gets called whenever the user starts dragging the slider handle
144 | `onChange` | function | | function gets called whenever the slider handle is being dragged or clicked
145 | `onChangeComplete` | function | | function gets called whenever the user stops dragging the slider handle.
146 |
147 |
148 | ## Development
149 | To work on the project locally, you need to pull its dependencies and run `npm start`.
150 |
151 | ```bash
152 | $ npm install
153 | $ npm start
154 | ```
155 |
156 | ## Issues
157 | Feel free to contribute. Submit a Pull Request or open an issue for further discussion.
158 |
159 | ## License
160 | MIT
161 |
162 |
163 | [npm_img]: https://img.shields.io/npm/v/react-rangeslider.svg?style=flat-square
164 | [npm_site]: https://www.npmjs.org/package/react-rangeslider
165 | [license_img]: https://img.shields.io/github/license/whoisandy/react-rangeslider.svg
166 | [license_site]: https://github.com/whoisandy/react-rangeslider/blob/master/LICENSE
167 | [npm_dm_img]: http://img.shields.io/npm/dm/react-rangeslider.svg?style=flat-square
168 | [npm_dm_site]: https://www.npmjs.org/package/react-rangeslider
169 | [trav_img]: https://api.travis-ci.org/whoisandy/react-rangeslider.svg
170 | [trav_site]: https://travis-ci.org/whoisandy/react-rangeslider
171 | [std_img]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg
172 | [std_site]: http://standardjs.com
173 | [unpkg]: https://unpkg.com/react-rangeslider/umd/ReactRangeslider.min.js
174 |
--------------------------------------------------------------------------------
/docs/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import Features from './components/Features'
4 | import Usage from './components/Usage'
5 | import Install from './components/Install'
6 | import Examples from './components/Examples'
7 | import Footer from './components/Footer'
8 | import '../src/rangeslider.less'
9 | import './app.less'
10 |
11 | function App () {
12 | return (
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | )
26 | }
27 |
28 | export default App
29 |
--------------------------------------------------------------------------------
/docs/app.less:
--------------------------------------------------------------------------------
1 | *,
2 | *:before,
3 | *:after{
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | }
8 |
9 | body {
10 | margin: 0;
11 | padding: 0;
12 | background-color: #F5F5F5;
13 | font-family: "Roboto Mono", sans-serif;
14 | font-weight: 400;
15 | font-size: 16px;
16 | line-height: 1.4;
17 | overflow-x: hidden;
18 | color: #454545;
19 | }
20 |
21 | #mount{
22 | position: relative;
23 | }
24 |
25 | hr {
26 | height: 1px;
27 | margin: 40px 0;
28 | background-color: #ccc;
29 | border: none;
30 | }
31 | h1, h2 {
32 | margin: 0 auto 20px;
33 | font-family: "Roboto Mono", sans-serif;
34 | font-weight: 700;
35 | }
36 | h1 {
37 | margin: 0;
38 | text-align: center;
39 | font-size: 36px;
40 | color: #2d2d2d;
41 | }
42 | h2 {
43 | padding-bottom: 14px;
44 | font-size: 24px;
45 | border-bottom: 1px #cacaca solid;
46 | color: #2d2d2d;
47 | }
48 | p {
49 | margin-bottom: 20px;
50 | }
51 | a {
52 | color: #00BCD4;
53 | }
54 |
55 | header, section {
56 | .block {
57 | width: 800px;
58 | margin: 0 auto;
59 | }
60 | }
61 |
62 | header {
63 | padding: 100px 0 60px;
64 | margin-bottom: 20px;
65 | background: #2d2d2d url(./images/bg.png) repeat;
66 | .block {
67 | > div {
68 | margin-top: 40px;
69 | }
70 | }
71 | a {
72 | color: #00BCD4;
73 | }
74 | h1 {
75 | margin-bottom: 40px;
76 | a {
77 | height: 130px;
78 | display: block;
79 | background: url(./images/rangeslider.png) no-repeat center center;
80 | text-indent: -9999px;
81 | }
82 | }
83 | p {
84 | text-align: center;
85 | color: #ffffff;
86 | }
87 | }
88 |
89 | .github-btn {
90 | font: bold 11px/14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
91 | height: 20px;
92 | margin-right: 20px;
93 | overflow: hidden;
94 | display: inline-block;
95 | }
96 | .gh-btn,
97 | .gh-count,
98 | .gh-ico {
99 | float: left;
100 | }
101 | .gh-btn,
102 | .gh-count {
103 | padding: 2px 5px 2px 4px;
104 | color: #333;
105 | text-decoration: none;
106 | white-space: nowrap;
107 | cursor: pointer;
108 | border-radius: 3px;
109 | }
110 | .gh-btn {
111 | background-color: #eee;
112 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fcfcfc), color-stop(100%, #eee));
113 | background-image: -webkit-linear-gradient(top, #fcfcfc 0, #eee 100%);
114 | background-image: -moz-linear-gradient(top, #fcfcfc 0, #eee 100%);
115 | background-image: -ms-linear-gradient(top, #fcfcfc 0, #eee 100%);
116 | background-image: -o-linear-gradient(top, #fcfcfc 0, #eee 100%);
117 | background-image: linear-gradient(to bottom, #fcfcfc 0, #eee 100%);
118 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fcfcfc', endColorstr='#eeeeee', GradientType=0);
119 | background-repeat: no-repeat;
120 | border: 1px solid #d5d5d5;
121 | }
122 | .gh-btn:hover,
123 | .gh-btn:focus {
124 | text-decoration: none;
125 | background-color: #ddd;
126 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #eee), color-stop(100%, #ddd));
127 | background-image: -webkit-linear-gradient(top, #eee 0, #ddd 100%);
128 | background-image: -moz-linear-gradient(top, #eee 0, #ddd 100%);
129 | background-image: -ms-linear-gradient(top, #eee 0, #ddd 100%);
130 | background-image: -o-linear-gradient(top, #eee 0, #ddd 100%);
131 | background-image: linear-gradient(to bottom, #eee 0, #ddd 100%);
132 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);
133 | border-color: #ccc;
134 | }
135 | .gh-btn:active {
136 | background-image: none;
137 | background-color: #dcdcdc;
138 | border-color: #b5b5b5;
139 | box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15);
140 | }
141 | .gh-ico {
142 | width: 14px;
143 | height: 14px;
144 | margin-right: 4px;
145 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjQwcHgiIGhlaWdodD0iNDBweCIgdmlld0JveD0iMTIgMTIgNDAgNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMTIgMTIgNDAgNDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGZpbGw9IiMzMzMzMzMiIGQ9Ik0zMiAxMy40Yy0xMC41IDAtMTkgOC41LTE5IDE5YzAgOC40IDUuNSAxNS41IDEzIDE4YzEgMC4yIDEuMy0wLjQgMS4zLTAuOWMwLTAuNSAwLTEuNyAwLTMuMiBjLTUuMyAxLjEtNi40LTIuNi02LjQtMi42QzIwIDQxLjYgMTguOCA0MSAxOC44IDQxYy0xLjctMS4yIDAuMS0xLjEgMC4xLTEuMWMxLjkgMC4xIDIuOSAyIDIuOSAyYzEuNyAyLjkgNC41IDIuMSA1LjUgMS42IGMwLjItMS4yIDAuNy0yLjEgMS4yLTIuNmMtNC4yLTAuNS04LjctMi4xLTguNy05LjRjMC0yLjEgMC43LTMuNyAyLTUuMWMtMC4yLTAuNS0wLjgtMi40IDAuMi01YzAgMCAxLjYtMC41IDUuMiAyIGMxLjUtMC40IDMuMS0wLjcgNC44LTAuN2MxLjYgMCAzLjMgMC4yIDQuNyAwLjdjMy42LTIuNCA1LjItMiA1LjItMmMxIDIuNiAwLjQgNC42IDAuMiA1YzEuMiAxLjMgMiAzIDIgNS4xYzAgNy4zLTQuNSA4LjktOC43IDkuNCBjMC43IDAuNiAxLjMgMS43IDEuMyAzLjVjMCAyLjYgMCA0LjYgMCA1LjJjMCAwLjUgMC40IDEuMSAxLjMgMC45YzcuNS0yLjYgMTMtOS43IDEzLTE4LjFDNTEgMjEuOSA0Mi41IDEzLjQgMzIgMTMuNHoiLz48L3N2Zz4=');
146 | background-size: 100% 100%;
147 | background-repeat: no-repeat;
148 | }
149 | .gh-count {
150 | position: relative;
151 | display: none; /* hidden to start */
152 | margin-left: 4px;
153 | background-color: #fafafa;
154 | border: 1px solid #d4d4d4;
155 | }
156 | .gh-count:hover,
157 | .gh-count:focus {
158 | color: #4183C4;
159 | }
160 | .gh-count:before,
161 | .gh-count:after {
162 | content: '';
163 | position: absolute;
164 | display: inline-block;
165 | width: 0;
166 | height: 0;
167 | border-color: transparent;
168 | border-style: solid;
169 | }
170 | .gh-count:before {
171 | top: 50%;
172 | left: -3px;
173 | margin-top: -4px;
174 | border-width: 4px 4px 4px 0;
175 | border-right-color: #fafafa;
176 | }
177 | .gh-count:after {
178 | top: 50%;
179 | left: -4px;
180 | z-index: -1;
181 | margin-top: -5px;
182 | border-width: 5px 5px 5px 0;
183 | border-right-color: #d4d4d4;
184 | }
185 | .github-btn-large {
186 | height: 30px;
187 | }
188 | .github-btn-large .gh-btn,
189 | .github-btn-large .gh-count {
190 | padding: 3px 10px 3px 8px;
191 | font-size: 16px;
192 | line-height: 22px;
193 | border-radius: 4px;
194 | }
195 | .github-btn-large .gh-ico {
196 | width: 20px;
197 | height: 20px;
198 | }
199 | .github-btn-large .gh-count {
200 | margin-left: 6px;
201 | }
202 | .github-btn-large .gh-count:before {
203 | left: -5px;
204 | margin-top: -6px;
205 | border-width: 6px 6px 6px 0;
206 | }
207 | .github-btn-large .gh-count:after {
208 | left: -6px;
209 | margin-top: -7px;
210 | border-width: 7px 7px 7px 0;
211 | }
212 |
213 | section {
214 | .block {
215 | margin-bottom: 40px;
216 | ul {
217 | margin: 0;
218 | padding: 0 0 0 40px;
219 | }
220 |
221 | .buttons {
222 | display: block;
223 | text-align: center;
224 | }
225 |
226 | .code {
227 | padding: 20px;
228 | margin-bottom: 20px;
229 | overflow-y: auto;
230 | }
231 | code {
232 | font-family: 'Fira Mono', monospace;
233 | font-size: 14px;
234 | }
235 |
236 | .value {
237 | text-align: center;
238 | padding-top: 20px;
239 | font-weight: 500;
240 | font-size: 24px;
241 | }
242 | }
243 | }
244 |
245 |
246 | .install {
247 | .code {
248 | margin-bottom: 40px;
249 | }
250 | }
251 |
252 | .demo-panel {
253 | margin-bottom: 80px;
254 | border: 1px #2d2d2d solid;
255 | .slider {
256 | padding: 40px;
257 | }
258 | }
259 |
260 | .demo-panel-title {
261 | padding: 10px 20px;
262 | background-color: #2d2d2d;
263 | background-color: smoke;
264 | border-bottom: 1px lighten(#37474F, 50%) solid;
265 | overflow: hidden;
266 | &:before,
267 | &:after {
268 | display: table;
269 | content: ' ';
270 | }
271 | &:after {
272 | clear: both;
273 | }
274 | h4, a {
275 | color: #ffffff;
276 | font-weight: 400;
277 | }
278 | h4 {
279 | float: left;
280 | font-size: 16px;
281 | }
282 | a {
283 | height: 18px;
284 | margin-top: 2px;
285 | margin-left: 20px;
286 | display: block;
287 | float: right;
288 | font-size: 12px;
289 | text-decoration: none;
290 | text-transform: uppercase;
291 | color: white;
292 | }
293 | #source {
294 | padding-left: 35px;
295 | background: url(./images/code.svg) no-repeat left center;
296 | background-size: contain;
297 | }
298 | #codesandbox {
299 | padding-left: 20px;
300 | background: url(./images/play.svg) no-repeat left center;
301 | background-size: contain;
302 | }
303 | }
304 | .demo-panel-content {
305 | padding-top: 20px;
306 | background-color: #ffffff;
307 | border-bottom-left-radius: 4px;
308 | border-bottom-right-radius: 4px;
309 | .code {
310 | display: none;
311 | }
312 | &.source {
313 | .code {
314 | display: block;
315 | margin-bottom: 0;
316 | }
317 | }
318 | }
319 |
320 | .custom-labels {
321 | .rangeslider-horizontal {
322 | .rangeslider__handle {
323 | width: 34px;
324 | height: 34px;
325 | border-radius: 30px;
326 | text-align: center;
327 | &:after {
328 | position: initial;
329 | }
330 | }
331 | .rangeslider__label {
332 | top: 18px;
333 | }
334 | .rangeslider__handle-tooltip {
335 | width: 60px;
336 | left: 50%;
337 | transform: translate3d(-50%, 0, 0);
338 | }
339 | .rangeslider__handle-label {
340 | position: absolute;
341 | top: 50%;
342 | left: 50%;
343 | transform: translate3d(-50%, -50%, 0);
344 | }
345 | }
346 | }
347 |
348 | .slider-group {
349 | display: flex;
350 | align-items: center;
351 | justify-content: center;
352 | }
353 | .slider-vertical,
354 | .slider-horizontal {
355 | flex: 1;
356 | }
357 | .slider-vertical {
358 | .rangeslider-vertical {
359 | width: 4px;
360 | }
361 | .rangeslider__fill {
362 | background-color: #EF5350;
363 | }
364 | .rangeslider__handle {
365 | width: 30px;
366 | height: 30px;
367 | left: -13px;
368 | border-radius: 50%;
369 | background-color: #fff;
370 | box-shadow: 0 1px 1px #333;
371 | }
372 | }
373 | .slider-horizontal {
374 | .rangeslider-horizontal,
375 | .rangeslider__fill,
376 | .rangeslider__handle {
377 | border-radius: 0;
378 | }
379 | .rangeslider-horizontal {
380 | height: 10px;
381 | }
382 | .rangeslider__fill {
383 | background-color: #1E88E5;
384 | }
385 | .rangeslider__handle {
386 | width: 10px;
387 | height: 30px;
388 | &:after {
389 | display: none;
390 | }
391 | }
392 | }
393 |
394 | .hearts {
395 | color: #EF5350;
396 | }
397 |
398 | footer {
399 | margin: 60px auto;
400 | p {
401 | margin-bottom: 4px;
402 | }
403 | .close {
404 | padding-top: 40px;
405 | text-align: center;
406 | > p {
407 | margin-bottom: 0;
408 | padding-bottom: 5px;
409 | line-height: 14px;
410 | font-size: 14px;
411 | color: #757575;
412 | }
413 | }
414 | }
415 |
416 | @media screen and (max-width: 1200px) {
417 | header, section {
418 | .block {
419 | width: 100%;
420 | margin-left: auto;
421 | margin-right: auto;
422 | padding: 0 20px;
423 | }
424 | }
425 | #source,
426 | #codesandbox {
427 | display: none;
428 | }
429 | footer.block {
430 | margin-bottom: 60px;
431 | }
432 | }
433 |
--------------------------------------------------------------------------------
/docs/components/Codeblock.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import marked from 'marked'
3 |
4 | class Codeblock extends Component {
5 | componentWillMount () {
6 | marked.setOptions({
7 | gfm: true,
8 | tables: true,
9 | breaks: false,
10 | pedantic: false,
11 | sanitize: false,
12 | smartLists: true,
13 | smartypants: false,
14 | highlight: function (code, lang) {
15 | return require('highlight.js').highlight(lang, code).value
16 | }
17 | })
18 | }
19 |
20 | render () {
21 | const text = `\`\`\`js
22 | ${this.props.children}
23 | \`\`\``
24 |
25 | return (
26 |
30 | )
31 | }
32 | }
33 |
34 | export default Codeblock
35 |
--------------------------------------------------------------------------------
/docs/components/Demo.js:
--------------------------------------------------------------------------------
1 | import cx from 'classnames'
2 | import React, { Component } from 'react'
3 |
4 | class Demo extends Component {
5 | constructor (props, context) {
6 | super(props, context)
7 | this.state = {
8 | source: false
9 | }
10 | }
11 |
12 | handleToggle = e => {
13 | e.preventDefault()
14 | this.setState({
15 | source: !this.state.source
16 | })
17 | };
18 |
19 | render () {
20 | const { source } = this.state
21 | const { title, children } = this.props
22 | const className = cx('demo-panel-content', { source: source })
23 | return (
24 |
25 |
34 |
35 | {children}
36 |
37 |
38 | )
39 | }
40 | }
41 |
42 | export default Demo
43 |
--------------------------------------------------------------------------------
/docs/components/Examples.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Horizontal, Negative, Float, Labels, Orientation } from './sliders'
3 | import Demo from './Demo'
4 | import Codeblock from './Codeblock'
5 |
6 | import horizontalExample from '!raw!./sliders/horizontal'
7 | import negativeExample from '!raw!./sliders/negative'
8 | import floatExample from '!raw!./sliders/float'
9 | import labelsExample from '!raw!./sliders/labels'
10 | import orientationExample from '!raw!./sliders/orientation'
11 |
12 | class Examples extends Component {
13 | render () {
14 | return (
15 |
16 | Examples
17 |
18 |
19 |
20 | {horizontalExample}
21 |
22 |
23 |
24 |
25 |
26 | {negativeExample}
27 |
28 |
29 |
30 |
31 |
32 | {floatExample}
33 |
34 |
35 |
36 |
37 |
38 | {labelsExample}
39 |
40 |
41 |
42 |
43 |
44 | {orientationExample}
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | export default Examples
53 |
--------------------------------------------------------------------------------
/docs/components/Features.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function Features () {
4 | return (
5 |
6 | Features
7 |
8 | Touchscreen friendly
9 | Suitable for use within responsive designs
10 | Small and fast (8.1Kb Gzipped)
11 |
12 |
13 | )
14 | }
15 |
16 | export default Features
17 |
--------------------------------------------------------------------------------
/docs/components/Install.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Codeblock from './Codeblock'
3 |
4 | function Install () {
5 | return (
6 |
7 | Installation
8 |
9 | Using npm (use --save
to include it in your package.json)
10 |
11 |
12 | {`$ npm install react-rangeslider --save`}
13 |
14 |
15 | Using yarn (use --dev
to include it in your package.json)
16 |
17 |
18 | {`$ yarn add react-rangeslider --save`}
19 |
20 |
21 | )
22 | }
23 |
24 | export default Install
25 |
--------------------------------------------------------------------------------
/docs/components/Usage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import Codeblock from './Codeblock'
3 |
4 | class Usage extends Component {
5 | render () {
6 | const text = `// Using an ES6 transpiler like Babel
7 | import Slider from 'react-rangeslider'
8 |
9 | // To include the default styles
10 | import 'react-rangeslider/lib/index.css'
11 |
12 | // Not using an ES6 transpiler
13 | var Slider = require('react-rangeslider')
14 | `
15 |
16 | const umdJs = ``
17 | const umdCss = ` `
18 |
19 | return (
20 |
21 | Usage
22 |
23 | React-Rangeslider is bundled with a single slider component. By default, basic styles are applied, but can be overridden depending on your design requirements.
24 |
25 |
26 | With a module bundler like webpack that supports either CommonJS or ES2015 modules, use as you would anything else:
27 |
28 |
29 | {text}
30 |
31 |
32 | The UMD build is also available on
33 | {' '}
34 |
35 | unpkg:
36 |
37 |
38 |
39 | {umdJs}
40 |
41 |
42 | You can find the library on
43 | {' '}
44 | window.ReactRangeslider
45 | . Optionally you can drop in the default styles by adding the stylesheet.
46 |
47 |
48 | {umdCss}
49 |
50 |
51 | )
52 | }
53 | }
54 |
55 | export default Usage
56 |
--------------------------------------------------------------------------------
/docs/components/footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function Footer () {
4 | return (
5 |
26 | )
27 | }
28 |
29 | export default Footer
30 |
--------------------------------------------------------------------------------
/docs/components/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import GitHubButton from 'react-github-button'
3 |
4 | function Header () {
5 | return (
6 |
7 |
8 |
9 | A fast & lightweight react component as a drop in replacement for HTML5 input range slider element.
10 |
11 |
12 | Please refer to the source on
13 | {' '}
14 | Github
15 |
16 |
17 |
23 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default Header
35 |
--------------------------------------------------------------------------------
/docs/components/sliders/float.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import Slider from 'react-rangeslider'
3 |
4 | class Float extends Component {
5 | constructor (props, context) {
6 | super(props, context)
7 | this.state = {
8 | value: 12.5
9 | }
10 | }
11 |
12 | handleChange = (value) => {
13 | this.setState({
14 | value: value
15 | })
16 | }
17 |
18 | render () {
19 | const { value } = this.state
20 | return (
21 |
31 | )
32 | }
33 | }
34 |
35 | export default Float
36 |
--------------------------------------------------------------------------------
/docs/components/sliders/horizontal.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import Slider from 'react-rangeslider'
3 |
4 | class Horizontal extends Component {
5 | constructor (props, context) {
6 | super(props, context)
7 | this.state = {
8 | value: 10
9 | }
10 | }
11 |
12 | handleChangeStart = () => {
13 | console.log('Change event started')
14 | };
15 |
16 | handleChange = value => {
17 | this.setState({
18 | value: value
19 | })
20 | };
21 |
22 | handleChangeComplete = () => {
23 | console.log('Change event completed')
24 | };
25 |
26 | render () {
27 | const { value } = this.state
28 | return (
29 |
40 | )
41 | }
42 | }
43 |
44 | export default Horizontal
45 |
--------------------------------------------------------------------------------
/docs/components/sliders/index.js:
--------------------------------------------------------------------------------
1 | export { default as Horizontal } from './horizontal'
2 | export { default as Float } from './float'
3 | export { default as Negative } from './negative'
4 | export { default as Labels } from './labels'
5 | export { default as Orientation } from './orientation'
6 |
--------------------------------------------------------------------------------
/docs/components/sliders/labels.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import Slider from 'react-rangeslider'
3 |
4 | class HorizontalCustomLabels extends Component {
5 | constructor (props, context) {
6 | super(props, context)
7 | this.state = {
8 | horizontal: 10,
9 | vertical: 50
10 | }
11 | }
12 |
13 | handleChangeHorizontal = value => {
14 | this.setState({
15 | horizontal: value
16 | })
17 | };
18 |
19 | handleChangeVertical = value => {
20 | this.setState({
21 | vertical: value
22 | })
23 | };
24 |
25 | render () {
26 | const { horizontal, vertical } = this.state
27 | const horizontalLabels = {
28 | 0: 'Low',
29 | 50: 'Medium',
30 | 100: 'High'
31 | }
32 |
33 | const verticalLabels = {
34 | 10: 'Getting started',
35 | 50: 'Half way',
36 | 90: 'Almost done',
37 | 100: 'Complete!'
38 | }
39 |
40 | const formatkg = value => value + ' kg'
41 | const formatPc = p => p + '%'
42 |
43 | return (
44 |
45 |
54 |
{formatkg(horizontal)}
55 |
56 |
65 |
{formatPc(vertical)}
66 |
67 | )
68 | }
69 | }
70 |
71 | export default HorizontalCustomLabels
72 |
--------------------------------------------------------------------------------
/docs/components/sliders/negative.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import Slider from 'react-rangeslider'
3 |
4 | class Negative extends Component {
5 | constructor (props, context) {
6 | super(props, context)
7 | this.state = {
8 | value: -10
9 | }
10 | }
11 |
12 | handleChange = (value) => {
13 | this.setState({
14 | value: value
15 | })
16 | }
17 |
18 | render () {
19 | const { value } = this.state
20 | return (
21 |
31 | )
32 | }
33 | }
34 |
35 | export default Negative
36 |
--------------------------------------------------------------------------------
/docs/components/sliders/orientation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import Slider from 'react-rangeslider'
3 |
4 | class Vertical extends Component {
5 | constructor (props, context) {
6 | super(props, context)
7 | this.state = {
8 | value: 25,
9 | reverseValue: 8
10 | }
11 | }
12 |
13 | handleChange = (value) => {
14 | this.setState({
15 | value: value
16 | })
17 | }
18 |
19 | handleChangeReverse = (value) => {
20 | this.setState({
21 | reverseValue: value
22 | })
23 | }
24 |
25 | render () {
26 | const { value, reverseValue } = this.state
27 | return (
28 |
29 |
30 |
40 |
41 |
48 |
{reverseValue}
49 |
50 |
51 |
52 | )
53 | }
54 | }
55 |
56 | export default Vertical
57 |
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whoisandy/react-rangeslider/28b957e41feb78624a52ecee204018e928873b6f/docs/favicon.ico
--------------------------------------------------------------------------------
/docs/images/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whoisandy/react-rangeslider/28b957e41feb78624a52ecee204018e928873b6f/docs/images/bg.png
--------------------------------------------------------------------------------
/docs/images/code.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | code
5 | Created with Sketch.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/images/play.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | play
5 | Created with Sketch.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/docs/images/rangeslider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whoisandy/react-rangeslider/28b957e41feb78624a52ecee204018e928873b6f/docs/images/rangeslider.png
--------------------------------------------------------------------------------
/docs/images/rangeslider_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whoisandy/react-rangeslider/28b957e41feb78624a52ecee204018e928873b6f/docs/images/rangeslider_dark.png
--------------------------------------------------------------------------------
/docs/images/slider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whoisandy/react-rangeslider/28b957e41feb78624a52ecee204018e928873b6f/docs/images/slider.png
--------------------------------------------------------------------------------
/docs/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Rangeslider
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/docs/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import ReactGA from 'react-ga'
4 | import App from './app'
5 |
6 | function render () {
7 | const mount = document.getElementById('mount')
8 | ReactGA.initialize('UA-100351333-1')
9 | ReactGA.ga('send', 'pageview', '/')
10 | ReactDOM.render( , mount)
11 | }
12 |
13 | render()
14 |
--------------------------------------------------------------------------------
/docs/server.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const webpackDevMiddleware = require('webpack-dev-middleware')
4 | const webpackHotMiddleware = require('webpack-hot-middleware')
5 | const config = require('./webpack.config')
6 |
7 | const app = new (require('express'))()
8 | const port = 3000
9 |
10 | const compiler = webpack(config)
11 | app.use(webpackDevMiddleware(compiler, {
12 | noInfo: true,
13 | publicPath: config.output.publicPath
14 | }))
15 | app.use(webpackHotMiddleware(compiler))
16 |
17 | app.get('/', (req, res) => {
18 | res.sendFile(path.join(__dirname, 'index.html'))
19 | })
20 |
21 | app.listen(port, (err) => {
22 | if (err) {
23 | console.log(err)
24 | } else {
25 | console.log(`Listening on ${port}`)
26 | }
27 | })
28 |
--------------------------------------------------------------------------------
/docs/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var path = require('path')
4 | var webpack = require('webpack')
5 | var ExtractPlugin = require('extract-text-webpack-plugin')
6 | var HtmlPlugin = require('html-webpack-plugin')
7 | var config = {
8 | devtool: '#cheap-eval-source-map',
9 |
10 | entry: process.env.NODE_ENV === 'development'
11 | ? [
12 | 'webpack-hot-middleware/client?http://localhost:3000',
13 | path.join(__dirname, 'index')
14 | ]
15 | : path.join(__dirname, 'index'),
16 |
17 | output: {
18 | path: process.env.NODE_ENV === 'development' ? __dirname : 'public',
19 | publicPath: process.env.NODE_ENV === 'development' ? '/static/' : '',
20 | filename: 'bundle.js'
21 | },
22 |
23 | resolve: {
24 | extensions: ['', '.js', '.css', '.less'],
25 | alias: {
26 | 'react-rangeslider': path.join(__dirname, '../src/index.js')
27 | }
28 | },
29 |
30 | module: {
31 | loaders: [
32 | {
33 | test: /\.jsx?$/,
34 | exclude: /node_modules/,
35 | loader: 'babel'
36 | },
37 | {
38 | test: /\.css$/,
39 | exclude: /node_modules/,
40 | loader: 'style!css'
41 | },
42 | {
43 | test: /\.txt$/,
44 | exclude: /node_modules/,
45 | loader: 'raw'
46 | },
47 | {
48 | test: /\.(jpg|png|svg)$/,
49 | loader: 'url-loader',
50 | options: {
51 | limit: 25000
52 | }
53 | }
54 | ]
55 | },
56 |
57 | externals: {
58 | react: 'React',
59 | 'react-dom': 'ReactDOM',
60 | 'react-ga': 'ReactGA'
61 | },
62 |
63 | plugins: []
64 | }
65 |
66 | // Dev config
67 | if (process.env.NODE_ENV === 'development') {
68 | config.module.loaders.push({
69 | test: /\.less$/,
70 | exclude: /node_modules/,
71 | loader: 'style!css!less'
72 | })
73 | config.plugins.push(
74 | new webpack.DefinePlugin({
75 | 'process.env': {
76 | NODE_ENV: JSON.stringify('development')
77 | }
78 | }),
79 | new webpack.NoErrorsPlugin(),
80 | new webpack.HotModuleReplacementPlugin()
81 | )
82 | }
83 |
84 | // Build config
85 | if (process.env.NODE_ENV === 'production') {
86 | config.module.loaders.push([
87 | {
88 | test: /\.less$/,
89 | exclude: /node_modules/,
90 | loader: ExtractPlugin.extract('style-loader', 'css-loader!less-loader')
91 | }
92 | ])
93 | config.plugins.push(
94 | new webpack.DefinePlugin({
95 | 'process.env': {
96 | NODE_ENV: JSON.stringify('production')
97 | }
98 | }),
99 | new webpack.optimize.UglifyJsPlugin({
100 | minimize: true,
101 | compress: {
102 | unused: true,
103 | dead_code: true,
104 | warnings: false
105 | }
106 | }),
107 | new webpack.optimize.OccurenceOrderPlugin(),
108 | new ExtractPlugin('bundle.css'),
109 | new HtmlPlugin({
110 | appMountId: 'mount',
111 | title: 'React RangeSlider',
112 | template: 'docs/index.ejs'
113 | })
114 | )
115 | }
116 |
117 | module.exports = config
118 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-rangeslider",
3 | "version": "2.2.0",
4 | "description": "A lightweight react component that acts as a HTML5 input range slider polyfill",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "clean:lib": "del lib umd",
8 | "clean:docs": "del public",
9 | "clean": "npm run clean:lib && npm run clean:docs",
10 | "lint": "eslint src docs",
11 | "test": "npm run lint && jest",
12 | "coverage": "npm test -- --coverage",
13 | "start": "cross-env NODE_ENV=development node -r babel-register docs/server.js",
14 | "build:less": "lessc ./src/Rangeslider.less ./lib/index.css",
15 | "build:less:umd": "lessc ./src/Rangeslider.less ./umd/rangeslider.css",
16 | "build:less:umd:min": "lessc --clean-css ./src/Rangeslider.less ./umd/rangeslider.min.css",
17 | "build:lib": "cross-env NODE_ENV=production babel ./src --stage 0 -d ./lib --ignore __tests__",
18 | "build:umd": "cross-env NODE_ENV=production webpack ./src/index.js ./umd/rangeslider.js",
19 | "build:min": "cross-env NODE_ENV=production webpack -p ./src/index.js ./umd/rangeslider.min.js",
20 | "build:docs": "cross-env NODE_ENV=production webpack -p --config=docs/webpack.config.js",
21 | "build": "npm run build:less && npm run build:lib && npm run build:umd && npm run build:min && npm run build:less:umd && npm run build:less:umd:min",
22 | "prebuild": "npm run clean:lib && npm test",
23 | "docs": "npm run clean:docs && npm run build:docs && cpy docs/favicon.ico public/",
24 | "deploy": "npm run docs && gh-pages -d public",
25 | "postpublish": "git push origin master --follow-tags",
26 | "minor": "npm version minor && npm publish",
27 | "major": "npm version major && npm publish",
28 | "patch": "npm version patch && npm publish"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "https://github.com/whoisandy/react-rangeslider.git"
33 | },
34 | "files": [
35 | "lib",
36 | "umd"
37 | ],
38 | "keywords": [
39 | "rangeslider",
40 | "range-slider",
41 | "react-rangeslider",
42 | "input",
43 | "range",
44 | "react",
45 | "slider"
46 | ],
47 | "author": {
48 | "name": "Bhargav Anand",
49 | "email": "rjn143@gmail.com",
50 | "url": "github.com/whoisandy"
51 | },
52 | "engines": {
53 | "node": ">=4"
54 | },
55 | "license": "MIT",
56 | "bugs": {
57 | "url": "https://github.com/whoisandy/react-rangeslider/issues"
58 | },
59 | "homepage": "https://github.com/whoisandy/react-rangeslider#readme",
60 | "devDependencies": {
61 | "babel": "^6.5.2",
62 | "babel-cli": "^6.18.0",
63 | "babel-core": "^6.14.0",
64 | "babel-eslint": "^6.1.2",
65 | "babel-jest": "^15.0.0",
66 | "babel-loader": "^6.2.5",
67 | "babel-preset-es2015": "^6.14.0",
68 | "babel-preset-react": "^6.11.1",
69 | "babel-preset-stage-0": "^6.5.0",
70 | "babel-register": "^6.14.0",
71 | "cpy-cli": "^1.0.1",
72 | "cross-env": "^2.0.1",
73 | "css-loader": "^0.25.0",
74 | "del-cli": "^0.2.1",
75 | "enzyme": "^2.4.1",
76 | "eslint": "^3.5.0",
77 | "eslint-config-standard": "^6.0.0",
78 | "eslint-config-standard-jsx": "^3.0.0",
79 | "eslint-config-standard-react": "^4.0.0",
80 | "eslint-loader": "^1.0.0",
81 | "eslint-plugin-import": "^1.14.0",
82 | "eslint-plugin-prettier": "^2.3.1",
83 | "eslint-plugin-promise": "^3.0.0",
84 | "eslint-plugin-react": "^6.2.0",
85 | "eslint-plugin-standard": "^2.0.0",
86 | "express": "^4.13.4",
87 | "extract-text-webpack-plugin": "^1.0.1",
88 | "file-loader": "^0.11.1",
89 | "gh-pages": "^0.11.0",
90 | "highlight.js": "^9.9.0",
91 | "html-webpack-plugin": "^2.22.0",
92 | "jest": "^15.1.1",
93 | "less": "^2.7.2",
94 | "less-loader": "^2.2.3",
95 | "less-plugin-clean-css": "^1.5.1",
96 | "marked": "^0.3.6",
97 | "opn-cli": "^3.1.0",
98 | "prop-types": "^15.5.9",
99 | "raw-loader": "^0.5.1",
100 | "react": "^15.3.1",
101 | "react-addons-test-utils": "^15.3.1",
102 | "react-dom": "^15.3.1",
103 | "react-ga": "^2.2.0",
104 | "react-github-button": "^0.1.11",
105 | "react-test-renderer": "^15.5.4",
106 | "style-loader": "^0.13.1",
107 | "url-loader": "^0.5.8",
108 | "webpack": "^1.13.0",
109 | "webpack-dev-middleware": "^1.6.1",
110 | "webpack-hot-middleware": "^2.10.0"
111 | },
112 | "dependencies": {
113 | "classnames": "^2.2.3",
114 | "resize-observer-polyfill": "^1.4.2"
115 | },
116 | "peerDependencies": {
117 | "react": "^0.14.0 || ^15.0.0"
118 | },
119 | "jest": {
120 | "moduleNameMapper": {
121 | ".*\\.less$": "/src/"
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/Rangeslider.js:
--------------------------------------------------------------------------------
1 | /* eslint no-debugger: "warn" */
2 | import cx from 'classnames'
3 | import React, { Component } from 'react'
4 | import PropTypes from 'prop-types'
5 | import ResizeObserver from 'resize-observer-polyfill'
6 | import { capitalize, clamp } from './utils'
7 |
8 | /**
9 | * Predefined constants
10 | * @type {Object}
11 | */
12 | const constants = {
13 | orientation: {
14 | horizontal: {
15 | dimension: 'width',
16 | direction: 'left',
17 | reverseDirection: 'right',
18 | coordinate: 'x'
19 | },
20 | vertical: {
21 | dimension: 'height',
22 | direction: 'top',
23 | reverseDirection: 'bottom',
24 | coordinate: 'y'
25 | }
26 | }
27 | }
28 |
29 | class Slider extends Component {
30 | static propTypes = {
31 | min: PropTypes.number,
32 | max: PropTypes.number,
33 | step: PropTypes.number,
34 | value: PropTypes.number,
35 | orientation: PropTypes.string,
36 | tooltip: PropTypes.bool,
37 | reverse: PropTypes.bool,
38 | labels: PropTypes.object,
39 | handleLabel: PropTypes.string,
40 | format: PropTypes.func,
41 | onChangeStart: PropTypes.func,
42 | onChange: PropTypes.func,
43 | onChangeComplete: PropTypes.func
44 | };
45 |
46 | static defaultProps = {
47 | min: 0,
48 | max: 100,
49 | step: 1,
50 | value: 0,
51 | orientation: 'horizontal',
52 | tooltip: true,
53 | reverse: false,
54 | labels: {},
55 | handleLabel: ''
56 | };
57 |
58 | constructor (props, context) {
59 | super(props, context)
60 |
61 | this.state = {
62 | active: false,
63 | limit: 0,
64 | grab: 0
65 | }
66 | }
67 |
68 | componentDidMount () {
69 | this.handleUpdate()
70 | const resizeObserver = new ResizeObserver(this.handleUpdate)
71 | resizeObserver.observe(this.slider)
72 | }
73 |
74 | /**
75 | * Format label/tooltip value
76 | * @param {Number} - value
77 | * @return {Formatted Number}
78 | */
79 | handleFormat = value => {
80 | const { format } = this.props
81 | return format ? format(value) : value
82 | };
83 |
84 | /**
85 | * Update slider state on change
86 | * @return {void}
87 | */
88 | handleUpdate = () => {
89 | if (!this.slider) {
90 | // for shallow rendering
91 | return
92 | }
93 | const { orientation } = this.props
94 | const dimension = capitalize(constants.orientation[orientation].dimension)
95 | const sliderPos = this.slider[`offset${dimension}`]
96 | const handlePos = this.handle[`offset${dimension}`]
97 |
98 | this.setState({
99 | limit: sliderPos - handlePos,
100 | grab: handlePos / 2
101 | })
102 | };
103 |
104 | /**
105 | * Attach event listeners to mousemove/mouseup events
106 | * @return {void}
107 | */
108 | handleStart = e => {
109 | const { onChangeStart } = this.props
110 | document.addEventListener('mousemove', this.handleDrag)
111 | document.addEventListener('mouseup', this.handleEnd)
112 | this.setState(
113 | {
114 | active: true
115 | },
116 | () => {
117 | onChangeStart && onChangeStart(e)
118 | }
119 | )
120 | };
121 |
122 | /**
123 | * Handle drag/mousemove event
124 | * @param {Object} e - Event object
125 | * @return {void}
126 | */
127 | handleDrag = e => {
128 | e.stopPropagation()
129 | const { onChange } = this.props
130 | const { target: { className, classList, dataset } } = e
131 | if (!onChange || className === 'rangeslider__labels') return
132 |
133 | let value = this.position(e)
134 |
135 | if (
136 | classList &&
137 | classList.contains('rangeslider__label-item') &&
138 | dataset.value
139 | ) {
140 | value = parseFloat(dataset.value)
141 | }
142 |
143 | onChange && onChange(value, e)
144 | };
145 |
146 | /**
147 | * Detach event listeners to mousemove/mouseup events
148 | * @return {void}
149 | */
150 | handleEnd = e => {
151 | const { onChangeComplete } = this.props
152 | this.setState(
153 | {
154 | active: false
155 | },
156 | () => {
157 | onChangeComplete && onChangeComplete(e)
158 | }
159 | )
160 | document.removeEventListener('mousemove', this.handleDrag)
161 | document.removeEventListener('mouseup', this.handleEnd)
162 | };
163 |
164 | /**
165 | * Support for key events on the slider handle
166 | * @param {Object} e - Event object
167 | * @return {void}
168 | */
169 | handleKeyDown = e => {
170 | e.preventDefault()
171 | const { keyCode } = e
172 | const { value, min, max, step, onChange } = this.props
173 | let sliderValue
174 |
175 | switch (keyCode) {
176 | case 38:
177 | case 39:
178 | sliderValue = value + step > max ? max : value + step
179 | onChange && onChange(sliderValue, e)
180 | break
181 | case 37:
182 | case 40:
183 | sliderValue = value - step < min ? min : value - step
184 | onChange && onChange(sliderValue, e)
185 | break
186 | }
187 | };
188 |
189 | /**
190 | * Calculate position of slider based on its value
191 | * @param {number} value - Current value of slider
192 | * @return {position} pos - Calculated position of slider based on value
193 | */
194 | getPositionFromValue = value => {
195 | const { limit } = this.state
196 | const { min, max } = this.props
197 | const diffMaxMin = max - min
198 | const diffValMin = value - min
199 | const percentage = diffValMin / diffMaxMin
200 | const pos = Math.round(percentage * limit)
201 |
202 | return pos
203 | };
204 |
205 | /**
206 | * Translate position of slider to slider value
207 | * @param {number} pos - Current position/coordinates of slider
208 | * @return {number} value - Slider value
209 | */
210 | getValueFromPosition = pos => {
211 | const { limit } = this.state
212 | const { orientation, min, max, step } = this.props
213 | const percentage = clamp(pos, 0, limit) / (limit || 1)
214 | const baseVal = step * Math.round(percentage * (max - min) / step)
215 | const value = orientation === 'horizontal' ? baseVal + min : max - baseVal
216 |
217 | return clamp(value, min, max)
218 | };
219 |
220 | /**
221 | * Calculate position of slider based on value
222 | * @param {Object} e - Event object
223 | * @return {number} value - Slider value
224 | */
225 | position = e => {
226 | const { grab } = this.state
227 | const { orientation, reverse } = this.props
228 |
229 | const node = this.slider
230 | const coordinateStyle = constants.orientation[orientation].coordinate
231 | const directionStyle = reverse
232 | ? constants.orientation[orientation].reverseDirection
233 | : constants.orientation[orientation].direction
234 | const clientCoordinateStyle = `client${capitalize(coordinateStyle)}`
235 | const coordinate = !e.touches
236 | ? e[clientCoordinateStyle]
237 | : e.touches[0][clientCoordinateStyle]
238 | const direction = node.getBoundingClientRect()[directionStyle]
239 | const pos = reverse
240 | ? direction - coordinate - grab
241 | : coordinate - direction - grab
242 | const value = this.getValueFromPosition(pos)
243 |
244 | return value
245 | };
246 |
247 | /**
248 | * Grab coordinates of slider
249 | * @param {Object} pos - Position object
250 | * @return {Object} - Slider fill/handle coordinates
251 | */
252 | coordinates = pos => {
253 | const { limit, grab } = this.state
254 | const { orientation } = this.props
255 | const value = this.getValueFromPosition(pos)
256 | const position = this.getPositionFromValue(value)
257 | const handlePos = orientation === 'horizontal' ? position + grab : position
258 | const fillPos = orientation === 'horizontal'
259 | ? handlePos
260 | : limit - handlePos
261 |
262 | return {
263 | fill: fillPos,
264 | handle: handlePos,
265 | label: handlePos
266 | }
267 | };
268 |
269 | renderLabels = labels => (
270 | {
272 | this.labels = sl
273 | }}
274 | className={cx('rangeslider__labels')}
275 | >
276 | {labels}
277 |
278 | );
279 |
280 | render () {
281 | const {
282 | value,
283 | orientation,
284 | className,
285 | tooltip,
286 | reverse,
287 | labels,
288 | min,
289 | max,
290 | handleLabel
291 | } = this.props
292 | const { active } = this.state
293 | const dimension = constants.orientation[orientation].dimension
294 | const direction = reverse
295 | ? constants.orientation[orientation].reverseDirection
296 | : constants.orientation[orientation].direction
297 | const position = this.getPositionFromValue(value)
298 | const coords = this.coordinates(position)
299 | const fillStyle = { [dimension]: `${coords.fill}px` }
300 | const handleStyle = { [direction]: `${coords.handle}px` }
301 | let showTooltip = tooltip && active
302 |
303 | let labelItems = []
304 | let labelKeys = Object.keys(labels)
305 |
306 | if (labelKeys.length > 0) {
307 | labelKeys = labelKeys.sort((a, b) => (reverse ? a - b : b - a))
308 |
309 | for (let key of labelKeys) {
310 | const labelPosition = this.getPositionFromValue(key)
311 | const labelCoords = this.coordinates(labelPosition)
312 | const labelStyle = { [direction]: `${labelCoords.label}px` }
313 |
314 | labelItems.push(
315 |
324 | {this.props.labels[key]}
325 |
326 | )
327 | }
328 | }
329 |
330 | return (
331 | {
333 | this.slider = s
334 | }}
335 | className={cx(
336 | 'rangeslider',
337 | `rangeslider-${orientation}`,
338 | { 'rangeslider-reverse': reverse },
339 | className
340 | )}
341 | onMouseDown={this.handleDrag}
342 | onMouseUp={this.handleEnd}
343 | onTouchStart={this.handleStart}
344 | onTouchEnd={this.handleEnd}
345 | aria-valuemin={min}
346 | aria-valuemax={max}
347 | aria-valuenow={value}
348 | aria-orientation={orientation}
349 | >
350 |
351 |
{
353 | this.handle = sh
354 | }}
355 | className='rangeslider__handle'
356 | onMouseDown={this.handleStart}
357 | onTouchMove={this.handleDrag}
358 | onTouchEnd={this.handleEnd}
359 | onKeyDown={this.handleKeyDown}
360 | style={handleStyle}
361 | tabIndex={0}
362 | >
363 | {showTooltip
364 | ?
{
366 | this.tooltip = st
367 | }}
368 | className='rangeslider__handle-tooltip'
369 | >
370 | {this.handleFormat(value)}
371 |
372 | : null}
373 |
{handleLabel}
374 |
375 | {labels ? this.renderLabels(labelItems) : null}
376 |
377 | )
378 | }
379 | }
380 |
381 | export default Slider
382 |
--------------------------------------------------------------------------------
/src/__tests__/Rangeslider.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow, mount } from 'enzyme'
3 | import renderer from 'react-test-renderer'
4 | import Slider from '../Rangeslider'
5 |
6 | describe('Rangeslider specs', () => {
7 | it('should render properly', () => {
8 | const slider = shallow( )
9 | expect(slider.hasClass('rangeslider')).toBeTruthy()
10 | expect(slider.children().length).toEqual(3)
11 | expect(slider.find('.rangeslider__fill').length).toEqual(1)
12 | expect(slider.find('.rangeslider__handle').length).toEqual(1)
13 | })
14 |
15 | it('should have default props', () => {
16 | const slider = mount( )
17 | expect(slider.prop('min')).toEqual(0)
18 | expect(slider.prop('max')).toEqual(100)
19 | expect(slider.prop('step')).toEqual(1)
20 | expect(slider.prop('value')).toEqual(0)
21 | expect(slider.prop('orientation')).toEqual('horizontal')
22 | expect(slider.prop('reverse')).toEqual(false)
23 | expect(slider.prop('handleLabel')).toEqual('')
24 | expect(slider.prop('labels')).toEqual({})
25 | })
26 |
27 | it('should render basic slider with defaults', () => {
28 | const tree = renderer.create( ).toJSON()
29 | expect(tree).toMatchSnapshot()
30 | })
31 |
32 | it('should render slider when props passed in', () => {
33 | const tree = renderer
34 | .create( )
35 | .toJSON()
36 | expect(tree).toMatchSnapshot()
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/src/__tests__/Sanity.spec.js:
--------------------------------------------------------------------------------
1 | describe('Sanity Specs', () => {
2 | it('should evaluate true to be truthy', () => {
3 | expect(true).toBeTruthy()
4 | })
5 | })
6 |
--------------------------------------------------------------------------------
/src/__tests__/__snapshots__/Rangeslider.spec.js.snap:
--------------------------------------------------------------------------------
1 | exports[`Rangeslider specs should render basic slider with defaults 1`] = `
2 |
39 | `;
40 |
41 | exports[`Rangeslider specs should render slider when props passed in 1`] = `
42 |
79 | `;
80 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Rangeslider from './Rangeslider'
2 | export default Rangeslider
3 |
--------------------------------------------------------------------------------
/src/rangeslider.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Rangeslider
3 | */
4 | .rangeslider {
5 | margin: 20px 0;
6 | position: relative;
7 | background: #e6e6e6;
8 | -ms-touch-action: none;
9 | touch-action: none;
10 |
11 | &,
12 | .rangeslider__fill {
13 | display: block;
14 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4);
15 | }
16 | .rangeslider__handle {
17 | background: #fff;
18 | border: 1px solid #ccc;
19 | cursor: pointer;
20 | display: inline-block;
21 | position: absolute;
22 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 -1px 3px rgba(0, 0, 0, 0.4);
23 | .rangeslider__active {
24 | opacity: 1;
25 | }
26 | }
27 |
28 | .rangeslider__handle-tooltip {
29 | width: 40px;
30 | height: 40px;
31 | text-align: center;
32 | position: absolute;
33 | background-color: rgba(0, 0, 0, 0.8);
34 | font-weight: normal;
35 | font-size: 14px;
36 | transition: all 100ms ease-in;
37 | border-radius: 4px;
38 | display: inline-block;
39 | color: white;
40 | left: 50%;
41 | transform: translate3d(-50%, 0, 0);
42 | span {
43 | margin-top: 12px;
44 | display: inline-block;
45 | line-height: 100%;
46 | }
47 | &:after {
48 | content: ' ';
49 | position: absolute;
50 | width: 0;
51 | height: 0;
52 | }
53 | }
54 | }
55 |
56 | /**
57 | * Rangeslider - Horizontal slider
58 | */
59 | .rangeslider-horizontal {
60 | height: 12px;
61 | border-radius: 10px;
62 | .rangeslider__fill {
63 | height: 100%;
64 | background-color: #7cb342;
65 | border-radius: 10px;
66 | top: 0;
67 | }
68 | .rangeslider__handle {
69 | width: 30px;
70 | height: 30px;
71 | border-radius: 30px;
72 | top: 50%;
73 | transform: translate3d(-50%, -50%, 0);
74 | &:after {
75 | content: ' ';
76 | position: absolute;
77 | width: 16px;
78 | height: 16px;
79 | top: 6px;
80 | left: 6px;
81 | border-radius: 50%;
82 | background-color: #dadada;
83 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4) inset,
84 | 0 -1px 3px rgba(0, 0, 0, 0.4) inset;
85 | }
86 | }
87 | .rangeslider__handle-tooltip {
88 | top: -55px;
89 | &:after {
90 | border-left: 8px solid transparent;
91 | border-right: 8px solid transparent;
92 | border-top: 8px solid rgba(0, 0, 0, 0.8);
93 | left: 50%;
94 | bottom: -8px;
95 | transform: translate3d(-50%, 0, 0);
96 | }
97 | }
98 | }
99 |
100 | /**
101 | * Rangeslider - Vertical slider
102 | */
103 | .rangeslider-vertical {
104 | margin: 20px auto;
105 | height: 150px;
106 | max-width: 10px;
107 | background-color: transparent;
108 |
109 | .rangeslider__fill,
110 | .rangeslider__handle {
111 | position: absolute;
112 | }
113 |
114 | .rangeslider__fill {
115 | width: 100%;
116 | background-color: #7cb342;
117 | box-shadow: none;
118 | bottom: 0;
119 | }
120 | .rangeslider__handle {
121 | width: 30px;
122 | height: 10px;
123 | left: -10px;
124 | box-shadow: none;
125 | }
126 | .rangeslider__handle-tooltip {
127 | left: -100%;
128 | top: 50%;
129 | transform: translate3d(-50%, -50%, 0);
130 | &:after {
131 | border-top: 8px solid transparent;
132 | border-bottom: 8px solid transparent;
133 | border-left: 8px solid rgba(0, 0, 0, 0.8);
134 | left: 100%;
135 | top: 12px;
136 | }
137 | }
138 | }
139 |
140 | /**
141 | * Rangeslider - Reverse
142 | */
143 |
144 | .rangeslider-reverse {
145 | &.rangeslider-horizontal {
146 | .rangeslider__fill {
147 | right: 0;
148 | }
149 | }
150 | &.rangeslider-vertical {
151 | .rangeslider__fill {
152 | top: 0;
153 | bottom: inherit;
154 | }
155 | }
156 | }
157 |
158 | /**
159 | * Rangeslider - Labels
160 | */
161 | .rangeslider__labels {
162 | position: relative;
163 | .rangeslider-vertical & {
164 | position: relative;
165 | list-style-type: none;
166 | margin: 0 0 0 24px;
167 | padding: 0;
168 | text-align: left;
169 | width: 250px;
170 | height: 100%;
171 | left: 10px;
172 |
173 | .rangeslider__label-item {
174 | position: absolute;
175 | transform: translate3d(0, -50%, 0);
176 |
177 | &::before {
178 | content: '';
179 | width: 10px;
180 | height: 2px;
181 | background: black;
182 | position: absolute;
183 | left: -14px;
184 | top: 50%;
185 | transform: translateY(-50%);
186 | z-index: -1;
187 | }
188 | }
189 | }
190 |
191 | .rangeslider__label-item {
192 | position: absolute;
193 | font-size: 14px;
194 | cursor: pointer;
195 | display: inline-block;
196 | top: 10px;
197 | transform: translate3d(-50%, 0, 0);
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Capitalize first letter of string
3 | * @private
4 | * @param {string} - String
5 | * @return {string} - String with first letter capitalized
6 | */
7 | export function capitalize (str) {
8 | return str.charAt(0).toUpperCase() + str.substr(1)
9 | }
10 |
11 | /**
12 | * Clamp position between a range
13 | * @param {number} - Value to be clamped
14 | * @param {number} - Minimum value in range
15 | * @param {number} - Maximum value in range
16 | * @return {number} - Clamped value
17 | */
18 | export function clamp (value, min, max) {
19 | return Math.min(Math.max(value, min), max)
20 | }
21 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var path = require('path')
4 | var ExtractPlugin = require('extract-text-webpack-plugin')
5 |
6 | module.exports = {
7 | entry: path.join(__dirname, 'src', 'index'),
8 |
9 | output: {
10 | library: 'ReactRangeslider',
11 | libraryTarget: 'umd'
12 | },
13 | module: {
14 | loaders: [
15 | {
16 | test: /\.js?$/,
17 | exclude: /node_modules/,
18 | loader: 'babel'
19 | },
20 | {
21 | test: /\.less$/,
22 | exclude: /node_modules/,
23 | loader: ExtractPlugin.extract('style-loader', 'css-loader!less-loader')
24 | }
25 | ]
26 | },
27 |
28 | plugins: [new ExtractPlugin('RangeSlider.css')],
29 |
30 | externals: [
31 | {
32 | react: {
33 | root: 'React',
34 | commonjs2: 'react',
35 | commonjs: 'react',
36 | amd: 'react'
37 | }
38 | }
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------