diff --git a/packages/frontend/component/src/ui/slider/index.css.ts b/packages/frontend/component/src/ui/slider/index.css.ts index 70b7528035..32c995b129 100644 --- a/packages/frontend/component/src/ui/slider/index.css.ts +++ b/packages/frontend/component/src/ui/slider/index.css.ts @@ -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', diff --git a/packages/frontend/component/src/ui/slider/slider.tsx b/packages/frontend/component/src/ui/slider/slider.tsx index cba5734ce5..4f53dcf9d0 100644 --- a/packages/frontend/component/src/ui/slider/slider.tsx +++ b/packages/frontend/component/src/ui/slider/slider.tsx @@ -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(null); return ( -
+
@@ -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%)'