Browse Source

feat: Disable motion (#41856)

* feat: Add motion off

* chore: update motion seed

* chore: all motion disabled

* test: add test case

* chore: fix lint

* chore: fix cov

* chore: fix motion of Switch & Segemented

* test: ignore empty
pull/42031/head
二货爱吃白萝卜 2 years ago
committed by GitHub
parent
commit
60dbef4963
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      .dumi/theme/common/ThemeSwitch/index.tsx
  2. 11
      .dumi/theme/layouts/GlobalLayout.tsx
  3. 2
      .dumi/theme/locales/en-US.json
  4. 2
      .dumi/theme/locales/zh-CN.json
  5. 22
      components/config-provider/MotionWrapper.tsx
  6. 4
      components/config-provider/index.tsx
  7. 4
      components/modal/index.en-US.md
  8. 4
      components/modal/index.zh-CN.md
  9. 27
      components/switch/style/index.ts
  10. 20
      components/theme/__tests__/token.test.tsx
  11. 9
      components/theme/interface/seeds.ts
  12. 3
      components/theme/themes/seed.ts
  13. 10
      components/theme/util/alias.ts
  14. 12
      docs/react/faq.en-US.md
  15. 12
      docs/react/faq.zh-CN.md
  16. 2
      package.json

27
.dumi/theme/common/ThemeSwitch/index.tsx

@ -1,13 +1,13 @@
import React from 'react';
import { BgColorsOutlined } from '@ant-design/icons';
import { FloatButton } from 'antd';
import { CompactTheme, DarkTheme, Motion } from 'antd-token-previewer/es/icons';
import { FormattedMessage, Link, useLocation } from 'dumi';
import { DarkTheme, CompactTheme } from 'antd-token-previewer/es/icons';
import { BgColorsOutlined } from '@ant-design/icons';
import React from 'react';
import useSiteToken from '../../../hooks/useSiteToken';
import { getLocalizedPathname, isZhCN } from '../../utils';
import ThemeIcon from './ThemeIcon';
export type ThemeName = 'light' | 'dark' | 'compact';
export type ThemeName = 'light' | 'dark' | 'compact' | 'motion-off';
export type ThemeSwitchProps = {
value?: ThemeName[];
@ -18,6 +18,9 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = (props: ThemeSwitchProps) => {
const { value = ['light'], onChange } = props;
const { token } = useSiteToken();
const { pathname, search } = useLocation();
const isMotionOff = value.includes('motion-off');
return (
<FloatButton.Group trigger="click" icon={<ThemeIcon />}>
<Link
@ -53,6 +56,22 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = (props: ThemeSwitchProps) => {
}}
tooltip={<FormattedMessage id="app.theme.switch.compact" />}
/>
<FloatButton
icon={<Motion />}
type={!isMotionOff ? 'primary' : 'default'}
onClick={() => {
if (isMotionOff) {
onChange(value.filter((theme) => theme !== 'motion-off'));
} else {
onChange([...value, 'motion-off']);
}
}}
tooltip={
<FormattedMessage
id={isMotionOff ? 'app.theme.switch.motion.off' : 'app.theme.switch.motion.on'}
/>
}
/>
</FloatButton.Group>
);
};

11
.dumi/theme/layouts/GlobalLayout.tsx

@ -5,17 +5,17 @@ import {
parentSelectorLinter,
StyleProvider,
} from '@ant-design/cssinjs';
import { App, ConfigProvider, theme as antdTheme } from 'antd';
import { theme as antdTheme, App, ConfigProvider } from 'antd';
import type { DirectionType } from 'antd/es/config-provider';
import { createSearchParams, useOutlet, useSearchParams } from 'dumi';
import React, { useCallback, useEffect, useMemo } from 'react';
import useLayoutState from '../../hooks/useLayoutState';
import SiteThemeProvider from '../SiteThemeProvider';
import useLocation from '../../hooks/useLocation';
import type { ThemeName } from '../common/ThemeSwitch';
import ThemeSwitch from '../common/ThemeSwitch';
import type { SiteContextProps } from '../slots/SiteContext';
import SiteContext from '../slots/SiteContext';
import useLayoutState from '../../hooks/useLayoutState';
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][];
type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
@ -42,10 +42,10 @@ const GlobalLayout: React.FC = () => {
const outlet = useOutlet();
const { pathname } = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const [{ theme, direction, isMobile }, setSiteState] = useLayoutState<SiteState>({
const [{ theme = [], direction, isMobile }, setSiteState] = useLayoutState<SiteState>({
isMobile: false,
direction: 'ltr',
theme: ['light'],
theme: ['light', 'motion-off'],
});
const updateSiteConfig = useCallback(
@ -116,6 +116,9 @@ const GlobalLayout: React.FC = () => {
<ConfigProvider
theme={{
algorithm: getAlgorithm(theme),
token: {
motion: !theme.includes('motion-off'),
},
}}
>
<SiteThemeProvider>

2
.dumi/theme/locales/en-US.json

@ -3,6 +3,8 @@
"app.theme.switch.default": "Default theme",
"app.theme.switch.dark": "Dark theme",
"app.theme.switch.compact": "Compact theme",
"app.theme.switch.motion.on": "Motion On",
"app.theme.switch.motion.off": "Motion Off",
"app.header.search": "Search...",
"app.header.menu.documentation": "Docs",
"app.header.menu.more": "More",

2
.dumi/theme/locales/zh-CN.json

@ -3,6 +3,8 @@
"app.theme.switch.default": "默认主题",
"app.theme.switch.dark": "暗黑主题",
"app.theme.switch.compact": "紧凑主题",
"app.theme.switch.motion.on": "动画开启",
"app.theme.switch.motion.off": "动画关闭",
"app.header.search": "全文本搜索...",
"app.header.menu.documentation": "文档",
"app.header.menu.more": "更多",

22
components/config-provider/MotionWrapper.tsx

@ -0,0 +1,22 @@
import { Provider as MotionProvider } from 'rc-motion';
import * as React from 'react';
import { useToken } from '../theme/internal';
export interface MotionWrapperProps {
children?: React.ReactNode;
}
export default function MotionWrapper(props: MotionWrapperProps): React.ReactElement {
const { children } = props;
const [, token] = useToken();
const { motion } = token;
const needWrapMotionProviderRef = React.useRef(false);
needWrapMotionProviderRef.current = needWrapMotionProviderRef.current || motion === false;
if (needWrapMotionProviderRef.current) {
return <MotionProvider motion={motion}>{children}</MotionProvider>;
}
return children as React.ReactElement;
}

4
components/config-provider/index.tsx

@ -30,6 +30,7 @@ import type { RenderEmptyHandler } from './defaultRenderEmpty';
import { DisabledContextProvider } from './DisabledContext';
import useConfig from './hooks/useConfig';
import useTheme from './hooks/useTheme';
import MotionWrapper from './MotionWrapper';
import type { SizeType } from './SizeContext';
import SizeContext, { SizeContextProvider } from './SizeContext';
import useStyle from './style';
@ -323,6 +324,9 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
childNode = <SizeContextProvider size={componentSize}>{childNode}</SizeContextProvider>;
}
// =================================== Motion ===================================
childNode = <MotionWrapper>{childNode}</MotionWrapper>;
// ================================ Dynamic theme ================================
const memoTheme = React.useMemo(() => {
const { algorithm, token, ...rest } = mergedTheme || {};

4
components/modal/index.en-US.md

@ -204,10 +204,6 @@ return (
> [App Package Component](/components/app) can be used to simplify the problem of `useModal` and other methods that need to manually implant contextHolder.
### How to disable motion?
You can config `transitionName=""` and `maskTransitionName=""` to remove motion class. But you should note that these prop is internal usage which we don't promise exist in next major version.
### How to set static methods prefixCls ?
You can config with [`ConfigProvider.config`](/components/config-provider#configproviderconfig-4130)

4
components/modal/index.zh-CN.md

@ -203,10 +203,6 @@ return (
> 可通过 [App 包裹组件](/components/app-cn) 简化 `useModal` 等方法需要手动植入 contextHolder 的问题。
### 如何关闭 Modal 动画?
你可以通过 `transitionName=""``maskTransitionName=""` 去除动画 CSS,但是需要注意的是。该方法为内部方法,我们不保证下个大版本重构时该属性会被保留。
### 静态方法如何设置 prefixCls ?
你可以通过 [`ConfigProvider.config`](/components/config-provider-cn#configproviderconfig-4130) 进行设置。

27
components/switch/style/index.ts

@ -1,8 +1,8 @@
import type { CSSObject } from '@ant-design/cssinjs';
import { TinyColor } from '@ctrl/tinycolor';
import { genFocusStyle, resetComponent } from '../../style';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genFocusStyle, resetComponent } from '../../style';
interface SwitchToken extends FullToken<'Switch'> {
switchMinWidth: number;
@ -130,7 +130,7 @@ const genSwitchLoadingStyle: GenerateStyle<SwitchToken, CSSObject> = (token) =>
};
const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
const { componentCls } = token;
const { componentCls, motion } = token;
const switchHandleCls = `${componentCls}-handle`;
return {
@ -161,17 +161,20 @@ const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
insetInlineStart: `calc(100% - ${token.switchPinSize + token.switchPadding}px)`,
},
[`&:not(${componentCls}-disabled):active`]: {
[`${switchHandleCls}::before`]: {
insetInlineEnd: token.switchHandleActiveInset,
insetInlineStart: 0,
},
[`&:not(${componentCls}-disabled):active`]: motion
? {
[`${switchHandleCls}::before`]: {
insetInlineEnd: token.switchHandleActiveInset,
insetInlineStart: 0,
},
[`&${componentCls}-checked ${switchHandleCls}::before`]: {
insetInlineEnd: 0,
insetInlineStart: token.switchHandleActiveInset,
},
},
[`&${componentCls}-checked ${switchHandleCls}::before`]: {
insetInlineEnd: 0,
insetInlineStart: token.switchHandleActiveInset,
},
}
: /* istanbul ignore next */
{},
},
};
};

20
components/theme/__tests__/token.test.tsx

@ -1,10 +1,10 @@
import { Theme } from '@ant-design/cssinjs';
import * as React from 'react';
import genRadius from '../themes/shared/genRadius';
import theme from '..';
import { render, renderHook } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import Row from '../../row';
import theme from '..';
import genRadius from '../themes/shared/genRadius';
const { useToken } = theme;
@ -205,4 +205,20 @@ describe('Theme', () => {
});
});
});
it('motion false token', () => {
const Shower = () => {
const { token } = useToken();
return <div className="duration">{token.motionDurationSlow}</div>;
};
const { container } = render(
<ConfigProvider theme={{ token: { motion: false } }}>
<Shower />
</ConfigProvider>,
);
expect(container.querySelector('.duration')?.textContent).toEqual('0s');
});
});

