chore: auto merge branches (#39111)

chore: feature merge master
This commit is contained in:
github-actions[bot] 2022-11-30 03:25:40 +00:00 committed by GitHub
commit 7037b470c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
108 changed files with 1135 additions and 539 deletions

View File

@ -1,4 +1,3 @@
import * as React from 'react';
import { useLocale as useDumiLocale } from 'dumi'; import { useLocale as useDumiLocale } from 'dumi';
export interface LocaleMap<Key extends string> { export interface LocaleMap<Key extends string> {

View File

@ -1,5 +1,6 @@
import React, { ReactNode, useMemo } from 'react'; import type { ReactNode } from 'react';
import { MenuProps } from 'antd'; import React, { useMemo } from 'react';
import type { MenuProps } from 'antd';
import { Link, useFullSidebarData, useSidebarData } from 'dumi'; import { Link, useFullSidebarData, useSidebarData } from 'dumi';
import useLocation from './useLocation'; import useLocation from './useLocation';
@ -115,7 +116,7 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] =>
} }
} else { } else {
result.push( result.push(
...group.children?.map((item) => ({ ...(group.children?.map((item) => ({
label: ( label: (
<Link to={`${item.link}${search}`}> <Link to={`${item.link}${search}`}>
{before} {before}
@ -124,7 +125,7 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] =>
</Link> </Link>
), ),
key: item.link.replace(/(-cn$)/g, ''), key: item.link.replace(/(-cn$)/g, ''),
})), })) ?? []),
); );
} }
return result; return result;

View File

@ -1 +1,3 @@
export { default } from '../index/index'; import Homepage from '../index/index';
export default Homepage;

View File

