Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit cb2be8c

Browse files
authored
fix: trigger thumb animation when selected changed (#31)
1 parent a9f646c commit cb2be8c

File tree

3 files changed

+109
-31
lines changed

3 files changed

+109
-31
lines changed

‎docs/examples/controlled.tsx‎

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,27 @@ export default class Demo extends React.Component<
1313

1414
render() {
1515
return (
16-
<Segmented
17-
options={['iOS', 'Android', 'Web3']}
18-
value={this.state.value}
19-
onChange={(e) =>
20-
this.setState({
21-
value: e.target.value,
22-
})
23-
}
24-
/>
16+
<>
17+
<Segmented
18+
options={['iOS', 'Android', 'Web3']}
19+
value={this.state.value}
20+
onChange={(e) =>
21+
this.setState({
22+
value: e.target.value,
23+
})
24+
}
25+
/>
26+
&nbsp;&nbsp;
27+
<Segmented
28+
options={['iOS', 'Android', 'Web3']}
29+
value={this.state.value}
30+
onChange={(e) =>
31+
this.setState({
32+
value: e.target.value,
33+
})
34+
}
35+
/>
36+
</>
2537
);
2638
}
2739
}

‎src/index.tsx‎

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const InternalSegmentedOption: React.FC<{
104104

105105
return (
106106
<label
107-
className={classNames(`${prefixCls}-item`,className, {
107+
className={classNames(className, {
108108
[`${prefixCls}-item-disabled`]: disabled,
109109
})}
110110
>
@@ -155,7 +155,7 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
155155
}, [options]);
156156

157157
const [selected, setSelected] = useMergedState(segmentedOptions[0]?.value, {
158-
value,
158+
value: props.value,
159159
defaultValue,
160160
});
161161

@@ -165,24 +165,60 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
165165

166166
const [thumbShow, setThumbShow] = React.useState(false);
167167

168-
const calcThumbMoveStatus = (
169-
event: React.ChangeEvent<HTMLInputElement>,
170-
) => {
171-
const toElement = event.target.closest(`.${prefixCls}-item`);
168+
const doThumbAnimation = React.useCallback(
169+
(selectedValue: SegmentedRawOption) => {
170+
const segmentedItemIndex = segmentedOptions.findIndex(
171+
(n) => n.value === selectedValue,
172+
);
172173

173-
constfromElement=containerRef.current?.querySelector(
174-
`.${prefixCls}-item-selected`,
175-
);
174+
if(segmentedItemIndex<0){
175+
return;
176+
}
176177

177-
if(fromElement&&toElement&&thumbMoveStatus.current){
178-
thumbMoveStatus.current.from=calcThumbStyle(
179-
fromElementasHTMLElement,
178+
// find target element
179+
consttoElement=containerRef.current?.querySelector(
180+
`.${prefixCls}-item:nth-child(${segmentedItemIndex+1})`,
180181
);
181-
thumbMoveStatus.current.to = calcThumbStyle(toElement as HTMLElement);
182182

183-
setThumbShow(true);
183+
if (toElement) {
184+
// find source element
185+
const fromElement = containerRef.current?.querySelector(
186+
`.${prefixCls}-item-selected`,
187+
);
188+
189+
if (fromElement && toElement && thumbMoveStatus.current) {
190+
// calculate for thumb moving animation
191+
thumbMoveStatus.current.from = calcThumbStyle(
192+
fromElement as HTMLElement,
193+
);
194+
thumbMoveStatus.current.to = calcThumbStyle(
195+
toElement as HTMLElement,
196+
);
197+
198+
// trigger css-motion starts
199+
setThumbShow(true);
200+
}
201+
}
202+
},
203+
[prefixCls, segmentedOptions],
204+
);
205+
206+
// get latest version of `visualSelected`
207+
const latestVisualSelected = React.useRef(visualSelected);
208+
React.useEffect(() => {
209+
latestVisualSelected.current = visualSelected;
210+
});
211+
212+
React.useEffect(() => {
213+
// Syncing `visualSelected` when `selected` changed
214+
// and do thumb animation
215+
if (
216+
(typeof selected === 'string' || typeof selected === 'number') &&
217+
selected !== latestVisualSelected.current
218+
) {
219+
doThumbAnimation(selected);
184220
}
185-
};
221+
},[selected]);
186222

187223
const handleChange = (
188224
event: React.ChangeEvent<HTMLInputElement>,
@@ -192,8 +228,6 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
192228
return;
193229
}
194230

195-
calcThumbMoveStatus(event);
196-
197231
setSelected(val);
198232

199233
if (onChange) {
@@ -275,10 +309,14 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
275309
<InternalSegmentedOption
276310
key={segmentedOption.value}
277311
prefixCls={prefixCls}
278-
className={classNames(segmentedOption.className, {
279-
[`${prefixCls}-item-selected`]:
280-
segmentedOption.value === visualSelected,
281-
})}
312+
className={classNames(
313+
segmentedOption.className,
314+
`${prefixCls}-item`,
315+
{
316+
[`${prefixCls}-item-selected`]:
317+
segmentedOption.value === visualSelected,
318+
},
319+
)}
282320
checked={segmentedOption.value === selected}
283321
onChange={handleChange}
284322
{...segmentedOption}

‎tests/index.spec.tsx‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ describe('rc-segmented', () => {
163163
expect(wrapper.render()).toMatchSnapshot();
164164
expect(
165165
wrapper
166-
.find('.rc-segmented-item')
166+
.find('label.rc-segmented-item')
167167
.at(1)
168168
.hasClass('rc-segmented-item-disabled'),
169169
).toBeTruthy();
@@ -285,6 +285,34 @@ describe('rc-segmented', () => {
285285
.at(1)
286286
.simulate('change');
287287
expect(wrapper.state().value).toBe('Android');
288+
289+
// change state directly
290+
wrapper.find(Demo).setState({ value: 'Web3' });
291+
292+
// Motion end
293+
wrapper.triggerMotionEvent();
294+
act(() => {
295+
jest.runAllTimers();
296+
wrapper.update();
297+
});
298+
299+
expect(
300+
wrapper.find('.rc-segmented-item-selected').contains('Web3'),
301+
).toBeTruthy();
302+
303+
// Motion end
304+
wrapper.triggerMotionEvent();
305+
act(() => {
306+
jest.runAllTimers();
307+
wrapper.update();
308+
});
309+
310+
// change it strangely
311+
wrapper.find(Demo).setState({ value: 'Web4' });
312+
// invalid changes
313+
expect(
314+
wrapper.find('.rc-segmented-item-selected').contains('Web3'),
315+
).toBeTruthy();
288316
});
289317

290318
it('render segmented with CSSMotion', () => {

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /