Skip to content

Commit 632e35d

Browse files
committed
2 parents e8cafe4 + 9871045 commit 632e35d

13 files changed

Lines changed: 685 additions & 37 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ The following APIs are shared by Slider and Range.
108108
| handle | (props) => React.ReactNode | | A handle generator which could be used to customized handle. |
109109
| included | boolean | `true` | If the value is `true`, it means a continuous value interval, otherwise, it is a independent value. |
110110
| reverse | boolean | `false` | If the value is `true`, it means the component is rendered reverse. |
111-
| disabled | boolean | `false` | If `true`, handles can't be moved. |
111+
| disabled | boolean \| boolean[] | `false` | If `true`, handles can't be moved. This prop can also be an array to disable specific handles in range mode, e.g. `[true, false, true]` disables first and third handles. When any rendered handle is disabled, `editable` mode will be disabled. |
112112
| keyboard | boolean | `true` | Support using keyboard to move handlers. |
113113
| dots | boolean | `false` | When the `step` value is greater than 1, you can set the `dots` to `true` if you want to render the slider with dots. |
114114
| onBeforeChange | Function | NOOP | `onBeforeChange` will be triggered when `ontouchstart` or `onmousedown` is triggered. |

assets/index.less

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@
105105
cursor: -webkit-grabbing;
106106
cursor: grabbing;
107107
}
108+
109+
&-disabled {
110+
background-color: #fff;
111+
border-color: @disabledColor;
112+
box-shadow: none;
113+
cursor: not-allowed;
114+
115+
&:hover,
116+
&:active {
117+
border-color: @disabledColor;
118+
box-shadow: none;
119+
cursor: not-allowed;
120+
}
121+
}
108122
}
109123

110124
&-mark {

docs/demo/disabled-handle.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: Disabled Handle
3+
title.zh-CN: 禁用特定滑块
4+
nav:
5+
title: Demo
6+
path: /demo
7+
---
8+
9+
<code src="../examples/disabled-handle.tsx"></code>

docs/examples/disabled-handle.tsx

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/* eslint react/no-multi-comp: 0, no-console: 0 */
2+
import Slider from '@rc-component/slider';
3+
import React, { useState } from 'react';
4+
import '../../assets/index.less';
5+
6+
const style: React.CSSProperties = {
7+
width: 400,
8+
margin: 50,
9+
};
10+
11+
const defaultValue = [0, 30, 60, 100];
12+
const BasicDisabledHandle = () => {
13+
const [disabled, setDisabled] = useState([true, false, false, false]);
14+
15+
return (
16+
<div>
17+
<Slider range={{ draggableTrack: true }} defaultValue={defaultValue} disabled={disabled} />
18+
Slider disabled {JSON.stringify(disabled)}
19+
<div style={{ marginTop: 16 }}>
20+
{defaultValue.map((_, index) => (
21+
<label key={index} style={{ marginRight: 16 }}>
22+
<input
23+
type="checkbox"
24+
checked={!!disabled[index]}
25+
onChange={() => {
26+
const newDisabled = [...disabled];
27+
newDisabled[index] = !newDisabled[index];
28+
setDisabled(newDisabled);
29+
}}
30+
/>
31+
Handle {index + 1} {disabled[index] ? 'Disabled' : 'Enabled'}
32+
</label>
33+
))}
34+
</div>
35+
</div>
36+
);
37+
};
38+
39+
const DisabledHandleAsBoundary = () => {
40+
const [value, setValue] = useState<number[]>([10, 50, 90]);
41+
42+
return (
43+
<div>
44+
<Slider
45+
range
46+
step={10}
47+
value={value}
48+
onChange={(v) => Array.isArray(v) && setValue(v)}
49+
disabled={[false, true, false]}
50+
/>
51+
<p style={{ marginTop: 8, color: '#999' }}>
52+
Middle handle (50) is disabled and acts as a boundary. First handle cannot go beyond 50,
53+
third handle cannot go below 50. Disabled handle has gray border and not-allowed cursor.
54+
</p>
55+
</div>
56+
);
57+
};
58+
59+
const PushableWithDisabledHandle = () => {
60+
const [value, setValue] = useState<number[]>([20, 40, 60, 80]);
61+
62+
return (
63+
<div>
64+
<Slider
65+
range
66+
value={value}
67+
onChange={(v) => setValue(v as number[])}
68+
disabled={[false, true, false, false]}
69+
pushable={10}
70+
/>
71+
<p style={{ marginTop: 8, color: '#999' }}>
72+
Second handle (40) is disabled. Drag the first handle toward it or push the last two handles
73+
together: enabled handles keep at least 10 apart without crossing the disabled handle.
74+
</p>
75+
</div>
76+
);
77+
};
78+
79+
const SingleSlider = () => {
80+
const [value1, setValue1] = useState(30);
81+
const [value2, setValue2] = useState(30);
82+
83+
return (
84+
<div>
85+
<Slider value={value1} onChange={(v) => setValue1(v as number)} disabled />
86+
<br />
87+
<Slider value={value2} onChange={(v) => setValue2(v as number)} disabled={false} />
88+
</div>
89+
);
90+
};
91+
92+
// Editable mode with disabled handles - editable is disabled when any handle is disabled
93+
const EditableWithDisabled = () => {
94+
const [value, setValue] = useState<number[]>([0, 30, 100]);
95+
const [disabled, setDisabled] = useState<boolean[]>([true, false, false]);
96+
97+
const hasDisabled = disabled.some((d) => d);
98+
99+
return (
100+
<div>
101+
<Slider
102+
range={{
103+
editable: true,
104+
minCount: 2,
105+
maxCount: 5,
106+
}}
107+
value={value}
108+
onChange={(v) => setValue(v as number[])}
109+
disabled={disabled}
110+
/>
111+
<p style={{ marginTop: 8, color: hasDisabled ? '#f5222d' : '#52c41a' }}>
112+
{hasDisabled
113+
? 'Editable mode is DISABLED because at least one handle is disabled. Clicking track will move nearest enabled handle.'
114+
: 'Editable mode is ENABLED. Click track to add handles, drag to edge to delete.'}
115+
</p>
116+
<div style={{ marginTop: 16 }}>
117+
{value.map((val, index) => (
118+
<label key={index} style={{ marginRight: 16 }}>
119+
<input
120+
type="checkbox"
121+
checked={!!disabled[index]}
122+
onChange={() => {
123+
const newDisabled = [...disabled];
124+
newDisabled[index] = !newDisabled[index];
125+
setDisabled(newDisabled);
126+
}}
127+
/>
128+
Handle {index + 1} ({val}) {disabled[index] ? 'Disabled' : 'Enabled'}
129+
</label>
130+
))}
131+
</div>
132+
<p style={{ marginTop: 8, color: '#999', fontSize: 12 }}>
133+
Try: Toggle checkboxes to enable/disable handles. When any handle is disabled, you cannot
134+
add or remove handles. When all handles are enabled, editable mode works normally.
135+
</p>
136+
</div>
137+
);
138+
};
139+
140+
export default () => (
141+
<div>
142+
<div>
143+
single handle disabled
144+
<SingleSlider />
145+
</div>
146+
<div style={style}>
147+
<h3>Disabled Handle</h3>
148+
<p>
149+
Toggle checkboxes to disable/enable specific handles. Track dragging is disabled when any
150+
handle is disabled.
151+
</p>
152+
<BasicDisabledHandle />
153+
</div>
154+
155+
<div style={style}>
156+
<h3>Disabled Handle as Boundary</h3>
157+
<DisabledHandleAsBoundary />
158+
</div>
159+
160+
<div style={style}>
161+
<h3>Disabled Handle + Pushable</h3>
162+
<PushableWithDisabledHandle />
163+
</div>
164+
165+
<div style={style}>
166+
<h3>Editable + Disabled (Editable Disabled When Any Handle Disabled)</h3>
167+
<EditableWithDisabled />
168+
</div>
169+
</div>
170+
);

src/Handles/Handle.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
5555
min,
5656
max,
5757
direction,
58-
disabled,
5958
keyboard,
6059
range,
6160
tabIndex,
@@ -65,15 +64,20 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
6564
ariaValueTextFormatterForHandle,
6665
styles,
6766
classNames,
67+
isHandleDisabled,
6868
} = React.useContext(SliderContext);
6969

