RangeSlider
rangeslider-1779378412658.tsx
'use client'
import { useState, useRef, useCallback } from 'react'
const MIN = 0
const MAX = 10000
export default function RangeSlider() {
const [range, setRange] = useState([1000, 7500])
const [active, setActive] = useState<number | null>(null)
const trackRef = useRef<HTMLDivElement>(null)
const getPercent = (val: number) => ((val - MIN) / (MAX - MIN)) * 100
const handleMouseDown = useCallback((i: number) => (e: React.MouseEvent) => {
e.preventDefault()
setActive(i)
const move = (me: MouseEvent) => {
const track = trackRef.current!
const rect = track.getBoundingClientRect()
const percent = Math.max(0, Math.min(1, (me.clientX - rect.left) / rect.width))
const value = Math.round(percent * (MAX - MIN) + MIN)
setRange(r => {
const next = [...r]
next[i] = value
if (i === 0 && value > next[1] - 500) return r
if (i === 1 && value < next[0] + 500) return r
return next
})
}
const up = () => { setActive(null); document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up) }
document.addEventListener('mousemove', move)
document.addEventListener('mouseup', up)
}, [])
const fmt = (n: number) => `€${n.toLocaleString()}`
return (
<div style={{ padding: 48, background: '#0A0A0A', display: 'flex', flexDirection: 'column', gap: 32 }}>
<div>
<div style={{ color: '#F5F5F0', fontWeight: 700, fontSize: 16, marginBottom: 4 }}>Price range</div>
<div style={{ color: '#C9A84C', fontSize: 20, fontWeight: 800 }}>{fmt(range[0])} — {fmt(range[1])}</div>
</div>
<div style={{ position: 'relative', height: 40 }}>
{/* Track */}
<div ref={trackRef} style={{
position: 'absolute', top: '50%', left: 0, right: 0,
height: 4, background: 'rgba(255,255,255,0.1)', borderRadius: 4,
transform: 'translateY(-50%)',
}}>
{/* Fill */}
<div style={{
position: 'absolute', height: '100%', background: '#C9A84C', borderRadius: 4,
left: `${getPercent(range[0])}%`, right: `${100 - getPercent(range[1])}%`,
}} />
</div>
{/* Handles */}
{range.map((val, i) => (
<div key={i}
onMouseDown={handleMouseDown(i)}
style={{
position: 'absolute', top: '50%', transform: 'translate(-50%, -50%)',
left: `${getPercent(val)}%`,
width: 22, height: 22, borderRadius: '50%',
background: '#C9A84C', border: '3px solid #0A0A0A',
cursor: 'grab', boxShadow: active === i ? '0 0 0 4px rgba(201,168,76,0.3)' : 'none',
transition: 'box-shadow 0.1s', zIndex: active === i ? 2 : 1,
userSelect: 'none',
}}
/>
))}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', color: '#555', fontSize: 12 }}>
{[0, 2500, 5000, 7500, 10000].map(v => <span key={v}>{fmt(v)}</span>)}
</div>
<button style={{
background: '#C9A84C', color: '#0A0A0A', border: 'none',
padding: '12px 24px', borderRadius: 8, fontWeight: 700, cursor: 'pointer', fontSize: 14,
}}>Apply filter</button>
</div>
)
}Component info
CategoryInputs & Forms
Frameworkreact
TierFREE
Views0
Copies0
About
Dual-handle price range slider with live preview and formatted labels
More from Inputs & Forms
'use client';
import { useState, useEffect } from 'react';
interface Tag {
id: number;
name: string;
}
const TagInput = () => {
const [tags, setTags] = useState<Tag[]>([]);
const [inputValue, setInputValue] = useState('');
const [suggestTagInput
Inputs & Forms
'use client';
import React, { useState } from 'react';
interface File {
name: string;
size: number;
progress: number;
}
const FileUpload = () => {
const [dragOver, setDragOver] = useState(false);
const [files, setFiles] = useState<File[]FileUpload
Inputs & Forms
import React, { useState } from 'react';
const OTPInput = () => {
const [otp, setOtp] = useState(new Array(6).fill(''));
const [activeIndex, setActiveIndex] = useState(0);
const handleChange = (e, index) => {
const value = e.target.value;OTPInput
Inputs & Forms
'use client';
import React, { useState } from 'react';
interface RangeSliderProps {
min: number;
max: number;
defaultValue: [number, number];
}
const RangeSlider: React.FC<RangeSliderProps> = ({ min, max, defaultValue }) => {
const [minValRangeSlider
Inputs & Forms