Skip to content

Commit

Permalink
docs: add theme toggle animation (ant-design#44655)
Browse files Browse the repository at this point in the history
* docs: add theme toggle animation

* fix: add compatibility judgment

* refactor: optimization code

* fix: server document not found

* fix: animation lag

* fix: transition issue

* fix: scroll bar theme color
  • Loading branch information
RedJue authored Sep 8, 2023
1 parent 584326b commit 2cf22e8
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 6 deletions.
126 changes: 126 additions & 0 deletions .dumi/hooks/useThemeAnimation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { useEffect, useRef } from 'react';
import { removeCSS, updateCSS } from 'rc-util/lib/Dom/dynamicCSS';

import theme from '../../components/theme';

const viewTransitionStyle = `
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
.dark::view-transition-old(root) {
z-index: 1;
}
.dark::view-transition-new(root) {
z-index: 999;
}
::view-transition-old(root) {
z-index: 999;
}
::view-transition-new(root) {
z-index: 1;
}
`;

const useThemeAnimation = () => {
const {
token: { colorBgElevated },
} = theme.useToken();

const animateRef = useRef<{
colorBgElevated: string;
}>({
colorBgElevated,
});

const startAnimationTheme = (clipPath: string[], isDark: boolean) => {
updateCSS(
`
* {
transition: none !important;
}
`,
'disable-transition',
);

document.documentElement
.animate(
{
clipPath: isDark ? [...clipPath].reverse() : clipPath,
},
{
duration: 500,
easing: 'ease-in',
pseudoElement: isDark ? '::view-transition-old(root)' : '::view-transition-new(root)',
},
)
.addEventListener('finish', () => {
removeCSS('disable-transition');
});
};

const toggleAnimationTheme = (event: MouseEvent, isDark: boolean) => {
// @ts-ignore
if (!(event && typeof document.startViewTransition === 'function')) return;
const x = event.clientX;
const y = event.clientY;
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
updateCSS(
`
[data-prefers-color='dark'] {
color-scheme: light !important;
}
[data-prefers-color='light'] {
color-scheme: dark !important;
}
`,
'color-scheme',
);
document
// @ts-ignore
.startViewTransition(async () => {
// wait for theme change end
while (colorBgElevated === animateRef.current.colorBgElevated) {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 1000 / 60);
});
}
const root = document.documentElement;
root.classList.remove(isDark ? 'dark' : 'light');
root.classList.add(isDark ? 'light' : 'dark');
})
.ready.then(() => {
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
];
removeCSS('color-scheme');
startAnimationTheme(clipPath, isDark);
});
};

// inject transition style
useEffect(() => {
// @ts-ignore
if (typeof document.startViewTransition === 'function') {
updateCSS(viewTransitionStyle, 'view-transition-style');
}
}, []);

useEffect(() => {
if (colorBgElevated !== animateRef.current.colorBgElevated) {
animateRef.current.colorBgElevated = colorBgElevated;
}
}, [colorBgElevated]);

return toggleAnimationTheme;
};

export default useThemeAnimation;
20 changes: 14 additions & 6 deletions .dumi/theme/common/ThemeSwitch/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react';
import { BgColorsOutlined, SmileOutlined } from '@ant-design/icons';
import { FloatButton } from 'antd';
import { useTheme } from 'antd-style';
import { CompactTheme, DarkTheme } from 'antd-token-previewer/es/icons';
// import { Motion } from 'antd-token-previewer/es/icons';
import { FormattedMessage, Link, useLocation } from 'dumi';
import React from 'react';
import { useTheme } from 'antd-style';
import { FloatButton } from 'antd';

import useThemeAnimation from '../../../hooks/useThemeAnimation';
import { getLocalizedPathname, isZhCN } from '../../utils';
import ThemeIcon from './ThemeIcon';

Expand All @@ -22,6 +24,9 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = (props) => {

// const isMotionOff = value.includes('motion-off');
const isHappyWork = value.includes('happy-work');
const isDark = value.includes('dark');

const toggleAnimationTheme = useThemeAnimation();

return (
<FloatButton.Group
Expand All @@ -42,9 +47,12 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = (props) => {
</Link>
<FloatButton
icon={<DarkTheme />}
type={value.includes('dark') ? 'primary' : 'default'}
onClick={() => {
if (value.includes('dark')) {
type={isDark ? 'primary' : 'default'}
onClick={(e) => {
// Toggle animation when switch theme
toggleAnimationTheme(e, isDark);

if (isDark) {
onChange(value.filter((theme) => theme !== 'dark'));
} else {
onChange([...value, 'dark']);
Expand Down

0 comments on commit 2cf22e8

Please sign in to comment.