fix(core): slider thumb offset (#8041)

fix AF-1307
This commit is contained in:
pengx17
2024-09-02 07:19:16 +00:00
parent 697f2c6520
commit 4caf32629a
2 changed files with 68 additions and 6 deletions

View File

@@ -1,5 +1,9 @@
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
import { createVar, style } from '@vanilla-extract/css';
export const thumbSize = createVar();
export const root = style({});
export const trackStyle = style({
width: '100%',
@@ -11,7 +15,8 @@ export const trackStyle = style({
cursor: 'pointer',
});
export const fakeTrackStyle = style({
width: '100%',
width: `calc(100% - ${thumbSize})`,
transform: `translateX(calc(${thumbSize} * 0.5))`,
height: '1px',
backgroundColor: cssVarV2('layer/insideBorder/border'),
position: 'relative',
@@ -29,8 +34,8 @@ export const filledTrackStyle = style({
});
export const thumbStyle = style({
width: '14px',
height: '14px',
width: thumbSize,
height: thumbSize,
backgroundColor: cssVarV2('icon/primary'),
borderRadius: '50%',
position: 'absolute',

View File

@@ -1,10 +1,13 @@
import * as Sliders from '@radix-ui/react-slider';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import { clamp } from 'lodash-es';
import { useRef } from 'react';
import * as styles from './index.css';
export interface SliderProps extends Sliders.SliderProps {
width?: number;
thumbSize?: number;
containerStyle?: React.CSSProperties;
rootStyle?: React.CSSProperties;
trackStyle?: React.CSSProperties;
@@ -14,6 +17,46 @@ export interface SliderProps extends Sliders.SliderProps {
nodes?: number[]; // The values where the nodes should be placed
}
// migrate https://github.com/radix-ui/primitives/blob/660060a765634e9cc7bf4513f41e8dabc9824d74/packages/react/slider/src/Slider.tsx#L708 to align step markers with thumbs
function calcStepMarkOffset(
index: number,
maxIndex: number,
thumbSize: number
) {
const percent = convertValueToPercentage(index, 0, maxIndex);
const thumbInBoundsOffset = getThumbInBoundsOffset(thumbSize, percent, 1);
return `calc(${percent}% + ${thumbInBoundsOffset}px)`;
function convertValueToPercentage(value: number, min: number, max: number) {
const maxSteps = max - min;
const percentPerStep = 100 / maxSteps;
const percentage = percentPerStep * (value - min);
return clamp(percentage, 0, 100);
}
function getThumbInBoundsOffset(
width: number,
left: number,
direction: number
) {
const halfWidth = width / 2;
const halfPercent = 50;
const offset = linearScale([0, halfPercent], [0, halfWidth]);
return (halfWidth - offset(left) * direction) * direction;
}
function linearScale(
input: readonly [number, number],
output: readonly [number, number]
) {
return (value: number) => {
if (input[0] === input[1] || output[0] === output[1]) return output[0];
const ratio = (output[1] - output[0]) / (input[1] - input[0]);
return output[0] + ratio * (value - input[0]);
};
}
}
export const Slider = ({
value,
min = 0,
@@ -27,18 +70,28 @@ export const Slider = ({
rangeStyle,
thumbStyle,
noteStyle,
thumbSize = 14,
...props
}: SliderProps) => {
const sliderRef = useRef<HTMLDivElement>(null);
return (
<div style={{ ...containerStyle, width: width ? `${width}px` : undefined }}>
<div
style={{
...containerStyle,
...assignInlineVars({
[styles.thumbSize]: thumbSize ? `${thumbSize}px` : undefined,
}),
width: width ? `${width}px` : undefined,
}}
>
<Sliders.Root
value={value}
min={min}
max={max}
step={step}
style={rootStyle}
className={styles.root}
{...props}
>
<Sliders.Track className={styles.trackStyle} ref={sliderRef}>
@@ -56,7 +109,11 @@ export const Slider = ({
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}%`,
left: calcStepMarkOffset(
index,
nodes.length - 1,
thumbSize / 2
),
transform:
index === 0
? 'translateY(-50%)'