-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(slider): add slider component (#146)
* feat(slider): WIP adding slider component * feat(slider): add correct styling * feat(slider): make filled track follow the thumb position * feat(slider): WIP math * feat(slider): WIP math, and add support for track click * feat(slider): WIP rounding math * feat(slider): fix rounding math * feat(slider): styling and text input option * feat(slider): add eventedState * feat(slider): refactor js * feat(slider): add support for shiftKey as a modifier * feat(slider): js cleanup * feat(slider): make stepMulitplier a configurable option * test(slider): WIP slider tests * feat(slider): fix focus/active states, and update tests * feat(slider): fix shiftKey multiplied math, tests * test(slider): fix slider tests raf bug * feat(slider): add min/max labels * test(slider): WIP fix raf conflict * test(slider): remove raf stub conflict * feat(slider): fix misaligned track * feat(slider): fix support for disabled slider * fix(slider): fix merge conflict * docs(slider): re-add README * fix(slider): fix typo failing tests * fix(slider): PR fixes * fix(slider): fix visual PR bug
- Loading branch information
1 parent
73b8e0d
commit 8b571fb
Showing
9 changed files
with
547 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
### SCSS | ||
|
||
#### Modifiers | ||
|
||
Use these modifiers with `.bx--slider` class. | ||
|
||
| Selector | Description | | ||
|--------------------------|---------------------------------------------------------------------| | ||
| .bx--slider--disabled | Applies disabled styling and prevents JS from running on user input | | ||
|
||
### Javascript | ||
|
||
#### Options | ||
|
||
| Option | Default Selector | Description | | ||
|--------------------------------|------------------------------|---------------------------------------------------------------------------| | ||
| `selectorInit` | `[data-slider]` | The selector to find the Slider element. | | ||
| `selectorTrack` | `.bx--slider__track` | The selector to find the Slider track element. | | ||
| `selectorFilledTrack` | `.bx--slider__filled-track` | The selector to find the Slider filled track element. | | ||
| `selectorThumb` | `.bx--slider__thumb` | The selector to find the Slider thumb element. | | ||
| `selectorInput` | `.bx--slider__input` | The selector to find the Slider input element. | | ||
| `eventBeforeSliderValueChange` | `slider-before-value-change` | The event emitted before the Slider value changes. | | ||
| `eventAfterSliderValueChange` | `slider-after-value-change` | The event emitted when the Slider value changes. | | ||
| `stepMultiplier` | `4` | The multiplier applied to arrow key inputs when the shift key is pressed. | | ||
|
||
### FAQ | ||
|
||
#### Keydown event | ||
|
||
Once `Slider` instance is initialized a user can use the following keys to control the slider. | ||
|
||
- `up` and `right` arrow keys increase the slider value by one step | ||
- `down` and `left` arrow keys decrease the slider value by one step | ||
- Pressing `shift` while changing the value of the slider will multiple the change by `Slider.options.stepMultiplier` (the default is 4) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
//----------------------------- | ||
// Slider | ||
//----------------------------- | ||
|
||
@import '../../globals/scss/colors'; | ||
@import '../../globals/scss/helper-mixins'; | ||
@import '../../globals/scss/layer'; | ||
@import '../../globals/scss/typography'; | ||
@import '../../globals/scss/layout'; | ||
@import '../../globals/scss/import-once'; | ||
@import '../form/form'; | ||
@import '../text-input/text-input'; | ||
|
||
@include exports('slider') { | ||
.bx--slider-container { | ||
max-width: rem(600px); | ||
min-width: rem(200px); | ||
display: flex; | ||
align-items: center; | ||
user-select: none; | ||
|
||
@media screen and (min-width: 768px) { | ||
min-width: rem(350px); | ||
} | ||
} | ||
|
||
.bx--slider { | ||
position: relative; | ||
width: 100%; | ||
margin: 0 1rem; | ||
} | ||
|
||
.bx--slider--disabled { | ||
opacity: .5; | ||
} | ||
|
||
.bx--slider--disabled .bx--slider__thumb { | ||
&:hover { | ||
transform: translate(-50%, -50%); | ||
} | ||
&:focus { | ||
box-shadow: none; | ||
outline: none; | ||
} | ||
&:active { | ||
background: $brand-01; | ||
transform: translate(-50%, -50%); | ||
} | ||
} | ||
|
||
.bx--slider__range-label { | ||
@include font-size('14'); | ||
color: $text-02; | ||
|
||
&:last-of-type { | ||
margin-right: 1rem; | ||
} | ||
} | ||
|
||
.bx--slider__track { | ||
position: absolute; | ||
width: 100%; | ||
height: rem(4px); | ||
background: $ui-05; | ||
cursor: pointer; | ||
transform: translate(0%, -50%); | ||
} | ||
|
||
.bx--slider__filled-track { | ||
position: absolute; | ||
width: 100%; | ||
height: rem(4px); | ||
background: $brand-01; | ||
transform-origin: left; | ||
pointer-events: none; | ||
transform: translate(0%, -50%); | ||
} | ||
|
||
.bx--slider__thumb { | ||
position: absolute; | ||
height: rem(24px); | ||
width: rem(24px); | ||
background: $brand-01; | ||
border-radius: 50%; | ||
top: 0; | ||
transform: translate(-50%, -50%); | ||
transition: transform 100ms $bx--standard-easing, background 100ms $bx--standard-easing; | ||
cursor: pointer; | ||
outline: none; | ||
|
||
&:hover { | ||
transform: translate(-50%, -50%) scale(1.05); | ||
} | ||
&:focus { | ||
@include focus-outline('blurred'); | ||
} | ||
&:active { | ||
background: darken($brand-01, 5%); | ||
transform: translate(-50%, -50%) scale(1.25); | ||
} | ||
} | ||
|
||
.bx--slider__input { | ||
display: none; | ||
} | ||
|
||
.bx-slider-text-input { | ||
max-width: rem(32px); | ||
min-width: 0; | ||
height: 2rem; | ||
padding: 0; | ||
text-align: center; | ||
font-weight: 700; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<div class="bx--form-item"> | ||
<label for="slider" class="bx--label">Slider Label</label> | ||
<div class="bx--slider-test"> | ||
<div class="bx--slider-container"> | ||
<span class="bx--slider__range-label">0</span> | ||
<div class="bx--slider" data-slider data-slider-input-box="#slider-input-box"> | ||
<div class="bx--slider__track"></div> | ||
<div class="bx--slider__filled-track"></div> | ||
<div class="bx--slider__thumb" tabindex="0"></div> | ||
<input id="slider" class="bx--slider__input" type="range" step="1" min="0" max="100" value="50"> | ||
</div> | ||
<span class="bx--slider__range-label">100</span> | ||
<input id="slider-input-box" type="text" class="bx--text-input bx-slider-text-input" placeholder="0"> | ||
</div> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
import mixin from '../../globals/js/misc/mixin'; | ||
import createComponent from '../../globals/js/mixins/create-component'; | ||
import initComponentBySearch from '../../globals/js/mixins/init-component-by-search'; | ||
import eventedState from '../../globals/js/mixins/evented-state'; | ||
import on from '../../globals/js/misc/on'; | ||
|
||
class Slider extends mixin(createComponent, initComponentBySearch, eventedState) { | ||
/** | ||
* Slider. | ||
* @extends CreateComponent | ||
* @extends InitComponentBySearch | ||
* @param {HTMLElement} element The element working as an slider. | ||
*/ | ||
constructor(element, options) { | ||
super(element, options); | ||
|
||
this.sliderActive = false; | ||
this.dragging = false; | ||
|
||
this.track = this.element.querySelector(this.options.selectorTrack); | ||
this.filledTrack = this.element.querySelector(this.options.selectorFilledTrack); | ||
this.thumb = this.element.querySelector(this.options.selectorThumb); | ||
this.input = this.element.querySelector(this.options.selectorInput); | ||
|
||
if (this.element.dataset.sliderInputBox) { | ||
this.boundInput = this.element.ownerDocument.querySelector(this.element.dataset.sliderInputBox); | ||
this._updateInput(); | ||
this.boundInput.addEventListener('change', (evt) => { this.setValue(evt.target.value); }); | ||
} | ||
|
||
this._updatePosition(); | ||
|
||
this.thumb.addEventListener('mousedown', () => { this.sliderActive = true; }); | ||
this.hDocumentMouseUp = on(this.element.ownerDocument, 'mouseup', () => { this.sliderActive = false; }); | ||
this.hDocumentMouseMove = on(this.element.ownerDocument, 'mousemove', (evt) => { | ||
const disabled = this.element.classList.contains('bx--slider--disabled'); | ||
if (this.sliderActive === true && !disabled) { | ||
this._updatePosition(evt); | ||
} | ||
}); | ||
this.thumb.addEventListener('keydown', (evt) => { | ||
const disabled = this.element.classList.contains('bx--slider--disabled'); | ||
if (!disabled) { | ||
this._updatePosition(evt); | ||
} | ||
}); | ||
this.track.addEventListener('click', (evt) => { | ||
const disabled = this.element.classList.contains('bx--slider--disabled'); | ||
if (!disabled) { | ||
this._updatePosition(evt); | ||
} | ||
}); | ||
} | ||
|
||
_changeState = (state, detail, callback) => { | ||
callback(); | ||
} | ||
|
||
|
||
_updatePosition(evt) { | ||
const { | ||
left, | ||
newValue, | ||
} = this._calcValue(evt); | ||
|
||
|
||
if (this.dragging) { | ||
return; | ||
} | ||
|
||
this.dragging = true; | ||
|
||
requestAnimationFrame(() => { | ||
this.dragging = false; | ||
this.thumb.style.left = `${left}%`; | ||
this.filledTrack.style.transform = `translate(0%, -50%) scaleX(${left / 100})`; | ||
this.input.value = newValue; | ||
this._updateInput(); | ||
this.changeState('slider-value-change', { value: newValue }); | ||
}); | ||
} | ||
|
||
_calcValue(evt) { | ||
const { | ||
value, | ||
min, | ||
max, | ||
step, | ||
} = this.getInputProps(); | ||
|
||
const range = max - min; | ||
const valuePercentage = (((value - min) / range) * 100); | ||
|
||
let left; | ||
let newValue; | ||
left = valuePercentage; | ||
newValue = value; | ||
|
||
if (evt) { | ||
const { type } = evt; | ||
|
||
if (type === 'keydown') { | ||
const direction = { | ||
40: -1, // decreasing | ||
37: -1, // decreasing | ||
38: 1, // increasing | ||
39: 1, // increasing | ||
}[evt.which]; | ||
|
||
if (direction !== undefined) { | ||
const multiplier = evt.shiftKey === true | ||
? (range / step) / this.options.stepMultiplier | ||
: 1; | ||
const stepMultiplied = step * multiplier; | ||
const stepSize = (stepMultiplied / range) * 100; | ||
left = valuePercentage + (stepSize * direction); | ||
newValue = Number(value) + (stepMultiplied * direction); | ||
} | ||
} | ||
if (type === 'mousemove' || type === 'click') { | ||
const track = this.track.getBoundingClientRect(); | ||
const unrounded = ((evt.clientX - track.left) / track.width); | ||
const rounded = Math.round(((range * unrounded) / step)) * step; | ||
left = (((rounded - min) / range) * 100); | ||
newValue = rounded; | ||
} | ||
} | ||
|
||
if (newValue <= Number(min)) { | ||
left = 0; | ||
newValue = min; | ||
} | ||
if (newValue >= Number(max)) { | ||
left = 100; | ||
newValue = max; | ||
} | ||
|
||
return { left, newValue }; | ||
} | ||
|
||
_updateInput() { | ||
if (this.boundInput) { | ||
this.boundInput.value = this.input.value; | ||
} | ||
} | ||
|
||
getInputProps() { | ||
const values = { | ||
value: this.input.value, | ||
min: this.input.min, | ||
max: this.input.max, | ||
step: this.input.step ? this.input.step : 1, | ||
}; | ||
return values; | ||
} | ||
|
||
setValue(value) { | ||
this.input.value = value; | ||
this._updatePosition(); | ||
} | ||
|
||
stepUp() { | ||
this.input.stepUp(); | ||
this._updatePosition(); | ||
} | ||
|
||
stepDown() { | ||
this.input.stepDown(); | ||
this._updatePosition(); | ||
} | ||
|
||
release() { | ||
if (this.hDocumentMouseUp) { | ||
this.hDocumentMouseUp = this.hDocumentMouseUp.release(); | ||
} | ||
if (this.hDocumentMouseMove) { | ||
this.hDocumentMouseMove = this.hDocumentMouseMove.release(); | ||
} | ||
super.release(); | ||
} | ||
|
||
/** | ||
* The map associating DOM element and Slider UI instance. | ||
* @type {WeakMap} | ||
*/ | ||
static components = new WeakMap(); | ||
|
||
/** | ||
* The component options. | ||
* If `options` is specified in the constructor, | ||
* properties in this object are overriden for the instance being created. | ||
* @property {string} selectorInit The CSS selector to find slider instances. | ||
*/ | ||
static options = { | ||
selectorInit: '[data-slider]', | ||
selectorTrack: '.bx--slider__track', | ||
selectorFilledTrack: '.bx--slider__filled-track', | ||
selectorThumb: '.bx--slider__thumb', | ||
selectorInput: '.bx--slider__input', | ||
eventBeforeSliderValueChange: 'slider-before-value-change', | ||
eventAfterSliderValueChange: 'slider-after-value-change', | ||
stepMultiplier: 4, | ||
} | ||
} | ||
|
||
export default Slider; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.