diff --git a/src/components/base/StepperNumber/index.js b/src/components/base/StepperNumber/index.js new file mode 100644 index 00000000..2727445c --- /dev/null +++ b/src/components/base/StepperNumber/index.js @@ -0,0 +1,150 @@ +// @flow + +import React, { PureComponent } from 'react' +import styled from 'styled-components' + +import debounce from 'lodash/debounce' +import noop from 'lodash/noop' + +import Box from 'components/base/Box' + +const Container = styled(Box).attrs({ + horizontal: true, + flow: 1, + alignItems: 'center', + justifyContent: 'center', + fontSize: 4, + ff: 'Rubik', + color: 'smoke', +})` + background-color: rgba(100, 144, 241, 0.1); + border-radius: 12px; + display: inline-flex; + height: 24px; + padding: 0 3px; +` + +const Btn = styled(Box).attrs({ + bg: p => (p.disabled ? 'rgba(100, 144, 241, 0.5)' : 'wallet'), + color: 'white', + alignItems: 'center', + justifyContent: 'center', +})` + border-radius: 50%; + cursor: ${p => (p.disabled ? 'default' : 'pointer')}; + height: 18px; + width: 18px; +` + +const Num = styled(Box).attrs({ + alignItems: 'center', + justifyContent: 'center', +})` + min-width: 20px; +` + +const DELAY_CLICK = 150 +const DEBOUNCE_ON_CHANGE = 250 + +type Props = { + max: number, + min: number, + onChange: Function, + step: number, + value: number, +} + +type State = { + value: number, +} + +class StepperNumber extends PureComponent { + static defaultProps = { + max: 10, + min: 0, + onChange: noop, + step: 1, + value: 0, + } + + state = { + value: this.props.value, + } + + _timeout = undefined + + isMax = (v: number) => v >= this.props.max + isMin = (v: number) => v <= this.props.min + + emitChange = (v: number) => { + this.setState({ + value: v, + }) + this.debounceOnChange(v) + } + + debounceOnChange = debounce(v => this.props.onChange(v), DEBOUNCE_ON_CHANGE) + + decrement = () => { + const { step, min } = this.props + const { value } = this.state + + const newValue = value - step + + if (newValue !== value) { + const isMin = this.isMin(newValue) + const v = isMin ? min : newValue + this.emitChange(v) + if (!isMin) { + this._timeout = setTimeout(this.decrement, DELAY_CLICK) + } + } + } + + increment = () => { + const { step, max } = this.props + const { value } = this.state + + const newValue = value + step + + if (newValue !== value) { + const isMax = this.isMax(newValue) + const v = isMax ? max : newValue + this.emitChange(v) + if (!isMax) { + this._timeout = setTimeout(this.increment, DELAY_CLICK) + } + } + } + + handleMouseUp = () => clearTimeout(this._timeout) + + render() { + const { value } = this.state + + const isMin = this.isMin(value) + const isMax = this.isMax(value) + + return ( + + + - + + {value} + + + + + + ) + } +} + +export default StepperNumber diff --git a/src/components/base/StepperNumber/stories.js b/src/components/base/StepperNumber/stories.js new file mode 100644 index 00000000..8d8b4746 --- /dev/null +++ b/src/components/base/StepperNumber/stories.js @@ -0,0 +1,45 @@ +// @flow + +import React, { Component } from 'react' +import { storiesOf } from '@storybook/react' +import { action } from '@storybook/addon-actions' +import { number } from '@storybook/addon-knobs' + +import StepperNumber from 'components/base/StepperNumber' + +const stories = storiesOf('Components/base', module) + +class Wrapper extends Component { + state = { + value: 0, + } + + handleChange = value => { + action('onChange')(value) + this.setState({ value }) + } + + render() { + const { render } = this.props + const { value } = this.state + + return render({ + onChange: this.handleChange, + value, + }) + } +} + +stories.add('StepperNumber', () => ( + ( + + )} + /> +))