@ -1,9 +1,9 @@
import * as React from 'react'; import * as React from 'react';
import { Button, Space, Typography } from 'antd'; import { Button, Space, Typography } from 'antd';
import { Link, useLocation } from 'dumi';
import useLocale from '../../../hooks/useLocale'; import useLocale from '../../../hooks/useLocale';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';
import { GroupMask } from './Group'; import { GroupMask } from './Group';
import { Link, useLocation } from 'dumi';
import * as utils from '../../../theme/utils'; import * as utils from '../../../theme/utils';
const locales = { const locales = {
@ -25,7 +25,7 @@ export interface BannerProps {
} }
export default function Banner({ children }: BannerProps) { export default function Banner({ children }: BannerProps) {
const [locale, lang] = useLocale(locales); const [locale] = useLocale(locales);
const { pathname, search } = useLocation(); const { pathname, search } = useLocation();
const { token } = useSiteToken(); const { token } = useSiteToken();
@ -82,6 +82,7 @@ export default function Banner({ children }: BannerProps) {
<img <img
style={{ position: 'absolute', right: 0, top: 240, width: 240 }} style={{ position: 'absolute', right: 0, top: 240, width: 240 }}
src="https://gw.alipayobjects.com/zos/bmw-prod/b3b8dc41-dce8-471f-9d81-9a0204f27d03.svg" src="https://gw.alipayobjects.com/zos/bmw-prod/b3b8dc41-dce8-471f-9d81-9a0204f27d03.svg"
alt="Ant Design"
/> />
<GroupMask <GroupMask
@ -95,11 +96,13 @@ export default function Banner({ children }: BannerProps) {
<img <img
style={{ position: 'absolute', left: 0, top: 0, width: 240 }} style={{ position: 'absolute', left: 0, top: 0, width: 240 }}
src="https://gw.alipayobjects.com/zos/bmw-prod/49f963db-b2a8-4f15-857a-270d771a1204.svg" src="https://gw.alipayobjects.com/zos/bmw-prod/49f963db-b2a8-4f15-857a-270d771a1204.svg"
alt="bg"
/> />
{/* Image Left Top */} {/* Image Left Top */}
<img <img
style={{ position: 'absolute', right: 120, top: 0, width: 240 }} style={{ position: 'absolute', right: 120, top: 0, width: 240 }}
src="https://gw.alipayobjects.com/zos/bmw-prod/e152223c-bcae-4913-8938-54fda9efe330.svg" src="https://gw.alipayobjects.com/zos/bmw-prod/e152223c-bcae-4913-8938-54fda9efe330.svg"
alt="bg"
/> />
<Typography.Title <Typography.Title

View File

@ -1,8 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { Skeleton, Typography } from 'antd';
import { css } from '@emotion/react';
import type { Extra, Icon } from './util'; import type { Extra, Icon } from './util';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';
import { Col, Row, Card, Typography, Skeleton } from 'antd';
import { css } from '@emotion/react';
const useStyle = () => { const useStyle = () => {
const { token } = useSiteToken(); const { token } = useSiteToken();
@ -52,20 +52,20 @@ export default function BannerRecommends({ extras = [], icons = [] }: BannerReco
textAlign: 'start', textAlign: 'start',
}} }}
> >
{first3.map((extra, index) => { {first3.map((extra) => {
if (!extra) { if (!extra) {
return <Skeleton key={index} />; return <Skeleton key={extra.title} />;
} }
const icon = icons.find((icon) => icon.name === extra.source); const icon = icons.find((i) => i.name === extra.source);
return ( return (
<a key={index} href={extra.href} target="_blank" css={style.card}> <a key={extra.title} href={extra.href} target="_blank" css={style.card} rel="noreferrer">
<Typography.Title level={5}>{extra.title}</Typography.Title> <Typography.Title level={5}>{extra.title}</Typography.Title>
<Typography.Paragraph type="secondary" style={{ flex: 'auto' }}> <Typography.Paragraph type="secondary" style={{ flex: 'auto' }}>
{extra.description} {extra.description}
</Typography.Paragraph> </Typography.Paragraph>
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography.Text>{extra.date}</Typography.Text> <Typography.Text>{extra.date}</Typography.Text>
{icon && <img src={icon.href} style={{ height: token.fontSize }} />} {icon && <img src={icon.href} style={{ height: token.fontSize }} alt="banner" />}
</div> </div>
</a> </a>
); );

View File

@ -1,4 +1,4 @@
import useSiteToken from '../../../hooks/useSiteToken'; /* eslint-disable react/jsx-pascal-case */
import React from 'react'; import React from 'react';
import { import {
Space, Space,
@ -14,6 +14,7 @@ import {
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons'; import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import useSiteToken from '../../../hooks/useSiteToken';
import useLocale from '../../../hooks/useLocale'; import useLocale from '../../../hooks/useLocale';
const SAMPLE_CONTENT_EN = const SAMPLE_CONTENT_EN =

View File

@ -1,9 +1,9 @@
import useSiteToken from '../../../hooks/useSiteToken';
import { Col, Row, Typography } from 'antd'; import { Col, Row, Typography } from 'antd';
import React from 'react'; import React from 'react';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import useLocale from '../../../hooks/useLocale';
import { Link, useLocation } from 'dumi'; import { Link, useLocation } from 'dumi';
import useLocale from '../../../hooks/useLocale';
import useSiteToken from '../../../hooks/useSiteToken';
import * as utils from '../../../theme/utils'; import * as utils from '../../../theme/utils';
const SECONDARY_LIST = [ const SECONDARY_LIST = [
@ -150,7 +150,7 @@ export default function DesignFramework() {
return ( return (
<Col key={index} span={8}> <Col key={index} span={8}>
<a css={style.cardMini} target="_blank" href={url}> <a css={style.cardMini} target="_blank" href={url} rel="noreferrer">
<img alt={title} src={img} style={{ transform: `scale(${imgScale})` }} /> <img alt={title} src={img} style={{ transform: `scale(${imgScale})` }} />
<Typography.Title <Typography.Title

View File

@ -58,7 +58,7 @@ export default function Group(props: GroupProps) {
boxSizing: 'border-box', boxSizing: 'border-box',
paddingInline: token.marginXXL, paddingInline: token.marginXXL,
}; };
let childNode = ( const childNode = (
<> <>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<Typography.Title <Typography.Title

View File

@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { Row, Col, Typography } from 'antd'; import { Row, Col, Typography } from 'antd';
import type { Recommendation } from './util';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import type { Recommendation } from './util';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';
const useStyle = () => { const useStyle = () => {

View File

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import useSiteToken from '../../../../hooks/useSiteToken'; import useSiteToken from '../../../../hooks/useSiteToken';
import { COLOR_IMAGES, DEFAULT_COLOR, getClosetColor } from './colorUtil'; import { COLOR_IMAGES, getClosetColor } from './colorUtil';
export interface BackgroundImageProps { export interface BackgroundImageProps {
colorPrimary?: string; colorPrimary?: string;
@ -38,6 +38,7 @@ export default function BackgroundImage({ colorPrimary, isLight }: BackgroundIma
objectPosition: 'right top', objectPosition: 'right top',
}} }}
src={url} src={url}
alt=""
/> />
); );
})} })}

View File

@ -1,10 +1,12 @@
import useSiteToken from '../../../../hooks/useSiteToken';
import { Input, Space, Popover } from 'antd'; import { Input, Space, Popover } from 'antd';
import React, { FC, useEffect, useState } from 'react'; import type { FC } from 'react';
import React, { useEffect, useState } from 'react';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import { TinyColor } from '@ctrl/tinycolor'; import { TinyColor } from '@ctrl/tinycolor';
import type { ColorPanelProps } from 'antd-token-previewer/es/ColorPanel';
import ColorPanel from 'antd-token-previewer/es/ColorPanel';
import { PRESET_COLORS } from './colorUtil'; import { PRESET_COLORS } from './colorUtil';
import ColorPanel, { ColorPanelProps } from 'antd-token-previewer/es/ColorPanel'; import useSiteToken from '../../../../hooks/useSiteToken';
const useStyle = () => { const useStyle = () => {
const { token } = useSiteToken(); const { token } = useSiteToken();
@ -109,10 +111,7 @@ export default function ColorPicker({ value, onChange }: RadiusPickerProps) {
key={color} key={color}
overlayInnerStyle={{ padding: 0 }} overlayInnerStyle={{ padding: 0 }}
content={ content={
<DebouncedColorPanel <DebouncedColorPanel color={value || ''} onChange={(c) => onChange?.(c)} />
color={value || ''}
onChange={(color) => onChange?.(color)}
/>
} }
trigger="click" trigger="click"
showArrow={false} showArrow={false}

View File

@ -85,6 +85,7 @@ export default function ThemePicker({ value, onChange }: ThemePickerProps) {
onClick={() => { onClick={() => {
onChange?.(theme); onChange?.(theme);
}} }}
alt=""
/> />
</div> </div>
<span>{locale[theme as keyof typeof locale]}</span> <span>{locale[theme as keyof typeof locale]}</span>

View File

@ -52,9 +52,9 @@ export function getClosetColor(colorPrimary?: string | null) {
const distance = COLOR_IMAGES.map(({ color }) => { const distance = COLOR_IMAGES.map(({ color }) => {
const colorObj = new TinyColor(color).toRgb(); const colorObj = new TinyColor(color).toRgb();
const dist = Math.sqrt( const dist = Math.sqrt(
Math.pow(colorObj.r - colorPrimaryRGB.r, 2) + (colorObj.r - colorPrimaryRGB.r) ** 2 +
Math.pow(colorObj.g - colorPrimaryRGB.g, 2) + (colorObj.g - colorPrimaryRGB.g) ** 2 +
Math.pow(colorObj.b - colorPrimaryRGB.b, 2), (colorObj.b - colorPrimaryRGB.b) ** 2,
); );
return { color, dist }; return { color, dist };

View File

@ -2,33 +2,34 @@ import * as React from 'react';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import { TinyColor } from '@ctrl/tinycolor'; import { TinyColor } from '@ctrl/tinycolor';
import { import {
HomeOutlined,
FolderOutlined,
BellOutlined, BellOutlined,
FolderOutlined,
HomeOutlined,
QuestionCircleOutlined, QuestionCircleOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import useLocale from '../../../../hooks/useLocale'; import type { MenuProps } from 'antd';
import useSiteToken from '../../../../hooks/useSiteToken';
import { import {
Typography, Breadcrumb,
Button,
Card,
ConfigProvider,
Form,
Layout, Layout,
Menu, Menu,
Breadcrumb,
MenuProps,
Space,
ConfigProvider,
Card,
Form,
Radio, Radio,
Space,
theme, theme,
Button, Typography,
} from 'antd'; } from 'antd';
import ThemePicker, { THEME } from './ThemePicker'; import useLocale from '../../../../hooks/useLocale';
import useSiteToken from '../../../../hooks/useSiteToken';
import type { THEME } from './ThemePicker';
import ThemePicker from './ThemePicker';
import ColorPicker from './ColorPicker'; import ColorPicker from './ColorPicker';
import RadiusPicker from './RadiusPicker'; import RadiusPicker from './RadiusPicker';
import Group from '../Group'; import Group from '../Group';
import BackgroundImage from './BackgroundImage'; import BackgroundImage from './BackgroundImage';
import { getClosetColor, DEFAULT_COLOR, getAvatarURL, PINK_COLOR } from './colorUtil'; import { DEFAULT_COLOR, getAvatarURL, getClosetColor, PINK_COLOR } from './colorUtil';
const { Header, Content, Sider } = Layout; const { Header, Content, Sider } = Layout;
@ -179,10 +180,6 @@ const useStyle = () => {
}; };
}; };
interface PickerProps {
title: React.ReactNode;
}
// ========================== Menu Config ========================== // ========================== Menu Config ==========================
const subMenuItems: MenuProps['items'] = [ const subMenuItems: MenuProps['items'] = [
{ {
@ -403,6 +400,7 @@ export default function Theme() {
? undefined ? undefined
: `drop-shadow(30px 0 0 ${logoColor})`, : `drop-shadow(30px 0 0 ${logoColor})`,
}} }}
alt=""
/> />
</div> </div>
<h1>Ant Design 5.0</h1> <h1>Ant Design 5.0</h1>
@ -422,7 +420,7 @@ export default function Theme() {
/> />
</Space> </Space>
</Header> </Header>
<Layout css={style.transBg}> <Layout css={style.transBg} hasSider>
<Sider css={style.transBg} width={200} className="site-layout-background"> <Sider css={style.transBg} width={200} className="site-layout-background">
<Menu <Menu
mode="inline" mode="inline"
@ -517,6 +515,7 @@ export default function Theme() {
height: 500, height: 500,
}} }}
src="https://gw.alipayobjects.com/zos/bmw-prod/bd71b0c6-f93a-4e52-9c8a-f01a9b8fe22b.svg" src="https://gw.alipayobjects.com/zos/bmw-prod/bd71b0c6-f93a-4e52-9c8a-f01a9b8fe22b.svg"
alt=""
/> />
{/* Image Right Bottom */} {/* Image Right Bottom */}
<img <img
@ -528,6 +527,7 @@ export default function Theme() {
height: 287, height: 287,
}} }}
src="https://gw.alipayobjects.com/zos/bmw-prod/84ad805a-74cb-4916-b7ba-9cdc2bdec23a.svg" src="https://gw.alipayobjects.com/zos/bmw-prod/84ad805a-74cb-4916-b7ba-9cdc2bdec23a.svg"
alt=""
/> />
</div> </div>
@ -542,11 +542,13 @@ export default function Theme() {
<img <img
style={{ ...posStyle, left: 0, top: -100, height: 500 }} style={{ ...posStyle, left: 0, top: -100, height: 500 }}
src="https://gw.alipayobjects.com/zos/bmw-prod/a213184a-f212-4afb-beec-1e8b36bb4b8a.svg" src="https://gw.alipayobjects.com/zos/bmw-prod/a213184a-f212-4afb-beec-1e8b36bb4b8a.svg"
alt=""
/> />
{/* Image Right Bottom */} {/* Image Right Bottom */}
<img <img
style={{ ...posStyle, right: 0, bottom: -100, height: 287 }} style={{ ...posStyle, right: 0, bottom: -100, height: 287 }}
src="https://gw.alipayobjects.com/zos/bmw-prod/bb74a2fb-bff1-4d0d-8c2d-2ade0cd9bb0d.svg" src="https://gw.alipayobjects.com/zos/bmw-prod/bb74a2fb-bff1-4d0d-8c2d-2ade0cd9bb0d.svg"
alt=""
/> />
</div> </div>

View File

@ -1,29 +1,14 @@
import React from 'react'; import React from 'react';
import { useLocale as useDumiLocale } from 'dumi'; import { useLocale as useDumiLocale } from 'dumi';
import { css } from '@emotion/react'; import { ConfigProvider } from 'antd';
import useLocale from '../../hooks/useLocale'; import useLocale from '../../hooks/useLocale';
import Banner from './components/Banner'; import Banner from './components/Banner';
import Group from './components/Group'; import Group from './components/Group';
import { useSiteData } from './components/util'; import { useSiteData } from './components/util';
import useSiteToken from '../../hooks/useSiteToken';
import Theme from './components/Theme'; import Theme from './components/Theme';
import BannerRecommends from './components/BannerRecommends'; import BannerRecommends from './components/BannerRecommends';
import ComponentsList from './components/ComponentsList'; import ComponentsList from './components/ComponentsList';
import DesignFramework from './components/DesignFramework'; import DesignFramework from './components/DesignFramework';
import { ConfigProvider } from 'antd';
const useStyle = () => {
const { token } = useSiteToken();
return {
container: css`
// padding: 0 116px;
// background: url(https://gw.alipayobjects.com/zos/bmw-prod/5741382d-cc22-4ede-b962-aea287a1d1a1/l4nq43o8_w2646_h1580.png);
// background-size: 20% 10%;
`,
};
};
const locales = { const locales = {
cn: { cn: {
@ -47,9 +32,7 @@ const Homepage: React.FC = () => {
const { id: localeId } = useDumiLocale(); const { id: localeId } = useDumiLocale();
const localeStr = localeId === 'zh-CN' ? 'cn' : 'en'; const localeStr = localeId === 'zh-CN' ? 'cn' : 'en';
const [siteData, loading] = useSiteData(); const [siteData] = useSiteData();
const style = useStyle();
return ( return (
<ConfigProvider theme={{ algorithm: undefined }}> <ConfigProvider theme={{ algorithm: undefined }}>
@ -58,7 +41,7 @@ const Homepage: React.FC = () => {
<BannerRecommends extras={siteData?.extras?.[localeStr]} icons={siteData?.icons} /> <BannerRecommends extras={siteData?.extras?.[localeStr]} icons={siteData?.icons} />
</Banner> </Banner>
<div css={style.container}> <div>
<Theme /> <Theme />
<Group <Group
background="#fff" background="#fff"
@ -79,6 +62,7 @@ const Homepage: React.FC = () => {
<img <img
style={{ position: 'absolute', left: 0, top: -50, height: 160 }} style={{ position: 'absolute', left: 0, top: -50, height: 160 }}
src="https://gw.alipayobjects.com/zos/bmw-prod/ba37a413-28e6-4be4-b1c5-01be1a0ebb1c.svg" src="https://gw.alipayobjects.com/zos/bmw-prod/ba37a413-28e6-4be4-b1c5-01be1a0ebb1c.svg"
alt=""
/> />
</> </>
} }

View File

@ -1 +1,3 @@
export { default } from '../theme-editor/index'; import ThemeEditor from '../theme-editor';
export default ThemeEditor;

View File

@ -1,20 +1,9 @@
import React, { useState } from 'react';
import { ThemeEditor } from 'antd-token-previewer'; import { ThemeEditor } from 'antd-token-previewer';
import { useState } from 'react';
import useLocale from '../../hooks/useLocale';
import { ConfigProvider } from 'antd'; import { ConfigProvider } from 'antd';
import { ThemeConfig } from 'antd/es/config-provider/context'; import type { ThemeConfig } from 'antd/es/config-provider/context';
const locales = {
cn: {
title: '主题编辑器',
},
en: {
title: 'Theme Editor',
},
};
const CustomTheme = () => { const CustomTheme = () => {
const [locale] = useLocale(locales);
const [theme, setTheme] = useState<ThemeConfig>({}); const [theme, setTheme] = useState<ThemeConfig>({});
return ( return (

View File

@ -6,7 +6,7 @@ import { type HastRoot, type UnifiedTransformer, unistUtilVisit } from 'dumi';
*/ */
function rehypeAntd(): UnifiedTransformer<HastRoot> { function rehypeAntd(): UnifiedTransformer<HastRoot> {
return (tree, vFile) => { return (tree, vFile) => {
const filename = (vFile.data.frontmatter as any).filename; const { filename } = vFile.data.frontmatter as any;
unistUtilVisit.visit(tree, 'element', (node) => { unistUtilVisit.visit(tree, 'element', (node) => {
if (node.tagName === 'DumiDemoGrid') { if (node.tagName === 'DumiDemoGrid') {

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
const APITable: React.FC = () => { const APITable: React.FC = () => (
// TODO: implement api table, depend on the new markdown data structure passed // TODO: implement api table, depend on the new markdown data structure passed
return <>API Table</>; <>API Table</>
}; );
export default APITable; export default APITable;

View File

@ -1,8 +1,10 @@
import { Alert, AlertProps } from 'antd'; import type { AlertProps } from 'antd';
import React, { FC } from 'react'; import { Alert } from 'antd';
import type { FC } from 'react';
import React from 'react';
const MdAlert: FC<AlertProps> = ({ style, ...props }) => { const MdAlert: FC<AlertProps> = ({ style, ...props }) => (
return <Alert {...props} style={{ margin: '24px 0', ...style }} />; <Alert {...props} style={{ margin: '24px 0', ...style }} />
}; );
export default MdAlert; export default MdAlert;

View File

@ -1,11 +1,11 @@
import React, { useState, memo, useMemo } from 'react'; import React, { memo, useMemo, useState } from 'react';
import { Link, useRouteMeta, useIntl, useSidebarData, Helmet } from 'dumi'; import { Link, useIntl, useSidebarData } from 'dumi';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { Input, Divider, Row, Col, Card, Typography, Tag, Space } from 'antd'; import { Card, Col, Divider, Input, Row, Space, Tag, Typography } from 'antd';
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import proComponentsList from './ProComponentsList';
import type { Component } from './ProComponentsList'; import type { Component } from './ProComponentsList';
import proComponentsList from './ProComponentsList';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';
const useStyle = () => { const useStyle = () => {
@ -80,11 +80,9 @@ const { Title } = Typography;
const Overview: React.FC = () => { const Overview: React.FC = () => {
const style = useStyle(); const style = useStyle();
const meta = useRouteMeta();
const data = useSidebarData(); const data = useSidebarData();
const { locale, formatMessage } = useIntl(); const { locale, formatMessage } = useIntl();
const documentTitle = `${meta.frontmatter.title} - Ant Design`;
const [search, setSearch] = useState<string>(''); const [search, setSearch] = useState<string>('');
@ -96,11 +94,11 @@ const Overview: React.FC = () => {
} }
}; };
const groups = useMemo<{ title: string; children: Component[] }[]>(() => { const groups = useMemo<{ title: string; children: Component[] }[]>(
return data () =>
data
.filter((item) => item.title) .filter((item) => item.title)
.map<{ title: string; children: Component[] }>((item) => { .map<{ title: string; children: Component[] }>((item) => ({
return {
title: item.title!, title: item.title!,
children: item.children.map((child) => ({ children: item.children.map((child) => ({
title: child.frontmatter.title, title: child.frontmatter.title,
@ -108,8 +106,7 @@ const Overview: React.FC = () => {
cover: child.frontmatter.cover, cover: child.frontmatter.cover,
link: child.link, link: child.link,
})), })),
}; }))
})
.concat([ .concat([
{ {
title: locale === 'zh-CN' ? '重型组件' : 'Others', title: locale === 'zh-CN' ? '重型组件' : 'Others',
@ -118,8 +115,9 @@ const Overview: React.FC = () => {
? proComponentsList ? proComponentsList
: proComponentsList.map((component) => ({ ...component, subtitle: '' })), : proComponentsList.map((component) => ({ ...component, subtitle: '' })),
}, },
]); ]),
}, [data, locale]); [data, locale],
);
return ( return (
<section className="markdown" ref={sectionRef}> <section className="markdown" ref={sectionRef}>

View File

@ -1,4 +1,4 @@
import React, { useContext, useLayoutEffect, useState } from 'react'; import React, { useContext, useState } from 'react';
import { DumiDemoGrid, FormattedMessage } from 'dumi'; import { DumiDemoGrid, FormattedMessage } from 'dumi';
import { Tooltip } from 'antd'; import { Tooltip } from 'antd';
import { BugFilled, BugOutlined, CodeFilled, CodeOutlined } from '@ant-design/icons'; import { BugFilled, BugOutlined, CodeFilled, CodeOutlined } from '@ant-design/icons';
@ -55,7 +55,7 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
</Tooltip> </Tooltip>
</span> </span>
{/* FIXME: find a new way instead of `key` to trigger re-render */} {/* FIXME: find a new way instead of `key` to trigger re-render */}
<DumiDemoGrid items={filteredItems} key={expandAll + '' + showDebug} /> <DumiDemoGrid items={filteredItems} key={`${expandAll}${showDebug}`} />
</div> </div>
); );
}; };

View File

@ -5,10 +5,8 @@ import JsonML from 'jsonml.js/lib/utils';
import toReactComponent from 'jsonml-to-react-element'; import toReactComponent from 'jsonml-to-react-element';
// @ts-ignore // @ts-ignore
import Prism from 'prismjs'; import Prism from 'prismjs';
import { useLocation } from 'dumi'; import { useLocation, useIntl, type IPreviewerProps } from 'dumi';
import { useIntl, type IPreviewerProps } from 'dumi';
import { ping } from '../../utils'; import { ping } from '../../utils';
import sylvanas from 'sylvanas';
let pingDeferrer: PromiseLike<boolean>; let pingDeferrer: PromiseLike<boolean>;
@ -56,13 +54,11 @@ export default function fromDumiProps<P extends object>(
toReactComponent(jsonML: any) { toReactComponent(jsonML: any) {
return toReactComponent(jsonML, [ return toReactComponent(jsonML, [
[ [
function (node: any) { (node: any) => JsonML.isElement(node) && JsonML.getTagName(node) === 'pre',
return JsonML.isElement(node) && JsonML.getTagName(node) === 'pre'; (node: any, index: any) => {
},
function (node: any, index: any) {
// @ts-ignore // @ts-ignore
// ref: https://github.com/benjycui/bisheng/blob/master/packages/bisheng/src/bisheng-plugin-highlight/lib/browser.js#L7 // ref: https://github.com/benjycui/bisheng/blob/master/packages/bisheng/src/bisheng-plugin-highlight/lib/browser.js#L7
var attr = JsonML.getAttributes(node); const attr = JsonML.getAttributes(node);
return React.createElement( return React.createElement(
'pre', 'pre',
{ {

View File

@ -120,13 +120,6 @@ class Demo extends React.Component {
}); });
} }
handleIframeReady = () => {
const { theme, setIframeTheme } = this.props;
if (this.iframeRef.current) {
// setIframeTheme(this.iframeRef.current, theme);
}
};
render() { render() {
const { state } = this; const { state } = this;
const { props } = this; const { props } = this;
@ -167,7 +160,7 @@ class Demo extends React.Component {
}); });
const localizedTitle = meta.title[locale] || meta.title; const localizedTitle = meta.title[locale] || meta.title;
const localizeIntro = content[locale] || content; const localizeIntro = content[locale] || content;
const introChildren = <div dangerouslySetInnerHTML={{ __html: localizeIntro }}></div>; const introChildren = <div dangerouslySetInnerHTML={{ __html: localizeIntro }} />;
const highlightClass = classNames('highlight-wrapper', { const highlightClass = classNames('highlight-wrapper', {
'highlight-wrapper-expand': codeExpand, 'highlight-wrapper-expand': codeExpand,
@ -458,7 +451,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
</Tooltip> </Tooltip>
</CopyToClipboard> </CopyToClipboard>
<Tooltip title={<FormattedMessage id="app.demo.separate" />}> <Tooltip title={<FormattedMessage id="app.demo.separate" />}>
<a className="code-box-code-action" target="_blank" rel="noreferer" href={src}> <a className="code-box-code-action" target="_blank" rel="noreferrer" href={src}>
<ExternalLinkIcon className="code-box-separate" /> <ExternalLinkIcon className="code-box-separate" />
</a> </a>
</Tooltip> </Tooltip>

View File

@ -3,9 +3,9 @@ import * as React from 'react';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { FormattedMessage, useIntl } from 'dumi'; import { FormattedMessage, useIntl } from 'dumi';
import { Tabs, Skeleton, Avatar, Divider, Empty } from 'antd'; import { Tabs, Skeleton, Avatar, Divider, Empty } from 'antd';
import { css } from '@emotion/react';
import { useSiteData } from '../../../pages/index/components/util'; import { useSiteData } from '../../../pages/index/components/util';
import type { Article, Authors } from '../../../pages/index/components/util'; import type { Article, Authors } from '../../../pages/index/components/util';
import { css } from '@emotion/react';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';
const useStyle = () => { const useStyle = () => {

View File

@ -86,7 +86,7 @@ const ResourceCard: React.FC<ResourceCardProps> = ({ resource }) => {
return ( return (
<Col xs={24} sm={12} md={8} lg={6} style={{ padding: 12 }}> <Col xs={24} sm={12} md={8} lg={6} style={{ padding: 12 }}>
<a css={styles.card} target="_blank" href={src}> <a css={styles.card} target="_blank" href={src} rel="noreferrer">
<img <img
css={styles.image} css={styles.image}
src={cover} src={cover}
@ -105,14 +105,12 @@ export type ResourceCardsProps = {
resources: Resource[]; resources: Resource[];
}; };
const ResourceCards: React.FC<ResourceCardsProps> = ({ resources }) => { const ResourceCards: React.FC<ResourceCardsProps> = ({ resources }) => (
return (
<Row style={{ margin: '-12px -12px 0 -12px' }}> <Row style={{ margin: '-12px -12px 0 -12px' }}>
{resources.map((item) => ( {resources.map((item) => (
<ResourceCard resource={item} key={item.title} /> <ResourceCard resource={item} key={item.title} />
))} ))}
</Row> </Row>
); );
};
export default ResourceCards; export default ResourceCards;

View File

@ -1,10 +1,14 @@
import React, { FC, useMemo } from 'react'; import type { FC } from 'react';
import React, { useMemo } from 'react';
/* eslint import/no-unresolved: 0 */
// @ts-ignore
import tokenMeta from 'antd/es/version/token-meta.json'; import tokenMeta from 'antd/es/version/token-meta.json';
import { getDesignToken } from 'antd-token-previewer'; import { getDesignToken } from 'antd-token-previewer';
import { Table, TableProps, Tag } from 'antd'; import type { TableProps } from 'antd';
import { Table } from 'antd';
import { css } from '@emotion/react';
import useLocale from '../../../hooks/useLocale'; import useLocale from '../../../hooks/useLocale';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';
import { css } from '@emotion/react';
type TokenTableProps = { type TokenTableProps = {
type: 'seed' | 'map' | 'alias'; type: 'seed' | 'map' | 'alias';
@ -89,7 +93,7 @@ const TokenTable: FC<TokenTableProps> = ({ type }) => {
boxShadow: 'inset 0 0 0 1px rgba(0, 0, 0, 0.06)', boxShadow: 'inset 0 0 0 1px rgba(0, 0, 0, 0.06)',
marginRight: 4, marginRight: 4,
}} }}
></span> />
)} )}
{typeof record.value !== 'string' ? JSON.stringify(record.value) : record.value} {typeof record.value !== 'string' ? JSON.stringify(record.value) : record.value}
</span> </span>
@ -97,16 +101,16 @@ const TokenTable: FC<TokenTableProps> = ({ type }) => {
}, },
]; ];
const data = useMemo<TokenData[]>(() => { const data = useMemo<TokenData[]>(
return tokenMeta[type].map((token) => { () =>
return { tokenMeta[type].map((token) => ({
name: token.name, name: token.name,
desc: lang === 'cn' ? token.desc : token.descEn, desc: lang === 'cn' ? token.desc : token.descEn,
type: token.type, type: token.type,
value: (defaultToken as any)[token.name], value: (defaultToken as any)[token.name],
}; })),
}); [type, lang],
}, [type, lang]); );
return <Table dataSource={data} columns={columns} pagination={false} bordered />; return <Table dataSource={data} columns={columns} pagination={false} bordered />;
}; };

View File

@ -1,3 +1,4 @@
import React from 'react';
import { Global, css } from '@emotion/react'; import { Global, css } from '@emotion/react';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';

View File

@ -6,7 +6,7 @@ const CommonHelmet = () => {
const [title, description] = useMemo(() => { const [title, description] = useMemo(() => {
const helmetTitle = `${meta.frontmatter.subtitle || ''} ${meta.frontmatter.title} - Ant Design`; const helmetTitle = `${meta.frontmatter.subtitle || ''} ${meta.frontmatter.title} - Ant Design`;
let helmetDescription = meta.frontmatter.description; const helmetDescription = meta.frontmatter.description;
return [helmetTitle, helmetDescription]; return [helmetTitle, helmetDescription];
}, [meta]); }, [meta]);

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import { Global, css } from '@emotion/react'; import { Global, css } from '@emotion/react';
import useSiteToken from '../../hooks/useSiteToken';
import { TinyColor } from '@ctrl/tinycolor'; import { TinyColor } from '@ctrl/tinycolor';
import ColorStyle from '../common/Color/ColorStyle'; import useSiteToken from '../../hooks/useSiteToken';
import ColorStyle from './Color/ColorStyle';
const GlobalStyles = () => { const GlobalStyles = () => {
const { token } = useSiteToken(); const { token } = useSiteToken();

View File

@ -1,10 +1,11 @@
import React, { ReactElement, useMemo } from 'react'; import type { ReactElement } from 'react';
import React, { useMemo } from 'react';
import { ClassNames, css } from '@emotion/react'; import { ClassNames, css } from '@emotion/react';
import useSiteToken from '../../hooks/useSiteToken'; import type { MenuProps } from 'antd';
import { Menu, MenuProps, Typography } from 'antd'; import type { MenuItemType } from 'antd/es/menu/hooks/useItems';
import useMenu from '../../hooks/useMenu';
import { MenuItemType } from 'antd/es/menu/hooks/useItems';
import { LeftOutlined, RightOutlined } from '@ant-design/icons'; import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import useMenu from '../../hooks/useMenu';
import useSiteToken from '../../hooks/useSiteToken';
const useStyle = () => { const useStyle = () => {
const { token } = useSiteToken(); const { token } = useSiteToken();
@ -114,9 +115,10 @@ const PrevAndNext = () => {
activeMenuItemIndex = i; activeMenuItemIndex = i;
} }
}); });
const prev = flatMenu[activeMenuItemIndex - 1]; return [
const next = flatMenu[activeMenuItemIndex + 1]; flatMenu[activeMenuItemIndex - 1] as MenuItemType,
return [prev as MenuItemType, next as MenuItemType]; flatMenu[activeMenuItemIndex + 1] as MenuItemType,
];
}, [menuItems, selectedKey]); }, [menuItems, selectedKey]);
return ( return (

View File

@ -1,7 +1,8 @@
import React, { FC } from 'react'; import type { FC } from 'react';
import React from 'react';
import { FloatButton, theme } from 'antd'; import { FloatButton, theme } from 'antd';
import ThemeIcon from './ThemeIcon';
import { DarkTheme, Light, CompactTheme } from 'antd-token-previewer/es/icons'; import { DarkTheme, Light, CompactTheme } from 'antd-token-previewer/es/icons';
import ThemeIcon from './ThemeIcon';
const { defaultAlgorithm, darkAlgorithm, compactAlgorithm } = theme; const { defaultAlgorithm, darkAlgorithm, compactAlgorithm } = theme;

View File

@ -1,17 +1,18 @@
import React, { useEffect, useMemo, useRef, useLayoutEffect } from 'react'; import React, { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
import 'dayjs/locale/zh-cn'; import 'dayjs/locale/zh-cn';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useOutlet, useSearchParams, Helmet } from 'dumi'; import { Helmet, useOutlet, useSearchParams } from 'dumi';
import Header from 'dumi/theme/slots/Header';
import Footer from 'dumi/theme/slots/Footer';
import '../../static/style'; import '../../static/style';
import useLocation from '../../../hooks/useLocation'; import type { DirectionType } from 'antd/es/config-provider';
import SiteContext from '../../slots/SiteContext'; import ConfigProvider from 'antd/es/config-provider';
import ConfigProvider, { DirectionType } from 'antd/es/config-provider';
import classNames from 'classnames'; import classNames from 'classnames';
import useLocale from '../../../hooks/useLocale';
import zhCN from 'antd/es/locale/zh_CN'; import zhCN from 'antd/es/locale/zh_CN';
import { createCache, StyleProvider } from '@ant-design/cssinjs'; import { createCache, StyleProvider } from '@ant-design/cssinjs';
import Header from '../../slots/Header';
import Footer from '../../slots/Footer';
import useLocale from '../../../hooks/useLocale';
import SiteContext from '../../slots/SiteContext';
import useLocation from '../../../hooks/useLocation';
import ResourceLayout from '../ResourceLayout'; import ResourceLayout from '../ResourceLayout';
import GlobalStyles from '../../common/GlobalStyles'; import GlobalStyles from '../../common/GlobalStyles';
import SidebarLayout from '../SidebarLayout'; import SidebarLayout from '../SidebarLayout';
@ -94,9 +95,9 @@ const DocLayout: React.FC = () => {
} }
}, [location]); }, [location]);
const changeDirection = (direction: DirectionType): void => { const changeDirection = (dir: DirectionType): void => {
setDirection(direction); setDirection(dir);
if (direction === 'ltr') { if (dir === 'ltr') {
searchParams.delete('direction'); searchParams.delete('direction');
} else { } else {
searchParams.set('direction', 'rtl'); searchParams.set('direction', 'rtl');
@ -115,17 +116,27 @@ const DocLayout: React.FC = () => {
<Footer /> <Footer />
</> </>
); );
} else if (pathname.startsWith('/docs/resource')) { }
if (pathname.startsWith('/docs/resource')) {
return <ResourceLayout>{outlet}</ResourceLayout>; return <ResourceLayout>{outlet}</ResourceLayout>;
} else if (pathname.startsWith('/theme-editor')) { }
return <>{outlet}</>; if (pathname.startsWith('/theme-editor')) {
return outlet;
} }
return <SidebarLayout>{outlet}</SidebarLayout>; return <SidebarLayout>{outlet}</SidebarLayout>;
}, [pathname, outlet]); }, [pathname, outlet]);
const siteContextValue = useMemo(
() => ({
isMobile,
direction,
}),
[isMobile, direction],
);
return ( return (
<StyleProvider cache={styleCache}> <StyleProvider cache={styleCache}>
<SiteContext.Provider value={{ isMobile, direction }}> <SiteContext.Provider value={siteContextValue}>
<Helmet encodeSpecialCharacters={false}> <Helmet encodeSpecialCharacters={false}>
<html <html
lang={lang} lang={lang}

View File

@ -1,8 +1,9 @@
import React, { useLayoutEffect } from 'react'; import React, { useLayoutEffect } from 'react';
import { useOutlet } from 'dumi'; import { useOutlet } from 'dumi';
import { ConfigProvider, theme as antdTheme } from 'antd'; import { ConfigProvider, theme as antdTheme } from 'antd';
import { ThemeConfig } from 'antd/es/config-provider/context'; import type { ThemeConfig } from 'antd/es/config-provider/context';
import ThemeContext, { ThemeContextProps } from '../slots/ThemeContext'; import type { ThemeContextProps } from '../slots/ThemeContext';
import ThemeContext from '../slots/ThemeContext';
import ThemeSwitch from '../common/ThemeSwitch'; import ThemeSwitch from '../common/ThemeSwitch';
import useLocation from '../../hooks/useLocation'; import useLocation from '../../hooks/useLocation';

View File

@ -1,10 +1,9 @@
import * as React from 'react'; import * as React from 'react';
import classNames from 'classnames';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import { Tabs } from 'antd'; import { Tabs } from 'antd';
import { css } from '@emotion/react';
import scrollTo from '../../../../components/_util/scrollTo'; import scrollTo from '../../../../components/_util/scrollTo';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';
import { css } from '@emotion/react';
const useStyle = () => { const useStyle = () => {
const { token } = useSiteToken(); const { token } = useSiteToken();

View File

@ -1,8 +1,9 @@
import React, { FC, PropsWithChildren } from 'react'; import type { FC, PropsWithChildren } from 'react';
import React from 'react';
import { useRouteMeta, FormattedMessage } from 'dumi'; import { useRouteMeta, FormattedMessage } from 'dumi';
import Footer from 'dumi/theme/slots/Footer';
import { Layout, Typography, ConfigProvider } from 'antd'; import { Layout, Typography, ConfigProvider } from 'antd';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import Footer from '../../slots/Footer';
import AffixTabs from './AffixTabs'; import AffixTabs from './AffixTabs';
import EditButton from '../../common/EditButton'; import EditButton from '../../common/EditButton';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';

View File

@ -1,16 +1,15 @@
import React, { FC, PropsWithChildren } from 'react'; import type { FC, PropsWithChildren } from 'react';
import React from 'react';
import Sidebar from '../../slots/Sidebar'; import Sidebar from '../../slots/Sidebar';
import Content from '../../slots/Content'; import Content from '../../slots/Content';
import CommonHelmet from '../../common/CommonHelmet'; import CommonHelmet from '../../common/CommonHelmet';
const SidebarLayout: FC<PropsWithChildren<{}>> = ({ children }) => { const SidebarLayout: FC<PropsWithChildren<{}>> = ({ children }) => (
return (
<main style={{ display: 'flex', marginTop: 40 }}> <main style={{ display: 'flex', marginTop: 40 }}>
<CommonHelmet /> <CommonHelmet />
<Sidebar /> <Sidebar />
<Content>{children}</Content> <Content>{children}</Content>
</main> </main>
); );
};
export default SidebarLayout; export default SidebarLayout;

View File

@ -5,10 +5,6 @@
"app.theme.switch.compact": "Compact theme", "app.theme.switch.compact": "Compact theme",
"app.header.search": "Search...", "app.header.search": "Search...",
"app.header.menu.documentation": "Docs", "app.header.menu.documentation": "Docs",
"app.header.menu.development": "Development",
"app.header.menu.components": "Components",
"app.header.menu.spec": "Design",
"app.header.menu.resource": "Resources",
"app.header.menu.more": "More", "app.header.menu.more": "More",
"app.header.menu.mobile": "Mobile", "app.header.menu.mobile": "Mobile",
"app.header.menu.pro.v4": "Ant Design Pro", "app.header.menu.pro.v4": "Ant Design Pro",

View File

@ -5,10 +5,6 @@
"app.theme.switch.compact": "紧凑主题", "app.theme.switch.compact": "紧凑主题",
"app.header.search": "全文本搜索...", "app.header.search": "全文本搜索...",
"app.header.menu.documentation": "文档", "app.header.menu.documentation": "文档",
"app.header.menu.development": "研发",
"app.header.menu.components": "组件",
"app.header.menu.spec": "设计",
"app.header.menu.resource": "资源",
"app.header.menu.more": "更多", "app.header.menu.more": "更多",
"app.header.menu.mobile": "移动版", "app.header.menu.mobile": "移动版",
"app.header.menu.pro.v4": "Ant Design Pro", "app.header.menu.pro.v4": "Ant Design Pro",

View File

@ -81,7 +81,7 @@ const RoutesPlugin = (api: IApi) => {
let styles = ''; let styles = '';
// extract all emotion style tags from body // extract all emotion style tags from body
file.content = file.content.replace(/<style data-emotion[\s\S\n]+?<\/style>/g, (s) => { file.content = file.content.replace(/<style data-emotion[\S\s]+?<\/style>/g, (s) => {
styles += s; styles += s;
return ''; return '';

View File

@ -1,16 +1,17 @@
import React, { ReactNode, useMemo, useState, useLayoutEffect, useContext } from 'react'; import type { ReactNode } from 'react';
import { useIntl, useRouteMeta } from 'dumi'; import React, { useMemo, useState, useLayoutEffect, useContext } from 'react';
import Footer from 'dumi/theme/slots/Footer'; import { useIntl, useRouteMeta, FormattedMessage } from 'dumi';
import { Col, Typography, Avatar, Tooltip, Affix, Anchor } from 'antd'; import { Col, Typography, Avatar, Tooltip, Affix, Anchor } from 'antd';
import EditButton from '../../common/EditButton';
import { FormattedMessage } from 'dumi';
import useLocation from '../../../hooks/useLocation';
import ContributorsList from '@qixian.cs/github-contributors-list'; import ContributorsList from '@qixian.cs/github-contributors-list';
import useSiteToken from '../../../hooks/useSiteToken';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import PrevAndNext from '../../common/PrevAndNext';
import DemoContext, { DemoContextProps } from '../DemoContext';
import classNames from 'classnames'; import classNames from 'classnames';
import Footer from '../Footer';
import EditButton from '../../common/EditButton';
import useLocation from '../../../hooks/useLocation';
import useSiteToken from '../../../hooks/useSiteToken';
import PrevAndNext from '../../common/PrevAndNext';
import type { DemoContextProps } from '../DemoContext';
import DemoContext from '../DemoContext';
import SiteContext from '../SiteContext'; import SiteContext from '../SiteContext';
const useStyle = () => { const useStyle = () => {
@ -120,8 +121,9 @@ const Content: React.FC<{ children: ReactNode }> = ({ children }) => {
[showDebug, debugDemos], [showDebug, debugDemos],
); );
const anchorItems = useMemo(() => { const anchorItems = useMemo(
return meta.toc.reduce<AnchorItem[]>((result, item) => { () =>
meta.toc.reduce<AnchorItem[]>((result, item) => {
if (item.depth === 2) { if (item.depth === 2) {
result.push({ ...item }); result.push({ ...item });
} else if (item.depth === 3) { } else if (item.depth === 3) {
@ -132,8 +134,9 @@ const Content: React.FC<{ children: ReactNode }> = ({ children }) => {
} }
} }
return result; return result;
}, []); }, []),
}, [meta.toc]); [meta.toc],
);
const isRTL = direction === 'rtl'; const isRTL = direction === 'rtl';

View File

@ -17,12 +17,12 @@ import {
ZhihuOutlined, ZhihuOutlined,
YuqueFilled, YuqueFilled,
} from '@ant-design/icons'; } from '@ant-design/icons';
import useLocation from '../../../hooks/useLocation';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import useLocale from '../../../hooks/useLocale';
import useSiteToken from '../../../hooks/useSiteToken';
import { TinyColor } from '@ctrl/tinycolor'; import { TinyColor } from '@ctrl/tinycolor';
import getAlphaColor from 'antd/es/theme/util/getAlphaColor'; import getAlphaColor from 'antd/es/theme/util/getAlphaColor';
import useLocation from '../../../hooks/useLocation';
import useLocale from '../../../hooks/useLocale';
import useSiteToken from '../../../hooks/useSiteToken';
import AdditionalInfo from './AdditionalInfo'; import AdditionalInfo from './AdditionalInfo';
const locales = { const locales = {

View File

@ -1,77 +0,0 @@
import * as React from 'react';
import classNames from 'classnames';
// @ts-ignore
import GitHubButton from 'react-github-button';
import useSiteToken from '../../../hooks/useSiteToken';
import { css } from '@emotion/react';
const useStyle = () => {
const { token } = useSiteToken();
const { antCls, colorPrimary } = token;
return {
githubBtn: css`
display: flex;
flex-flow: nowrap;
height: auto;
.gh-btn {
height: auto;
padding: 1px 4px;
background: transparent;
border: 0;
.gh-ico {
width: 20px;
height: 20px;
margin: 0;
}
.gh-text {
display: none;
}
}
.gh-count {
height: auto;
padding: 4px 8px;
color: #000;
font-weight: normal;
background: #fff;
&:hover {
color: ${colorPrimary};
}
}
${antCls}-row-rtl & {
.gh-count {
display: none !important;
}
}
`,
responsiveMode: css`
.gh-count {
display: none !important;
}
`,
};
};
export interface GithubProps {
responsive: null | 'narrow' | 'crowded';
}
export default ({ responsive }: GithubProps) => {
const style = useStyle();
return (
<GitHubButton
css={[style.githubBtn, responsive && style.responsiveMode]}
type="stargazers"
namespace="ant-design"
repo="ant-design"
/>
);
};

View File

@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { Link, useLocation } from 'dumi'; import { Link, useLocation } from 'dumi';
import * as utils from '../../utils';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import * as utils from '../../utils';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';
const useStyle = () => { const useStyle = () => {

View File

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
import { Dropdown, Menu, Button } from 'antd'; import { Button, Dropdown } from 'antd';
import { FormattedMessage } from 'dumi'; import { FormattedMessage } from 'dumi';
import { DownOutlined } from '@ant-design/icons'; import { DownOutlined } from '@ant-design/icons';
import type { SharedProps } from './interface'; import type { SharedProps } from './interface';

View File

@ -1,15 +1,35 @@
import * as React from 'react'; import * as React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Link, useLocation, FormattedMessage } from 'dumi'; import { FormattedMessage, Link, useFullSidebarData, useLocation } from 'dumi';
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
import { MenuOutlined } from '@ant-design/icons';
import { Menu } from 'antd'; import { Menu } from 'antd';
import { MenuOutlined } from '@ant-design/icons';
import { css } from '@emotion/react';
import { getEcosystemGroup } from './More'; import { getEcosystemGroup } from './More';
import * as utils from '../../utils'; import * as utils from '../../utils';
import type { SharedProps } from './interface'; import type { SharedProps } from './interface';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';
import { css } from '@emotion/react'; import useLocale from '../../../hooks/useLocale';
// ============================= Theme =============================
const locales = {
cn: {
design: '设计',
development: '研发',
components: '组件',
resources: '资源',
blog: '博客',
},
en: {
design: 'Design',
development: 'Development',
components: 'Components',
resources: 'Resources',
blog: 'Blog',
},
};
// ============================= Style =============================
const useStyle = () => { const useStyle = () => {
const { token } = useSiteToken(); const { token } = useSiteToken();
@ -83,7 +103,6 @@ export interface NavigationProps extends SharedProps {
isClient: boolean; isClient: boolean;
responsive: null | 'narrow' | 'crowded'; responsive: null | 'narrow' | 'crowded';
directionText: string; directionText: string;
showTechUIButton: boolean;
onLangChange: () => void; onLangChange: () => void;
onDirectionChange: () => void; onDirectionChange: () => void;
} }
@ -94,11 +113,14 @@ export default ({
isMobile, isMobile,
responsive, responsive,
directionText, directionText,
showTechUIButton,
onLangChange, onLangChange,
onDirectionChange, onDirectionChange,
}: NavigationProps) => { }: NavigationProps) => {
const { pathname, search } = useLocation(); const { pathname, search } = useLocation();
const [locale] = useLocale(locales);
const sidebarData = useFullSidebarData();
const blogList = sidebarData['/docs/blog']?.[0]?.children || [];
const style = useStyle(); const style = useStyle();
@ -160,7 +182,7 @@ export default ({
{ {
label: ( label: (
<Link to={utils.getLocalizedPathname('/docs/spec/introduce', isZhCN, search)}> <Link to={utils.getLocalizedPathname('/docs/spec/introduce', isZhCN, search)}>
<FormattedMessage id="app.header.menu.spec" /> {locale.design}
</Link> </Link>
), ),
key: 'docs/spec', key: 'docs/spec',
@ -168,7 +190,7 @@ export default ({
{ {
label: ( label: (
<Link to={utils.getLocalizedPathname('/docs/react/introduce', isZhCN, search)}> <Link to={utils.getLocalizedPathname('/docs/react/introduce', isZhCN, search)}>
<FormattedMessage id="app.header.menu.development" /> {locale.development}
</Link> </Link>
), ),
key: 'docs/react', key: 'docs/react',
@ -176,29 +198,29 @@ export default ({
{ {
label: ( label: (
<Link to={utils.getLocalizedPathname('/components/overview/', isZhCN, search)}> <Link to={utils.getLocalizedPathname('/components/overview/', isZhCN, search)}>
<FormattedMessage id="app.header.menu.components" /> {locale.components}
</Link> </Link>
), ),
key: 'components', key: 'components',
}, },
blogList.length
? {
label: (
<Link to={utils.getLocalizedPathname(blogList[0].link, isZhCN, search)}>
{locale.blog}
</Link>
),
key: 'docs/blog',
}
: null,
{ {
label: ( label: (
<Link to={utils.getLocalizedPathname('/docs/resources', isZhCN, search)}> <Link to={utils.getLocalizedPathname('/docs/resources', isZhCN, search)}>
<FormattedMessage id="app.header.menu.resource" /> {locale.resources}
</Link> </Link>
), ),
key: 'docs/resources', key: 'docs/resources',
}, },
showTechUIButton
? {
label: (
<a href="https://techui.alipay.com" target="__blank" rel="noopener noreferrer">
TechUI
</a>
),
key: 'tech-ui',
}
: null,
isZhCN && isZhCN &&
isClient && isClient &&
window.location.host !== 'ant-design.antgroup.com' && window.location.host !== 'ant-design.antgroup.com' &&

View File

@ -0,0 +1,143 @@
import * as React from 'react';
import { Tooltip } from 'antd';
import { css } from '@emotion/react';
import useSiteToken from '../../../hooks/useSiteToken';
export interface LangBtnProps {
label1: React.ReactNode;
label2: React.ReactNode;
tooltip1?: React.ReactNode;
tooltip2?: React.ReactNode;
value: 1 | 2;
pure?: boolean;
onClick?: React.MouseEventHandler;
}
const BASE_SIZE = '1.2em';
const useStyle = () => {
const { token } = useSiteToken();
const { controlHeight, motionDurationMid } = token;
return {
btn: css`
color: ${token.colorText};
border-color: ${token.colorBorder};
padding: 0 !important;
width: ${controlHeight}px;
height: ${controlHeight}px;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
border-radius: ${token.borderRadius}px;
transition: all ${motionDurationMid};
cursor: pointer;
.btn-inner {
transition: all ${motionDurationMid};
}
&:hover {
background: ${token.colorBgTextHover};
.btn-inner {
transform: scale(1.1);
}
}
img {
width: ${BASE_SIZE};
height: ${BASE_SIZE};
}
.anticon {
font-size: ${BASE_SIZE};
}
`,
};
};
export default function LangBtn({
label1,
label2,
tooltip1,
tooltip2,
value,
pure,
onClick,
}: LangBtnProps) {
const { token } = useSiteToken();
const style = useStyle();
let label1Style: React.CSSProperties;
let label2Style: React.CSSProperties;
const iconStyle: React.CSSProperties = {
position: 'absolute',
fontSize: BASE_SIZE,
lineHeight: 1,
border: `1px solid ${token.colorText}`,
color: token.colorText,
};
const fontStyle: React.CSSProperties = {
left: '-5%',
top: 0,
zIndex: 1,
background: token.colorText,
color: token.colorTextLightSolid,
transformOrigin: '0 0',
transform: `scale(0.7)`,
};
const backStyle: React.CSSProperties = {
right: '-5%',
bottom: 0,
zIndex: 0,
transformOrigin: '100% 100%',
transform: `scale(0.5)`,
};
if (value === 1) {
label1Style = fontStyle;
label2Style = backStyle;
} else {
label1Style = backStyle;
label2Style = fontStyle;
}
let node = (
<button onClick={onClick} css={[style.btn]} key="lang-button">
<div className="btn-inner">
{pure && (value === 1 ? label1 : label2)}
{!pure && (
<div style={{ position: 'relative', width: BASE_SIZE, height: BASE_SIZE }}>
<span
style={{
...iconStyle,
...label1Style,
}}
>
{label1}
</span>
<span
style={{
...iconStyle,
...label2Style,
}}
>
{label2}
</span>
</div>
)}
</div>
</button>
);
if (tooltip1 || tooltip2) {
node = <Tooltip title={value === 1 ? tooltip1 : tooltip2}>{node}</Tooltip>;
}
return node;
}

View File

@ -1,24 +1,22 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { FormattedMessage, useIntl } from 'dumi'; import { useLocation } from 'dumi';
import DumiSearchBar from 'dumi/theme-default/slots/SearchBar'; import DumiSearchBar from 'dumi/theme-default/slots/SearchBar';
import classNames from 'classnames'; import classNames from 'classnames';
import { Button, Col, Modal, Popover, Row, Select, Typography } from 'antd'; import { Col, Modal, Popover, Row, Select, Typography } from 'antd';
import { MenuOutlined } from '@ant-design/icons'; import { GithubOutlined, MenuOutlined } from '@ant-design/icons';
import canUseDom from 'rc-util/lib/Dom/canUseDom';
import type { DirectionType } from 'antd/es/config-provider'; import type { DirectionType } from 'antd/es/config-provider';
import { ClassNames, css } from '@emotion/react';
import * as utils from '../../utils'; import * as utils from '../../utils';
import { getThemeConfig, ping } from '../../utils'; import { getThemeConfig, ping } from '../../utils';
import packageJson from '../../../../package.json'; import packageJson from '../../../../package.json';
import Logo from './Logo'; import Logo from './Logo';
import More from './More'; import More from './More';
import Navigation from './Navigation'; import Navigation from './Navigation';
import Github from './Github';
import type { SiteContextProps } from '../SiteContext'; import type { SiteContextProps } from '../SiteContext';
import SiteContext from '../SiteContext'; import SiteContext from '../SiteContext';
import { useLocation, useNavigate } from 'dumi';
import { ClassNames, css } from '@emotion/react';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';
import useLocale from '../../../hooks/useLocale'; import useLocale from '../../../hooks/useLocale';
import SwitchBtn from './SwitchBtn';
const RESPONSIVE_XS = 1120; const RESPONSIVE_XS = 1120;
const RESPONSIVE_SM = 1200; const RESPONSIVE_SM = 1200;
@ -55,8 +53,8 @@ const useStyle = () => {
} }
.nav-search-wrapper { .nav-search-wrapper {
flex: auto;
display: flex; display: flex;
flex: auto;
} }
.dumi-default-search-bar { .dumi-default-search-bar {
@ -121,10 +119,6 @@ const useStyle = () => {
} }
} }
`, `,
headerButton: css`
color: ${token.colorText};
border-color: ${token.colorBorder};
`,
popoverMenu: { popoverMenu: {
width: 300, width: 300,
@ -139,18 +133,6 @@ export interface HeaderProps {
changeDirection: (direction: DirectionType) => void; changeDirection: (direction: DirectionType) => void;
} }
let docsearch: any;
const triggerDocSearchImport = () => {
if (docsearch) {
return Promise.resolve();
}
// @ts-ignore
return import('docsearch.js').then((ds) => {
docsearch = ds.default;
});
};
const V5_NOTIFICATION = 'antd@4.0.0-notification-sent'; const V5_NOTIFICATION = 'antd@4.0.0-notification-sent';
const SHOULD_OPEN_ANT_DESIGN_MIRROR_MODAL = 'ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL'; const SHOULD_OPEN_ANT_DESIGN_MIRROR_MODAL = 'ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL';
@ -166,12 +148,10 @@ interface HeaderState {
menuVisible: boolean; menuVisible: boolean;
windowWidth: number; windowWidth: number;
searching: boolean; searching: boolean;
showTechUIButton: boolean;
} }
// ================================= Header ================================= // ================================= Header =================================
const Header: React.FC<HeaderProps> = (props) => { const Header: React.FC<HeaderProps> = (props) => {
const intl = useIntl();
const { changeDirection } = props; const { changeDirection } = props;
const [isClient, setIsClient] = React.useState(false); const [isClient, setIsClient] = React.useState(false);
const [locale, lang] = useLocale(locales); const [locale, lang] = useLocale(locales);
@ -207,13 +187,11 @@ const Header: React.FC<HeaderProps> = (props) => {
menuVisible: false, menuVisible: false,
windowWidth: 1400, windowWidth: 1400,
searching: false, searching: false,
showTechUIButton: false,
}); });
const { direction, isMobile } = useContext<SiteContextProps>(SiteContext); const { direction, isMobile } = useContext<SiteContextProps>(SiteContext);
const pingTimer = useRef<NodeJS.Timeout | null>(null); const pingTimer = useRef<NodeJS.Timeout | null>(null);
const location = useLocation(); const location = useLocation();
const { pathname, search } = location; const { pathname, search } = location;
const navigate = useNavigate();
const style = useStyle(); const style = useStyle();
@ -223,9 +201,6 @@ const Header: React.FC<HeaderProps> = (props) => {
const onWindowResize = useCallback(() => { const onWindowResize = useCallback(() => {
setHeaderState((prev) => ({ ...prev, windowWidth: window.innerWidth })); setHeaderState((prev) => ({ ...prev, windowWidth: window.innerWidth }));
}, []); }, []);
const onTriggerSearching = useCallback((searching: boolean) => {
setHeaderState((prev) => ({ ...prev, searching }));
}, []);
const handleShowMenu = useCallback(() => { const handleShowMenu = useCallback(() => {
setHeaderState((prev) => ({ ...prev, menuVisible: true })); setHeaderState((prev) => ({ ...prev, menuVisible: true }));
}, []); }, []);
@ -246,7 +221,6 @@ const Header: React.FC<HeaderProps> = (props) => {
window.addEventListener('resize', onWindowResize); window.addEventListener('resize', onWindowResize);
pingTimer.current = ping((status) => { pingTimer.current = ping((status) => {
if (status !== 'timeout' && status !== 'error') { if (status !== 'timeout' && status !== 'error') {
setHeaderState((prev) => ({ ...prev, showTechUIButton: true }));
if ( if (
// process.env.NODE_ENV === 'production' && // process.env.NODE_ENV === 'production' &&
window.location.host !== 'ant-design.antgroup.com' && window.location.host !== 'ant-design.antgroup.com' &&
@ -317,7 +291,7 @@ const Header: React.FC<HeaderProps> = (props) => {
[direction], [direction],
); );
const { menuVisible, windowWidth, searching, showTechUIButton } = headerState; const { menuVisible, windowWidth, searching } = headerState;
const docVersions: Record<string, string> = { const docVersions: Record<string, string> = {
[antdVersion]: antdVersion, [antdVersion]: antdVersion,
...themeConfig?.docVersions, ...themeConfig?.docVersions,
@ -356,7 +330,6 @@ const Header: React.FC<HeaderProps> = (props) => {
{...sharedProps} {...sharedProps}
responsive={responsive} responsive={responsive}
isMobile={isMobile} isMobile={isMobile}
showTechUIButton={showTechUIButton}
directionText={nextDirectionText} directionText={nextDirectionText}
onLangChange={onLangChange} onLangChange={onLangChange}
onDirectionChange={onDirectionChange} onDirectionChange={onDirectionChange}
@ -366,6 +339,7 @@ const Header: React.FC<HeaderProps> = (props) => {
let menu: (React.ReactElement | null)[] = [ let menu: (React.ReactElement | null)[] = [
navigationNode, navigationNode,
<Popover <Popover
key="version"
open={!!notify} open={!!notify}
title={locale.title} title={locale.title}
content={ content={
@ -415,19 +389,44 @@ const Header: React.FC<HeaderProps> = (props) => {
{versionOptions} {versionOptions}
</Select> </Select>
</Popover>, </Popover>,
<Button size="small" onClick={onLangChange} css={style.headerButton} key="lang-button">
<FormattedMessage id="app.header.lang" />
</Button>,
<Button
size="small"
onClick={onDirectionChange}
css={style.headerButton}
key="direction-button"
>
{nextDirectionText}
</Button>,
<More key="more" {...sharedProps} />, <More key="more" {...sharedProps} />,
<Github key="github" responsive={responsive} />, <SwitchBtn
key="lang"
onClick={onLangChange}
value={utils.isZhCN(pathname) ? 1 : 2}
label1="中"
label2="En"
tooltip1="中文 / English"
tooltip2="English / 中文"
/>,
<SwitchBtn
key="direction"
onClick={onDirectionChange}
value={direction === 'rtl' ? 2 : 1}
label1={
<img
src="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*6k0CTJA-HxUAAAAAAAAAAAAADrJ8AQ/original"
alt="direction"
/>
}
tooltip1="LTR"
label2={
<img
src="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*SZoaQqm2hwsAAAAAAAAAAAAADrJ8AQ/original"
alt="LTR"
/>
}
tooltip2="RTL"
pure
/>,
<a
key="github"
href="https://github.com/ant-design/ant-design"
target="_blank"
rel="noreferrer"
>
<SwitchBtn value={1} label1={<GithubOutlined />} tooltip1="Github" label2={null} pure />
</a>,
]; ];
if (windowWidth < RESPONSIVE_XS) { if (windowWidth < RESPONSIVE_XS) {
@ -447,9 +446,9 @@ const Header: React.FC<HeaderProps> = (props) => {
<header css={style.header} className={headerClassName}> <header css={style.header} className={headerClassName}>
{isMobile && ( {isMobile && (
<ClassNames> <ClassNames>
{({ css }) => ( {({ css: cssFn }) => (
<Popover <Popover
overlayClassName={css(style.popoverMenu)} overlayClassName={cssFn(style.popoverMenu)}
placement="bottomRight" placement="bottomRight"
content={menu} content={menu}
trigger="click" trigger="click"

View File

@ -2,10 +2,10 @@ import React, { useContext } from 'react';
import { useSidebarData } from 'dumi'; import { useSidebarData } from 'dumi';
import { Affix, Col, Menu } from 'antd'; import { Affix, Col, Menu } from 'antd';
import MobileMenu from 'rc-drawer'; import MobileMenu from 'rc-drawer';
import { css } from '@emotion/react';
import SiteContext from '../SiteContext'; import SiteContext from '../SiteContext';
import useMenu from '../../../hooks/useMenu'; import useMenu from '../../../hooks/useMenu';
import useSiteToken from '../../../hooks/useSiteToken'; import useSiteToken from '../../../hooks/useSiteToken';
import { css } from '@emotion/react';
const useStyle = () => { const useStyle = () => {
const { token } = useSiteToken(); const { token } = useSiteToken();

View File

@ -1,5 +1,5 @@
import { createContext } from 'react'; import { createContext } from 'react';
import { ThemeConfig } from 'antd/es/config-provider/context'; import type { ThemeConfig } from 'antd/es/config-provider/context';
export type ThemeContextProps = { export type ThemeContextProps = {
theme: ThemeConfig; theme: ThemeConfig;

View File

@ -139,6 +139,7 @@ export function getLocalizedPathname(
fullPath = pathname.replace(/\/$/, '-cn/'); fullPath = pathname.replace(/\/$/, '-cn/');
} else { } else {
fullPath = `${pathname}-cn`; fullPath = `${pathname}-cn`;
fullPath = fullPath.replace(/(-cn)+/, '-cn');
} }
if (hash) { if (hash) {

View File

@ -3,6 +3,8 @@
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "@emotion/react", "jsxImportSource": "@emotion/react",
"esModuleInterop": true,
"resolveJsonModule": true,
"baseUrl": "../", "baseUrl": "../",
"paths": { "paths": {
"@@/*": [".dumi/tmp/*"], "@@/*": [".dumi/tmp/*"],

View File

@ -11,6 +11,8 @@ lib/**/*
locale locale
server server
.dumi/tmp .dumi/tmp
.dumi/tmp-production
!.dumi/
node_modules node_modules
_site _site
dist dist

View File

@ -98,6 +98,22 @@ module.exports = {
'react/no-access-state-in-setstate': 0, 'react/no-access-state-in-setstate': 0,
}, },
}, },
{
files: ['.dumi/**/*.ts', '.dumi/**/*.tsx', '.dumi/**/*.js', '.dumi/**/*.jsx'],
rules: {
'import/no-extraneous-dependencies': 0,
'no-console': 0,
'compat/compat': 0,
'react/no-unstable-nested-components': 0,
'jsx-a11y/control-has-associated-label': 0,
'class-methods-use-this': 0,
'react/no-access-state-in-setstate': 0,
'react/no-unknown-property': ['error', { ignore: ['css'] }],
'react/no-array-index-key': 0,
'react/button-has-type': 0,
'react/no-danger': 0,
},
},
], ],
rules: { rules: {
'react/jsx-one-expression-per-line': 0, 'react/jsx-one-expression-per-line': 0,

View File

@ -16,7 +16,9 @@ jobs:
- name: send to dingtalk - name: send to dingtalk
uses: visiky/dingtalk-release-notify@main uses: visiky/dingtalk-release-notify@main
with: with:
DING_TALK_TOKEN: ${{ secrets.DINGDING_BOT_TOKEN }} DING_TALK_TOKEN: |
${{ secrets.DINGDING_BOT_TOKEN }}
${{ secrets.DINGDING_BOT_COLLABORATOR_TOKEN }}
notify_title: '🔥 @${{ github.event.discussion.user.login }} 创建了讨论:${{ github.event.discussion.title }}' notify_title: '🔥 @${{ github.event.discussion.user.login }} 创建了讨论:${{ github.event.discussion.title }}'
notify_body: '### 🔥 @${{ github.event.discussion.user.login }} 创建了讨论:[${{ github.event.discussion.title }}](${{ github.event.discussion.html_url }}) <hr /> ![](https://gw.alipayobjects.com/zos/antfincdn/5Cl2G7JjF/jieping2022-03-20%252520xiawu11.06.04.png)' notify_body: '### 🔥 @${{ github.event.discussion.user.login }} 创建了讨论:[${{ github.event.discussion.title }}](${{ github.event.discussion.html_url }}) <hr /> ![](https://gw.alipayobjects.com/zos/antfincdn/5Cl2G7JjF/jieping2022-03-20%252520xiawu11.06.04.png)'
notify_footer: '> 💬 欢迎前往 GitHub 进行讨论,社区可能需要你的帮助。' notify_footer: '> 💬 欢迎前往 GitHub 进行讨论,社区可能需要你的帮助。'

View File

@ -89,7 +89,9 @@ jobs:
- name: send to dingtalk - name: send to dingtalk
uses: visiky/dingtalk-release-notify@main uses: visiky/dingtalk-release-notify@main
with: with:
DING_TALK_TOKEN: ${{ secrets.DINGDING_BOT_TOKEN }} DING_TALK_TOKEN: |
${{ secrets.DINGDING_BOT_TOKEN }}
${{ secrets.DINGDING_BOT_COLLABORATOR_TOKEN }}
notify_title: '🔥 @${{ github.event.issue.user.login }} 创建了 issue${{ github.event.issue.title }}' notify_title: '🔥 @${{ github.event.issue.user.login }} 创建了 issue${{ github.event.issue.title }}'
notify_body: '### 🔥 @${{ github.event.issue.user.login }} 创建了 issue[${{ github.event.issue.title }}](${{ github.event.issue.html_url }}) <hr /> ![](https://gw.alipayobjects.com/zos/antfincdn/5Cl2G7JjF/jieping2022-03-20%252520xiawu11.06.04.png)' notify_body: '### 🔥 @${{ github.event.issue.user.login }} 创建了 issue[${{ github.event.issue.title }}](${{ github.event.issue.html_url }}) <hr /> ![](https://gw.alipayobjects.com/zos/antfincdn/5Cl2G7JjF/jieping2022-03-20%252520xiawu11.06.04.png)'
notify_footer: '> 💬 欢迎前往 GitHub 进行讨论,社区可能需要你的帮助。' notify_footer: '> 💬 欢迎前往 GitHub 进行讨论,社区可能需要你的帮助。'

View File

@ -27,7 +27,7 @@ jobs:
changelogs: 'CHANGELOG.en-US.md, CHANGELOG.zh-CN.md' changelogs: 'CHANGELOG.en-US.md, CHANGELOG.zh-CN.md'
branch: 'master, 4.x-stable' branch: 'master, 4.x-stable'
tag: '5*, 4*' tag: '5*, 4*'
dingding-token: ${{ secrets.DINGDING_BOT_TOKEN }} dingding-token: ${{ secrets.DINGDING_BOT_TOKEN }} ${{ secrets.DINGDING_BOT_COLLABORATOR_TOKEN }} ${{ secrets.DINGDING_BOT_MAINTAINER_TOKEN }}
dingding-msg: 'CHANGELOG.zh-CN.md' dingding-msg: 'CHANGELOG.zh-CN.md'
msg-title: '# Ant Design {{v}} 发布日志' msg-title: '# Ant Design {{v}} 发布日志'
msg-poster: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*zx7LTI_ECSAAAAAAAAAAAABkARQnAQ' msg-poster: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*zx7LTI_ECSAAAAAAAAAAAABkARQnAQ'

View File

@ -15,6 +15,28 @@ timeline: true
--- ---
## 5.0.2
`2022-11-27`
- 💄 Fix Card radius style broken when customize `bodyStyle` background color. [#38973](https://github.com/ant-design/ant-design/pull/38973) [@Yukiniro](https://github.com/Yukiniro)
- 💄 Optimize default algorithm for error color. [#38933](https://github.com/ant-design/ant-design/pull/38933)
- 💄 Optimize the style issue in RTL mode. [#38829](https://github.com/ant-design/ant-design/pull/38829) [@Wxh16144](https://github.com/Wxh16144)
- Space.Compact
- 💄 Optimize Space.Compact style when wrapping a single child component. [#38896](https://github.com/ant-design/ant-design/pull/38896) [@foryuki](https://github.com/foryuki)
- 💄 Fix Space.Compact component style problem when wrapping Modal, Dropdown, Drawer and other components. [#38870](https://github.com/ant-design/ant-design/pull/38870) [@foryuki](https://github.com/foryuki)
- 🐞 Fix horizontal Menu that has wrong width when is overflow. [#38989](https://github.com/ant-design/ant-design/pull/38989)
- 🐞 Fix Table that the old filter state still takes effect when the list filter column changes. [#38982](https://github.com/ant-design/ant-design/pull/38982)
- 🐞 Fix Select and Pagination incorrect text color in dark theme. [#38979](https://github.com/ant-design/ant-design/pull/38979) [@Dunqing](https://github.com/Dunqing)
- 🐞 Fix that Mentions `options` props not working. [#38968](https://github.com/ant-design/ant-design/pull/38968) [@heiyu4585](https://github.com/heiyu4585)
- 🐞 Fix that `dist/reset.css` may be dropped in production. [#38956](https://github.com/ant-design/ant-design/pull/38956) [@passerV](https://github.com/passerV)
- 🐞 Fix Badge that `showZero` can't be used with custom color. [#38967](https://github.com/ant-design/ant-design/pull/38967) [@Wxh16144](https://github.com/Wxh16144)
- 🐞 Fix Form validation motion flick issue. [#38962](https://github.com/ant-design/ant-design/pull/38962)
- 🐞 Fix Tabs dropdown motion not work. [#38892](https://github.com/ant-design/ant-design/pull/38892)
- 🐞 Fix ConfigProvider that `componentDisabled` is not work. [#38886](https://github.com/ant-design/ant-design/pull/38886) [@lidianhao123](https://github.com/lidianhao123)
- 🐞 Fix Button `block` prop is not working when `shape="round"`. [#38869](https://github.com/ant-design/ant-design/pull/38869) [@jjlstruggle](https://github.com/jjlstruggle)
- 🐞 Fix Dropdown.Button that `dropdownRender` is not executed. [#38862](https://github.com/ant-design/ant-design/pull/38862) [@imoctopus](https://github.com/imoctopus)
## 5.0.1 ## 5.0.1
`2022-11-22` `2022-11-22`

View File

@ -15,6 +15,28 @@ timeline: true
--- ---
## 5.0.2
`2022-11-27`
- 💄 修复 Card 组件设置 `bodyStyle` 的背景颜色后圆角失效的问题。[#38973](https://github.com/ant-design/ant-design/pull/38973) [@Yukiniro](https://github.com/Yukiniro)
- 💄 优化错误色的默认算法。[#38933](https://github.com/ant-design/ant-design/pull/38933)
- 💄 修复 RTL 模式下的样式问题。[#38829](https://github.com/ant-design/ant-design/pull/38829) [@Wxh16144](https://github.com/Wxh16144)
- Space.Compact
- 💄 Space.Compact 包裹单个子组件时,展示该子组件本身的样式。[#38896](https://github.com/ant-design/ant-design/pull/38896) [@foryuki](https://github.com/foryuki)
- 💄 修复 Space.Compact 组件嵌套 ModalDropdownDrawer 等组件时的样式问题。[#38870](https://github.com/ant-design/ant-design/pull/38870) [@foryuki](https://github.com/foryuki)
- 🐞 修复横向 Menu 组件有溢出时宽度问题。[#38989](https://github.com/ant-design/ant-design/pull/38989)
- 🐞 修复 Table 组件过滤列被移除后过滤效果仍然影响列表数据的问题。[#38982](https://github.com/ant-design/ant-design/pull/38982)
- 🐞 修复 Select 和 Pagination 在暗色主题下文字颜色不正确。[#38979](https://github.com/ant-design/ant-design/pull/38979) [@Dunqing](https://github.com/Dunqing)
- 🐞 修复 Mentions `options` 不生效的问题。[#38968](https://github.com/ant-design/ant-design/pull/38968) [@heiyu4585](https://github.com/heiyu4585)
- 🐞 修复 `reset.css` 不会被打包的问题。[#38956](https://github.com/ant-design/ant-design/pull/38956) [@passerV](https://github.com/passerV)
- 🐞 修复 Badge 组件 `showZero``color` 不能一起使用问题。[#38967](https://github.com/ant-design/ant-design/pull/38967) [@Wxh16144](https://github.com/Wxh16144)
- 🐞 修复 Form 校验信息动效卡顿的问题。[#38962](https://github.com/ant-design/ant-design/pull/38962)
- 🐞 修复 Tabs 下拉菜单动画消失的问题。[#38892](https://github.com/ant-design/ant-design/pull/38892)
- 🐞 修复 ConfigProvider `componentDisabled` 失效问题。[#38886](https://github.com/ant-design/ant-design/pull/38886) [@lidianhao123](https://github.com/lidianhao123)
- 🐞 修复 Button `block` 属性有时不生效的问题。[#38869](https://github.com/ant-design/ant-design/pull/38869) [@jjlstruggle](https://github.com/jjlstruggle)
- 🐞 修复 Dropdown.Button 的 `dropdownRender` 未执行的问题。[#38862](https://github.com/ant-design/ant-design/pull/38862) [@imoctopus](https://github.com/imoctopus)
## 5.0.1 ## 5.0.1
`2022-11-22` `2022-11-22`

View File

@ -2,7 +2,7 @@ describe('Test warning', () => {
let spy: jest.SpyInstance; let spy: jest.SpyInstance;
beforeAll(() => { beforeAll(() => {
spy = jest.spyOn(console, 'error'); spy = jest.spyOn(console, 'error').mockImplementation(() => {});
}); });
afterAll(() => { afterAll(() => {

View File

@ -76,9 +76,8 @@ describe('Alert', () => {
}); });
it('should show error as ErrorBoundary when children have error', () => { it('should show error as ErrorBoundary when children have error', () => {
jest.spyOn(console, 'error').mockImplementation(() => undefined); const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
// eslint-disable-next-line no-console expect(warnSpy).toHaveBeenCalledTimes(0);
expect(console.error).toHaveBeenCalledTimes(0);
// @ts-expect-error // @ts-expect-error
// eslint-disable-next-line react/jsx-no-undef // eslint-disable-next-line react/jsx-no-undef
const ThrowError = () => <NotExisted />; const ThrowError = () => <NotExisted />;
@ -91,8 +90,7 @@ describe('Alert', () => {
expect(screen.getByRole('alert')).toHaveTextContent( expect(screen.getByRole('alert')).toHaveTextContent(
'ReferenceError: NotExisted is not defined', 'ReferenceError: NotExisted is not defined',
); );
// eslint-disable-next-line no-console warnSpy.mockRestore();
(console.error as any).mockRestore();
}); });
it('could be used with Tooltip', async () => { it('could be used with Tooltip', async () => {

View File

@ -49,7 +49,7 @@ describe('AutoComplete', () => {
}); });
it('AutoComplete throws error when contains invalid dataSource', () => { it('AutoComplete throws error when contains invalid dataSource', () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
render( render(
// @ts-ignore // @ts-ignore
@ -82,13 +82,10 @@ describe('AutoComplete', () => {
}); });
it('should not warning when getInputElement is null', () => { it('should not warning when getInputElement is null', () => {
jest.spyOn(console, 'warn').mockImplementation(() => undefined); const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
render(<AutoComplete placeholder="input here" allowClear />); render(<AutoComplete placeholder="input here" allowClear />);
// eslint-disable-next-line no-console expect(warnSpy).not.toHaveBeenCalled();
expect(console.warn).not.toHaveBeenCalled(); warnSpy.mockRestore();
// @ts-ignore
// eslint-disable-next-line no-console
console.warn.mockRestore();
}); });
it('should not override custom input className', () => { it('should not override custom input className', () => {

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import React from 'react'; import React from 'react';
import { PoweroffOutlined } from '@ant-design/icons'; import { PoweroffOutlined } from '@ant-design/icons';
import { Button, Space } from 'antd'; import { Button, Space } from 'antd';

View File

@ -1,5 +1,4 @@
import classNames from 'classnames'; import classNames from 'classnames';
import padStart from 'lodash/padStart';
import { PickerPanel as RCPickerPanel } from 'rc-picker'; import { PickerPanel as RCPickerPanel } from 'rc-picker';
import type { GenerateConfig } from 'rc-picker/lib/generate'; import type { GenerateConfig } from 'rc-picker/lib/generate';
import type { Locale } from 'rc-picker/lib/interface'; import type { Locale } from 'rc-picker/lib/interface';
@ -198,7 +197,7 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
})} })}
> >
<div className={`${calendarPrefixCls}-date-value`}> <div className={`${calendarPrefixCls}-date-value`}>
{padStart(String(generateConfig.getDate(date)), 2, '0')} {String(generateConfig.getDate(date)).padStart(2, '0')}
</div> </div>
<div className={`${calendarPrefixCls}-date-content`}> <div className={`${calendarPrefixCls}-date-content`}>
{dateCellRender && dateCellRender(date)} {dateCellRender && dateCellRender(date)}

View File

@ -174,6 +174,7 @@ const genCardMetaStyle: GenerateStyle<CardToken> = (token): CSSObject => ({
'&-detail': { '&-detail': {
overflow: 'hidden', overflow: 'hidden',
flex: 1,
'> div:not(:last-child)': { '> div:not(:last-child)': {
marginBottom: token.marginXS, marginBottom: token.marginXS,

View File

@ -71,7 +71,7 @@ const defaultGetPrefixCls = (suffixCls?: string, customizePrefixCls?: string) =>
return suffixCls ? `ant-${suffixCls}` : 'ant'; return suffixCls ? `ant-${suffixCls}` : 'ant';
}; };
// zombieJ: 🚨 Do not pass `defaultRenderEmpty` here since it will case circular dependency. // zombieJ: 🚨 Do not pass `defaultRenderEmpty` here since it will cause circular dependency.
export const ConfigContext = React.createContext<ConfigConsumerProps>({ export const ConfigContext = React.createContext<ConfigConsumerProps>({
// We provide a default function for Context without provider // We provide a default function for Context without provider
getPrefixCls: defaultGetPrefixCls, getPrefixCls: defaultGetPrefixCls,
@ -98,7 +98,7 @@ export function withConfigConsumer<ExportProps extends BasicExportProps>(config:
return function withConfigConsumerFunc<ComponentDef>( return function withConfigConsumerFunc<ComponentDef>(
Component: React.ComponentType<ExportProps>, Component: React.ComponentType<ExportProps>,
): React.FC<ExportProps> & ComponentDef { ): React.FC<ExportProps> & ComponentDef {
// Wrap with ConfigConsumer. Since we need compatible with react 15, be care when using ref methods // Wrap with ConfigConsumer. Since we need compatible with react 15, be careful when using ref methods
const SFC = ((props: ExportProps) => ( const SFC = ((props: ExportProps) => (
<ConfigConsumer> <ConfigConsumer>
{(configProps: ConfigConsumerProps) => { {(configProps: ConfigConsumerProps) => {

View File

@ -104,3 +104,7 @@ When you config `getPopupContainer` to parentNode globally, Modal will throw err
<App /> <App />
</ConfigProvider> </ConfigProvider>
``` ```
#### Why can't ConfigProvider props (like `prefixCls` and `theme`) affect ReactNode inside `message.info`, `notification.open`, `Modal.confirm`?
antd will dynamic create React instance by `ReactDOM.render` when call message methods. Whose context is different with origin code located context. We recommend `useMessage`, `useNotification` and `useModal` which , the methods came from `message/notification/Modal` has been deprecated in 5.x.

View File

@ -105,3 +105,7 @@ ConfigProvider.config({
<App /> <App />
</ConfigProvider> </ConfigProvider>
``` ```
#### 为什么 message.info、notification.open 或 Modal.confirm 等方法内的 ReactNode 无法继承 ConfigProvider 的属性?比如 `prefixCls``theme`
静态方法是使用 ReactDOM.render 重新渲染一个 React 根节点上,和主应用的 React 节点是脱离的。我们建议使用 useMessage、useNotification 和 useModal 来使用相关方法。原先的静态方法在 5.0 中已被废弃。

View File

@ -8,7 +8,7 @@ const { QuarterPicker } = DatePicker;
describe('QuarterPicker', () => { describe('QuarterPicker', () => {
it('should support style prop', () => { it('should support style prop', () => {
resetWarned(); resetWarned();
const warnSpy = jest.spyOn(console, 'error'); const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { container } = render(<QuarterPicker style={{ width: 400 }} />); const { container } = render(<QuarterPicker style={{ width: 400 }} />);
expect(container.firstChild).toMatchSnapshot(); expect(container.firstChild).toMatchSnapshot();

View File

@ -116,7 +116,7 @@ describe('Dropdown', () => {
}); });
it('should warn if use topCenter or bottomCenter', () => { it('should warn if use topCenter or bottomCenter', () => {
const error = jest.spyOn(console, 'error'); const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render( render(
<div> <div>
<Dropdown menu={{ items }} placement="bottomCenter"> <Dropdown menu={{ items }} placement="bottomCenter">

View File

@ -29,6 +29,8 @@ FloatButton. Available since `5.0.0`.
## API ## API
> This component is available since `antd@5.0.0`.
### common API ### common API
| Property | Description | Type | Default | Version | | Property | Description | Type | Default | Version |

View File

@ -30,6 +30,8 @@ demo:
## API ## API
> 自 `antd@5.0.0` 版本开始提供该组件。
### 共同的 API ### 共同的 API
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |

View File

@ -4,6 +4,7 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal'; import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { initFadeMotion } from '../../style/motion/fade'; import { initFadeMotion } from '../../style/motion/fade';
import { resetComponent } from '../../style'; import { resetComponent } from '../../style';
import { initMotion } from '../../style/motion/motion';
/** Component only token. Which will handle additional calculation of alias token */ /** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken { export interface ComponentToken {
@ -23,9 +24,8 @@ type FloatButtonToken = FullToken<'FloatButton'> & {
floatButtonInsetInlineEnd: number; floatButtonInsetInlineEnd: number;
}; };
// ============================== Group ============================== const initFloatButtonGroupMotion = (token: FloatButtonToken) => {
const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token) => { const { componentCls, floatButtonSize, motionDurationSlow, motionEaseInOutCirc } = token;
const { componentCls, floatButtonSize, margin, borderRadius, motionDurationSlow } = token;
const groupPrefixCls = `${componentCls}-group`; const groupPrefixCls = `${componentCls}-group`;
const moveDownIn = new Keyframes('antFloatButtonMoveDownIn', { const moveDownIn = new Keyframes('antFloatButtonMoveDownIn', {
'0%': { '0%': {
@ -53,6 +53,35 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
opacity: 0, opacity: 0,
}, },
}); });
return [
{
[`${groupPrefixCls}-wrap`]: {
...initMotion(`${groupPrefixCls}-wrap`, moveDownIn, moveDownOut, motionDurationSlow, true),
},
},
{
[`${groupPrefixCls}-wrap`]: {
[`
&${groupPrefixCls}-wrap-enter,
&${groupPrefixCls}-wrap-appear
`]: {
opacity: 0,
animationTimingFunction: motionEaseInOutCirc,
},
[`&${groupPrefixCls}-wrap-leave`]: {
animationTimingFunction: motionEaseInOutCirc,
},
},
},
];
};
// ============================== Group ==============================
const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token) => {
const { componentCls, floatButtonSize, margin, borderRadius } = token;
const groupPrefixCls = `${componentCls}-group`;
return { return {
[groupPrefixCls]: { [groupPrefixCls]: {
...resetComponent(token), ...resetComponent(token),
@ -135,15 +164,6 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
}, },
}, },
[`${groupPrefixCls}-wrap-enter,${groupPrefixCls}-wrap-enter-active`]: {
animationName: moveDownIn,
animationDuration: motionDurationSlow,
},
[`${groupPrefixCls}-wrap-leave`]: {
animationName: moveDownOut,
animationDuration: motionDurationSlow,
},
[`${groupPrefixCls}-circle-shadow`]: { [`${groupPrefixCls}-circle-shadow`]: {
boxShadow: 'none', boxShadow: 'none',
}, },
@ -308,5 +328,6 @@ export default genComponentStyleHook<'FloatButton'>('FloatButton', (token) => {
floatButtonGroupStyle(floatButtonToken), floatButtonGroupStyle(floatButtonToken),
sharedFloatButtonStyle(floatButtonToken), sharedFloatButtonStyle(floatButtonToken),
initFadeMotion(token), initFadeMotion(token),
initFloatButtonGroupMotion(floatButtonToken),
]; ];
}); });

View File

@ -1040,7 +1040,7 @@ describe('Menu', () => {
}); });
it('should not warning deprecated message when items={undefined}', () => { it('should not warning deprecated message when items={undefined}', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined); const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<Menu items={undefined} />); render(<Menu items={undefined} />);
expect(errorSpy).not.toHaveBeenCalledWith( expect(errorSpy).not.toHaveBeenCalledWith(
expect.stringContaining('`children` will be removed in next major version'), expect.stringContaining('`children` will be removed in next major version'),

View File

@ -177,7 +177,6 @@ const getThemeStyle = (token: MenuToken, themeSuffix: string): CSSInterpolation
}, },
[`&:hover, &-active, &-open`]: { [`&:hover, &-active, &-open`]: {
color: colorItemTextSelectedHorizontal,
'&::after': { '&::after': {
borderWidth: `${colorActiveBarHeight}px`, borderWidth: `${colorActiveBarHeight}px`,
borderBottomColor: colorItemTextSelectedHorizontal, borderBottomColor: colorItemTextSelectedHorizontal,

View File

@ -36,7 +36,6 @@ This components provides some static methods, with usage and arguments as follow
- `message.error(content, [duration], onClose)` - `message.error(content, [duration], onClose)`
- `message.info(content, [duration], onClose)` - `message.info(content, [duration], onClose)`
- `message.warning(content, [duration], onClose)` - `message.warning(content, [duration], onClose)`
- `message.warn(content, [duration], onClose)` // alias of warning
- `message.loading(content, [duration], onClose)` - `message.loading(content, [duration], onClose)`
| Argument | Description | Type | Default | | Argument | Description | Type | Default |
@ -59,7 +58,6 @@ Supports passing parameters wrapped in an object:
- `message.error(config)` - `message.error(config)`
- `message.info(config)` - `message.info(config)`
- `message.warning(config)` - `message.warning(config)`
- `message.warn(config)` // alias of warning
- `message.loading(config)` - `message.loading(config)`
The properties of config are as follows: The properties of config are as follows:
@ -111,7 +109,7 @@ message.config({
## FAQ ## FAQ
### Why I can not access context, redux, ConfigProvider `locale/prefixCls` in message? ### Why I can not access context, redux, ConfigProvider `locale/prefixCls/theme` in message?
antd will dynamic create React instance by `ReactDOM.render` when call message methods. Whose context is different with origin code located context. antd will dynamic create React instance by `ReactDOM.render` when call message methods. Whose context is different with origin code located context.

View File

@ -37,7 +37,6 @@ demo:
- `message.error(content, [duration], onClose)` - `message.error(content, [duration], onClose)`
- `message.info(content, [duration], onClose)` - `message.info(content, [duration], onClose)`
- `message.warning(content, [duration], onClose)` - `message.warning(content, [duration], onClose)`
- `message.warn(content, [duration], onClose)` // alias of warning
- `message.loading(content, [duration], onClose)` - `message.loading(content, [duration], onClose)`
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
@ -60,7 +59,6 @@ demo:
- `message.error(config)` - `message.error(config)`
- `message.info(config)` - `message.info(config)`
- `message.warning(config)` - `message.warning(config)`
- `message.warn(config)` // alias of warning
- `message.loading(config)` - `message.loading(config)`
`config` 对象属性如下: `config` 对象属性如下:
@ -112,7 +110,7 @@ message.config({
## FAQ ## FAQ
### 为什么 message 不能获取 context、redux 的内容和 ConfigProvider 的 `locale/prefixCls` 配置? ### 为什么 message 不能获取 context、redux 的内容和 ConfigProvider 的 `locale/prefixCls/theme` 配置?
直接调用 message 方法antd 会通过 `ReactDOM.render` 动态创建新的 React 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。 直接调用 message 方法antd 会通过 `ReactDOM.render` 动态创建新的 React 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。

View File

@ -41,7 +41,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
// }); // });
// jest.spyOn(window, 'cancelAnimationFrame').mockImplementation(id => window.clearTimeout(id)); // jest.spyOn(window, 'cancelAnimationFrame').mockImplementation(id => window.clearTimeout(id));
const errorSpy = jest.spyOn(console, 'error'); const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
/* eslint-disable no-console */ /* eslint-disable no-console */
// Hack error to remove act warning // Hack error to remove act warning

View File

@ -171,7 +171,7 @@ return <div>{contextHolder}</div>;
Modal 在关闭时会将内容进行 memo 从而避免关闭过程中的内容跳跃。也因此如果你在配合使用 Form 有关闭时重置 `initialValues` 的操作,请通过在 effect 中调用 `resetFields` 来重置。 Modal 在关闭时会将内容进行 memo 从而避免关闭过程中的内容跳跃。也因此如果你在配合使用 Form 有关闭时重置 `initialValues` 的操作,请通过在 effect 中调用 `resetFields` 来重置。
### 为什么 Modal 方法不能获取 context、redux、的内容和 ConfigProvider `locale/prefixCls` 配置? ### 为什么 Modal 方法不能获取 context、redux、的内容和 ConfigProvider `locale/prefixCls/theme` 配置?
直接调用 Modal 方法antd 会通过 `ReactDOM.render` 动态创建新的 React 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。 直接调用 Modal 方法antd 会通过 `ReactDOM.render` 动态创建新的 React 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。

View File

@ -93,7 +93,7 @@ notification.config({
## FAQ ## FAQ
### Why I can not access context, redux, ConfigProvider `locale/prefixCls` in notification? ### Why I can not access context, redux, ConfigProvider `locale/prefixCls/theme` in notification?
antd will dynamic create React instance by `ReactDOM.render` when call notification methods. Whose context is different with origin code located context. antd will dynamic create React instance by `ReactDOM.render` when call notification methods. Whose context is different with origin code located context.

View File

@ -93,7 +93,7 @@ notification.config({
## FAQ ## FAQ
### 为什么 notification 不能获取 context、redux 的内容和 ConfigProvider 的 `locale/prefixCls` 配置? ### 为什么 notification 不能获取 context、redux 的内容和 ConfigProvider 的 `locale/prefixCls/theme` 配置?
直接调用 notification 方法antd 会通过 `ReactDOM.render` 动态创建新的 React 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。 直接调用 notification 方法antd 会通过 `ReactDOM.render` 动态创建新的 React 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。

View File

@ -1,13 +1,13 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Segmented } from 'antd'; import { Segmented } from 'antd';
const Demo = () => { const Demo: React.FC = () => {
const [foo, setFoo] = useState('AND'); const [foo, setFoo] = useState<string | number>('AND');
return ( return (
<> <>
<Segmented value={foo} options={['AND', 'OR', 'NOT']} onChange={setFoo} /> <Segmented value={foo} options={['AND', 'OR', 'NOT']} onChange={setFoo} />
&nbsp;&nbsp; &nbsp;&nbsp;
<Segmented value={foo} options={['AND', 'OR', 'NOT']} onChange={(value) => setFoo(value)} /> <Segmented value={foo} options={['AND', 'OR', 'NOT']} onChange={setFoo} />
</> </>
); );
}; };

View File

@ -58,7 +58,7 @@ To input a value in a range.
### tooltip ### tooltip
| 参数 | 说明 | 类型 | 默认值 | 版本 | | Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| open | If true, Tooltip will show always, or it will not show anyway, even if dragging or hovering | boolean | - | 4.23.0 | | open | If true, Tooltip will show always, or it will not show anyway, even if dragging or hovering | boolean | - | 4.23.0 |
| placement | Set Tooltip display position. Ref [Tooltip](/components/tooltip/) | string | - | 4.23.0 | | placement | Set Tooltip display position. Ref [Tooltip](/components/tooltip/) | string | - | 4.23.0 |

View File

@ -181,7 +181,7 @@ describe('Space', () => {
// https://github.com/ant-design/ant-design/issues/35305 // https://github.com/ant-design/ant-design/issues/35305
it('should not throw duplicated key warning', () => { it('should not throw duplicated key warning', () => {
jest.spyOn(console, 'error').mockImplementation(() => undefined); const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
render( render(
<Space> <Space>
<div key="1" /> <div key="1" />
@ -190,11 +190,11 @@ describe('Space', () => {
<div /> <div />
</Space>, </Space>,
); );
expect(console.error).not.toHaveBeenCalledWith( expect(spy).not.toHaveBeenCalledWith(
expect.stringContaining('Encountered two children with the same key'), expect.stringContaining('Encountered two children with the same key'),
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
); );
(console.error as any).mockRestore(); spy.mockRestore();
}); });
}); });

View File

@ -10,7 +10,7 @@ Set components spacing.
## When To Use ## When To Use
- Avoid components clinging together and set a unified space. - Avoid components clinging together and set a unified space.
- Use Space.Compact when child form components are compactly connected and the border is collapsed. - Use Space.Compact when child form components are compactly connected and the border is collapsed (After version `antd@4.24.0` Supported).
## Examples ## Examples

View File

@ -14,7 +14,7 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/wc6%263gJ0Y8/Space.svg
- 适合行内元素的水平间距。 - 适合行内元素的水平间距。
- 可以设置各种水平对齐方式。 - 可以设置各种水平对齐方式。
- 需要表单组件之间紧凑连接且合并边框时,使用 Space.Compact自 antd@4.24.0 版本开始提供该组件)。 - 需要表单组件之间紧凑连接且合并边框时,使用 Space.Compact`antd@4.24.0` 版本开始提供该组件)。
## 代码演示 ## 代码演示

View File

@ -287,6 +287,156 @@ exports[`renders ./components/spin/demo/size.tsx extend context correctly 1`] =
`; `;
exports[`renders ./components/spin/demo/tip.tsx extend context correctly 1`] = ` exports[`renders ./components/spin/demo/tip.tsx extend context correctly 1`] = `
<div
class="ant-space ant-space-vertical"
style="width:100%"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<div
class="ant-spin-nested-loading"
>
<div>
<div
aria-busy="true"
aria-live="polite"
class="ant-spin ant-spin-sm ant-spin-spinning ant-spin-show-text"
>
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
<div
class="ant-spin-text"
>
Loading
</div>
</div>
</div>
<div
class="ant-spin-container ant-spin-blur"
>
<div
class="content"
/>
</div>
</div>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<div
class="ant-spin-nested-loading"
>
<div>
<div
aria-busy="true"
aria-live="polite"
class="ant-spin ant-spin-spinning ant-spin-show-text"
>
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
<div
class="ant-spin-text"
>
Loading
</div>
</div>
</div>
<div
class="ant-spin-container ant-spin-blur"
>
<div
class="content"
/>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-spin-nested-loading"
>
<div>
<div
aria-busy="true"
aria-live="polite"
class="ant-spin ant-spin-lg ant-spin-spinning ant-spin-show-text"
>
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
<div
class="ant-spin-text"
>
Loading
</div>
</div>
</div>
<div
class="ant-spin-container ant-spin-blur"
>
<div
class="content"
/>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div <div
class="ant-spin-nested-loading" class="ant-spin-nested-loading"
> >
@ -344,4 +494,6 @@ exports[`renders ./components/spin/demo/tip.tsx extend context correctly 1`] = `
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
`; `;

View File

@ -287,6 +287,156 @@ exports[`renders ./components/spin/demo/size.tsx correctly 1`] = `
`; `;
exports[`renders ./components/spin/demo/tip.tsx correctly 1`] = ` exports[`renders ./components/spin/demo/tip.tsx correctly 1`] = `
<div
class="ant-space ant-space-vertical"
style="width:100%"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<div
class="ant-spin-nested-loading"
>
<div>
<div
aria-busy="true"
aria-live="polite"
class="ant-spin ant-spin-sm ant-spin-spinning ant-spin-show-text"
>
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
<div
class="ant-spin-text"
>
Loading
</div>
</div>
</div>
<div
class="ant-spin-container ant-spin-blur"
>
<div
class="content"
/>
</div>
</div>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<div
class="ant-spin-nested-loading"
>
<div>
<div
aria-busy="true"
aria-live="polite"
class="ant-spin ant-spin-spinning ant-spin-show-text"
>
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
<div
class="ant-spin-text"
>
Loading
</div>
</div>
</div>
<div
class="ant-spin-container ant-spin-blur"
>
<div
class="content"
/>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-spin-nested-loading"
>
<div>
<div
aria-busy="true"
aria-live="polite"
class="ant-spin ant-spin-lg ant-spin-spinning ant-spin-show-text"
>
<span
class="ant-spin-dot ant-spin-dot-spin"
>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
<i
class="ant-spin-dot-item"
/>
</span>
<div
class="ant-spin-text"
>
Loading
</div>
</div>
</div>
<div
class="ant-spin-container ant-spin-blur"
>
<div
class="content"
/>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div <div
class="ant-spin-nested-loading" class="ant-spin-nested-loading"
> >
@ -344,4 +494,6 @@ exports[`renders ./components/spin/demo/tip.tsx correctly 1`] = `
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
`; `;

View File

@ -4,4 +4,10 @@
## en-US ## en-US
Customized description content. ```css
.content {
padding: 50px;
background: rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
```

View File

@ -1,7 +1,20 @@
import React from 'react'; import React from 'react';
import { Alert, Spin } from 'antd'; import { Alert, Space, Spin } from 'antd';
const App: React.FC = () => ( const App: React.FC = () => (
<Space direction="vertical" style={{ width: '100%' }}>
<Space>
<Spin tip="Loading" size="small">
<div className="content" />
</Spin>
<Spin tip="Loading">
<div className="content" />
</Spin>
<Spin tip="Loading" size="large">
<div className="content" />
</Spin>
</Space>
<Spin tip="Loading..."> <Spin tip="Loading...">
<Alert <Alert
message="Alert message title" message="Alert message title"
@ -9,6 +22,7 @@ const App: React.FC = () => (
type="info" type="info"
/> />
</Spin> </Spin>
</Space>
); );
export default App; export default App;

View File

@ -71,7 +71,7 @@ const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject =>
marginTop: -(token.spinDotSize / 2) - 10, marginTop: -(token.spinDotSize / 2) - 10,
}, },
[`> div > ${token.componentCls}-sm`]: { '&-sm': {
[`${token.componentCls}-dot`]: { [`${token.componentCls}-dot`]: {
margin: -token.spinDotSizeSM / 2, margin: -token.spinDotSizeSM / 2,
}, },
@ -83,7 +83,7 @@ const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject =>
}, },
}, },
[`> div > ${token.componentCls}-lg`]: { '&-lg': {
[`${token.componentCls}-dot`]: { [`${token.componentCls}-dot`]: {
margin: -(token.spinDotSizeLG / 2), margin: -(token.spinDotSizeLG / 2),
}, },

View File

@ -1,4 +1,3 @@
import padEnd from 'lodash/padEnd';
import * as React from 'react'; import * as React from 'react';
import type { FormatConfig, valueType } from './utils'; import type { FormatConfig, valueType } from './utils';
@ -30,7 +29,7 @@ const StatisticNumber: React.FC<NumberProps> = (props) => {
int = int.replace(/\B(?=(\d{3})+(?!\d))/g, groupSeparator); int = int.replace(/\B(?=(\d{3})+(?!\d))/g, groupSeparator);
if (typeof precision === 'number') { if (typeof precision === 'number') {
decimal = padEnd(decimal, precision, '0').slice(0, precision > 0 ? precision : 0); decimal = decimal.padEnd(precision, '0').slice(0, precision > 0 ? precision : 0);
} }
if (decimal) { if (decimal) {

View File

@ -1,4 +1,3 @@
import padStart from 'lodash/padStart';
import type * as React from 'react'; import type * as React from 'react';
export type valueType = number | string; export type valueType = number | string;
@ -46,7 +45,7 @@ export function formatTimeStr(duration: number, format: string) {
leftDuration -= value * unit; leftDuration -= value * unit;
return current.replace(new RegExp(`${name}+`, 'g'), (match: string) => { return current.replace(new RegExp(`${name}+`, 'g'), (match: string) => {
const len = match.length; const len = match.length;
return padStart(value.toString(), len, '0'); return value.toString().padStart(len, '0');
}); });
} }
return current; return current;

View File

@ -117,7 +117,7 @@ describe('Table.filter', () => {
}); });
it('renders empty menu correctly', () => { it('renders empty menu correctly', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined); const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { container } = render( const { container } = render(
createTable({ createTable({
columns: [ columns: [

View File

@ -117,7 +117,7 @@ const columns = [
| getPopupContainer | The render container of dropdowns in table | (triggerNode) => HTMLElement | () => TableHtmlElement | | | getPopupContainer | The render container of dropdowns in table | (triggerNode) => HTMLElement | () => TableHtmlElement | |
| loading | Loading status of table | boolean \| [Spin Props](/components/spin/#API) | false | | | loading | Loading status of table | boolean \| [Spin Props](/components/spin/#API) | false | |
| locale | The i18n text including filter, sort, empty text, etc | object | [Default Value](https://github.com/ant-design/ant-design/blob/6dae4a7e18ad1ba193aedd5ab6867e1d823e2aa4/components/locale/en_US.tsx#L19-L37) | | | locale | The i18n text including filter, sort, empty text, etc | object | [Default Value](https://github.com/ant-design/ant-design/blob/6dae4a7e18ad1ba193aedd5ab6867e1d823e2aa4/components/locale/en_US.tsx#L19-L37) | |
| pagination | Config of pagination. You can ref table pagination [config](#pagination) or full [`pagination`](/components/pagination/) document, hide it by setting it to `false` | object | - | | | pagination | Config of pagination. You can ref table pagination [config](#pagination) or full [`pagination`](/components/pagination/) document, hide it by setting it to `false` | object \| `false` | - | |
| rowClassName | Row's className | function(record, index): string | - | | | rowClassName | Row's className | function(record, index): string | - | |
| rowKey | Row's unique key, could be a string or function that returns a string | string \| function(record): string | `key` | | | rowKey | Row's unique key, could be a string or function that returns a string | string \| function(record): string | `key` | |
| rowSelection | Row selection [config](#rowSelection) | object | - | | | rowSelection | Row selection [config](#rowSelection) | object | - | |

View File

@ -118,7 +118,7 @@ const columns = [
| getPopupContainer | 设置表格内各类浮层的渲染节点,如筛选菜单 | (triggerNode) => HTMLElement | () => TableHtmlElement | | | getPopupContainer | 设置表格内各类浮层的渲染节点,如筛选菜单 | (triggerNode) => HTMLElement | () => TableHtmlElement | |
| loading | 页面是否加载中 | boolean \| [Spin Props](/components/spin/#API) | false | | | loading | 页面是否加载中 | boolean \| [Spin Props](/components/spin/#API) | false | |
| locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | [默认值](https://github.com/ant-design/ant-design/blob/6dae4a7e18ad1ba193aedd5ab6867e1d823e2aa4/components/locale/zh_CN.tsx#L20-L37) | | | locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | [默认值](https://github.com/ant-design/ant-design/blob/6dae4a7e18ad1ba193aedd5ab6867e1d823e2aa4/components/locale/zh_CN.tsx#L20-L37) | |
| pagination | 分页器,参考[配置项](#pagination)或 [pagination](/components/pagination/) 文档,设为 false 时不展示和进行分页 | object | - | | | pagination | 分页器,参考[配置项](#pagination)或 [pagination](/components/pagination/) 文档,设为 false 时不展示和进行分页 | object \| `false` | - | |
| rowClassName | 表格行的类名 | function(record, index): string | - | | | rowClassName | 表格行的类名 | function(record, index): string | - | |
| rowKey | 表格行 key 的取值,可以是字符串或一个函数 | string \| function(record): string | `key` | | | rowKey | 表格行 key 的取值,可以是字符串或一个函数 | string \| function(record): string | `key` | |
| rowSelection | 表格行是否可选择,[配置项](#rowSelection) | object | - | | | rowSelection | 表格行是否可选择,[配置项](#rowSelection) | object | - | |

View File

@ -55,11 +55,6 @@ export const DesignTokenContext = React.createContext<{
}>(defaultConfig); }>(defaultConfig);
// ================================== Hook ================================== // ================================== Hook ==================================
// In dev env, we refresh salt per hour to avoid user use this
// Note: Do not modify this to real time update which will make debug harder
const saltPrefix =
process.env.NODE_ENV === 'production' ? version : `${version}-${new Date().getHours()}`;
export function useToken(): [Theme<SeedToken, MapToken>, GlobalToken, string] { export function useToken(): [Theme<SeedToken, MapToken>, GlobalToken, string] {
const { const {
token: rootDesignToken, token: rootDesignToken,
@ -68,7 +63,7 @@ export function useToken(): [Theme<SeedToken, MapToken>, GlobalToken, string] {
components, components,
} = React.useContext(DesignTokenContext); } = React.useContext(DesignTokenContext);
const salt = `${saltPrefix}-${hashed || ''}`; const salt = `${version}-${hashed || ''}`;
const mergedTheme = theme || defaultTheme; const mergedTheme = theme || defaultTheme;

View File

@ -4,14 +4,14 @@ import type {
TourStepProps as RCTourStepProps, TourStepProps as RCTourStepProps,
} from '@rc-component/tour'; } from '@rc-component/tour';
export type TourProps = Omit<RCTourProps, 'renderPanel'> & { export interface TourProps extends Omit<RCTourProps, 'renderPanel'> {
steps?: TourStepProps[]; steps?: TourStepProps[];
className?: string; className?: string;
prefixCls?: string; prefixCls?: string;
current?: number; current?: number;
stepRender?: (current: number, total: number) => ReactNode; stepRender?: (current: number, total: number) => ReactNode;
type?: 'default' | 'primary'; // default 类型,影响底色与文字颜色 type?: 'default' | 'primary'; // default 类型,影响底色与文字颜色
}; }
export interface TourStepProps extends RCTourStepProps { export interface TourStepProps extends RCTourStepProps {
cover?: ReactNode; // 展示的图片或者视频 cover?: ReactNode; // 展示的图片或者视频

View File

@ -0,0 +1,26 @@
---
order: 0
title: Component-level CSS-in-JS
---
On November 18, 2022, we released Ant Design 5.0. At the same time, Ant Design's unique CSS-in-JS solution was brought into everyone's view. Through this solution, Ant Design achieves higher performance than other CSS-in-JS libraries, but at the cost of sacrificing its flexibility for free use in applications. So we call it a "component-level" CSS-in-JS solution. <a name="W668Z"></a>
## Dilemma of CSS-in-JS
In CSS-in-JS, hash is used to confirm whether a style has been inserted. The way to calculate the hash is usually to convert a complete css into a hash value. For example, in emotion, we can see such a style tag by checking the elements on the page. The hash value corresponding to such a style tag is unique:<br />![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*X5tDQ5VIpcoAAAAAAAAAAAAADrJ8AQ/original)<br />In this way, you can find a problem that CSS-in-JS has been criticized for a long time. What we write when coding is not the final css. So every time we need to serialize to get the css and calculate the hash again. If your page or component has a very complex or a large amount of CSS-in-JS code, and even the style will follow the component's props change, then this performance issue becomes non-negligible.<br />To solve this problem, each CSS-in-JS library will have its own way to deal with it. Lets take a look at Ant Designs solution. <a name="Wd3XQ"></a>
## Hash
In fact, it is not difficult for us to find that the problem lies in the process of serializing css. How about reducing the times of serializing css by caching? For application-level CSS-in-JS, it is difficult for us to find a suitable key for cache. But if it is a component library, the final style is relatively stable. <br />According to the style structure we determined from v4 and previous versions, the style of each component will not change under the same theme variable and the same version. Conversely, the style may change only if the theme variable is modified, or the version of antd is changed. <br />From this we get a very simple way to calculate the hash:<br />![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*XuVYRJ_27Q0AAAAAAAAAAAAADrJ8AQ/original)<br />We will apply the **same** **hash** to all antd components. In this way, when using the antd component, we only perform hash calculations on the current version and theme variables. Version can be obtained directly from `package.json`, and theme variables can be obtained directly from context. So we don't need to serialize css again and again to get a stable hash, and the performance is improved finally. <a name="GxLK1"></a>
## Cache for Components
In the above way, we have taken the first step of "component level" CSS-in-JS, but this is not enough. Since it is "component level", we can also optimize it again with components.<br />In Ant Design, the style of a component is usually complete. That is to say, no matter what variant the component has, its style exist in the whole component style. In this way, we can draw a conclusion again: the props of antd components will not affect the component style. <br />This is very important. In the application-level CSS-in-JS solution, since props may affect the component style, it is inevitable that the component style will be regenerated during the rendering phase. No matter how to optimize this point, it cannot be ignored. Now that we have adopted a "component-level" solution, this problem can be easily solved: do style caching for components.<br />![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*yZMNSYVtxnAAAAAAAAAAAAAADrJ8AQ/original)<br />In the case of the same hash, no matter how many times the same component is used and rendered, the style will only be generated once at the first mount, and will hit the cache for the rest of the time. This is the second insurance for "component level" CSS-in-JS solutions. <a name="DUbKx"></a>
## Benchmark
At the release of Ant Design 5.0, we simply made a benchmark, and here are some supplementary instructions:<br />![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*upmYSqZ5FwsAAAAAAAAAAAAADrJ8AQ/original)<br />The benchmark is based on generating a very long unchanging style to test the performance of basic usage of the three libraries. It can be seen that under the "component level" usage scenario of Ant Design, @ant-design/cssinjs has a performance advantage whether it is the first rendering or the second rendering. Since styled has certain optimizations when dealing with stable styles, the performance of secondary rendering in this benchmark is better, but it will still be affected by recalculation like emotion when props participate in style calculation. <a name="JOmkZ"></a>
## Limitation
In the above comparison, it cannot be said that antd is definitely better than styled and emotion, but in the component-level usage scenarios, we have made corresponding optimizations to obtain performance advantages. Conversely, due to the limitation of "component level", antd's CSS-in-JS solution is not suitable for construction applications.<br />Due to the special hash calculation method and component cache, when applying antd's CSS-in-JS solution, developers must provide stable hash and unique component names by themselves. For applications, automatic hash capabilities such as css modules are more needed. At the same time, caching a large number of components in the application also requires additional management costs. Once an error occurs, it is difficult to troubleshoot. Therefore, we recommend using the "component-level" CSS-in-JS solution in component libraries.

View File

@ -0,0 +1,28 @@
---
order: 0
title: 组件级别的 CSS-in-JS
---
- 2022-11-25
在 2022 年 11 月 18 日,我们发布了 Ant Design 5.0 的正式版本,同时带入大家视野中的还有 Ant Design 独特的 CSS-in-JS 方案。通过这个方案Ant Design 获得了相较于其他 CSS-in-JS 库更高的性能,但代价则是牺牲了其在应用中自由使用的灵活性。所以我们把它称为“组件级”的 CSS-in-JS 方案。 <a name="W668Z"></a>
## CSS-in-JS 的困境
在 CSS-in-JS 中hash 会用于确认一段 style 是否已经插入。而计算 hash 的方法通常是将一段完整的 css 转换为 hash 值。比如在 emotion 中,我们检查页面中的元素就可以看到这样的 style 标签,这样的 style 标签对应的 hash 值每一段都是不一样的:<br />![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*X5tDQ5VIpcoAAAAAAAAAAAAADrJ8AQ/original)<br />如此便可以引入一个 CSS-in-JS 被诟病已久的问题:我们在编写代码时写的并不是最终的 css所以每次都需要重新序列化得到 css 后再次计算 hash这就在每次渲染组件时带来了而外的开销。如果你的页面或者组件带有非常复杂或者大量的 CSS-in-JS 代码,甚至样式会跟随组件的 props 变化,那么这个性能消耗便变得不可忽视。<br />针对这个问题,各个 CSS-in-JS 库会有自己的应对方式,这里就先不做赘述,让我们来看一看 Ant Design 的方案。 <a name="Wd3XQ"></a>
## 计算 hash
其实我们不难发现,问题其实在于序列化 css 的过程。如果通过缓存的方法去减少序列化 css 的次数呢?对于应用级的 CSS-in-JS 来说,我们很难去找到一个很合适的 key 去确认缓存。但是如果是组件库的话,最终得到的样式则是比较稳定的。根据我们从 v4 及之前版本确定下来的样式结构,每一个组件的样式在相同的主题变量和相同的版本下是不会改变的。反过来说,只有修改了主题变量,或者改变了 antd 的版本,样式才可能会变化。由此我们得到了一个非常简单的计算 hash 的方法:<br />![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*XuVYRJ_27Q0AAAAAAAAAAAAADrJ8AQ/original)<br />我们会对所有的 antd 组件应用**相同的** **hash**。如此一来,使用 antd 组件时,我们只会对当前的版本和主题变量进行 hash 计算,而前者可以直接由 `package.json`中得到,后者可以直接从 context 中得到,所以我们并不需要进行繁重的序列化 css 的操作,就可以得到稳定的 hash从而大幅地减少性能消耗。 <a name="GxLK1"></a>
## 组件缓存
通过上述的方式,我们迈出了“组件级” CSS-in-JS 的第一步,但是这还不够。既然是“组件级”,那我们也可以针对组件再次进行优化。<br />在 Ant Design 中一个组件的样式通常来说是“完整”的也就是说不管这个组件有什么样的变体他的样式会一并存在于组件样式中。如此我们可以再次得到一个结论antd 组件的 props **不会**影响组件样式。这是非常重要的一点,在应用级的 CSS-in-JS 方案中,由于存在 props 影响组件样式的可能,所以不可避免地会在渲染阶段重新生成组件样式,不管如何去优化这一点仍无法忽视。既然我们采用了“组件级”的方案,那么这个问题就可以很轻松地解决:对组件做样式缓存。<br />![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*yZMNSYVtxnAAAAAAAAAAAAAADrJ8AQ/original)<br />在 hash 相同的情况下,同一个组件无论使用了多少次、渲染了多少次,样式永远只会在第一次 mount 时生成一次,剩下的时间里都会命中缓存,这便是“组件级” CSS-in-JS 方案的第二重保险。 <a name="DUbKx"></a>
## Benchmark
在 Ant Design 5.0 的发布会上,我们简单地做了一次 benchmark在这里可以做一些补充说明<br />![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*upmYSqZ5FwsAAAAAAAAAAAAADrJ8AQ/original)<br />这个 benchmark 的成立条件是产生一段非常长的不会变更的样式,以此来测试这三个库的基本用法的性能。可以看出在 Ant Design 的“组件级”使用场景下无论是初次渲染还是二次渲染antd 都拥有性能上的优势。由于 styled 在处理稳定的样式时有一定优化,所以这个 benchmark 中二次渲染的性能较好,但在有 props 参与样式计算时仍会和 emotion 一样受到重新计算的影响。 <a name="JOmkZ"></a>
## “组件级”的局限
在上述的对比中,其实并不能说 antd 一定优于 styled 和 emotion而是在 antd 的组件级使用场景下我们做了相应的优化以取得了性能上的优势。反过来说由于“组件级”的局限性antd 的 CSS-in-JS 方案并不能适用于日常构建应用。<br />由于特殊的 hash 计算方法和组件缓存,在套用 antd 的 CSS-in-JS 方案时,开发者必须自己提供稳定的 hash 和独特的组件名。对于应用来说,像 css module 这样的自动 hash 的能力反而是更为需要的,同时对应用中海量的组件进行缓存也需要额外的管理成本,一旦出错问题是很难排查的。因此我们更加推荐在组件库中使用“组件级”的 CSS-in-JS 方案。

Some files were not shown because too many files have changed in this diff Show More