mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-23 21:06:22 +08:00
feat: add animation to mode switch when hover (#1489)
Co-authored-by: Yifeng Wang <doodlewind@toeverything.info> Co-authored-by: himself65 <himself65@outlook.com>
This commit is contained in:
@@ -34,6 +34,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-is": "^18.2.0",
|
"react-is": "^18.2.0",
|
||||||
|
"react-lottie": "^1.2.3",
|
||||||
"swr": "^2.0.4",
|
"swr": "^2.0.4",
|
||||||
"y-indexeddb": "^9.0.9",
|
"y-indexeddb": "^9.0.9",
|
||||||
"y-protocols": "^1.0.5",
|
"y-protocols": "^1.0.5",
|
||||||
@@ -47,6 +48,7 @@
|
|||||||
"@swc-jotai/react-refresh": "^0.0.4",
|
"@swc-jotai/react-refresh": "^0.0.4",
|
||||||
"@types/react": "^18.0.28",
|
"@types/react": "^18.0.28",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
|
"@types/react-lottie": "^1.2.6",
|
||||||
"@types/webpack-env": "^1.18.0",
|
"@types/webpack-env": "^1.18.0",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"eslint": "^8.35.0",
|
"eslint": "^8.35.0",
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ import { useWorkspacesHelper } from '../../hooks/use-workspaces';
|
|||||||
import { ThemeProvider } from '../../providers/ThemeProvider';
|
import { ThemeProvider } from '../../providers/ThemeProvider';
|
||||||
import { pathGenerator, RemWorkspaceFlavour } from '../../shared';
|
import { pathGenerator, RemWorkspaceFlavour } from '../../shared';
|
||||||
import { WorkSpaceSliderBar } from '../pure/workspace-slider-bar';
|
import { WorkSpaceSliderBar } from '../pure/workspace-slider-bar';
|
||||||
|
vi.mock('react-lottie', () => ({
|
||||||
|
default: (props: React.PropsWithChildren) => <>{props.children}</>,
|
||||||
|
}));
|
||||||
|
|
||||||
const fetchMocker = createFetchMock(vi);
|
const fetchMocker = createFetchMock(vi);
|
||||||
|
|
||||||
|
|||||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
@@ -1,5 +1,4 @@
|
|||||||
import { toast } from '@affine/component';
|
import { toast } from '@affine/component';
|
||||||
import { EdgelessIcon, PaperIcon } from '@blocksuite/icons';
|
|
||||||
import { assertExists } from '@blocksuite/store';
|
import { assertExists } from '@blocksuite/store';
|
||||||
import { CSSProperties } from 'react';
|
import { CSSProperties } from 'react';
|
||||||
|
|
||||||
@@ -8,7 +7,8 @@ import {
|
|||||||
usePageMetaHelper,
|
usePageMetaHelper,
|
||||||
} from '../../../../hooks/use-page-meta';
|
} from '../../../../hooks/use-page-meta';
|
||||||
import { BlockSuiteWorkspace } from '../../../../shared';
|
import { BlockSuiteWorkspace } from '../../../../shared';
|
||||||
import { StyledEditorModeSwitch, StyledSwitchItem } from './style';
|
import { StyledEditorModeSwitch } from './style';
|
||||||
|
import { EdgelessSwitchItem, PageSwitchItem } from './switch-items';
|
||||||
|
|
||||||
export type EditorModeSwitchProps = {
|
export type EditorModeSwitchProps = {
|
||||||
// todo(himself65): combine these two properties
|
// todo(himself65): combine these two properties
|
||||||
@@ -35,7 +35,7 @@ export const EditorModeSwitch = ({
|
|||||||
switchLeft={mode === 'page'}
|
switchLeft={mode === 'page'}
|
||||||
showAlone={trash}
|
showAlone={trash}
|
||||||
>
|
>
|
||||||
<StyledSwitchItem
|
<PageSwitchItem
|
||||||
data-testid="switch-page-mode-button"
|
data-testid="switch-page-mode-button"
|
||||||
active={mode === 'page'}
|
active={mode === 'page'}
|
||||||
hide={trash && mode !== 'page'}
|
hide={trash && mode !== 'page'}
|
||||||
@@ -43,10 +43,8 @@ export const EditorModeSwitch = ({
|
|||||||
setPageMeta(pageId, { mode: 'page' });
|
setPageMeta(pageId, { mode: 'page' });
|
||||||
toast('Page mode');
|
toast('Page mode');
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<PaperIcon />
|
<EdgelessSwitchItem
|
||||||
</StyledSwitchItem>
|
|
||||||
<StyledSwitchItem
|
|
||||||
data-testid="switch-edgeless-mode-button"
|
data-testid="switch-edgeless-mode-button"
|
||||||
active={mode === 'edgeless'}
|
active={mode === 'edgeless'}
|
||||||
hide={trash && mode !== 'edgeless'}
|
hide={trash && mode !== 'edgeless'}
|
||||||
@@ -54,9 +52,7 @@ export const EditorModeSwitch = ({
|
|||||||
setPageMeta(pageId, { mode: 'edgeless' });
|
setPageMeta(pageId, { mode: 'edgeless' });
|
||||||
toast('Edgeless mode');
|
toast('Edgeless mode');
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<EdgelessIcon />
|
|
||||||
</StyledSwitchItem>
|
|
||||||
</StyledEditorModeSwitch>
|
</StyledEditorModeSwitch>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ export const StyledEditorModeSwitch = styled('div')<{
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const StyledSwitchItem = styled('button')<{
|
export const StyledSwitchItem = styled('button')<{
|
||||||
active: boolean;
|
active?: boolean;
|
||||||
hide?: boolean;
|
hide?: boolean;
|
||||||
}>(({ theme, active, hide = false }) => {
|
}>(({ theme, active = false, hide = false }) => {
|
||||||
return {
|
return {
|
||||||
width: '24px',
|
width: '24px',
|
||||||
height: '24px',
|
height: '24px',
|
||||||
@@ -54,5 +54,8 @@ export const StyledSwitchItem = styled('button')<{
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
fontSize: '20px',
|
fontSize: '20px',
|
||||||
|
path: {
|
||||||
|
fill: 'currentColor',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import React, { cloneElement, HTMLAttributes, useState } from 'react';
|
||||||
|
import Lottie from 'react-lottie';
|
||||||
|
|
||||||
|
import * as edgelessHoverAnimationData from './animation-data/edgeless-hover.json';
|
||||||
|
import * as pageHoverAnimationData from './animation-data/page-hover.json';
|
||||||
|
import { StyledSwitchItem } from './style';
|
||||||
|
|
||||||
|
type HoverAnimateControllerProps = {
|
||||||
|
active?: boolean;
|
||||||
|
hide?: boolean;
|
||||||
|
children: React.ReactElement;
|
||||||
|
} & HTMLAttributes<HTMLButtonElement>;
|
||||||
|
|
||||||
|
const HoverAnimateController = ({
|
||||||
|
active,
|
||||||
|
hide,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: HoverAnimateControllerProps) => {
|
||||||
|
const [startAnimate, setStartAnimate] = useState(false);
|
||||||
|
return (
|
||||||
|
<StyledSwitchItem
|
||||||
|
hide={hide}
|
||||||
|
active={active}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
setStartAnimate(true);
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
setStartAnimate(false);
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{cloneElement(children, {
|
||||||
|
isStopped: !startAnimate,
|
||||||
|
speed: 5,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
})}
|
||||||
|
</StyledSwitchItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PageSwitchItem = (
|
||||||
|
props: Omit<HoverAnimateControllerProps, 'children'>
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<HoverAnimateController {...props}>
|
||||||
|
<Lottie
|
||||||
|
options={{
|
||||||
|
loop: false,
|
||||||
|
autoplay: false,
|
||||||
|
animationData: pageHoverAnimationData,
|
||||||
|
rendererSettings: {
|
||||||
|
preserveAspectRatio: 'xMidYMid slice',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</HoverAnimateController>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EdgelessSwitchItem = (
|
||||||
|
props: Omit<HoverAnimateControllerProps, 'children'>
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<HoverAnimateController {...props}>
|
||||||
|
<Lottie
|
||||||
|
options={{
|
||||||
|
loop: false,
|
||||||
|
autoplay: false,
|
||||||
|
animationData: edgelessHoverAnimationData,
|
||||||
|
rendererSettings: {
|
||||||
|
preserveAspectRatio: 'xMidYMid slice',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</HoverAnimateController>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -42,6 +42,9 @@ import {
|
|||||||
useSyncRouterWithCurrentWorkspaceAndPage,
|
useSyncRouterWithCurrentWorkspaceAndPage,
|
||||||
} from '../use-sync-router-with-current-workspace-and-page';
|
} from '../use-sync-router-with-current-workspace-and-page';
|
||||||
import { useWorkspaces, useWorkspacesHelper } from '../use-workspaces';
|
import { useWorkspaces, useWorkspacesHelper } from '../use-workspaces';
|
||||||
|
vi.mock('react-lottie', () => ({
|
||||||
|
default: (props: React.PropsWithChildren) => <>{props.children}</>,
|
||||||
|
}));
|
||||||
|
|
||||||
let blockSuiteWorkspace: BlockSuiteWorkspace;
|
let blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
|||||||
Generated
+43
-1
@@ -154,6 +154,7 @@ importers:
|
|||||||
'@swc-jotai/react-refresh': ^0.0.4
|
'@swc-jotai/react-refresh': ^0.0.4
|
||||||
'@types/react': ^18.0.28
|
'@types/react': ^18.0.28
|
||||||
'@types/react-dom': ^18.0.11
|
'@types/react-dom': ^18.0.11
|
||||||
|
'@types/react-lottie': ^1.2.6
|
||||||
'@types/webpack-env': ^1.18.0
|
'@types/webpack-env': ^1.18.0
|
||||||
cmdk: ^0.1.22
|
cmdk: ^0.1.22
|
||||||
css-spring: ^4.1.0
|
css-spring: ^4.1.0
|
||||||
@@ -172,6 +173,7 @@ importers:
|
|||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
react-dom: ^18.2.0
|
react-dom: ^18.2.0
|
||||||
react-is: ^18.2.0
|
react-is: ^18.2.0
|
||||||
|
react-lottie: ^1.2.3
|
||||||
redux: ^4.2.1
|
redux: ^4.2.1
|
||||||
swc-plugin-coverage-instrument: ^0.0.14
|
swc-plugin-coverage-instrument: ^0.0.14
|
||||||
swr: ^2.0.4
|
swr: ^2.0.4
|
||||||
@@ -207,6 +209,7 @@ importers:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
react-is: 18.2.0
|
react-is: 18.2.0
|
||||||
|
react-lottie: 1.2.3_react@18.2.0
|
||||||
swr: 2.0.4_react@18.2.0
|
swr: 2.0.4_react@18.2.0
|
||||||
y-indexeddb: 9.0.9_yjs@13.5.48
|
y-indexeddb: 9.0.9_yjs@13.5.48
|
||||||
y-protocols: 1.0.5
|
y-protocols: 1.0.5
|
||||||
@@ -219,6 +222,7 @@ importers:
|
|||||||
'@swc-jotai/react-refresh': 0.0.4
|
'@swc-jotai/react-refresh': 0.0.4
|
||||||
'@types/react': 18.0.28
|
'@types/react': 18.0.28
|
||||||
'@types/react-dom': 18.0.11
|
'@types/react-dom': 18.0.11
|
||||||
|
'@types/react-lottie': 1.2.6
|
||||||
'@types/webpack-env': 1.18.0
|
'@types/webpack-env': 1.18.0
|
||||||
dotenv: 16.0.3
|
dotenv: 16.0.3
|
||||||
eslint: 8.35.0
|
eslint: 8.35.0
|
||||||
@@ -5171,6 +5175,12 @@ packages:
|
|||||||
'@types/react': 18.0.28
|
'@types/react': 18.0.28
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/react-lottie/1.2.6:
|
||||||
|
resolution: {integrity: sha512-fvGJHD7SeUdVESHo7f7erRnXkTWaa/6Mo5TB+R0/ieSftKoFspA4sMlF2qMH6BljXI7ehFJbBtrD5bzDxPCkGg==}
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.0.28
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/react-transition-group/4.4.5:
|
/@types/react-transition-group/4.4.5:
|
||||||
resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==}
|
resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5364,7 +5374,7 @@ packages:
|
|||||||
'@babel/plugin-transform-react-jsx-source': 7.19.6_@babel+core@7.21.0
|
'@babel/plugin-transform-react-jsx-source': 7.19.6_@babel+core@7.21.0
|
||||||
magic-string: 0.27.0
|
magic-string: 0.27.0
|
||||||
react-refresh: 0.14.0
|
react-refresh: 0.14.0
|
||||||
vite: 4.1.4
|
vite: 4.1.4_@types+node@18.14.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
@@ -6004,6 +6014,13 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/babel-runtime/6.26.0:
|
||||||
|
resolution: {integrity: sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==}
|
||||||
|
dependencies:
|
||||||
|
core-js: 2.6.12
|
||||||
|
regenerator-runtime: 0.11.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/balanced-match/1.0.2:
|
/balanced-match/1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
@@ -6569,6 +6586,12 @@ packages:
|
|||||||
browserslist: 4.21.5
|
browserslist: 4.21.5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/core-js/2.6.12:
|
||||||
|
resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==}
|
||||||
|
deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/core-util-is/1.0.3:
|
/core-util-is/1.0.3:
|
||||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||||
|
|
||||||
@@ -9590,6 +9613,10 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
|
|
||||||
|
/lottie-web/5.10.2:
|
||||||
|
resolution: {integrity: sha512-d0PFIGiwuMsJYaF4uPo+qG8dEorlI+xFI2zrrFtE1bGO4WoLIz+NjremxEq1swpR7juR10aeOtmNh3d6G3ub0A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/loupe/2.3.6:
|
/loupe/2.3.6:
|
||||||
resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
|
resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -10960,6 +10987,17 @@ packages:
|
|||||||
/react-is/18.2.0:
|
/react-is/18.2.0:
|
||||||
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
|
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
|
||||||
|
|
||||||
|
/react-lottie/1.2.3_react@18.2.0:
|
||||||
|
resolution: {integrity: sha512-qLCERxUr8M+4mm1LU0Ruxw5Y5Fn/OmYkGfnA+JDM/dZb3oKwVAJCjwnjkj9TMHtzR2U6sMEUD3ZZ1RaHagM7kA==}
|
||||||
|
engines: {npm: ^3.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^0.14.7 || ^15.0.0 || ^16.0.0
|
||||||
|
dependencies:
|
||||||
|
babel-runtime: 6.26.0
|
||||||
|
lottie-web: 5.10.2
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-refresh/0.14.0:
|
/react-refresh/0.14.0:
|
||||||
resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
|
resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -11150,6 +11188,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
|
resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/regenerator-runtime/0.11.1:
|
||||||
|
resolution: {integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/regenerator-runtime/0.13.11:
|
/regenerator-runtime/0.13.11:
|
||||||
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user