9
components/theme/interface/seeds.ts

@ -259,4 +259,13 @@ export interface SeedToken extends PresetColorType {
* @default false
*/
wireframe: boolean;
/**
* @nameZH
* @nameEN Motion Style
* @desc `false`
* @descEN Used to configure the motion effect, when it is `false`, the motion is turned off
* @default false
*/
motion: boolean;
}

3
components/theme/themes/seed.ts

@ -73,5 +73,8 @@ const seedToken: SeedToken = {
// Wireframe
wireframe: false,
// Motion
motion: true,
};
export default seedToken;

10
components/theme/util/alias.ts

@ -1,7 +1,7 @@
import { TinyColor } from '@ctrl/tinycolor';
import type { AliasToken, MapToken, OverrideToken, SeedToken } from '../interface';
import getAlphaColor from './getAlphaColor';
import seedToken from '../themes/seed';
import getAlphaColor from './getAlphaColor';
/** Raw merge of `@ant-design/cssinjs` token. Which need additional process */
type RawMergedToken = MapToken & OverrideToken & { override: Partial<AliasToken> };
@ -31,6 +31,14 @@ export default function formatToken(derivativeToken: RawMergedToken): AliasToken
const screenXL = 1200;
const screenXXL = 1600;
// Motion
if (mergedToken.motion === false) {
const fastDuration = '0s';
mergedToken.motionDurationFast = fastDuration;
mergedToken.motionDurationMid = fastDuration;
mergedToken.motionDurationSlow = fastDuration;
}
// Generate alias token
const aliasToken: AliasToken = {
...mergedToken,

12
docs/react/faq.en-US.md

@ -191,6 +191,18 @@ For historical reasons, the display names of the pop components are not uniform,
Please ref dynamic theme document [Compatible Adjustment](/docs/react/customize-theme#compatible-adjustment) part.
## How to disable motion?
Config with SeedToken:
```jsx
import { ConfigProvider } from 'antd';
<ConfigProvider theme={{ token: { motion: false } }}>
<App />
</ConfigProvider>;
```
## CSS-in-JS css priority conflict with tailwindcss?
Same as above. You can adjust antd css priority to override. Related issue: [#38794](https://github.com/ant-design/ant-design/issues/38794)

12
docs/react/faq.zh-CN.md

@ -212,6 +212,18 @@ ConfigProvider.config({
请参考动态主题文档 [兼容性调整](/docs/react/customize-theme-cn#兼容性调整) 部分内容。
## 如何关闭组件动画
通过 SeedToken 可以很方便的实现:
```jsx
import { ConfigProvider } from 'antd';
<ConfigProvider theme={{ token: { motion: false } }}>
<App />
</ConfigProvider>;
```
## CSS-in-JS 与 tailwindcss 优先级冲突?
同上,你可以调整 antd 样式优先级以覆盖。相关 issue: [#38794](https://github.com/ant-design/ant-design/issues/38794)

2
package.json

@ -139,7 +139,7 @@
"rc-progress": "~3.4.1",
"rc-rate": "~2.10.0",
"rc-resize-observer": "^1.2.0",
"rc-segmented": "~2.1.2",
"rc-segmented": "~2.2.0",
"rc-select": "~14.4.3",
"rc-slider": "~10.1.0",
"rc-steps": "~6.0.0",

Loading…
Cancel
Save