70+
const mergedDisabled = isHandleDisabled(valueIndex);
71+
7072
const handlePrefixCls = `${prefixCls}-handle`;
7173

7274
// ============================ Events ============================
7375
const onInternalStartMove = (e: React.MouseEvent | React.TouchEvent) => {
74-
if (!disabled) {
75-
onStartMove(e, valueIndex);
76+
if (mergedDisabled) {
77+
e.stopPropagation();
78+
return;
7679
}
80+
onStartMove(e, valueIndex);
7781
};
7882

7983
const onInternalFocus = (e: React.FocusEvent<HTMLDivElement>) => {
@@ -86,7 +90,7 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
8690

8791
// =========================== Keyboard ===========================
8892
const onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (e) => {
89-
if (!disabled && keyboard) {
93+
if (!mergedDisabled && keyboard) {
9094
let offset: number | 'min' | 'max' | undefined;
9195

9296
// Change the value
@@ -161,12 +165,12 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
161165

162166
if (valueIndex !== null) {
163167
divProps = {
164-
tabIndex: disabled ? undefined : getIndex(tabIndex, valueIndex) ?? undefined,
168+
tabIndex: mergedDisabled ? undefined : getIndex(tabIndex, valueIndex) ?? undefined,
165169
role: 'slider',
166170
'aria-valuemin': min,
167171
'aria-valuemax': max,
168172
'aria-valuenow': value,
169-
'aria-disabled': disabled,
173+
'aria-disabled': mergedDisabled,
170174
'aria-label': getIndex(ariaLabelForHandle, valueIndex),
171175
'aria-labelledby': getIndex(ariaLabelledByForHandle, valueIndex),
172176
'aria-required': getIndex(ariaRequired, valueIndex),
@@ -190,6 +194,7 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
190194
[`${handlePrefixCls}-${valueIndex + 1}`]: valueIndex !== null && range,
191195
[`${handlePrefixCls}-dragging`]: dragging,
192196
[`${handlePrefixCls}-dragging-delete`]: draggingDelete,
197+
[`${handlePrefixCls}-disabled`]: mergedDisabled,
193198
},
194199
classNames.handle,
195200
)}

0 commit comments

Comments
 (0)