Skip to content

feat(components/slider): 新增 keepRangeSorted 维持 range 排序状态 #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/components/src/slider/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const _Slider: React.FC<SliderStaticProps> = React.forwardRef<
formatter = (v) => v,
value: outerValue,
tooltipProps = {},
keepRangeSorted,
attrs = {},
disabled,
vertical,
Expand Down Expand Up @@ -117,6 +118,7 @@ const _Slider: React.FC<SliderStaticProps> = React.forwardRef<
/>
<Handlers
getValueFromMousePos={getValueFromMousePos}
keepRangeSorted={keepRangeSorted}
tooltipProps={tooltipProps}
valuesRef={valuesRef}
formatter={formatter}
Expand Down
55 changes: 43 additions & 12 deletions packages/components/src/slider/components/Handlers.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { MutableRefObject, useEffect, useMemo, useRef } from 'react';
import type { ConvertOptional, Point } from '@tool-pack/types';
import type { SliderStaticProps } from '../slider.types';
import { forEachRight, forEach } from '@tool-pack/basic';
import { getClasses, Placement } from '@pkg/shared';
import { onDragEvent } from '@tool-pack/dom';
import { Tooltip } from '~/tooltip';
Expand All @@ -9,6 +10,7 @@ interface Props
extends ConvertOptional<
Pick<
SliderStaticProps,
| 'keepRangeSorted'
| 'tooltipProps'
| 'formatter'
| 'disabled'
Expand All @@ -33,6 +35,7 @@ export const Handlers: React.FC<Props> = (props) => {
getValueFromMousePos,
formatter = (v) => v,
tooltipProps = {},
keepRangeSorted,
setValues,
valuesRef,
vertical,
Expand Down Expand Up @@ -64,11 +67,28 @@ export const Handlers: React.FC<Props> = (props) => {
const values = valuesRef.current;
const prev = values.slice(0, index);
const next = values.slice(index + 1);
setValues([
...prev,
getValueFromMousePos([currentXY.x, currentXY.y]),
...next,
]);
const curr = getValueFromMousePos([currentXY.x, currentXY.y]);

if (keepRangeSorted && values.length > 1) {
if (prev.length > 0) keepPrevSorted();
if (next.length > 0) keepNextSorted();
}
setValues([...prev, curr, ...next]);

function keepPrevSorted() {
forEachRight(prev, (v, i): false | void => {
if (v > curr) {
prev[i] = curr;
} else return false;
});
}
function keepNextSorted() {
forEach(next, (v, i): false | void => {
if (v < curr) {
next[i] = curr;
} else return false;
});
}
});
},
{ el: child },
Expand All @@ -77,7 +97,15 @@ export const Handlers: React.FC<Props> = (props) => {
) as Array<() => void>;

return () => cancels.forEach((cancel) => cancel());
}, [valuesRef.current.length, max, min, disabled, step, vertical]);
}, [
valuesRef.current.length,
keepRangeSorted,
vertical,
disabled,
step,
max,
min,
]);

