fix: optimize history animation (#5973)

- adjust timing
- make sure card do not show the bottom border on transitioning

<div class='graphite__hidden'>
          <div>🎥 Video uploaded on Graphite:</div>
            <a href="https://app.graphite.dev/media/video/T2klNLEk0wxLh4NRDzhk/7a225ec1-eb33-45ab-bf27-646bb2519a83.mp4">
              <img src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/T2klNLEk0wxLh4NRDzhk/7a225ec1-eb33-45ab-bf27-646bb2519a83.mp4">
            </a>
          </div>
<video src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/T2klNLEk0wxLh4NRDzhk/7a225ec1-eb33-45ab-bf27-646bb2519a83.mp4">Kapture 2024-03-01 at 10.56.09.mp4</video>
This commit is contained in:
Peng Xiao
2024-03-01 06:08:08 +00:00
parent de939bb6f6
commit a1ea19fcb7
2 changed files with 43 additions and 41 deletions

View File

@@ -17,7 +17,6 @@ import type { DialogContentProps } from '@radix-ui/react-dialog';
import { Doc, type PageMode, Workspace } from '@toeverything/infra'; import { Doc, type PageMode, Workspace } from '@toeverything/infra';
import { useService } from '@toeverything/infra/di'; import { useService } from '@toeverything/infra/di';
import { atom, useAtom, useSetAtom } from 'jotai'; import { atom, useAtom, useSetAtom } from 'jotai';
import { range } from 'lodash-es';
import { import {
Fragment, Fragment,
type PropsWithChildren, type PropsWithChildren,
@@ -25,7 +24,6 @@ import {
useCallback, useCallback,
useLayoutEffect, useLayoutEffect,
useMemo, useMemo,
useRef,
useState, useState,
} from 'react'; } from 'react';
import { encodeStateAsUpdate } from 'yjs'; import { encodeStateAsUpdate } from 'yjs';
@@ -116,7 +114,7 @@ const HistoryEditorPreview = ({
const content = useMemo(() => { const content = useMemo(() => {
return ( return (
<> <div className={styles.previewContent}>
<div className={styles.previewHeader}> <div className={styles.previewHeader}>
<StyledEditorModeSwitch switchLeft={mode === 'page'}> <StyledEditorModeSwitch switchLeft={mode === 'page'}>
<PageSwitchItem <PageSwitchItem
@@ -154,7 +152,7 @@ const HistoryEditorPreview = ({
<Loading size={24} /> <Loading size={24} />
</div> </div>
)} )}
</> </div>
); );
}, [ }, [
mode, mode,
@@ -165,23 +163,23 @@ const HistoryEditorPreview = ({
ts, ts,
]); ]);
const previewRef = useRef<HTMLDivElement | null>(null);
return ( return (
<div className={styles.previewWrapper} ref={previewRef}> <div className={styles.previewWrapper}>
{range(0, historyList.length).map(i => { {historyList.map((item, i) => {
const historyIndex = historyList.findIndex(h => h.timestamp === ts); const historyIndex = historyList.findIndex(h => h.timestamp === ts);
const distance = i - historyIndex; const distance = i - historyIndex;
const flag = const flag =
distance === 0 distance > 20
? 'current' ? '> 20'
: distance > 2 : distance < -20
? '> 2' ? '< -20'
: distance < 0 : distance.toString();
? '< 0'
: distance;
return ( return (
<div data-distance={flag} key={i} className={styles.previewContainer}> <div
data-distance={flag}
key={item.id}
className={styles.previewContainer}
>
{historyIndex === i ? content : null} {historyIndex === i ? content : null}
</div> </div>
); );

View File

@@ -1,5 +1,7 @@
import { cssVar } from '@toeverything/theme'; import { cssVar } from '@toeverything/theme';
import { createVar, globalStyle, style } from '@vanilla-extract/css'; import { createVar, globalStyle, style } from '@vanilla-extract/css';
import { range } from 'lodash-es';
const headerHeight = createVar('header-height'); const headerHeight = createVar('header-height');
const footerHeight = createVar('footer-height'); const footerHeight = createVar('footer-height');
const historyListWidth = createVar('history-list-width'); const historyListWidth = createVar('history-list-width');
@@ -35,51 +37,53 @@ export const previewWrapper = style({
width: `calc(100% - ${historyListWidth})`, width: `calc(100% - ${historyListWidth})`,
backgroundColor: cssVar('backgroundSecondaryColor'), backgroundColor: cssVar('backgroundSecondaryColor'),
}); });
export const previewContainer = style({ export const previewContainer = style({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexGrow: 1, flexGrow: 1,
position: 'absolute', position: 'absolute',
bottom: 0, top: 0,
left: 40, left: 40,
borderTopLeftRadius: 8, borderRadius: 8,
borderTopRightRadius: 8,
overflow: 'hidden', overflow: 'hidden',
boxShadow: cssVar('shadow3'), boxShadow: cssVar('shadow3'),
height: 'calc(100% - 40px)', height: '200%',
width: `calc(100% - 80px)`, width: `calc(100% - 80px)`,
backgroundColor: cssVar('backgroundPrimaryColor'), backgroundColor: cssVar('backgroundPrimaryColor'),
transformOrigin: 'top center', transformOrigin: 'top center',
transition: 'all 0.5s ease-in-out', transition: 'transform 0.3s 0.1s ease-in-out, opacity 0.3s ease-in-out',
selectors: { selectors: {
'&[data-distance="> 2"]': { ...Object.fromEntries(
transform: 'scale(0.60)', range(-20, 20).map(i => [
`&[data-distance="${i}"]`,
{
transform: `scale(${1 - 0.05 * i}) translateY(${-8 * i + 40}px)`,
opacity: [0, 1, 2].includes(i) ? 1 : 0,
zIndex: -i,
pointerEvents: i === 0 ? 'auto' : 'none',
},
])
),
'&[data-distance="> 20"]': {
transform: `scale(0) translateY(${-8 * 20 + 40}px)`,
opacity: 0, opacity: 0,
zIndex: -3, zIndex: -20,
pointerEvents: 'none', pointerEvents: 'none',
}, },
'&[data-distance="2"]': { '&[data-distance="< -20"]': {
transform: 'scale(0.90) translateY(-16px)', transform: `scale(2) translateY(${-8 * -20 + 40}px)`,
zIndex: -2,
pointerEvents: 'none',
},
'&[data-distance="1"]': {
transform: 'scale(0.95) translateY(-8px)',
zIndex: -1,
pointerEvents: 'none',
},
'&[data-distance="current"]': {
opacity: 1,
zIndex: 0,
},
'&[data-distance="< 0"]': {
transform: 'scale(1.60) translateY(18px)',
opacity: 0, opacity: 0,
zIndex: 1, zIndex: 20,
pointerEvents: 'none', pointerEvents: 'none',
}, },
}, },
}); });
export const previewContent = style({
height: '50%',
});
export const previewHeader = style({ export const previewHeader = style({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',