mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-30 05:34:21 +03:00
refactor(component): use react-transition-state to simplify exit animation (#5923)
## **User description**
use react-transition-state to simplify exit animation
___
## **Type**
enhancement
___
## **Description**
- Integrated `react-transition-state` for managing animations in `ResizePanel` and `CMDKModal` components, simplifying the code and improving maintainability.
- Introduced a shared animation timeout variable to standardize animation durations across components.
- Added `react-transition-state` to dependencies to enable the new animation handling.
___
## **Changes walkthrough**
<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
<td>
<details>
<summary><strong>resize-panel.tsx</strong><dd><code>Integrate `react-transition-state` for Resize Panel Animations</code></dd></summary>
<hr>
packages/frontend/component/src/components/resize-panel/resize-panel.tsx
<li>Introduced <code>useTransition</code> hook from <code>react-transition-state</code> for managing <br>animations.<br> <li> Added a constant for animation timeout and applied it to the <br>transition.<br> <li> Utilized the transition state to toggle the open state of the resize <br>panel.<br>
</details>
</td>
<td><a href="https:/toeverything/AFFiNE/pull/5923/files#diff-a4d6e633862f63f97c167ff41ba62aff8aebf3e3b2f6e7ce13d5a0e22e8ff287">+12/-0</a> </td>
</tr>
<tr>
<td>
<details>
<summary><strong>modal.css.ts</strong><dd><code>Use Variable for Animation Timeout in Modal CSS</code> </dd></summary>
<hr>
packages/frontend/core/src/components/pure/cmdk/modal.css.ts
<li>Created a variable for animation timeout.<br> <li> Updated animation durations to use the new timeout variable.<br>
</details>
</td>
<td><a href="https:/toeverything/AFFiNE/pull/5923/files#diff-ba8935611b9c1695153d92d08ecb0f7dac73a6197f54ccda5a6e791902cd651d">+6/-3</a> </td>
</tr>
<tr>
<td>
<details>
<summary><strong>modal.tsx</strong><dd><code>Simplify CMDK Modal Animation with `react-transition-state`</code></dd></summary>
<hr>
packages/frontend/core/src/components/pure/cmdk/modal.tsx
<li>Replaced custom animation state management with <code>useTransition</code> hook.<br> <li> Removed old animation state logic and simplified the component.<br> <li> Added animation timeout variable and applied it to the transition.<br>
</details>
</td>
<td><a href="https:/toeverything/AFFiNE/pull/5923/files#diff-ab3fe66c9b1d3a691fafd1aebc9988e840fedfcd09b5b89570838a5a2a9469c9">+13/-33</a> </td>
</tr>
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
<td>
<details>
<summary><strong>package.json</strong><dd><code>Add `react-transition-state` Dependency</code> </dd></summary>
<hr>
packages/frontend/core/package.json
- Added `react-transition-state` as a dependency.
</details>
</td>
<td><a href="https:/toeverything/AFFiNE/pull/5923/files#diff-23e0e5dc0ceb004a0a5d3d13e7d00545de7487535ca0e5eab4c5047f1e24eff0">+1/-0</a> </td>
</tr>
</table></td></tr></tr></tbody></table>
___
> ✨ **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools and their descriptions
This commit is contained in:
parent
c724b3fece
commit
148807879b
@ -65,6 +65,7 @@
|
|||||||
"react-is": "^18.2.0",
|
"react-is": "^18.2.0",
|
||||||
"react-paginate": "^8.2.0",
|
"react-paginate": "^8.2.0",
|
||||||
"react-router-dom": "^6.22.1",
|
"react-router-dom": "^6.22.1",
|
||||||
|
"react-transition-state": "^2.1.1",
|
||||||
"react-virtuoso": "~4.6.3",
|
"react-virtuoso": "~4.6.3",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
|
@ -5,6 +5,7 @@ export const resizeHandleOffsetVar = createVar('resize-handle-offset');
|
|||||||
export const resizeHandleVerticalPadding = createVar(
|
export const resizeHandleVerticalPadding = createVar(
|
||||||
'resize-handle-vertical-padding'
|
'resize-handle-vertical-padding'
|
||||||
);
|
);
|
||||||
|
export const animationTimeout = createVar();
|
||||||
export const root = style({
|
export const root = style({
|
||||||
vars: {
|
vars: {
|
||||||
[panelWidthVar]: '256px',
|
[panelWidthVar]: '256px',
|
||||||
@ -30,13 +31,14 @@ export const root = style({
|
|||||||
marginRight: `calc(${panelWidthVar} * -1)`,
|
marginRight: `calc(${panelWidthVar} * -1)`,
|
||||||
},
|
},
|
||||||
'&[data-enable-animation="true"]': {
|
'&[data-enable-animation="true"]': {
|
||||||
transition: 'margin-left .3s .05s, margin-right .3s .05s, width .3s .05s',
|
transition: `margin-left ${animationTimeout} .05s, margin-right ${animationTimeout} .05s, width ${animationTimeout} .05s`,
|
||||||
},
|
},
|
||||||
'&[data-is-floating="false"][data-transparent=true]': {
|
'&[data-is-floating="false"][data-transparent=true]': {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
},
|
},
|
||||||
'&[data-enable-animation="false"][data-open="false"]': {
|
'&[data-transition-state="exited"]': {
|
||||||
display: 'none',
|
// avoid focus on hidden panel
|
||||||
|
visibility: 'hidden',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,7 @@ import { assertExists } from '@blocksuite/global/utils';
|
|||||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTransition } from 'react-transition-state';
|
||||||
|
|
||||||
import * as styles from './resize-panel.css';
|
import * as styles from './resize-panel.css';
|
||||||
|
|
||||||
@ -116,6 +117,7 @@ const ResizeHandle = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// delay initial animation to avoid flickering
|
||||||
function useEnableAnimation() {
|
function useEnableAnimation() {
|
||||||
const [enable, setEnable] = useState(false);
|
const [enable, setEnable] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -126,6 +128,8 @@ function useEnableAnimation() {
|
|||||||
return enable;
|
return enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const animationTimeout = 300;
|
||||||
|
|
||||||
export const ResizePanel = forwardRef<HTMLDivElement, ResizePanelProps>(
|
export const ResizePanel = forwardRef<HTMLDivElement, ResizePanelProps>(
|
||||||
function ResizePanel(
|
function ResizePanel(
|
||||||
{
|
{
|
||||||
@ -150,15 +154,23 @@ export const ResizePanel = forwardRef<HTMLDivElement, ResizePanelProps>(
|
|||||||
) {
|
) {
|
||||||
const enableAnimation = useEnableAnimation() && _enableAnimation;
|
const enableAnimation = useEnableAnimation() && _enableAnimation;
|
||||||
const safeWidth = Math.min(maxWidth, Math.max(minWidth, width));
|
const safeWidth = Math.min(maxWidth, Math.max(minWidth, width));
|
||||||
|
const [{ status }, toggle] = useTransition({
|
||||||
|
timeout: animationTimeout,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
toggle(open);
|
||||||
|
}, [open]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...rest}
|
{...rest}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={assignInlineVars({
|
style={assignInlineVars({
|
||||||
[styles.panelWidthVar]: `${safeWidth}px`,
|
[styles.panelWidthVar]: `${safeWidth}px`,
|
||||||
|
[styles.animationTimeout]: `${animationTimeout}ms`,
|
||||||
})}
|
})}
|
||||||
className={clsx(className, styles.root)}
|
className={clsx(className, styles.root)}
|
||||||
data-open={open}
|
data-open={open}
|
||||||
|
data-transition-state={status}
|
||||||
data-is-floating={floating}
|
data-is-floating={floating}
|
||||||
data-handle-position={resizeHandlePos}
|
data-handle-position={resizeHandlePos}
|
||||||
data-enable-animation={enableAnimation && !resizing}
|
data-enable-animation={enableAnimation && !resizing}
|
||||||
|
@ -85,6 +85,7 @@
|
|||||||
"react-error-boundary": "^4.0.12",
|
"react-error-boundary": "^4.0.12",
|
||||||
"react-is": "18.2.0",
|
"react-is": "18.2.0",
|
||||||
"react-router-dom": "^6.22.1",
|
"react-router-dom": "^6.22.1",
|
||||||
|
"react-transition-state": "^2.1.1",
|
||||||
"react-virtuoso": "~4.6.3",
|
"react-virtuoso": "~4.6.3",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"ses": "^1.3.0",
|
"ses": "^1.3.0",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { keyframes, style } from '@vanilla-extract/css';
|
import { createVar, keyframes, style } from '@vanilla-extract/css';
|
||||||
const contentShow = keyframes({
|
const contentShow = keyframes({
|
||||||
from: {
|
from: {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
@ -35,6 +35,9 @@ export const modalContentWrapper = style({
|
|||||||
zIndex: cssVar('zIndexModal'),
|
zIndex: cssVar('zIndexModal'),
|
||||||
padding: '13vh 16px 16px',
|
padding: '13vh 16px 16px',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const animationTimeout = createVar();
|
||||||
|
|
||||||
export const modalContent = style({
|
export const modalContent = style({
|
||||||
width: 640,
|
width: 640,
|
||||||
// height: 530,
|
// height: 530,
|
||||||
@ -51,11 +54,11 @@ export const modalContent = style({
|
|||||||
willChange: 'transform, opacity',
|
willChange: 'transform, opacity',
|
||||||
selectors: {
|
selectors: {
|
||||||
'&[data-state=entered], &[data-state=entering]': {
|
'&[data-state=entered], &[data-state=entering]': {
|
||||||
animation: `${contentShow} 120ms cubic-bezier(0.42, 0, 0.58, 1)`,
|
animation: `${contentShow} ${animationTimeout} cubic-bezier(0.42, 0, 0.58, 1)`,
|
||||||
animationFillMode: 'forwards',
|
animationFillMode: 'forwards',
|
||||||
},
|
},
|
||||||
'&[data-state=exited], &[data-state=exiting]': {
|
'&[data-state=exited], &[data-state=exiting]': {
|
||||||
animation: `${contentHide} 120ms cubic-bezier(0.42, 0, 0.58, 1)`,
|
animation: `${contentHide} ${animationTimeout} cubic-bezier(0.42, 0, 0.58, 1)`,
|
||||||
animationFillMode: 'forwards',
|
animationFillMode: 'forwards',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import * as Dialog from '@radix-ui/react-dialog';
|
import * as Dialog from '@radix-ui/react-dialog';
|
||||||
import { useEffect, useReducer } from 'react';
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useTransition } from 'react-transition-state';
|
||||||
|
|
||||||
import * as styles from './modal.css';
|
import * as styles from './modal.css';
|
||||||
|
|
||||||
@ -11,52 +13,30 @@ export interface CMDKModalProps {
|
|||||||
onOpenChange?: (open: boolean) => void;
|
onOpenChange?: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModalAnimationState = 'entering' | 'entered' | 'exiting' | 'exited';
|
const animationTimeout = 120;
|
||||||
|
|
||||||
function reduceAnimationState(
|
|
||||||
state: ModalAnimationState,
|
|
||||||
action: 'open' | 'close' | 'finish'
|
|
||||||
) {
|
|
||||||
switch (action) {
|
|
||||||
case 'open':
|
|
||||||
return state === 'entered' || state === 'entering' ? state : 'entering';
|
|
||||||
case 'close':
|
|
||||||
return state === 'exited' || state === 'exiting' ? state : 'exiting';
|
|
||||||
case 'finish':
|
|
||||||
return state === 'entering' ? 'entered' : 'exited';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CMDKModal = ({
|
export const CMDKModal = ({
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
open,
|
open,
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren<CMDKModalProps>) => {
|
}: React.PropsWithChildren<CMDKModalProps>) => {
|
||||||
const [animationState, dispatch] = useReducer(reduceAnimationState, 'exited');
|
const [{ status }, toggle] = useTransition({
|
||||||
|
timeout: animationTimeout,
|
||||||
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(open ? 'open' : 'close');
|
toggle(open);
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
dispatch('finish');
|
|
||||||
}, 120);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Root
|
<Dialog.Root modal open={status !== 'exited'} onOpenChange={onOpenChange}>
|
||||||
modal
|
|
||||||
open={animationState !== 'exited'}
|
|
||||||
onOpenChange={onOpenChange}
|
|
||||||
>
|
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Overlay className={styles.modalOverlay} />
|
<Dialog.Overlay className={styles.modalOverlay} />
|
||||||
<div className={styles.modalContentWrapper}>
|
<div className={styles.modalContentWrapper}>
|
||||||
<Dialog.Content
|
<Dialog.Content
|
||||||
|
style={assignInlineVars({
|
||||||
|
[styles.animationTimeout]: `${animationTimeout}ms`,
|
||||||
|
})}
|
||||||
className={styles.modalContent}
|
className={styles.modalContent}
|
||||||
data-state={animationState}
|
data-state={status}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
|
12
yarn.lock
12
yarn.lock
@ -269,6 +269,7 @@ __metadata:
|
|||||||
react-is: "npm:^18.2.0"
|
react-is: "npm:^18.2.0"
|
||||||
react-paginate: "npm:^8.2.0"
|
react-paginate: "npm:^8.2.0"
|
||||||
react-router-dom: "npm:^6.22.1"
|
react-router-dom: "npm:^6.22.1"
|
||||||
|
react-transition-state: "npm:^2.1.1"
|
||||||
react-virtuoso: "npm:~4.6.3"
|
react-virtuoso: "npm:~4.6.3"
|
||||||
rxjs: "npm:^7.8.1"
|
rxjs: "npm:^7.8.1"
|
||||||
storybook: "npm:^7.6.17"
|
storybook: "npm:^7.6.17"
|
||||||
@ -381,6 +382,7 @@ __metadata:
|
|||||||
react-error-boundary: "npm:^4.0.12"
|
react-error-boundary: "npm:^4.0.12"
|
||||||
react-is: "npm:18.2.0"
|
react-is: "npm:18.2.0"
|
||||||
react-router-dom: "npm:^6.22.1"
|
react-router-dom: "npm:^6.22.1"
|
||||||
|
react-transition-state: "npm:^2.1.1"
|
||||||
react-virtuoso: "npm:~4.6.3"
|
react-virtuoso: "npm:~4.6.3"
|
||||||
rxjs: "npm:^7.8.1"
|
rxjs: "npm:^7.8.1"
|
||||||
ses: "npm:^1.3.0"
|
ses: "npm:^1.3.0"
|
||||||
@ -30819,6 +30821,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-transition-state@npm:^2.1.1":
|
||||||
|
version: 2.1.1
|
||||||
|
resolution: "react-transition-state@npm:2.1.1"
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8.0"
|
||||||
|
react-dom: ">=16.8.0"
|
||||||
|
checksum: 10/992f6a44070c74bdcbeae4f6a2a5dd2429ab031e74a942822f9f2bd40b938e0efcdfbc0736f3f1a1003399191de9e5659397e99b4e901600cd03cc639579da54
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-virtuoso@npm:~4.6.3":
|
"react-virtuoso@npm:~4.6.3":
|
||||||
version: 4.6.3
|
version: 4.6.3
|
||||||
resolution: "react-virtuoso@npm:4.6.3"
|
resolution: "react-virtuoso@npm:4.6.3"
|
||||||
|
Loading…
Reference in New Issue
Block a user