Skip to content

Commit

Permalink
fix: transform logic (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
zombieJ committed Apr 28, 2022
1 parent 921d58f commit a1f5a8c
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 210 deletions.
1 change: 1 addition & 0 deletions assets/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
}

// transition effect when `enter-active`
&-thumb-motion-appear-active,
&-thumb-motion-enter-active {
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/controlled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default class Demo extends React.Component<

render() {
return (
<>
<React.StrictMode>
<Segmented
options={['iOS', 'Android', 'Web3']}
value={this.state.value}
Expand All @@ -33,7 +33,7 @@ export default class Demo extends React.Component<
})
}
/>
</>
</React.StrictMode>
);
}
}
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
"lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md",
"prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
"pretty-quick": "pretty-quick",
"test": "father test",
"coverage": "father test --coverage"
"test": "umi-test",
"coverage": "umi-test --coverage"
},
"dependencies": {
"@babel/runtime": "^7.11.1",
Expand All @@ -57,9 +57,11 @@
"@types/react": "^17.0.13",
"@types/react-dom": "^16.9.0",
"@umijs/fabric": "^2.0.8",
"@umijs/test": "^3.5.23",
"coveralls": "^3.0.6",
"cross-env": "^7.0.2",
"dumi": "^1.1.0",
"cssstyle": "^2.3.0",
"dumi": "^1.1.41-rc.0",
"eslint": "^7.0.0",
"father": "^2.13.4",
"father-build": "^1.18.6",
Expand Down
142 changes: 142 additions & 0 deletions src/MotionThumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import * as React from 'react';
import CSSMotion from 'rc-motion';
import classNames from 'classnames';
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
import { composeRef } from 'rc-util/lib/ref';
import type { SegmentedValue } from '.';

type ThumbReact = {
left: number;
width: number;
} | null;

export interface MotionThumbInterface {
containerRef: React.RefObject<HTMLDivElement>;
value: SegmentedValue;
getValueIndex: (value: SegmentedValue) => number;
prefixCls: string;
motionName: string;
onMotionStart: VoidFunction;
onMotionEnd: VoidFunction;
}

const calcThumbStyle = (
targetElement: HTMLElement | null | undefined,
): ThumbReact =>
targetElement
? {
left: targetElement.offsetLeft,
width: targetElement.clientWidth,
}
: null;

const toPX = (value: number) =>
value !== undefined ? `${value}px` : undefined;

export default function MotionThumb(props: MotionThumbInterface) {
const {
prefixCls,
containerRef,
value,
getValueIndex,
motionName,
onMotionStart,
onMotionEnd,
} = props;

const thumbRef = React.useRef<HTMLDivElement>(null);
const [prevValue, setPrevValue] = React.useState(value);

// =========================== Effect ===========================
const findValueElement = (val: SegmentedValue) => {
const index = getValueIndex(val);

const ele = containerRef.current?.querySelectorAll<HTMLDivElement>(
`.${prefixCls}-item`,
)[index];

return ele;
};

const [prevStyle, setPrevStyle] = React.useState<ThumbReact>(null);
const [nextStyle, setNextStyle] = React.useState<ThumbReact>(null);

useLayoutEffect(() => {
if (prevValue !== value) {
const prev = findValueElement(prevValue);
const next = findValueElement(value);

const calcPrevStyle = calcThumbStyle(prev);
const calcNextStyle = calcThumbStyle(next);

setPrevValue(value);
setPrevStyle(calcPrevStyle);
setNextStyle(calcNextStyle);

if (prev && next) {
onMotionStart();
} else {
onMotionEnd();
}
}
}, [value]);

// =========================== Motion ===========================
const onAppearStart = () => {
return {
transform: `translateX(var(--thumb-start-left))`,
width: `var(--thumb-start-width)`,
};
};
const onAppearActive = () => {
return {
transform: `translateX(var(--thumb-active-left))`,
width: `var(--thumb-active-width)`,
};
};
const onAppearEnd = () => {
setPrevStyle(null);
setNextStyle(null);
onMotionEnd();
};

// =========================== Render ===========================
// No need motion when nothing exist in queue
if (!prevStyle || !nextStyle) {
return null;
}

return (
<CSSMotion
visible
motionName={motionName}
motionAppear
onAppearStart={onAppearStart}
onAppearActive={onAppearActive}
onAppearEnd={onAppearEnd}
>
{({ className: motionClassName, style: motionStyle }, ref) => {
const mergedStyle = {
...motionStyle,
'--thumb-start-left': toPX(prevStyle?.left),
'--thumb-start-width': toPX(prevStyle?.width),
'--thumb-active-left': toPX(nextStyle?.left),
'--thumb-active-width': toPX(nextStyle?.width),
} as React.CSSProperties;

// It's little ugly which should be refactor when @umi/test update to latest jsdom
const motionProps = {
ref: composeRef(thumbRef, ref),
style: mergedStyle,
className: classNames(`${prefixCls}-thumb`, motionClassName),
};

if (process.env.NODE_ENV === 'test') {
(motionProps as any)['data-test-style'] = JSON.stringify(mergedStyle);
}

return <div {...motionProps} />;
}}
</CSSMotion>
);
}

0 comments on commit a1f5a8c

Please sign in to comment.