Skip to content

Commit 229bb02

Browse files
author
vvo
committed
feat(slider): first iteration
eyes only, webpack build in error
1 parent cd25b7f commit 229bb02

File tree

7 files changed

+301
-1
lines changed

7 files changed

+301
-1
lines changed

README.md

+27
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ npm run test:coverage
9999
[toggle]: ./widgets-screenshots/toggle.png
100100
[refinementList]: ./widgets-screenshots/refinement-list.png
101101
[menu]: ./widgets-screenshots/menu.png
102+
[rangeSlider]: ./widgets-screenshots/rangeSlider.png
102103

103104
### searchBox
104105

@@ -344,3 +345,29 @@ search.addWidget(
344345
})
345346
);
346347
```
348+
349+
### rangeSlider
350+
351+
![Example of the rangeSlider widget][rangeSlider]
352+
353+
#### API
354+
355+
```js
356+
357+
```
358+
359+
360+
#### Usage
361+
362+
```html
363+
<div id="price"></div>
364+
```
365+
366+
```js
367+
search.addWidget(
368+
instantsearch.widgets.rangeSlider({
369+
container: '#price',
370+
facetName: 'price'
371+
})
372+
);
373+
```

components/Slider.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
var React = require('react');
2+
3+
var Nouislider = require('react-nouislider');
4+
5+
class Slider extends React.Component {
6+
7+
// we are only interested in rawValues
8+
handleChange(formattedValues, handleId, rawValues) {
9+
this.props.onChange(rawValues);
10+
}
11+
12+
render() {
13+
return (
14+
<Nouislider
15+
{...this.props}
16+
onChange={this.handleChange.bind(this)}
17+
/>
18+
);
19+
}
20+
}
21+
22+
Slider.propTypes = {
23+
onSlide: React.PropTypes.func,
24+
onChange: React.PropTypes.func,
25+
range: React.PropTypes.object.isRequired,
26+
start: React.PropTypes.arrayOf(React.PropTypes.number).isRequired
27+
};
28+
29+
module.exports = Slider;

example/app.js

+7
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,11 @@ search.addWidget(
9393
})
9494
);
9595

96+
search.addWidget(
97+
instantsearch.widgets.rangeSlider({
98+
container: '#price',
99+
facetName: 'price'
100+
})
101+
);
102+
96103
search.start();

example/index.html

+173-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,171 @@
55
<title>Instant search demo built with instantsearch.js</title>
66
<link rel="stylesheet" href="//cdn.jsdelivr.net/bootstrap/3.3.5/css/bootstrap.min.css">
77
<link rel="stylesheet" href="//cdn.jsdelivr.net/bootstrap/3.3.5/css/bootstrap-theme.min.css">
8+
<style>
9+
10+
/* Functional styling;
11+
* These styles are required for noUiSlider to function.
12+
* You don't need to change these rules to apply your design.
13+
*/
14+
.noUi-target,
15+
.noUi-target * {
16+
-webkit-touch-callout: none;
17+
-webkit-user-select: none;
18+
-ms-touch-action: none;
19+
-ms-user-select: none;
20+
-moz-user-select: none;
21+
-moz-box-sizing: border-box;
22+
box-sizing: border-box;
23+
}
24+
.noUi-target {
25+
position: relative;
26+
direction: ltr;
27+
}
28+
.noUi-base {
29+
width: 100%;
30+
height: 100%;
31+
position: relative;
32+
z-index: 1; /* Fix 401 */
33+
}
34+
.noUi-origin {
35+
position: absolute;
36+
right: 0;
37+
top: 0;
38+
left: 0;
39+
bottom: 0;
40+
}
41+
.noUi-handle {
42+
position: relative;
43+
z-index: 1;
44+
}
45+
.noUi-stacking .noUi-handle {
46+
/* This class is applied to the lower origin when
47+
its values is > 50%. */
48+
z-index: 10;
49+
}
50+
.noUi-state-tap .noUi-origin {
51+
-webkit-transition: left 0.3s, top 0.3s;
52+
transition: left 0.3s, top 0.3s;
53+
}
54+
.noUi-state-drag * {
55+
cursor: inherit !important;
56+
}
57+
58+
/* Painting and performance;
59+
* Browsers can paint handles in their own layer.
60+
*/
61+
.noUi-base {
62+
-webkit-transform: translate3d(0,0,0);
63+
transform: translate3d(0,0,0);
64+
}
65+
66+
/* Slider size and handle placement;
67+
*/
68+
.noUi-horizontal {
69+
height: 18px;
70+
}
71+
.noUi-horizontal .noUi-handle {
72+
width: 34px;
73+
height: 28px;
74+
left: -17px;
75+
top: -6px;
76+
}
77+
.noUi-vertical {
78+
width: 18px;
79+
}
80+
.noUi-vertical .noUi-handle {
81+
width: 28px;
82+
height: 34px;
83+
left: -6px;
84+
top: -17px;
85+
}
86+
87+
/* Styling;
88+
*/
89+
.noUi-background {
90+
background: #FAFAFA;
91+
box-shadow: inset 0 1px 1px #f0f0f0;
92+
}
93+
.noUi-connect {
94+
background: #3FB8AF;
95+
box-shadow: inset 0 0 3px rgba(51,51,51,0.45);
96+
-webkit-transition: background 450ms;
97+
transition: background 450ms;
98+
}
99+
.noUi-origin {
100+
border-radius: 2px;
101+
}
102+
.noUi-target {
103+
border-radius: 4px;
104+
border: 1px solid #D3D3D3;
105+
box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB;
106+
}
107+
.noUi-target.noUi-connect {
108+
box-shadow: inset 0 0 3px rgba(51,51,51,0.45), 0 3px 6px -5px #BBB;
109+
}
110+
111+
/* Handles and cursors;
112+
*/
113+
.noUi-dragable {
114+
cursor: w-resize;
115+
}
116+
.noUi-vertical .noUi-dragable {
117+
cursor: n-resize;
118+
}
119+
.noUi-handle {
120+
border: 1px solid #D9D9D9;
121+
border-radius: 3px;
122+
background: #FFF;
123+
cursor: default;
124+
box-shadow: inset 0 0 1px #FFF,
125+
inset 0 1px 7px #EBEBEB,
126+
0 3px 6px -3px #BBB;
127+
}
128+
.noUi-active {
129+
box-shadow: inset 0 0 1px #FFF,
130+
inset 0 1px 7px #DDD,
131+
0 3px 6px -3px #BBB;
132+
}
133+
134+
/* Handle stripes;
135+
*/
136+
.noUi-handle:before,
137+
.noUi-handle:after {
138+
content: "";
139+
display: block;
140+
position: absolute;
141+
height: 14px;
142+
width: 1px;
143+
background: #E8E7E6;
144+
left: 14px;
145+
top: 6px;
146+
}
147+
.noUi-handle:after {
148+
left: 17px;
149+
}
150+
.noUi-vertical .noUi-handle:before,
151+
.noUi-vertical .noUi-handle:after {
152+
width: 14px;
153+
height: 1px;
154+
left: 6px;
155+
top: 14px;
156+
}
157+
.noUi-vertical .noUi-handle:after {
158+
top: 17px;
159+
}
160+
161+
/* Disabled state;
162+
*/
163+
[disabled].noUi-connect,
164+
[disabled] .noUi-connect {
165+
background: #B8B8B8;
166+
}
167+
[disabled].noUi-origin,
168+
[disabled] .noUi-handle {
169+
cursor: not-allowed;
170+
}
171+
172+
</style>
8173
</head>
9174
<body>
10175
<div class="container">
@@ -34,14 +199,21 @@ <h1>Instant search demo <small>using instantsearch.js</small></h1>
34199
</div>
35200

36201
<div class="panel panel-default">
37-
<div class="panel-heading">Toggle filters</div>
202+
<div class="panel-heading">Shipping</div>
38203
<div class="panel-body">
39204
<ul class="nav nav-stacked">
40205
<li><div id="free_shipping"></div></li>
41206
</ul>
42207
</div>
43208
</div>
44209

210+
<div class="panel panel-default">
211+
<div class="panel-heading">Price</div>
212+
<div class="panel-body">
213+
<div id="price"></div>
214+
</div>
215+
</div>
216+
45217
</div>
46218
<div class="col-md-9">
47219
<div class="row">

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ instantsearch.widgets = {
1010
refinementList: require('./widgets/refinement-list'),
1111
pagination: require('./widgets/pagination'),
1212
searchBox: require('./widgets/search-box'),
13+
rangeSlider: require('./widgets/range-slider'),
1314
stats: require('./widgets/stats'),
1415
toggle: require('./widgets/toggle')
1516
};

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"lodash": "3.10.1",
5454
"raw-loader": "0.5.1",
5555
"react": "0.13.3",
56+
"react-nouislider": "1.0.0",
5657
"to-factory": "1.0.0"
5758
},
5859
"license": "MIT"

widgets/range-slider.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
var React = require('react');
2+
3+
var utils = require('../lib/widget-utils.js');
4+
5+
/**
6+
* Instantiate a slider based on a numeric attribute
7+
* @param {String|DOMElement} options.container Valid CSS Selector as a string or DOMElement
8+
* @param {String} options.facetName Name of the attribute for faceting
9+
* @return {Object}
10+
*/
11+
function rangeSlider({
12+
container = null,
13+
facetName = null
14+
}) {
15+
var Slider = require('../components/Slider');
16+
17+
var containerNode = utils.getContainerNode(container);
18+
19+
return {
20+
getConfiguration: () => ({
21+
disjunctiveFacets: [facetName]
22+
}),
23+
_currentValues: {
24+
min: -Infinity,
25+
max: Infinity
26+
},
27+
_refine(helper, stats, newValues) {
28+
var min = helper.state.getNumericRefinement(facetName, '>=');
29+
var max = helper.state.getNumericRefinement(facetName, '<=');
30+
31+
this._currentValues = {
32+
min: Math.max(min && min.length && min[0] || 0, stats.min),
33+
max: Math.min(max && max.length && max[0] || Infinity, stats.max)
34+
};
35+
36+
if (this._currentValues.min !== newValues[0] || this._currentValues.max !== newValues[1]) {
37+
this._currentValues = {
38+
min: newValues[0],
39+
max: newValues[1]
40+
};
41+
42+
helper.clearRefinements(facetName);
43+
helper.addNumericRefinement(facetName, '>=', this._currentValues.min);
44+
helper.addNumericRefinement(facetName, '<=', this._currentValues.max);
45+
helper.search();
46+
}
47+
},
48+
render(results, state, helper) {
49+
var stats = results.getFacetStats(facetName);
50+
51+
React.render(
52+
<Slider
53+
start={[this._currentValues.min, this._currentValues.max]}
54+
range={{min: stats.min, max: stats.max}}
55+
onChange={this._refine.bind(this, helper, stats)}
56+
/>,
57+
containerNode
58+
);
59+
}
60+
};
61+
}
62+
63+
module.exports = rangeSlider;

0 commit comments

Comments
 (0)