Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aberba/6a7b513e670ae6ca5bb14e775d1a12fa to your computer and use it in GitHub Desktop.
Save aberba/6a7b513e670ae6ca5bb14e775d1a12fa to your computer and use it in GitHub Desktop.
import { PRIMARY, PRIMARY_LIGHT, IRON_GREY } from '@styles/variables/colors'
import React, { useEffect, useRef, useState } from 'react'
import styled from 'styled-components'
const Slider = styled.div`
position: relative;
margin: 16px auto;
width: calc(100% - 12px);
height: 8px;
background: ${PRIMARY_LIGHT};
border-radius: 5px;
cursor: pointer;
> div {
position: absolute;
top: 0;
width: 0;
height: 8px;
border-radius: 5px;
&:first-child {
z-index: 2;
background: ${PRIMARY_LIGHT};
}
&:last-child {
z-index: 1;
background: ${PRIMARY};
}
> div {
position: absolute;
right: -12px;
top: -8px;
width: 24px;
height: 24px;
border-radius: 50%;
background: ${PRIMARY};
transition: all 0.3s;
&:hover {
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.05);
}
&:active {
transform: scale(1.15, 1.15);
}
}
}
`
const SliderValues = styled.div`
display: flex;
justify-content: space-between;
width: 100%;
> * {
color: ${IRON_GREY};
font-size: 14px;
}
`
const SliderWrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
padding: 16px 0;
box-sizing: border-box;
`
const roundToNearestHundredThousand = (number) => {
return Math.round(number / 100_000) * 100_000
}
const DEFAULT_FORMAT_LABELS = (value) => `${value}`
const DoubleRangeSlider = ({
min = 0,
max = 30_000_000,
onChange,
initialMinValue = 0,
initialMaxValue = 30_000_000,
formatLabels = DEFAULT_FORMAT_LABELS,
}) => {
const [initialize, setInitialize] = useState(false)
const [currentMin, setCurrentMin] = useState(initialMinValue)
const [currentMax, setCurrentMax] = useState(initialMaxValue)
const [sliderWidth, setSliderWidth] = useState(0)
const [offsetSliderWidth, setOffsetSliderWidth] = useState(0)
const [dragEnded, setDragEnded] = useState(false)
const minValueBetween = 500_000
const minValue = useRef(null)
const maxValue = useRef(null)
const slider = useRef(null)
const minValueDrag = useRef(null)
const maxValueDrag = useRef(null)
useEffect(() => {
minValue.current.style.width = (currentMin * 100) / max + '%'
maxValue.current.style.width = (currentMax * 100) / max + '%'
const [{ left }] = slider.current.getClientRects()
setSliderWidth(slider.current.offsetWidth)
setOffsetSliderWidth(left)
if (!initialize) setTimeout(() => setInitialize(true), 1000)
}, [initialize])
useEffect(() => {
if (dragEnded) onChange([currentMin, currentMax])
}, [dragEnded])
const onMouseMoveMin = (e) => {
const draggedWidth = (e.clientX || e.touches[0]?.pageX) - offsetSliderWidth
const draggedWidthInPercent = Math.max((draggedWidth * 100) / sliderWidth, 0)
const activeMin = Math.max(roundToNearestHundredThousand((max * draggedWidthInPercent) / 100), min)
if (activeMin <= currentMax - minValueBetween && minValue.current) {
minValue.current.style.width = draggedWidthInPercent + '%'
minValue.current.dataset.content = activeMin
setCurrentMin(activeMin)
}
}
const onMouseUpMin = () => {
document.removeEventListener('mouseup', onMouseUpMin)
document.removeEventListener('mousemove', onMouseMoveMin)
document.removeEventListener('touchend', onMouseUpMin)
document.removeEventListener('touchmove', onMouseMoveMin)
setDragEnded(true)
}
const changeMinValue = (e) => {
setDragEnded(false)
if (e.type !== 'touchstart') e.preventDefault()
document.addEventListener('mousemove', onMouseMoveMin)
document.addEventListener('mouseup', onMouseUpMin)
document.addEventListener('touchmove', onMouseMoveMin)
document.addEventListener('touchend', onMouseUpMin)
}
const changeMaxValue = (e) => {
setDragEnded(false)
if (e.type !== 'touchstart') e.preventDefault()
document.addEventListener('mousemove', onMouseMoveMax)
document.addEventListener('mouseup', onMouseUpMax)
document.addEventListener('touchmove', onMouseMoveMax)
document.addEventListener('touchend', onMouseUpMax)
}
const onMouseMoveMax = (e) => {
const draggedWidth = (e.clientX || e.touches[0]?.pageX) - offsetSliderWidth
const draggedWidthInPercent = Math.min((draggedWidth * 100) / sliderWidth, 100)
const activeMax = Math.min(roundToNearestHundredThousand((max * draggedWidthInPercent) / 100), max)
if (activeMax >= currentMin + minValueBetween && maxValue.current) {
maxValue.current.style.width = draggedWidthInPercent + '%'
maxValue.current.dataset.content = activeMax
setCurrentMax(activeMax)
}
}
const onMouseUpMax = () => {
document.removeEventListener('mouseup', onMouseUpMax)
document.removeEventListener('mousemove', onMouseMoveMax)
document.removeEventListener('touchend', onMouseUpMax)
document.removeEventListener('touchmove', onMouseMoveMax)
setDragEnded(true)
}
return (
<SliderWrapper>
<SliderValues>
<span>{formatLabels(currentMin)}</span>
<span>{formatLabels(currentMax)}</span>
</SliderValues>
<Slider ref={slider}>
<div ref={minValue} data-content={currentMin}>
<div ref={minValueDrag} onMouseDown={changeMinValue} onTouchStart={changeMinValue}></div>
</div>
<div ref={maxValue} data-content={currentMax}>
<div ref={maxValueDrag} onMouseDown={changeMaxValue} onTouchStart={changeMaxValue}></div>
</div>
</Slider>
</SliderWrapper>
)
}
export default DoubleRangeSlider
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment