mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-27 20:49:53 +08:00
commit
7037b470c0
@ -1,4 +1,3 @@
|
||||
import * as React from 'react';
|
||||
import { useLocale as useDumiLocale } from 'dumi';
|
||||
|
||||
export interface LocaleMap<Key extends string> {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { ReactNode, useMemo } from 'react';
|
||||
import { MenuProps } from 'antd';
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { Link, useFullSidebarData, useSidebarData } from 'dumi';
|
||||
import useLocation from './useLocation';
|
||||
|
||||
@ -115,7 +116,7 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] =>
|
||||
}
|
||||
} else {
|
||||
result.push(
|
||||
...group.children?.map((item) => ({
|
||||
...(group.children?.map((item) => ({
|
||||
label: (
|
||||
<Link to={`${item.link}${search}`}>
|
||||
{before}
|
||||
@ -124,7 +125,7 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] =>
|
||||
</Link>
|
||||
),
|
||||
key: item.link.replace(/(-cn$)/g, ''),
|
||||
})),
|
||||
})) ?? []),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
|
@ -1 +1,3 @@
|
||||
export { default } from '../index/index';
|
||||
import Homepage from '../index/index';
|
||||
|
||||
export default Homepage;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { Button, Space, Typography } from 'antd';
|
||||
import { Link, useLocation } from 'dumi';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
import { GroupMask } from './Group';
|
||||
import { Link, useLocation } from 'dumi';
|
||||
import * as utils from '../../../theme/utils';
|
||||
|
||||
const locales = {
|
||||
@ -25,7 +25,7 @@ export interface BannerProps {
|
||||
}
|
||||
|
||||
export default function Banner({ children }: BannerProps) {
|
||||
const [locale, lang] = useLocale(locales);
|
||||
const [locale] = useLocale(locales);
|
||||
const { pathname, search } = useLocation();
|
||||
const { token } = useSiteToken();
|
||||
|
||||
@ -82,6 +82,7 @@ export default function Banner({ children }: BannerProps) {
|
||||
<img
|
||||
style={{ position: 'absolute', right: 0, top: 240, width: 240 }}
|
||||
src="https://gw.alipayobjects.com/zos/bmw-prod/b3b8dc41-dce8-471f-9d81-9a0204f27d03.svg"
|
||||
alt="Ant Design"
|
||||
/>
|
||||
|
||||
<GroupMask
|
||||
@ -95,11 +96,13 @@ export default function Banner({ children }: BannerProps) {
|
||||
<img
|
||||
style={{ position: 'absolute', left: 0, top: 0, width: 240 }}
|
||||
src="https://gw.alipayobjects.com/zos/bmw-prod/49f963db-b2a8-4f15-857a-270d771a1204.svg"
|
||||
alt="bg"
|
||||
/>
|
||||
{/* Image Left Top */}
|
||||
<img
|
||||
style={{ position: 'absolute', right: 120, top: 0, width: 240 }}
|
||||
src="https://gw.alipayobjects.com/zos/bmw-prod/e152223c-bcae-4913-8938-54fda9efe330.svg"
|
||||
alt="bg"
|
||||
/>
|
||||
|
||||
<Typography.Title
|
||||
|
@ -1,8 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { Skeleton, Typography } from 'antd';
|
||||
import { css } from '@emotion/react';
|
||||
import type { Extra, Icon } from './util';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
import { Col, Row, Card, Typography, Skeleton } from 'antd';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
const useStyle = () => {
|
||||
const { token } = useSiteToken();
|
||||
@ -52,20 +52,20 @@ export default function BannerRecommends({ extras = [], icons = [] }: BannerReco
|
||||
textAlign: 'start',
|
||||
}}
|
||||
>
|
||||
{first3.map((extra, index) => {
|
||||
{first3.map((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 (
|
||||
<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.Paragraph type="secondary" style={{ flex: 'auto' }}>
|
||||
{extra.description}
|
||||
</Typography.Paragraph>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<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>
|
||||
</a>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
import React from 'react';
|
||||
import {
|
||||
Space,
|
||||
@ -14,6 +14,7 @@ import {
|
||||
import dayjs from 'dayjs';
|
||||
import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { css } from '@emotion/react';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
|
||||
const SAMPLE_CONTENT_EN =
|
||||
|
@ -1,9 +1,9 @@
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
import { Col, Row, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import { Link, useLocation } from 'dumi';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
import * as utils from '../../../theme/utils';
|
||||
|
||||
const SECONDARY_LIST = [
|
||||
@ -150,7 +150,7 @@ export default function DesignFramework() {
|
||||
|
||||
return (
|
||||
<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})` }} />
|
||||
|
||||
<Typography.Title
|
||||
|
@ -58,7 +58,7 @@ export default function Group(props: GroupProps) {
|
||||
boxSizing: 'border-box',
|
||||
paddingInline: token.marginXXL,
|
||||
};
|
||||
let childNode = (
|
||||
const childNode = (
|
||||
<>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Typography.Title
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { Row, Col, Typography } from 'antd';
|
||||
import type { Recommendation } from './util';
|
||||
import { css } from '@emotion/react';
|
||||
import type { Recommendation } from './util';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
|
||||
const useStyle = () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import useSiteToken from '../../../../hooks/useSiteToken';
|
||||
import { COLOR_IMAGES, DEFAULT_COLOR, getClosetColor } from './colorUtil';
|
||||
import { COLOR_IMAGES, getClosetColor } from './colorUtil';
|
||||
|
||||
export interface BackgroundImageProps {
|
||||
colorPrimary?: string;
|
||||
@ -38,6 +38,7 @@ export default function BackgroundImage({ colorPrimary, isLight }: BackgroundIma
|
||||
objectPosition: 'right top',
|
||||
}}
|
||||
src={url}
|
||||
alt=""
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import useSiteToken from '../../../../hooks/useSiteToken';
|
||||
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 { 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 ColorPanel, { ColorPanelProps } from 'antd-token-previewer/es/ColorPanel';
|
||||
import useSiteToken from '../../../../hooks/useSiteToken';
|
||||
|
||||
const useStyle = () => {
|
||||
const { token } = useSiteToken();
|
||||
@ -109,10 +111,7 @@ export default function ColorPicker({ value, onChange }: RadiusPickerProps) {
|
||||
key={color}
|
||||
overlayInnerStyle={{ padding: 0 }}
|
||||
content={
|
||||
<DebouncedColorPanel
|
||||
color={value || ''}
|
||||
onChange={(color) => onChange?.(color)}
|
||||
/>
|
||||
<DebouncedColorPanel color={value || ''} onChange={(c) => onChange?.(c)} />
|
||||
}
|
||||
trigger="click"
|
||||
showArrow={false}
|
||||
|
@ -85,6 +85,7 @@ export default function ThemePicker({ value, onChange }: ThemePickerProps) {
|
||||
onClick={() => {
|
||||
onChange?.(theme);
|
||||
}}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<span>{locale[theme as keyof typeof locale]}</span>
|
||||
|
@ -52,9 +52,9 @@ export function getClosetColor(colorPrimary?: string | null) {
|
||||
const distance = COLOR_IMAGES.map(({ color }) => {
|
||||
const colorObj = new TinyColor(color).toRgb();
|
||||
const dist = Math.sqrt(
|
||||
Math.pow(colorObj.r - colorPrimaryRGB.r, 2) +
|
||||
Math.pow(colorObj.g - colorPrimaryRGB.g, 2) +
|
||||
Math.pow(colorObj.b - colorPrimaryRGB.b, 2),
|
||||
(colorObj.r - colorPrimaryRGB.r) ** 2 +
|
||||
(colorObj.g - colorPrimaryRGB.g) ** 2 +
|
||||
(colorObj.b - colorPrimaryRGB.b) ** 2,
|
||||
);
|
||||
|
||||
return { color, dist };
|
||||
|
@ -2,33 +2,34 @@ import * as React from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
import {
|
||||
HomeOutlined,
|
||||
FolderOutlined,
|
||||
BellOutlined,
|
||||
FolderOutlined,
|
||||
HomeOutlined,
|
||||
QuestionCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import useLocale from '../../../../hooks/useLocale';
|
||||
import useSiteToken from '../../../../hooks/useSiteToken';
|
||||
import type { MenuProps } from 'antd';
|
||||
import {
|
||||
Typography,
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Card,
|
||||
ConfigProvider,
|
||||
Form,
|
||||
Layout,
|
||||
Menu,
|
||||
Breadcrumb,
|
||||
MenuProps,
|
||||
Space,
|
||||
ConfigProvider,
|
||||
Card,
|
||||
Form,
|
||||
Radio,
|
||||
Space,
|
||||
theme,
|
||||
Button,
|
||||
Typography,
|
||||
} 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 RadiusPicker from './RadiusPicker';
|
||||
import Group from '../Group';
|
||||
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;
|
||||
|
||||
@ -179,10 +180,6 @@ const useStyle = () => {
|
||||
};
|
||||
};
|
||||
|
||||
interface PickerProps {
|
||||
title: React.ReactNode;
|
||||
}
|
||||
|
||||
// ========================== Menu Config ==========================
|
||||
const subMenuItems: MenuProps['items'] = [
|
||||
{
|
||||
@ -403,6 +400,7 @@ export default function Theme() {
|
||||
? undefined
|
||||
: `drop-shadow(30px 0 0 ${logoColor})`,
|
||||
}}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<h1>Ant Design 5.0</h1>
|
||||
@ -422,7 +420,7 @@ export default function Theme() {
|
||||
/>
|
||||
</Space>
|
||||
</Header>
|
||||
<Layout css={style.transBg}>
|
||||
<Layout css={style.transBg} hasSider>
|
||||
<Sider css={style.transBg} width={200} className="site-layout-background">
|
||||
<Menu
|
||||
mode="inline"
|
||||
@ -517,6 +515,7 @@ export default function Theme() {
|
||||
height: 500,
|
||||
}}
|
||||
src="https://gw.alipayobjects.com/zos/bmw-prod/bd71b0c6-f93a-4e52-9c8a-f01a9b8fe22b.svg"
|
||||
alt=""
|
||||
/>
|
||||
{/* Image Right Bottom */}
|
||||
<img
|
||||
@ -528,6 +527,7 @@ export default function Theme() {
|
||||
height: 287,
|
||||
}}
|
||||
src="https://gw.alipayobjects.com/zos/bmw-prod/84ad805a-74cb-4916-b7ba-9cdc2bdec23a.svg"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -542,11 +542,13 @@ export default function Theme() {
|
||||
<img
|
||||
style={{ ...posStyle, left: 0, top: -100, height: 500 }}
|
||||
src="https://gw.alipayobjects.com/zos/bmw-prod/a213184a-f212-4afb-beec-1e8b36bb4b8a.svg"
|
||||
alt=""
|
||||
/>
|
||||
{/* Image Right Bottom */}
|
||||
<img
|
||||
style={{ ...posStyle, right: 0, bottom: -100, height: 287 }}
|
||||
src="https://gw.alipayobjects.com/zos/bmw-prod/bb74a2fb-bff1-4d0d-8c2d-2ade0cd9bb0d.svg"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -1,29 +1,14 @@
|
||||
import React from 'react';
|
||||
import { useLocale as useDumiLocale } from 'dumi';
|
||||
import { css } from '@emotion/react';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import useLocale from '../../hooks/useLocale';
|
||||
import Banner from './components/Banner';
|
||||
import Group from './components/Group';
|
||||
import { useSiteData } from './components/util';
|
||||
import useSiteToken from '../../hooks/useSiteToken';
|
||||
import Theme from './components/Theme';
|
||||
import BannerRecommends from './components/BannerRecommends';
|
||||
import ComponentsList from './components/ComponentsList';
|
||||
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 = {
|
||||
cn: {
|
||||
@ -47,9 +32,7 @@ const Homepage: React.FC = () => {
|
||||
const { id: localeId } = useDumiLocale();
|
||||
const localeStr = localeId === 'zh-CN' ? 'cn' : 'en';
|
||||
|
||||
const [siteData, loading] = useSiteData();
|
||||
|
||||
const style = useStyle();
|
||||
const [siteData] = useSiteData();
|
||||
|
||||
return (
|
||||
<ConfigProvider theme={{ algorithm: undefined }}>
|
||||
@ -58,7 +41,7 @@ const Homepage: React.FC = () => {
|
||||
<BannerRecommends extras={siteData?.extras?.[localeStr]} icons={siteData?.icons} />
|
||||
</Banner>
|
||||
|
||||
<div css={style.container}>
|
||||
<div>
|
||||
<Theme />
|
||||
<Group
|
||||
background="#fff"
|
||||
@ -79,6 +62,7 @@ const Homepage: React.FC = () => {
|
||||
<img
|
||||
style={{ position: 'absolute', left: 0, top: -50, height: 160 }}
|
||||
src="https://gw.alipayobjects.com/zos/bmw-prod/ba37a413-28e6-4be4-b1c5-01be1a0ebb1c.svg"
|
||||
alt=""
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
@ -1 +1,3 @@
|
||||
export { default } from '../theme-editor/index';
|
||||
import ThemeEditor from '../theme-editor';
|
||||
|
||||
export default ThemeEditor;
|
||||
|
@ -1,20 +1,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ThemeEditor } from 'antd-token-previewer';
|
||||
import { useState } from 'react';
|
||||
import useLocale from '../../hooks/useLocale';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import { ThemeConfig } from 'antd/es/config-provider/context';
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
title: '主题编辑器',
|
||||
},
|
||||
en: {
|
||||
title: 'Theme Editor',
|
||||
},
|
||||
};
|
||||
import type { ThemeConfig } from 'antd/es/config-provider/context';
|
||||
|
||||
const CustomTheme = () => {
|
||||
const [locale] = useLocale(locales);
|
||||
const [theme, setTheme] = useState<ThemeConfig>({});
|
||||
|
||||
return (
|
||||
|
@ -6,7 +6,7 @@ import { type HastRoot, type UnifiedTransformer, unistUtilVisit } from 'dumi';
|
||||
*/
|
||||
function rehypeAntd(): UnifiedTransformer<HastRoot> {
|
||||
return (tree, vFile) => {
|
||||
const filename = (vFile.data.frontmatter as any).filename;
|
||||
const { filename } = vFile.data.frontmatter as any;
|
||||
|
||||
unistUtilVisit.visit(tree, 'element', (node) => {
|
||||
if (node.tagName === 'DumiDemoGrid') {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
const APITable: React.FC = () => {
|
||||
const APITable: React.FC = () => (
|
||||
// TODO: implement api table, depend on the new markdown data structure passed
|
||||
return <>API Table</>;
|
||||
};
|
||||
<>API Table</>
|
||||
);
|
||||
|
||||
export default APITable;
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Alert, AlertProps } from 'antd';
|
||||
import React, { FC } from 'react';
|
||||
import type { AlertProps } from 'antd';
|
||||
import { Alert } from 'antd';
|
||||
import type { FC } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
const MdAlert: FC<AlertProps> = ({ style, ...props }) => {
|
||||
return <Alert {...props} style={{ margin: '24px 0', ...style }} />;
|
||||
};
|
||||
const MdAlert: FC<AlertProps> = ({ style, ...props }) => (
|
||||
<Alert {...props} style={{ margin: '24px 0', ...style }} />
|
||||
);
|
||||
|
||||
export default MdAlert;
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { useState, memo, useMemo } from 'react';
|
||||
import { Link, useRouteMeta, useIntl, useSidebarData, Helmet } from 'dumi';
|
||||
import React, { memo, useMemo, useState } from 'react';
|
||||
import { Link, useIntl, useSidebarData } from 'dumi';
|
||||
import { css } from '@emotion/react';
|
||||
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 proComponentsList from './ProComponentsList';
|
||||
import type { Component } from './ProComponentsList';
|
||||
import proComponentsList from './ProComponentsList';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
|
||||
const useStyle = () => {
|
||||
@ -80,11 +80,9 @@ const { Title } = Typography;
|
||||
|
||||
const Overview: React.FC = () => {
|
||||
const style = useStyle();
|
||||
const meta = useRouteMeta();
|
||||
const data = useSidebarData();
|
||||
|
||||
const { locale, formatMessage } = useIntl();
|
||||
const documentTitle = `${meta.frontmatter.title} - Ant Design`;
|
||||
|
||||
const [search, setSearch] = useState<string>('');
|
||||
|
||||
@ -96,11 +94,11 @@ const Overview: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const groups = useMemo<{ title: string; children: Component[] }[]>(() => {
|
||||
return data
|
||||
const groups = useMemo<{ title: string; children: Component[] }[]>(
|
||||
() =>
|
||||
data
|
||||
.filter((item) => item.title)
|
||||
.map<{ title: string; children: Component[] }>((item) => {
|
||||
return {
|
||||
.map<{ title: string; children: Component[] }>((item) => ({
|
||||
title: item.title!,
|
||||
children: item.children.map((child) => ({
|
||||
title: child.frontmatter.title,
|
||||
@ -108,8 +106,7 @@ const Overview: React.FC = () => {
|
||||
cover: child.frontmatter.cover,
|
||||
link: child.link,
|
||||
})),
|
||||
};
|
||||
})
|
||||
}))
|
||||
.concat([
|
||||
{
|
||||
title: locale === 'zh-CN' ? '重型组件' : 'Others',
|
||||
@ -118,8 +115,9 @@ const Overview: React.FC = () => {
|
||||
? proComponentsList
|
||||
: proComponentsList.map((component) => ({ ...component, subtitle: '' })),
|
||||
},
|
||||
]);
|
||||
}, [data, locale]);
|
||||
]),
|
||||
[data, locale],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="markdown" ref={sectionRef}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext, useLayoutEffect, useState } from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { DumiDemoGrid, FormattedMessage } from 'dumi';
|
||||
import { Tooltip } from 'antd';
|
||||
import { BugFilled, BugOutlined, CodeFilled, CodeOutlined } from '@ant-design/icons';
|
||||
@ -55,7 +55,7 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
|
||||
</Tooltip>
|
||||
</span>
|
||||
{/* 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>
|
||||
);
|
||||
};
|
||||
|
@ -5,10 +5,8 @@ import JsonML from 'jsonml.js/lib/utils';
|
||||
import toReactComponent from 'jsonml-to-react-element';
|
||||
// @ts-ignore
|
||||
import Prism from 'prismjs';
|
||||
import { useLocation } from 'dumi';
|
||||
import { useIntl, type IPreviewerProps } from 'dumi';
|
||||
import { useLocation, useIntl, type IPreviewerProps } from 'dumi';
|
||||
import { ping } from '../../utils';
|
||||
import sylvanas from 'sylvanas';
|
||||
|
||||
let pingDeferrer: PromiseLike<boolean>;
|
||||
|
||||
@ -56,13 +54,11 @@ export default function fromDumiProps<P extends object>(
|
||||
toReactComponent(jsonML: any) {
|
||||
return toReactComponent(jsonML, [
|
||||
[
|
||||
function (node: any) {
|
||||
return JsonML.isElement(node) && JsonML.getTagName(node) === 'pre';
|
||||
},
|
||||
function (node: any, index: any) {
|
||||
(node: any) => JsonML.isElement(node) && JsonML.getTagName(node) === 'pre',
|
||||
(node: any, index: any) => {
|
||||
// @ts-ignore
|
||||
// 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(
|
||||
'pre',
|
||||
{
|
||||
|
@ -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() {
|
||||
const { state } = this;
|
||||
const { props } = this;
|
||||
@ -167,7 +160,7 @@ class Demo extends React.Component {
|
||||
});
|
||||
const localizedTitle = meta.title[locale] || meta.title;
|
||||
const localizeIntro = content[locale] || content;
|
||||
const introChildren = <div dangerouslySetInnerHTML={{ __html: localizeIntro }}></div>;
|
||||
const introChildren = <div dangerouslySetInnerHTML={{ __html: localizeIntro }} />;
|
||||
|
||||
const highlightClass = classNames('highlight-wrapper', {
|
||||
'highlight-wrapper-expand': codeExpand,
|
||||
@ -458,7 +451,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
||||
</Tooltip>
|
||||
</CopyToClipboard>
|
||||
<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" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
|
@ -3,9 +3,9 @@ import * as React from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import { FormattedMessage, useIntl } from 'dumi';
|
||||
import { Tabs, Skeleton, Avatar, Divider, Empty } from 'antd';
|
||||
import { css } from '@emotion/react';
|
||||
import { useSiteData } from '../../../pages/index/components/util';
|
||||
import type { Article, Authors } from '../../../pages/index/components/util';
|
||||
import { css } from '@emotion/react';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
|
||||
const useStyle = () => {
|
||||
|
@ -86,7 +86,7 @@ const ResourceCard: React.FC<ResourceCardProps> = ({ resource }) => {
|
||||
|
||||
return (
|
||||
<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
|
||||
css={styles.image}
|
||||
src={cover}
|
||||
@ -105,14 +105,12 @@ export type ResourceCardsProps = {
|
||||
resources: Resource[];
|
||||
};
|
||||
|
||||
const ResourceCards: React.FC<ResourceCardsProps> = ({ resources }) => {
|
||||
return (
|
||||
const ResourceCards: React.FC<ResourceCardsProps> = ({ resources }) => (
|
||||
<Row style={{ margin: '-12px -12px 0 -12px' }}>
|
||||
{resources.map((item) => (
|
||||
<ResourceCard resource={item} key={item.title} />
|
||||
))}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
export default ResourceCards;
|
||||
|
@ -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 { 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 useSiteToken from '../../../hooks/useSiteToken';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
type TokenTableProps = {
|
||||
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)',
|
||||
marginRight: 4,
|
||||
}}
|
||||
></span>
|
||||
/>
|
||||
)}
|
||||
{typeof record.value !== 'string' ? JSON.stringify(record.value) : record.value}
|
||||
</span>
|
||||
@ -97,16 +101,16 @@ const TokenTable: FC<TokenTableProps> = ({ type }) => {
|
||||
},
|
||||
];
|
||||
|
||||
const data = useMemo<TokenData[]>(() => {
|
||||
return tokenMeta[type].map((token) => {
|
||||
return {
|
||||
const data = useMemo<TokenData[]>(
|
||||
() =>
|
||||
tokenMeta[type].map((token) => ({
|
||||
name: token.name,
|
||||
desc: lang === 'cn' ? token.desc : token.descEn,
|
||||
type: token.type,
|
||||
value: (defaultToken as any)[token.name],
|
||||
};
|
||||
});
|
||||
}, [type, lang]);
|
||||
})),
|
||||
[type, lang],
|
||||
);
|
||||
|
||||
return <Table dataSource={data} columns={columns} pagination={false} bordered />;
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import React from 'react';
|
||||
import { Global, css } from '@emotion/react';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
|
||||
|
@ -6,7 +6,7 @@ const CommonHelmet = () => {
|
||||
|
||||
const [title, description] = useMemo(() => {
|
||||
const helmetTitle = `${meta.frontmatter.subtitle || ''} ${meta.frontmatter.title} - Ant Design`;
|
||||
let helmetDescription = meta.frontmatter.description;
|
||||
const helmetDescription = meta.frontmatter.description;
|
||||
return [helmetTitle, helmetDescription];
|
||||
}, [meta]);
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Global, css } from '@emotion/react';
|
||||
import useSiteToken from '../../hooks/useSiteToken';
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
import ColorStyle from '../common/Color/ColorStyle';
|
||||
import useSiteToken from '../../hooks/useSiteToken';
|
||||
import ColorStyle from './Color/ColorStyle';
|
||||
|
||||
const GlobalStyles = () => {
|
||||
const { token } = useSiteToken();
|
||||
|
@ -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 useSiteToken from '../../hooks/useSiteToken';
|
||||
import { Menu, MenuProps, Typography } from 'antd';
|
||||
import useMenu from '../../hooks/useMenu';
|
||||
import { MenuItemType } from 'antd/es/menu/hooks/useItems';
|
||||
import type { MenuProps } from 'antd';
|
||||
import type { MenuItemType } from 'antd/es/menu/hooks/useItems';
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import useMenu from '../../hooks/useMenu';
|
||||
import useSiteToken from '../../hooks/useSiteToken';
|
||||
|
||||
const useStyle = () => {
|
||||
const { token } = useSiteToken();
|
||||
@ -114,9 +115,10 @@ const PrevAndNext = () => {
|
||||
activeMenuItemIndex = i;
|
||||
}
|
||||
});
|
||||
const prev = flatMenu[activeMenuItemIndex - 1];
|
||||
const next = flatMenu[activeMenuItemIndex + 1];
|
||||
return [prev as MenuItemType, next as MenuItemType];
|
||||
return [
|
||||
flatMenu[activeMenuItemIndex - 1] as MenuItemType,
|
||||
flatMenu[activeMenuItemIndex + 1] as MenuItemType,
|
||||
];
|
||||
}, [menuItems, selectedKey]);
|
||||
|
||||
return (
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { FC } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import React from 'react';
|
||||
import { FloatButton, theme } from 'antd';
|
||||
import ThemeIcon from './ThemeIcon';
|
||||
import { DarkTheme, Light, CompactTheme } from 'antd-token-previewer/es/icons';
|
||||
import ThemeIcon from './ThemeIcon';
|
||||
|
||||
const { defaultAlgorithm, darkAlgorithm, compactAlgorithm } = theme;
|
||||
|
||||
|
@ -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 from 'dayjs';
|
||||
import { useOutlet, useSearchParams, Helmet } from 'dumi';
|
||||
import Header from 'dumi/theme/slots/Header';
|
||||
import Footer from 'dumi/theme/slots/Footer';
|
||||
import { Helmet, useOutlet, useSearchParams } from 'dumi';
|
||||
import '../../static/style';
|
||||
import useLocation from '../../../hooks/useLocation';
|
||||
import SiteContext from '../../slots/SiteContext';
|
||||
import ConfigProvider, { DirectionType } from 'antd/es/config-provider';
|
||||
import type { DirectionType } from 'antd/es/config-provider';
|
||||
import ConfigProvider from 'antd/es/config-provider';
|
||||
import classNames from 'classnames';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import zhCN from 'antd/es/locale/zh_CN';
|
||||
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 GlobalStyles from '../../common/GlobalStyles';
|
||||
import SidebarLayout from '../SidebarLayout';
|
||||
@ -94,9 +95,9 @@ const DocLayout: React.FC = () => {
|
||||
}
|
||||
}, [location]);
|
||||
|
||||
const changeDirection = (direction: DirectionType): void => {
|
||||
setDirection(direction);
|
||||
if (direction === 'ltr') {
|
||||
const changeDirection = (dir: DirectionType): void => {
|
||||
setDirection(dir);
|
||||
if (dir === 'ltr') {
|
||||
searchParams.delete('direction');
|
||||
} else {
|
||||
searchParams.set('direction', 'rtl');
|
||||
@ -115,17 +116,27 @@ const DocLayout: React.FC = () => {
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
} else if (pathname.startsWith('/docs/resource')) {
|
||||
}
|
||||
if (pathname.startsWith('/docs/resource')) {
|
||||
return <ResourceLayout>{outlet}</ResourceLayout>;
|
||||
} else if (pathname.startsWith('/theme-editor')) {
|
||||
return <>{outlet}</>;
|
||||
}
|
||||
if (pathname.startsWith('/theme-editor')) {
|
||||
return outlet;
|
||||
}
|
||||
return <SidebarLayout>{outlet}</SidebarLayout>;
|
||||
}, [pathname, outlet]);
|
||||
|
||||
const siteContextValue = useMemo(
|
||||
() => ({
|
||||
isMobile,
|
||||
direction,
|
||||
}),
|
||||
[isMobile, direction],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyleProvider cache={styleCache}>
|
||||
<SiteContext.Provider value={{ isMobile, direction }}>
|
||||
<SiteContext.Provider value={siteContextValue}>
|
||||
<Helmet encodeSpecialCharacters={false}>
|
||||
<html
|
||||
lang={lang}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import { useOutlet } from 'dumi';
|
||||
import { ConfigProvider, theme as antdTheme } from 'antd';
|
||||
import { ThemeConfig } from 'antd/es/config-provider/context';
|
||||
import ThemeContext, { ThemeContextProps } from '../slots/ThemeContext';
|
||||
import type { ThemeConfig } from 'antd/es/config-provider/context';
|
||||
import type { ThemeContextProps } from '../slots/ThemeContext';
|
||||
import ThemeContext from '../slots/ThemeContext';
|
||||
import ThemeSwitch from '../common/ThemeSwitch';
|
||||
import useLocation from '../../hooks/useLocation';
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { Tabs } from 'antd';
|
||||
import { css } from '@emotion/react';
|
||||
import scrollTo from '../../../../components/_util/scrollTo';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
const useStyle = () => {
|
||||
const { token } = useSiteToken();
|
||||
|
@ -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 Footer from 'dumi/theme/slots/Footer';
|
||||
import { Layout, Typography, ConfigProvider } from 'antd';
|
||||
import { css } from '@emotion/react';
|
||||
import Footer from '../../slots/Footer';
|
||||
import AffixTabs from './AffixTabs';
|
||||
import EditButton from '../../common/EditButton';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
|
@ -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 Content from '../../slots/Content';
|
||||
import CommonHelmet from '../../common/CommonHelmet';
|
||||
|
||||
const SidebarLayout: FC<PropsWithChildren<{}>> = ({ children }) => {
|
||||
return (
|
||||
const SidebarLayout: FC<PropsWithChildren<{}>> = ({ children }) => (
|
||||
<main style={{ display: 'flex', marginTop: 40 }}>
|
||||
<CommonHelmet />
|
||||
<Sidebar />
|
||||
<Content>{children}</Content>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
export default SidebarLayout;
|
||||
|
@ -5,10 +5,6 @@
|
||||
"app.theme.switch.compact": "Compact theme",
|
||||
"app.header.search": "Search...",
|
||||
"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.mobile": "Mobile",
|
||||
"app.header.menu.pro.v4": "Ant Design Pro",
|
||||
|
@ -5,10 +5,6 @@
|
||||
"app.theme.switch.compact": "紧凑主题",
|
||||
"app.header.search": "全文本搜索...",
|
||||
"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.mobile": "移动版",
|
||||
"app.header.menu.pro.v4": "Ant Design Pro",
|
||||
|
@ -81,7 +81,7 @@ const RoutesPlugin = (api: IApi) => {
|
||||
let styles = '';
|
||||
|
||||
// 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;
|
||||
|
||||
return '';
|
||||
|
@ -1,16 +1,17 @@
|
||||
import React, { ReactNode, useMemo, useState, useLayoutEffect, useContext } from 'react';
|
||||
import { useIntl, useRouteMeta } from 'dumi';
|
||||
import Footer from 'dumi/theme/slots/Footer';
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useMemo, useState, useLayoutEffect, useContext } from 'react';
|
||||
import { useIntl, useRouteMeta, FormattedMessage } from 'dumi';
|
||||
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 useSiteToken from '../../../hooks/useSiteToken';
|
||||
import { css } from '@emotion/react';
|
||||
import PrevAndNext from '../../common/PrevAndNext';
|
||||
import DemoContext, { DemoContextProps } from '../DemoContext';
|
||||
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';
|
||||
|
||||
const useStyle = () => {
|
||||
@ -120,8 +121,9 @@ const Content: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
[showDebug, debugDemos],
|
||||
);
|
||||
|
||||
const anchorItems = useMemo(() => {
|
||||
return meta.toc.reduce<AnchorItem[]>((result, item) => {
|
||||
const anchorItems = useMemo(
|
||||
() =>
|
||||
meta.toc.reduce<AnchorItem[]>((result, item) => {
|
||||
if (item.depth === 2) {
|
||||
result.push({ ...item });
|
||||
} else if (item.depth === 3) {
|
||||
@ -132,8 +134,9 @@ const Content: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
}, [meta.toc]);
|
||||
}, []),
|
||||
[meta.toc],
|
||||
);
|
||||
|
||||
const isRTL = direction === 'rtl';
|
||||
|
||||
|
@ -17,12 +17,12 @@ import {
|
||||
ZhihuOutlined,
|
||||
YuqueFilled,
|
||||
} from '@ant-design/icons';
|
||||
import useLocation from '../../../hooks/useLocation';
|
||||
import { css } from '@emotion/react';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
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';
|
||||
|
||||
const locales = {
|
||||
|
@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { Link, useLocation } from 'dumi';
|
||||
import * as utils from '../../utils';
|
||||
import { css } from '@emotion/react';
|
||||
import * as utils from '../../utils';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
|
||||
const useStyle = () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { Dropdown, Menu, Button } from 'antd';
|
||||
import { Button, Dropdown } from 'antd';
|
||||
import { FormattedMessage } from 'dumi';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import type { SharedProps } from './interface';
|
||||
|
@ -1,15 +1,35 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Link, useLocation, FormattedMessage } from 'dumi';
|
||||
import { FormattedMessage, Link, useFullSidebarData, useLocation } from 'dumi';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
import { Menu } from 'antd';
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
import { css } from '@emotion/react';
|
||||
import { getEcosystemGroup } from './More';
|
||||
import * as utils from '../../utils';
|
||||
import type { SharedProps } from './interface';
|
||||
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 { token } = useSiteToken();
|
||||
|
||||
@ -83,7 +103,6 @@ export interface NavigationProps extends SharedProps {
|
||||
isClient: boolean;
|
||||
responsive: null | 'narrow' | 'crowded';
|
||||
directionText: string;
|
||||
showTechUIButton: boolean;
|
||||
onLangChange: () => void;
|
||||
onDirectionChange: () => void;
|
||||
}
|
||||
@ -94,11 +113,14 @@ export default ({
|
||||
isMobile,
|
||||
responsive,
|
||||
directionText,
|
||||
showTechUIButton,
|
||||
onLangChange,
|
||||
onDirectionChange,
|
||||
}: NavigationProps) => {
|
||||
const { pathname, search } = useLocation();
|
||||
const [locale] = useLocale(locales);
|
||||
|
||||
const sidebarData = useFullSidebarData();
|
||||
const blogList = sidebarData['/docs/blog']?.[0]?.children || [];
|
||||
|
||||
const style = useStyle();
|
||||
|
||||
@ -160,7 +182,7 @@ export default ({
|
||||
{
|
||||
label: (
|
||||
<Link to={utils.getLocalizedPathname('/docs/spec/introduce', isZhCN, search)}>
|
||||
<FormattedMessage id="app.header.menu.spec" />
|
||||
{locale.design}
|
||||
</Link>
|
||||
),
|
||||
key: 'docs/spec',
|
||||
@ -168,7 +190,7 @@ export default ({
|
||||
{
|
||||
label: (
|
||||
<Link to={utils.getLocalizedPathname('/docs/react/introduce', isZhCN, search)}>
|
||||
<FormattedMessage id="app.header.menu.development" />
|
||||
{locale.development}
|
||||
</Link>
|
||||
),
|
||||
key: 'docs/react',
|
||||
@ -176,29 +198,29 @@ export default ({
|
||||
{
|
||||
label: (
|
||||
<Link to={utils.getLocalizedPathname('/components/overview/', isZhCN, search)}>
|
||||
<FormattedMessage id="app.header.menu.components" />
|
||||
{locale.components}
|
||||
</Link>
|
||||
),
|
||||
key: 'components',
|
||||
},
|
||||
blogList.length
|
||||
? {
|
||||
label: (
|
||||
<Link to={utils.getLocalizedPathname(blogList[0].link, isZhCN, search)}>
|
||||
{locale.blog}
|
||||
</Link>
|
||||
),
|
||||
key: 'docs/blog',
|
||||
}
|
||||
: null,
|
||||
{
|
||||
label: (
|
||||
<Link to={utils.getLocalizedPathname('/docs/resources', isZhCN, search)}>
|
||||
<FormattedMessage id="app.header.menu.resource" />
|
||||
{locale.resources}
|
||||
</Link>
|
||||
),
|
||||
key: 'docs/resources',
|
||||
},
|
||||
showTechUIButton
|
||||
? {
|
||||
label: (
|
||||
<a href="https://techui.alipay.com" target="__blank" rel="noopener noreferrer">
|
||||
TechUI
|
||||
</a>
|
||||
),
|
||||
key: 'tech-ui',
|
||||
}
|
||||
: null,
|
||||
isZhCN &&
|
||||
isClient &&
|
||||
window.location.host !== 'ant-design.antgroup.com' &&
|
||||
|
143
.dumi/theme/slots/Header/SwitchBtn.tsx
Normal file
143
.dumi/theme/slots/Header/SwitchBtn.tsx
Normal 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;
|
||||
}
|
@ -1,24 +1,22 @@
|
||||
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 classNames from 'classnames';
|
||||
import { Button, Col, Modal, Popover, Row, Select, Typography } from 'antd';
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
import canUseDom from 'rc-util/lib/Dom/canUseDom';
|
||||
import { Col, Modal, Popover, Row, Select, Typography } from 'antd';
|
||||
import { GithubOutlined, MenuOutlined } from '@ant-design/icons';
|
||||
import type { DirectionType } from 'antd/es/config-provider';
|
||||
import { ClassNames, css } from '@emotion/react';
|
||||
import * as utils from '../../utils';
|
||||
import { getThemeConfig, ping } from '../../utils';
|
||||
import packageJson from '../../../../package.json';
|
||||
import Logo from './Logo';
|
||||
import More from './More';
|
||||
import Navigation from './Navigation';
|
||||
import Github from './Github';
|
||||
import type { SiteContextProps } from '../SiteContext';
|
||||
import SiteContext from '../SiteContext';
|
||||
import { useLocation, useNavigate } from 'dumi';
|
||||
import { ClassNames, css } from '@emotion/react';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import SwitchBtn from './SwitchBtn';
|
||||
|
||||
const RESPONSIVE_XS = 1120;
|
||||
const RESPONSIVE_SM = 1200;
|
||||
@ -55,8 +53,8 @@ const useStyle = () => {
|
||||
}
|
||||
|
||||
.nav-search-wrapper {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.dumi-default-search-bar {
|
||||
@ -121,10 +119,6 @@ const useStyle = () => {
|
||||
}
|
||||
}
|
||||
`,
|
||||
headerButton: css`
|
||||
color: ${token.colorText};
|
||||
border-color: ${token.colorBorder};
|
||||
`,
|
||||
popoverMenu: {
|
||||
width: 300,
|
||||
|
||||
@ -139,18 +133,6 @@ export interface HeaderProps {
|
||||
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 SHOULD_OPEN_ANT_DESIGN_MIRROR_MODAL = 'ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL';
|
||||
|
||||
@ -166,12 +148,10 @@ interface HeaderState {
|
||||
menuVisible: boolean;
|
||||
windowWidth: number;
|
||||
searching: boolean;
|
||||
showTechUIButton: boolean;
|
||||
}
|
||||
|
||||
// ================================= Header =================================
|
||||
const Header: React.FC<HeaderProps> = (props) => {
|
||||
const intl = useIntl();
|
||||
const { changeDirection } = props;
|
||||
const [isClient, setIsClient] = React.useState(false);
|
||||
const [locale, lang] = useLocale(locales);
|
||||
@ -207,13 +187,11 @@ const Header: React.FC<HeaderProps> = (props) => {
|
||||
menuVisible: false,
|
||||
windowWidth: 1400,
|
||||
searching: false,
|
||||
showTechUIButton: false,
|
||||
});
|
||||
const { direction, isMobile } = useContext<SiteContextProps>(SiteContext);
|
||||
const pingTimer = useRef<NodeJS.Timeout | null>(null);
|
||||
const location = useLocation();
|
||||
const { pathname, search } = location;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const style = useStyle();
|
||||
|
||||
@ -223,9 +201,6 @@ const Header: React.FC<HeaderProps> = (props) => {
|
||||
const onWindowResize = useCallback(() => {
|
||||
setHeaderState((prev) => ({ ...prev, windowWidth: window.innerWidth }));
|
||||
}, []);
|
||||
const onTriggerSearching = useCallback((searching: boolean) => {
|
||||
setHeaderState((prev) => ({ ...prev, searching }));
|
||||
}, []);
|
||||
const handleShowMenu = useCallback(() => {
|
||||
setHeaderState((prev) => ({ ...prev, menuVisible: true }));
|
||||
}, []);
|
||||
@ -246,7 +221,6 @@ const Header: React.FC<HeaderProps> = (props) => {
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
pingTimer.current = ping((status) => {
|
||||
if (status !== 'timeout' && status !== 'error') {
|
||||
setHeaderState((prev) => ({ ...prev, showTechUIButton: true }));
|
||||
if (
|
||||
// process.env.NODE_ENV === 'production' &&
|
||||
window.location.host !== 'ant-design.antgroup.com' &&
|
||||
@ -317,7 +291,7 @@ const Header: React.FC<HeaderProps> = (props) => {
|
||||
[direction],
|
||||
);
|
||||
|
||||
const { menuVisible, windowWidth, searching, showTechUIButton } = headerState;
|
||||
const { menuVisible, windowWidth, searching } = headerState;
|
||||
const docVersions: Record<string, string> = {
|
||||
[antdVersion]: antdVersion,
|
||||
...themeConfig?.docVersions,
|
||||
@ -356,7 +330,6 @@ const Header: React.FC<HeaderProps> = (props) => {
|
||||
{...sharedProps}
|
||||
responsive={responsive}
|
||||
isMobile={isMobile}
|
||||
showTechUIButton={showTechUIButton}
|
||||
directionText={nextDirectionText}
|
||||
onLangChange={onLangChange}
|
||||
onDirectionChange={onDirectionChange}
|
||||
@ -366,6 +339,7 @@ const Header: React.FC<HeaderProps> = (props) => {
|
||||
let menu: (React.ReactElement | null)[] = [
|
||||
navigationNode,
|
||||
<Popover
|
||||
key="version"
|
||||
open={!!notify}
|
||||
title={locale.title}
|
||||
content={
|
||||
@ -415,19 +389,44 @@ const Header: React.FC<HeaderProps> = (props) => {
|
||||
{versionOptions}
|
||||
</Select>
|
||||
</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} />,
|
||||
<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) {
|
||||
@ -447,9 +446,9 @@ const Header: React.FC<HeaderProps> = (props) => {
|
||||
<header css={style.header} className={headerClassName}>
|
||||
{isMobile && (
|
||||
<ClassNames>
|
||||
{({ css }) => (
|
||||
{({ css: cssFn }) => (
|
||||
<Popover
|
||||
overlayClassName={css(style.popoverMenu)}
|
||||
overlayClassName={cssFn(style.popoverMenu)}
|
||||
placement="bottomRight"
|
||||
content={menu}
|
||||
trigger="click"
|
||||
|
@ -2,10 +2,10 @@ import React, { useContext } from 'react';
|
||||
import { useSidebarData } from 'dumi';
|
||||
import { Affix, Col, Menu } from 'antd';
|
||||
import MobileMenu from 'rc-drawer';
|
||||
import { css } from '@emotion/react';
|
||||
import SiteContext from '../SiteContext';
|
||||
import useMenu from '../../../hooks/useMenu';
|
||||
import useSiteToken from '../../../hooks/useSiteToken';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
const useStyle = () => {
|
||||
const { token } = useSiteToken();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createContext } from 'react';
|
||||
import { ThemeConfig } from 'antd/es/config-provider/context';
|
||||
import type { ThemeConfig } from 'antd/es/config-provider/context';
|
||||
|
||||
export type ThemeContextProps = {
|
||||
theme: ThemeConfig;
|
||||
|
@ -139,6 +139,7 @@ export function getLocalizedPathname(
|
||||
fullPath = pathname.replace(/\/$/, '-cn/');
|
||||
} else {
|
||||
fullPath = `${pathname}-cn`;
|
||||
fullPath = fullPath.replace(/(-cn)+/, '-cn');
|
||||
}
|
||||
|
||||
if (hash) {
|
||||
|
@ -3,6 +3,8 @@
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@emotion/react",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": "../",
|
||||
"paths": {
|
||||
"@@/*": [".dumi/tmp/*"],
|
||||
|
@ -11,6 +11,8 @@ lib/**/*
|
||||
locale
|
||||
server
|
||||
.dumi/tmp
|
||||
.dumi/tmp-production
|
||||
!.dumi/
|
||||
node_modules
|
||||
_site
|
||||
dist
|
||||
|
16
.eslintrc.js
16
.eslintrc.js
@ -98,6 +98,22 @@ module.exports = {
|
||||
'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: {
|
||||
'react/jsx-one-expression-per-line': 0,
|
||||
|
4
.github/workflows/disscustion-open-check.yml
vendored
4
.github/workflows/disscustion-open-check.yml
vendored
@ -16,7 +16,9 @@ jobs:
|
||||
- name: send to dingtalk
|
||||
uses: visiky/dingtalk-release-notify@main
|
||||
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_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 进行讨论,社区可能需要你的帮助。'
|
||||
|
4
.github/workflows/issue-open-check.yml
vendored
4
.github/workflows/issue-open-check.yml
vendored
@ -89,7 +89,9 @@ jobs:
|
||||
- name: send to dingtalk
|
||||
uses: visiky/dingtalk-release-notify@main
|
||||
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_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 进行讨论,社区可能需要你的帮助。'
|
||||
|
2
.github/workflows/release-helper.yml
vendored
2
.github/workflows/release-helper.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
changelogs: 'CHANGELOG.en-US.md, CHANGELOG.zh-CN.md'
|
||||
branch: 'master, 4.x-stable'
|
||||
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'
|
||||
msg-title: '# Ant Design {{v}} 发布日志'
|
||||
msg-poster: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*zx7LTI_ECSAAAAAAAAAAAABkARQnAQ'
|
||||
|
@ -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
|
||||
|
||||
`2022-11-22`
|
||||
|
@ -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 组件嵌套 Modal,Dropdown,Drawer 等组件时的样式问题。[#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
|
||||
|
||||
`2022-11-22`
|
||||
|
@ -2,7 +2,7 @@ describe('Test warning', () => {
|
||||
let spy: jest.SpyInstance;
|
||||
|
||||
beforeAll(() => {
|
||||
spy = jest.spyOn(console, 'error');
|
||||
spy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
@ -76,9 +76,8 @@ describe('Alert', () => {
|
||||
});
|
||||
|
||||
it('should show error as ErrorBoundary when children have error', () => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.error).toHaveBeenCalledTimes(0);
|
||||
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
expect(warnSpy).toHaveBeenCalledTimes(0);
|
||||
// @ts-expect-error
|
||||
// eslint-disable-next-line react/jsx-no-undef
|
||||
const ThrowError = () => <NotExisted />;
|
||||
@ -91,8 +90,7 @@ describe('Alert', () => {
|
||||
expect(screen.getByRole('alert')).toHaveTextContent(
|
||||
'ReferenceError: NotExisted is not defined',
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
(console.error as any).mockRestore();
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('could be used with Tooltip', async () => {
|
||||
|
@ -49,7 +49,7 @@ describe('AutoComplete', () => {
|
||||
});
|
||||
|
||||
it('AutoComplete throws error when contains invalid dataSource', () => {
|
||||
const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
render(
|
||||
// @ts-ignore
|
||||
@ -82,13 +82,10 @@ describe('AutoComplete', () => {
|
||||
});
|
||||
|
||||
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 />);
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.warn).not.toHaveBeenCalled();
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn.mockRestore();
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not override custom input className', () => {
|
||||
|
@ -1,3 +1,4 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { PoweroffOutlined } from '@ant-design/icons';
|
||||
import { Button, Space } from 'antd';
|
||||
|
@ -1,5 +1,4 @@
|
||||
import classNames from 'classnames';
|
||||
import padStart from 'lodash/padStart';
|
||||
import { PickerPanel as RCPickerPanel } from 'rc-picker';
|
||||
import type { GenerateConfig } from 'rc-picker/lib/generate';
|
||||
import type { Locale } from 'rc-picker/lib/interface';
|
||||
@ -198,7 +197,7 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
|
||||
})}
|
||||
>
|
||||
<div className={`${calendarPrefixCls}-date-value`}>
|
||||
{padStart(String(generateConfig.getDate(date)), 2, '0')}
|
||||
{String(generateConfig.getDate(date)).padStart(2, '0')}
|
||||
</div>
|
||||
<div className={`${calendarPrefixCls}-date-content`}>
|
||||
{dateCellRender && dateCellRender(date)}
|
||||
|
@ -174,6 +174,7 @@ const genCardMetaStyle: GenerateStyle<CardToken> = (token): CSSObject => ({
|
||||
|
||||
'&-detail': {
|
||||
overflow: 'hidden',
|
||||
flex: 1,
|
||||
|
||||
'> div:not(:last-child)': {
|
||||
marginBottom: token.marginXS,
|
||||
|
@ -71,7 +71,7 @@ const defaultGetPrefixCls = (suffixCls?: string, customizePrefixCls?: string) =>
|
||||
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>({
|
||||
// We provide a default function for Context without provider
|
||||
getPrefixCls: defaultGetPrefixCls,
|
||||
@ -98,7 +98,7 @@ export function withConfigConsumer<ExportProps extends BasicExportProps>(config:
|
||||
return function withConfigConsumerFunc<ComponentDef>(
|
||||
Component: React.ComponentType<ExportProps>,
|
||||
): 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) => (
|
||||
<ConfigConsumer>
|
||||
{(configProps: ConfigConsumerProps) => {
|
||||
|
@ -104,3 +104,7 @@ When you config `getPopupContainer` to parentNode globally, Modal will throw err
|
||||
<App />
|
||||
</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.
|
||||
|
@ -105,3 +105,7 @@ ConfigProvider.config({
|
||||
<App />
|
||||
</ConfigProvider>
|
||||
```
|
||||
|
||||
#### 为什么 message.info、notification.open 或 Modal.confirm 等方法内的 ReactNode 无法继承 ConfigProvider 的属性?比如 `prefixCls` 和 `theme`。
|
||||
|
||||
静态方法是使用 ReactDOM.render 重新渲染一个 React 根节点上,和主应用的 React 节点是脱离的。我们建议使用 useMessage、useNotification 和 useModal 来使用相关方法。原先的静态方法在 5.0 中已被废弃。
|
||||
|
@ -8,7 +8,7 @@ const { QuarterPicker } = DatePicker;
|
||||
describe('QuarterPicker', () => {
|
||||
it('should support style prop', () => {
|
||||
resetWarned();
|
||||
const warnSpy = jest.spyOn(console, 'error');
|
||||
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
const { container } = render(<QuarterPicker style={{ width: 400 }} />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
|
@ -116,7 +116,7 @@ describe('Dropdown', () => {
|
||||
});
|
||||
|
||||
it('should warn if use topCenter or bottomCenter', () => {
|
||||
const error = jest.spyOn(console, 'error');
|
||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(
|
||||
<div>
|
||||
<Dropdown menu={{ items }} placement="bottomCenter">
|
||||
|
@ -29,6 +29,8 @@ FloatButton. Available since `5.0.0`.
|
||||
|
||||
## API
|
||||
|
||||
> This component is available since `antd@5.0.0`.
|
||||
|
||||
### common API
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
|
@ -30,6 +30,8 @@ demo:
|
||||
|
||||
## API
|
||||
|
||||
> 自 `antd@5.0.0` 版本开始提供该组件。
|
||||
|
||||
### 共同的 API
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|
@ -4,6 +4,7 @@ import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { initFadeMotion } from '../../style/motion/fade';
|
||||
import { resetComponent } from '../../style';
|
||||
import { initMotion } from '../../style/motion/motion';
|
||||
|
||||
/** Component only token. Which will handle additional calculation of alias token */
|
||||
export interface ComponentToken {
|
||||
@ -23,9 +24,8 @@ type FloatButtonToken = FullToken<'FloatButton'> & {
|
||||
floatButtonInsetInlineEnd: number;
|
||||
};
|
||||
|
||||
// ============================== Group ==============================
|
||||
const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token) => {
|
||||
const { componentCls, floatButtonSize, margin, borderRadius, motionDurationSlow } = token;
|
||||
const initFloatButtonGroupMotion = (token: FloatButtonToken) => {
|
||||
const { componentCls, floatButtonSize, motionDurationSlow, motionEaseInOutCirc } = token;
|
||||
const groupPrefixCls = `${componentCls}-group`;
|
||||
const moveDownIn = new Keyframes('antFloatButtonMoveDownIn', {
|
||||
'0%': {
|
||||
@ -53,6 +53,35 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
|
||||
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 {
|
||||
[groupPrefixCls]: {
|
||||
...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`]: {
|
||||
boxShadow: 'none',
|
||||
},
|
||||
@ -308,5 +328,6 @@ export default genComponentStyleHook<'FloatButton'>('FloatButton', (token) => {
|
||||
floatButtonGroupStyle(floatButtonToken),
|
||||
sharedFloatButtonStyle(floatButtonToken),
|
||||
initFadeMotion(token),
|
||||
initFloatButtonGroupMotion(floatButtonToken),
|
||||
];
|
||||
});
|
||||
|
@ -1040,7 +1040,7 @@ describe('Menu', () => {
|
||||
});
|
||||
|
||||
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} />);
|
||||
expect(errorSpy).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining('`children` will be removed in next major version'),
|
||||
|
@ -177,7 +177,6 @@ const getThemeStyle = (token: MenuToken, themeSuffix: string): CSSInterpolation
|
||||
},
|
||||
|
||||
[`&:hover, &-active, &-open`]: {
|
||||
color: colorItemTextSelectedHorizontal,
|
||||
'&::after': {
|
||||
borderWidth: `${colorActiveBarHeight}px`,
|
||||
borderBottomColor: colorItemTextSelectedHorizontal,
|
||||
|
@ -36,7 +36,6 @@ This components provides some static methods, with usage and arguments as follow
|
||||
- `message.error(content, [duration], onClose)`
|
||||
- `message.info(content, [duration], onClose)`
|
||||
- `message.warning(content, [duration], onClose)`
|
||||
- `message.warn(content, [duration], onClose)` // alias of warning
|
||||
- `message.loading(content, [duration], onClose)`
|
||||
|
||||
| Argument | Description | Type | Default |
|
||||
@ -59,7 +58,6 @@ Supports passing parameters wrapped in an object:
|
||||
- `message.error(config)`
|
||||
- `message.info(config)`
|
||||
- `message.warning(config)`
|
||||
- `message.warn(config)` // alias of warning
|
||||
- `message.loading(config)`
|
||||
|
||||
The properties of config are as follows:
|
||||
@ -111,7 +109,7 @@ message.config({
|
||||
|
||||
## 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.
|
||||
|
||||
|
@ -37,7 +37,6 @@ demo:
|
||||
- `message.error(content, [duration], onClose)`
|
||||
- `message.info(content, [duration], onClose)`
|
||||
- `message.warning(content, [duration], onClose)`
|
||||
- `message.warn(content, [duration], onClose)` // alias of warning
|
||||
- `message.loading(content, [duration], onClose)`
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
@ -60,7 +59,6 @@ demo:
|
||||
- `message.error(config)`
|
||||
- `message.info(config)`
|
||||
- `message.warning(config)`
|
||||
- `message.warn(config)` // alias of warning
|
||||
- `message.loading(config)`
|
||||
|
||||
`config` 对象属性如下:
|
||||
@ -112,7 +110,7 @@ message.config({
|
||||
|
||||
## FAQ
|
||||
|
||||
### 为什么 message 不能获取 context、redux 的内容和 ConfigProvider 的 `locale/prefixCls` 配置?
|
||||
### 为什么 message 不能获取 context、redux 的内容和 ConfigProvider 的 `locale/prefixCls/theme` 等配置?
|
||||
|
||||
直接调用 message 方法,antd 会通过 `ReactDOM.render` 动态创建新的 React 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。
|
||||
|
||||
|
@ -41,7 +41,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
// });
|
||||
// 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 */
|
||||
// Hack error to remove act warning
|
||||
|
@ -171,7 +171,7 @@ return <div>{contextHolder}</div>;
|
||||
|
||||
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 信息。
|
||||
|
||||
|
@ -93,7 +93,7 @@ notification.config({
|
||||
|
||||
## 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.
|
||||
|
||||
|
@ -93,7 +93,7 @@ notification.config({
|
||||
|
||||
## FAQ
|
||||
|
||||
### 为什么 notification 不能获取 context、redux 的内容和 ConfigProvider 的 `locale/prefixCls` 配置?
|
||||
### 为什么 notification 不能获取 context、redux 的内容和 ConfigProvider 的 `locale/prefixCls/theme` 等配置?
|
||||
|
||||
直接调用 notification 方法,antd 会通过 `ReactDOM.render` 动态创建新的 React 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Segmented } from 'antd';
|
||||
|
||||
const Demo = () => {
|
||||
const [foo, setFoo] = useState('AND');
|
||||
const Demo: React.FC = () => {
|
||||
const [foo, setFoo] = useState<string | number>('AND');
|
||||
return (
|
||||
<>
|
||||
<Segmented value={foo} options={['AND', 'OR', 'NOT']} onChange={setFoo} />
|
||||
|
||||
<Segmented value={foo} options={['AND', 'OR', 'NOT']} onChange={(value) => setFoo(value)} />
|
||||
<Segmented value={foo} options={['AND', 'OR', 'NOT']} onChange={setFoo} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -58,7 +58,7 @@ To input a value in a range.
|
||||
|
||||
### 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 |
|
||||
| placement | Set Tooltip display position. Ref [Tooltip](/components/tooltip/) | string | - | 4.23.0 |
|
||||
|
@ -181,7 +181,7 @@ describe('Space', () => {
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/35305
|
||||
it('should not throw duplicated key warning', () => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(
|
||||
<Space>
|
||||
<div key="1" />
|
||||
@ -190,11 +190,11 @@ describe('Space', () => {
|
||||
<div />
|
||||
</Space>,
|
||||
);
|
||||
expect(console.error).not.toHaveBeenCalledWith(
|
||||
expect(spy).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining('Encountered two children with the same key'),
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
);
|
||||
(console.error as any).mockRestore();
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
@ -10,7 +10,7 @@ Set components spacing.
|
||||
## When To Use
|
||||
|
||||
- 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
|
||||
|
||||
|
@ -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` 版本开始提供该组件)。
|
||||
|
||||
## 代码演示
|
||||
|
||||
|
@ -288,8 +288,158 @@ exports[`renders ./components/spin/demo/size.tsx extend context correctly 1`] =
|
||||
|
||||
exports[`renders ./components/spin/demo/tip.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-spin-nested-loading"
|
||||
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
|
||||
class="ant-spin-nested-loading"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-busy="true"
|
||||
@ -343,5 +493,7 @@ exports[`renders ./components/spin/demo/tip.tsx extend context correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -288,8 +288,158 @@ exports[`renders ./components/spin/demo/size.tsx correctly 1`] = `
|
||||
|
||||
exports[`renders ./components/spin/demo/tip.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-spin-nested-loading"
|
||||
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
|
||||
class="ant-spin-nested-loading"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-busy="true"
|
||||
@ -343,5 +493,7 @@ exports[`renders ./components/spin/demo/tip.tsx correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -4,4 +4,10 @@
|
||||
|
||||
## en-US
|
||||
|
||||
Customized description content.
|
||||
```css
|
||||
.content {
|
||||
padding: 50px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
```
|
||||
|
@ -1,7 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Alert, Spin } from 'antd';
|
||||
import { Alert, Space, Spin } from 'antd';
|
||||
|
||||
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...">
|
||||
<Alert
|
||||
message="Alert message title"
|
||||
@ -9,6 +22,7 @@ const App: React.FC = () => (
|
||||
type="info"
|
||||
/>
|
||||
</Spin>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
@ -71,7 +71,7 @@ const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject =>
|
||||
marginTop: -(token.spinDotSize / 2) - 10,
|
||||
},
|
||||
|
||||
[`> div > ${token.componentCls}-sm`]: {
|
||||
'&-sm': {
|
||||
[`${token.componentCls}-dot`]: {
|
||||
margin: -token.spinDotSizeSM / 2,
|
||||
},
|
||||
@ -83,7 +83,7 @@ const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject =>
|
||||
},
|
||||
},
|
||||
|
||||
[`> div > ${token.componentCls}-lg`]: {
|
||||
'&-lg': {
|
||||
[`${token.componentCls}-dot`]: {
|
||||
margin: -(token.spinDotSizeLG / 2),
|
||||
},
|
||||
|
@ -1,4 +1,3 @@
|
||||
import padEnd from 'lodash/padEnd';
|
||||
import * as React from 'react';
|
||||
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);
|
||||
|
||||
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) {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import padStart from 'lodash/padStart';
|
||||
import type * as React from 'react';
|
||||
|
||||
export type valueType = number | string;
|
||||
@ -46,7 +45,7 @@ export function formatTimeStr(duration: number, format: string) {
|
||||
leftDuration -= value * unit;
|
||||
return current.replace(new RegExp(`${name}+`, 'g'), (match: string) => {
|
||||
const len = match.length;
|
||||
return padStart(value.toString(), len, '0');
|
||||
return value.toString().padStart(len, '0');
|
||||
});
|
||||
}
|
||||
return current;
|
||||
|
@ -117,7 +117,7 @@ describe('Table.filter', () => {
|
||||
});
|
||||
|
||||
it('renders empty menu correctly', () => {
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const { container } = render(
|
||||
createTable({
|
||||
columns: [
|
||||
|
@ -117,7 +117,7 @@ const columns = [
|
||||
| getPopupContainer | The render container of dropdowns in table | (triggerNode) => HTMLElement | () => TableHtmlElement | |
|
||||
| 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) | |
|
||||
| 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 | - | |
|
||||
| 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 | - | |
|
||||
|
@ -118,7 +118,7 @@ const columns = [
|
||||
| getPopupContainer | 设置表格内各类浮层的渲染节点,如筛选菜单 | (triggerNode) => HTMLElement | () => TableHtmlElement | |
|
||||
| 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) | |
|
||||
| pagination | 分页器,参考[配置项](#pagination)或 [pagination](/components/pagination/) 文档,设为 false 时不展示和进行分页 | object | - | |
|
||||
| pagination | 分页器,参考[配置项](#pagination)或 [pagination](/components/pagination/) 文档,设为 false 时不展示和进行分页 | object \| `false` | - | |
|
||||
| rowClassName | 表格行的类名 | function(record, index): string | - | |
|
||||
| rowKey | 表格行 key 的取值,可以是字符串或一个函数 | string \| function(record): string | `key` | |
|
||||
| rowSelection | 表格行是否可选择,[配置项](#rowSelection) | object | - | |
|
||||
|
@ -55,11 +55,6 @@ export const DesignTokenContext = React.createContext<{
|
||||
}>(defaultConfig);
|
||||
|
||||
// ================================== 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] {
|
||||
const {
|
||||
token: rootDesignToken,
|
||||
@ -68,7 +63,7 @@ export function useToken(): [Theme<SeedToken, MapToken>, GlobalToken, string] {
|
||||
components,
|
||||
} = React.useContext(DesignTokenContext);
|
||||
|
||||
const salt = `${saltPrefix}-${hashed || ''}`;
|
||||
const salt = `${version}-${hashed || ''}`;
|
||||
|
||||
const mergedTheme = theme || defaultTheme;
|
||||
|
||||
|
@ -4,14 +4,14 @@ import type {
|
||||
TourStepProps as RCTourStepProps,
|
||||
} from '@rc-component/tour';
|
||||
|
||||
export type TourProps = Omit<RCTourProps, 'renderPanel'> & {
|
||||
export interface TourProps extends Omit<RCTourProps, 'renderPanel'> {
|
||||
steps?: TourStepProps[];
|
||||
className?: string;
|
||||
prefixCls?: string;
|
||||
current?: number;
|
||||
stepRender?: (current: number, total: number) => ReactNode;
|
||||
type?: 'default' | 'primary'; // default 类型,影响底色与文字颜色
|
||||
};
|
||||
}
|
||||
|
||||
export interface TourStepProps extends RCTourStepProps {
|
||||
cover?: ReactNode; // 展示的图片或者视频
|
||||
|
26
docs/blog/css-in-js.en-US.md
Normal file
26
docs/blog/css-in-js.en-US.md
Normal 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. Let’s take a look at Ant Design’s 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.
|
28
docs/blog/css-in-js.zh-CN.md
Normal file
28
docs/blog/css-in-js.zh-CN.md
Normal 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
Loading…
Reference in New Issue
Block a user