Files
aurora-admin/frameworks/Slider.jsx
T

146 lines
4.3 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useRef, useState, useEffect } from 'react';
import './Slider.css';
/**
* Aurora Admin Slider(React)
* 对齐 组件7.txt SliderInput 规范:单/双滑块、刻度、输入框联动
*
* props:
* - value / defaultValue : number | [lo, hi]
* - min / max / step
* - range : 双滑块
* - showTicks : 刻度
* - showInput : 数值输入框联动
* - disabled
* - onChange(value)
*/
export default function Slider({
value,
defaultValue = 0,
min = 0,
max = 100,
step = 1,
range = false,
showTicks = false,
showInput = false,
disabled = false,
onChange
}) {
const trackRef = useRef(null);
const [dragIndex, setDragIndex] = useState(-1);
const current = value !== undefined ? value : defaultValue;
const pct = (v) => ((v - min) / (max - min)) * 100;
const clamp = (v) => {
const snapped = Math.round((v - min) / step) * step + min;
return Math.max(min, Math.min(max, snapped));
};
const valueFromClientX = (x) => {
const rect = trackRef.current.getBoundingClientRect();
return clamp(min + (x - rect.left) / rect.width * (max - min));
};
const setVal = (next) => {
if (value === undefined) onChange && onChange(next);
else onChange && onChange(next);
};
const onMove = (e) => {
if (dragIndex < 0) return;
const nv = valueFromClientX(e.clientX);
if (range) {
const arr = [...current]; arr[dragIndex] = nv; arr.sort((a, b) => a - b);
setVal(arr);
} else {
setVal(nv);
}
};
const onUp = () => {
setDragIndex(-1);
window.removeEventListener('pointermove', onMove);
window.removeEventListener('pointerup', onUp);
};
const onHandleDown = (i, e) => {
if (disabled) return;
e.preventDefault();
setDragIndex(i);
window.addEventListener('pointermove', onMove);
window.addEventListener('pointerup', onUp);
};
const onTrackDown = (e) => {
if (disabled || e.target.classList.contains('aa-slider-handle')) return;
const nv = valueFromClientX(e.clientX);
if (range) {
const i = Math.abs(nv - current[0]) <= Math.abs(nv - current[1]) ? 0 : 1;
const arr = [...current]; arr[i] = nv; arr.sort((a, b) => a - b);
setVal(arr);
} else {
setVal(nv);
}
};
const onInput = (i, e) => {
const v = +e.target.value;
if (range) {
const arr = [...current]; arr[i] = v; arr.sort((a, b) => a - b);
setVal(arr);
} else {
setVal(v);
}
};
useEffect(() => () => onUp(), []); // eslint-disable-line
const handlePercents = range ? [pct(current[0]), pct(current[1])] : [pct(current)];
const fillStyle = range
? { left: pct(current[0]) + '%', width: (pct(current[1]) - pct(current[0])) + '%' }
: { left: '0%', width: pct(current) + '%' };
const tickPercents = Array.from(
{ length: Math.floor((max - min) / step) + 1 },
(_, i) => pct(min + i * step)
);
return (
<div className={`aa-slider${disabled ? ' is-disabled' : ''}`}>
<div className="aa-slider-track" ref={trackRef} onPointerDown={onTrackDown}>
<div className="aa-slider-rail"></div>
<div className="aa-slider-fill" style={fillStyle}></div>
{showTicks && (
<div className="aa-slider-ticks">
{tickPercents.map((t, i) => (
<div key={i} className="aa-slider-tick" style={{ left: t + '%' }}></div>
))}
</div>
)}
{handlePercents.map((h, i) => (
<div
key={i}
className="aa-slider-handle"
style={{ left: h + '%' }}
onPointerDown={(e) => onHandleDown(i, e)}
></div>
))}
</div>
{showInput && (
<div className="aa-slider-value">
{range ? (
<>
<input type="number" value={current[0]} onChange={(e) => onInput(0, e)} />
<span>—</span>
<input type="number" value={current[1]} onChange={(e) => onInput(1, e)} />
</>
) : (
<input type="number" value={current} onChange={(e) => onInput(null, e)} />
)}
</div>
)}
</div>
);
}
/*
用法示例:
import Slider from './Slider';
<Slider min={0} max={100} defaultValue={30} showInput onChange={setV} />
<Slider range min={0} max={100} defaultValue={[20, 60]} onChange={setRange} />
*/