refactor(component): impl with @radix-ui/react-slider

This commit is contained in:
Jimmfly
2024-08-15 14:18:36 +08:00
committed by EYHN
parent bc77d7a648
commit 87767df0fd
4 changed files with 52 additions and 90 deletions

View File

@@ -43,6 +43,7 @@
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-slider": "^1.2.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-toolbar": "^1.0.4",

View File

@@ -1,4 +1,5 @@
import type { Meta, StoryFn } from '@storybook/react';
import { useState } from 'react';
import type { SliderProps } from './index';
import { Slider } from './index';
@@ -8,7 +9,15 @@ export default {
component: Slider,
} satisfies Meta<typeof Slider>;
const Template: StoryFn<SliderProps> = args => <Slider {...args} />;
const Template: StoryFn<SliderProps> = args => {
const [value, setValue] = useState<number[]>([0]);
return <Slider value={value} onValueChange={setValue} {...args} />;
};
export const Default: StoryFn<SliderProps> = Template.bind(undefined);
Default.args = {};
Default.args = {
min: 0,
max: 100,
step: 1,
nodes: [0, 50, 100],
};

View File

@@ -1,101 +1,52 @@
import type { KeyboardEvent, MouseEvent } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import * as Sliders from '@radix-ui/react-slider';
import { useRef } from 'react';
import * as styles from './index.css';
export interface SliderProps {
value?: number;
onChange?: (value: number) => void;
min?: number;
max?: number;
step?: number;
export interface SliderProps extends Sliders.SliderProps {
nodes?: number[]; // The values where the nodes should be placed
}
export const Slider = ({
value: propValue = 50,
onChange,
min = 0,
max = 100,
step = 1,
nodes = [20, 40, 60, 80],
value,
min,
max,
step,
nodes,
...props
}: SliderProps) => {
const [value, setValue] = useState(propValue);
const sliderRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setValue(propValue);
}, [propValue]);
const handleThumbMove = useCallback(
(e: MouseEvent) => {
if (!sliderRef.current) return;
const sliderRect = sliderRef.current.getBoundingClientRect();
const newValue =
((e.clientX - sliderRect.left) / sliderRect.width) * (max - min) + min;
const clampedValue = Math.min(
max,
Math.max(min, Math.round(newValue / step) * step)
);
setValue(clampedValue);
if (onChange) {
onChange(clampedValue);
}
},
[max, min, onChange, step]
);
const handleThumbKeyDown = useCallback(
(e: KeyboardEvent) => {
let newValue = value;
if (e.key === 'ArrowLeft') {
newValue = Math.max(min, value - step);
} else if (e.key === 'ArrowRight') {
newValue = Math.min(max, value + step);
}
setValue(newValue);
if (onChange) {
onChange(newValue);
}
},
[max, min, onChange, step, value]
);
const thumbPosition = useMemo(
() => ((value - min) / (max - min)) * 100,
[max, min, value]
);
return (
<div className={styles.sliderContainerStyle}>
<div
className={styles.trackStyle}
ref={sliderRef}
onMouseDown={handleThumbMove}
>
<div
className={styles.filledTrackStyle}
style={{ width: `${thumbPosition}%` }}
/>
{nodes.map((nodeValue, index) => (
<div
key={index}
className={styles.nodeStyle}
data-active={value >= nodeValue}
style={{
left: `${((nodeValue - min) / (max - min)) * 100}%`,
}}
/>
))}
<div
className={styles.thumbStyle}
style={{ left: `${thumbPosition}%` }}
onMouseDown={e => e.stopPropagation()}
onMouseMove={handleThumbMove}
onKeyDown={handleThumbKeyDown}
tabIndex={0}
/>
</div>
</div>
<Sliders.Root
value={value}
min={min}
max={max}
step={step}
className={styles.sliderContainerStyle}
{...props}
>
<Sliders.Track className={styles.trackStyle} ref={sliderRef}>
<Sliders.Range className={styles.filledTrackStyle} />
{!!nodes &&
nodes.map((nodeValue, index) => (
<div
key={index}
className={styles.nodeStyle}
data-active={value && value[0] >= nodeValue}
style={{
left: `${((nodeValue - (min !== undefined ? min : 0)) / (max !== undefined ? max - (min !== undefined ? min : 0) : 1)) * 100}%`,
transform:
index === 0
? 'translateY(-50%)'
: index === nodes.length - 1
? 'translateY(-50%) translateX(-100%)'
: undefined,
}}
/>
))}
<Sliders.Thumb className={styles.thumbStyle} />
</Sliders.Track>
</Sliders.Root>
);
};