From 56975dc9933c78eb595d48e1771cbcefee6c1177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=A2=E6=9E=9C=E6=B1=81?= Date: Mon, 6 Feb 2023 13:58:30 +0800 Subject: [PATCH] fix: rtl switch issue https://github.com/ant-design/ant-design/issues/40128 (#167) fix https://github.com/ant-design/ant-design/issues/40128 --- assets/index.less | 8 ++++++-- docs/demo/rtl.tsx | 42 ++++++++++++++++++++++++++++++++++++++++++ docs/example.md | 4 ++++ src/MotionThumb.tsx | 30 ++++++++++++++++++++++++++---- src/index.tsx | 1 + tests/index.test.tsx | 34 +++++++++++++++++++++++++++++++++- 6 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 docs/demo/rtl.tsx diff --git a/assets/index.less b/assets/index.less index 0f93795..801039c 100644 --- a/assets/index.less +++ b/assets/index.less @@ -79,8 +79,8 @@ .segmented-item-selected(); position: absolute; - top: 0; - left: 0; + // top: 0; + // left: 0; width: 0; height: 100%; padding: 4px 0; @@ -93,4 +93,8 @@ width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); will-change: transform, width; } + + &-rtl { + direction: rtl; + } } diff --git a/docs/demo/rtl.tsx b/docs/demo/rtl.tsx new file mode 100644 index 0000000..91c3761 --- /dev/null +++ b/docs/demo/rtl.tsx @@ -0,0 +1,42 @@ +import '../../assets/style.less'; +import React, { useState } from 'react'; +import Segmented from 'rc-segmented'; + +export default function App() { + const [direction, setDirection] = useState<'rtl' | 'ltr'>('rtl'); + return ( +
+ + +

+ console.log(value, typeof value)} + direction={direction} + /> +

+ ); +} diff --git a/docs/example.md b/docs/example.md index adcb7f2..222034d 100644 --- a/docs/example.md +++ b/docs/example.md @@ -28,3 +28,7 @@ nav: ## refs + +## rtl + + diff --git a/src/MotionThumb.tsx b/src/MotionThumb.tsx index 6853c50..384aeb8 100644 --- a/src/MotionThumb.tsx +++ b/src/MotionThumb.tsx @@ -1,12 +1,13 @@ -import * as React from 'react'; -import CSSMotion from 'rc-motion'; import classNames from 'classnames'; +import CSSMotion from 'rc-motion'; import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect'; import { composeRef } from 'rc-util/lib/ref'; +import * as React from 'react'; import type { SegmentedValue } from '.'; type ThumbReact = { left: number; + right: number; width: number; } | null; @@ -18,6 +19,7 @@ export interface MotionThumbInterface { motionName: string; onMotionStart: VoidFunction; onMotionEnd: VoidFunction; + direction?: 'ltr' | 'rtl'; } const calcThumbStyle = ( @@ -26,6 +28,10 @@ const calcThumbStyle = ( targetElement ? { left: targetElement.offsetLeft, + right: + (targetElement.parentElement!.clientWidth as number) - + targetElement.clientWidth - + targetElement.offsetLeft, width: targetElement.clientWidth, } : null; @@ -42,6 +48,7 @@ export default function MotionThumb(props: MotionThumbInterface) { motionName, onMotionStart, onMotionEnd, + direction, } = props; const thumbRef = React.useRef(null); @@ -81,6 +88,21 @@ export default function MotionThumb(props: MotionThumbInterface) { } }, [value]); + const thumbStart = React.useMemo( + () => + direction === 'rtl' + ? toPX(-(prevStyle?.right as number)) + : toPX(prevStyle?.left as number), + [direction, prevStyle], + ); + const thumbActive = React.useMemo( + () => + direction === 'rtl' + ? toPX(-(nextStyle?.right as number)) + : toPX(nextStyle?.left as number), + [direction, nextStyle], + ); + // =========================== Motion =========================== const onAppearStart = () => { return { @@ -118,9 +140,9 @@ export default function MotionThumb(props: MotionThumbInterface) { {({ className: motionClassName, style: motionStyle }, ref) => { const mergedStyle = { ...motionStyle, - '--thumb-start-left': toPX(prevStyle?.left), + '--thumb-start-left': thumbStart, '--thumb-start-width': toPX(prevStyle?.width), - '--thumb-active-left': toPX(nextStyle?.left), + '--thumb-active-left': thumbActive, '--thumb-active-width': toPX(nextStyle?.width), } as React.CSSProperties; diff --git a/src/index.tsx b/src/index.tsx index 117e43e..37933eb 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -184,6 +184,7 @@ const Segmented = React.forwardRef( value={rawValue} containerRef={containerRef} motionName={`${prefixCls}-${motionName}`} + direction={direction} getValueIndex={(val) => segmentedOptions.findIndex((n) => n.value === val) } diff --git a/tests/index.test.tsx b/tests/index.test.tsx index fc8a6eb..59fb1b1 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -27,7 +27,7 @@ describe('rc-segmented', () => { const styleText = container .querySelector('.rc-segmented-thumb') ?.getAttribute('data-test-style'); - const style = JSON.parse(styleText!) || {}; + const style = styleText ? JSON.parse(styleText!) : {}; expect(style).toMatchObject(matchStyle); } @@ -523,4 +523,36 @@ describe('rc-segmented', () => { expectMatchChecked(container, [true, false, false]); }); + + it('click can work as expected with rtl', () => { + const offsetParentSpy = jest + .spyOn(HTMLElement.prototype, 'offsetParent', 'get') + .mockImplementation(() => { + return container; + }); + const handleValueChange = jest.fn(); + const { container } = render( + handleValueChange(value)} + />, + ); + + fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[1]); + expectMatchChecked(container, [false, true, false]); + expect(handleValueChange).toBeCalledWith('Android'); + + // Motion to active + act(() => { + jest.runAllTimers(); + }); + + exceptThumbHaveStyle(container, { + '--thumb-active-left': '-22px', + '--thumb-active-width': '118px', + }); + + offsetParentSpy.mockRestore(); + }); });