type Styles = React.CSSProperties[];
const styles: Styles = useMemo(() => {
Expand All @@ -104,22 +132,25 @@ export const Handlers: React.FC<Props> = (props) => {
return styles;
}, [valuesRef.current, total, vertical, reverse]);

const _placement =
vertical && !tooltipProps.placement ? 'right' : tooltipProps.placement;

return (
<div className={cls.root} ref={handlersRef}>
{valuesRef.current.map((item, index) => (
<Tooltip
{...tooltipProps}
placement={
vertical && !tooltipProps.placement
? 'right'
: tooltipProps.placement
}
disabled={tooltipDisabled}
visible={tooltipVisible}
title={formatter(item)}
placement={_placement}
key={index}
>
<div className={cls.__.handler} style={styles[index]}></div>
<div
className={cls.__.handler}
style={styles[index]}
draggable={false}
></div>
</Tooltip>
))}
</div>
Expand Down
20 changes: 20 additions & 0 deletions packages/components/src/slider/demo/keep-range-sorted.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* title: 范围维持排序状态
* description: 当 value 为数组并设置 keepRangeSorted 为 true 时,当拖动的值小于前面的值会推动前面的把手,
* 当拖动的值大于后面的值会推动后面的把手,而不是穿过去
*/

import { Slider } from '@tool-pack/react-ui';
import React, { useState } from 'react';

const App: React.FC = () => {
const [value, setValue] = useState<number[]>([0, 30, 50, 60, 100]);
return (
<>
<Slider onChange={(v) => setValue(v)} keepRangeSorted value={value} />
<br />[{value.toString()}]
</>
);
};

export default App;
32 changes: 17 additions & 15 deletions packages/components/src/slider/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,25 @@ Slider 滑块。
<code src="./demo/marks.tsx"></code>
<code src="./demo/step-mark.tsx"></code>
<code src="./demo/ranges.tsx"></code>
<code src="./demo/keep-range-sorted.tsx"></code>

## API

Slider 的属性说明如下:

| 属性 | 说明 | 类型 | 默认值 | 版本 |
| ------------ | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------ | ---- |
| value | 当前值,当值类型为数组时为范围选择 | number[] | 0 | -- |
| max | 最大值 | number | 100 | -- |
| min | 最小值 | number | 0 | -- |
| marks | 刻度标记,key 的类型必须为 number 且取值在闭区间 \[min, max\] 内,每个标签可以单独设置样式 | Record<number,{ label: React.ReactNode; reverse?: boolean }> | -- | -- |
| step | 步长。当 step 为 'mark' 并且 marks 不为空时,步长为 marks 的 key 值;当 step 为 number 时不能小于 0 | number \| 'mark' | 1 | -- |
| reverse | 反向 | boolean | -- | -- |
| disabled | 禁用 | boolean | -- | -- |
| vertical | 垂直排列 | boolean | -- | -- |
| tooltip | 设置 tooltip 的显隐 | boolean | true | -- |
| tooltipProps | tooltip 参数,见 [Tooltip](./tooltip#api) | -- | -- | -- |
| formatter | 格式化 tooltip 文案 | (value: number) => React.ReactNode | -- | -- |
| onChange | 当 value 变化时触发的回调 | (value: number[] \| number) => void; | -- | -- |
| attrs | html 标签属性 | Partial\<React.HTMLAttributes\<HTMLDivElement>> | -- | -- |
| 属性 | 说明 | 类型 | 默认值 | 版本 |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------ | ---- |
| value | 当前值,当值类型为数组时为范围选择 | number[] | 0 | -- |
| max | 最大值 | number | 100 | -- |
| min | 最小值 | number | 0 | -- |
| marks | 刻度标记,key 的类型必须为 number 且取值在闭区间 \[min, max\] 内,每个标签可以单独设置样式 | Record<number,{ label: React.ReactNode; reverse?: boolean }> | -- | -- |
| step | 步长。当 step 为 'mark' 并且 marks 不为空时,步长为 marks 的 key 值;当 step 为 number 时不能小于 0 | number \| 'mark' | 1 | -- |
| reverse | 反向 | boolean | -- | -- |
| disabled | 禁用 | boolean | -- | -- |
| vertical | 垂直排列 | boolean | -- | -- |
| tooltip | 设置 tooltip 的显隐 | boolean | true | -- |
| tooltipProps | tooltip 参数,见 [Tooltip](./tooltip#api) | -- | -- | -- |
| formatter | 格式化 tooltip 文案 | (value: number) => React.ReactNode | -- | -- |
| onChange | 当 value 变化时触发的回调 | (value: number[] \| number) => void; | -- | -- |
| keepRangeSorted | 当 value 为数组并设置 keepRangeSorted 为 true时,当拖动的值小于前面的值会推动前面的把手,当拖动的值大于后面的值会推动后面的把手 | boolean | -- | -- |
| attrs | html 标签属性 | Partial\<React.HTMLAttributes\<HTMLDivElement>> | -- | -- |
1 change: 1 addition & 0 deletions packages/components/src/slider/slider.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface SliderStaticProps
tooltipProps?: Partial<TooltipProps>;
onChange?: (value: any) => void;
tooltip?: 'always' | boolean;
keepRangeSorted?: boolean;
value?: number[] | number;
step?: number | 'mark';
marks?: SliderMarks;
Expand Down