mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-03 04:30:06 +08:00
commit
a8fc2ec3f3
@ -1,13 +1,26 @@
|
||||
import { RightOutlined, LinkOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { LinkOutlined, QuestionCircleOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { ConfigProvider, Popover, Table, Typography } from 'antd';
|
||||
import { createStyles, css, useTheme } from 'antd-style';
|
||||
import { getDesignToken } from 'antd-token-previewer';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import tokenMeta from 'antd/es/version/token-meta.json';
|
||||
import tokenData from 'antd/es/version/token.json';
|
||||
import { ConfigProvider, Table, Popover, Typography } from 'antd';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import { useColumns } from '../TokenTable';
|
||||
|
||||
const compare = (token1: string, token2: string) => {
|
||||
const hasColor1 = token1.toLowerCase().includes('color');
|
||||
const hasColor2 = token2.toLowerCase().includes('color');
|
||||
if (hasColor1 && !hasColor2) {
|
||||
return -1;
|
||||
}
|
||||
if (!hasColor1 && hasColor2) {
|
||||
return 1;
|
||||
}
|
||||
return token1 < token2 ? -1 : 1;
|
||||
};
|
||||
|
||||
const defaultToken = getDesignToken();
|
||||
|
||||
const locales = {
|
||||
@ -18,6 +31,8 @@ const locales = {
|
||||
value: '默认值',
|
||||
componentToken: '组件 Token',
|
||||
globalToken: '全局 Token',
|
||||
componentComment: '这里是你的组件 token',
|
||||
globalComment: '这里是你的全局 token',
|
||||
help: '如何定制?',
|
||||
customizeTokenLink: '/docs/react/customize-theme-cn#修改主题变量',
|
||||
customizeComponentTokenLink: '/docs/react/customize-theme-cn#修改组件变量',
|
||||
@ -29,6 +44,8 @@ const locales = {
|
||||
value: 'Default Value',
|
||||
componentToken: 'Component Token',
|
||||
globalToken: 'Global Token',
|
||||
componentComment: 'here is your component tokens',
|
||||
globalComment: 'here is your global tokens',
|
||||
help: 'How to use?',
|
||||
customizeTokenLink: '/docs/react/customize-theme#customize-design-token',
|
||||
customizeComponentTokenLink: 'docs/react/customize-theme#customize-component-token',
|
||||
@ -46,13 +63,13 @@ const useStyle = createStyles(() => ({
|
||||
`,
|
||||
arrowIcon: css`
|
||||
font-size: 16px;
|
||||
margin-right: 8px;
|
||||
margin-inline-end: 8px;
|
||||
& svg {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
`,
|
||||
help: css`
|
||||
margin-left: 8px;
|
||||
margin-inline-start: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
color: #999;
|
||||
@ -69,16 +86,14 @@ interface SubTokenTableProps {
|
||||
helpLink: string;
|
||||
tokens: string[];
|
||||
component?: string;
|
||||
comment?: {
|
||||
componentComment?: string;
|
||||
globalComment?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const SubTokenTable: React.FC<SubTokenTableProps> = ({
|
||||
defaultOpen,
|
||||
tokens,
|
||||
title,
|
||||
helpText,
|
||||
helpLink,
|
||||
component,
|
||||
}) => {
|
||||
const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
|
||||
const { defaultOpen, tokens, title, helpText, helpLink, component, comment } = props;
|
||||
const [, lang] = useLocale(locales);
|
||||
const token = useTheme();
|
||||
const columns = useColumns();
|
||||
@ -92,24 +107,7 @@ const SubTokenTable: React.FC<SubTokenTableProps> = ({
|
||||
}
|
||||
|
||||
const data = tokens
|
||||
.sort(
|
||||
component
|
||||
? undefined
|
||||
: (token1, token2) => {
|
||||
const hasColor1 = token1.toLowerCase().includes('color');
|
||||
const hasColor2 = token2.toLowerCase().includes('color');
|
||||
|
||||
if (hasColor1 && !hasColor2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!hasColor1 && hasColor2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return token1 < token2 ? -1 : 1;
|
||||
},
|
||||
)
|
||||
.sort(component ? undefined : compare)
|
||||
.map((name) => {
|
||||
const meta = component
|
||||
? tokenMeta.components[component].find((item) => item.token === name)
|
||||
@ -133,7 +131,7 @@ const SubTokenTable: React.FC<SubTokenTableProps> = ({
|
||||
theme={{
|
||||
components: {
|
||||
${component}: {
|
||||
/* here is your component tokens */
|
||||
/* ${comment?.componentComment} */
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -143,7 +141,7 @@ const SubTokenTable: React.FC<SubTokenTableProps> = ({
|
||||
: `<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
/* here is your global tokens */
|
||||
/* ${comment?.globalComment} */
|
||||
},
|
||||
}}
|
||||
>
|
||||
@ -161,16 +159,17 @@ const SubTokenTable: React.FC<SubTokenTableProps> = ({
|
||||
popupStyle={{ width: 400 }}
|
||||
content={
|
||||
<Typography>
|
||||
{/* <SourceCode lang="jsx">{code}</SourceCode> */}
|
||||
<pre style={{ fontSize: 12 }}>{code}</pre>
|
||||
<a href={helpLink} target="_blank" rel="noreferrer">
|
||||
<LinkOutlined style={{ marginRight: 4 }} />
|
||||
<LinkOutlined style={{ marginInlineEnd: 4 }} />
|
||||
{helpText}
|
||||
</a>
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<span className={styles.help}>
|
||||
<QuestionCircleOutlined style={{ marginRight: 3 }} />
|
||||
<QuestionCircleOutlined style={{ marginInlineEnd: 4 }} />
|
||||
{helpText}
|
||||
</span>
|
||||
</Popover>
|
||||
@ -217,12 +216,16 @@ const ComponentTokenTable: React.FC<ComponentTokenTableProps> = ({ component })
|
||||
<>
|
||||
{tokenMeta.components[component] && (
|
||||
<SubTokenTable
|
||||
defaultOpen
|
||||
title={locale.componentToken}
|
||||
helpText={locale.help}
|
||||
helpLink={locale.customizeTokenLink}
|
||||
tokens={tokenMeta.components[component].map((item) => item.token)}
|
||||
component={component}
|
||||
defaultOpen
|
||||
comment={{
|
||||
componentComment: locale.componentComment,
|
||||
globalComment: locale.globalComment,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<SubTokenTable
|
||||
@ -230,6 +233,10 @@ const ComponentTokenTable: React.FC<ComponentTokenTableProps> = ({ component })
|
||||
helpText={locale.help}
|
||||
helpLink={locale.customizeComponentTokenLink}
|
||||
tokens={mergedGlobalTokens}
|
||||
comment={{
|
||||
componentComment: locale.componentComment,
|
||||
globalComment: locale.globalComment,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
39
.dumi/theme/builtins/Container/index.tsx
Normal file
39
.dumi/theme/builtins/Container/index.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* copied: https://github.com/arvinxx/dumi-theme-antd-style/tree/master/src/builtins/Container
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import { Alert } from 'antd';
|
||||
import { type FC, type ReactNode } from 'react';
|
||||
import useStyles from './style';
|
||||
|
||||
const Container: FC<{
|
||||
type: 'info' | 'warning' | 'success' | 'error';
|
||||
title?: string;
|
||||
children: ReactNode;
|
||||
}> = ({ type, title, children }) => {
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
return (
|
||||
<div data-type={type} className={styles.container}>
|
||||
<Alert
|
||||
showIcon
|
||||
type={type}
|
||||
message={title || type.toUpperCase()}
|
||||
description={
|
||||
<div
|
||||
className={cx(
|
||||
styles.desc,
|
||||
// 为了让 markdown 的样式生效,需要在这里添加一个额外的 class
|
||||
'markdown',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
className={styles.alert}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Container;
|
22
.dumi/theme/builtins/Container/style.ts
Normal file
22
.dumi/theme/builtins/Container/style.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { createStyles } from 'antd-style';
|
||||
|
||||
const useStyles = createStyles(({ prefixCls, css }) => ({
|
||||
container: css`
|
||||
margin: 8px 0;
|
||||
`,
|
||||
|
||||
alert: css`
|
||||
.${prefixCls}-alert-message {
|
||||
font-weight: bold;
|
||||
}
|
||||
`,
|
||||
|
||||
/* 使用 `&&` 加一点点权重 */
|
||||
desc: css`
|
||||
&& p {
|
||||
margin: 0;
|
||||
}
|
||||
`,
|
||||
}));
|
||||
|
||||
export default useStyles;
|
38
.dumi/theme/slots/Content/ContributorAvatar.tsx
Normal file
38
.dumi/theme/slots/Content/ContributorAvatar.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Avatar, Skeleton, Tooltip } from 'antd';
|
||||
|
||||
const AvatarPlaceholder: React.FC<{ num?: number }> = ({ num = 3 }) => (
|
||||
<li>
|
||||
{Array.from({ length: num }).map((_, i) => (
|
||||
<Skeleton.Avatar size="small" active key={i} style={{ marginLeft: i === 0 ? 0 : -8 }} />
|
||||
))}
|
||||
</li>
|
||||
);
|
||||
|
||||
interface ContributorAvatarProps {
|
||||
username?: string;
|
||||
url?: string;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const ContributorAvatar: React.FC<ContributorAvatarProps> = ({ username, url, loading }) => {
|
||||
if (loading) {
|
||||
return <AvatarPlaceholder />;
|
||||
}
|
||||
if (username?.includes('github-actions')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Tooltip title={username}>
|
||||
<li>
|
||||
<a href={`https://github.com/${username}`} target="_blank" rel="noopener noreferrer">
|
||||
<Avatar size="small" src={url} alt={username}>
|
||||
{username}
|
||||
</Avatar>
|
||||
</a>
|
||||
</li>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContributorAvatar;
|
82
.dumi/theme/slots/Content/Contributors.tsx
Normal file
82
.dumi/theme/slots/Content/Contributors.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useIntl } from 'dumi';
|
||||
import { createStyles } from 'antd-style';
|
||||
import ContributorsList from '@qixian.cs/github-contributors-list';
|
||||
import ContributorAvatar from './ContributorAvatar';
|
||||
import SiteContext from '../SiteContext';
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => {
|
||||
const { antCls } = token;
|
||||
|
||||
return {
|
||||
contributorsList: css`
|
||||
margin-top: 120px !important;
|
||||
`,
|
||||
listMobile: css`
|
||||
margin: 1em 0 !important;
|
||||
`,
|
||||
title: css`
|
||||
font-size: 12px;
|
||||
opacity: 0.45;
|
||||
`,
|
||||
list: css`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
clear: both;
|
||||
|
||||
li {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
li,
|
||||
${antCls}-avatar + ${antCls}-avatar {
|
||||
transition: all ${token.motionDurationSlow};
|
||||
margin-inline-end: -8px;
|
||||
}
|
||||
&:hover {
|
||||
li,
|
||||
${antCls}-avatar {
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
interface ContributorsProps {
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { styles } = useStyle();
|
||||
const { isMobile } = useContext(SiteContext);
|
||||
|
||||
if (!filename) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.contributorsList, { [styles.listMobile]: isMobile })}>
|
||||
<div className={styles.title}>{formatMessage({ id: 'app.content.contributors' })}</div>
|
||||
<ContributorsList
|
||||
cache
|
||||
repo="ant-design"
|
||||
owner="ant-design"
|
||||
fileName={filename}
|
||||
className={styles.list}
|
||||
renderItem={(item, loading) => (
|
||||
<ContributorAvatar
|
||||
key={item?.username}
|
||||
username={item?.username}
|
||||
url={item?.url}
|
||||
loading={loading}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contributors;
|
137
.dumi/theme/slots/Content/DocAnchor.tsx
Normal file
137
.dumi/theme/slots/Content/DocAnchor.tsx
Normal file
@ -0,0 +1,137 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Anchor } from 'antd';
|
||||
import { createStyles, useTheme } from 'antd-style';
|
||||
import { useRouteMeta, useTabMeta } from 'dumi';
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => {
|
||||
const { antCls } = token;
|
||||
|
||||
return {
|
||||
toc: css`
|
||||
${antCls}-anchor {
|
||||
${antCls}-anchor-link-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
tocWrapper: css`
|
||||
position: fixed;
|
||||
top: ${token.headerHeight + token.contentMarginTop}px;
|
||||
inset-inline-end: 0;
|
||||
width: 160px;
|
||||
margin: 0 0 12px 0;
|
||||
padding: 8px 0;
|
||||
padding-inline: 4px 8px;
|
||||
backdrop-filter: blur(8px);
|
||||
border-radius: ${token.borderRadius}px;
|
||||
box-sizing: border-box;
|
||||
z-index: 1000;
|
||||
|
||||
.toc-debug {
|
||||
color: ${token.purple6};
|
||||
|
||||
&:hover {
|
||||
color: ${token.purple5};
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-height: calc(100vh - 40px) !important;
|
||||
margin: 0 auto;
|
||||
overflow: auto;
|
||||
padding-inline: 4px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${token.screenLG}px) {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
articleWrapper: css`
|
||||
padding: 0 170px 32px 64px;
|
||||
|
||||
&.rtl {
|
||||
padding: 0 64px 144px 170px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${token.screenLG}px) {
|
||||
&,
|
||||
&.rtl {
|
||||
padding: 0 48px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
interface DocAnchorProps {
|
||||
showDebug?: boolean;
|
||||
debugDemos?: string[];
|
||||
}
|
||||
|
||||
type AnchorItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
children?: AnchorItem[];
|
||||
};
|
||||
|
||||
const DocAnchor: React.FC<DocAnchorProps> = ({ showDebug, debugDemos = [] }) => {
|
||||
const { styles } = useStyle();
|
||||
const token = useTheme();
|
||||
const meta = useRouteMeta();
|
||||
const tab = useTabMeta();
|
||||
|
||||
const renderAnchorItem = (item: AnchorItem) => ({
|
||||
href: `#${item.id}`,
|
||||
title: item.title,
|
||||
key: item.id,
|
||||
children: item.children
|
||||
?.filter((child) => showDebug || !debugDemos.includes(child.id))
|
||||
.map((child) => ({
|
||||
key: child.id,
|
||||
href: `#${child.id}`,
|
||||
title: (
|
||||
<span className={classNames(debugDemos.includes(child.id) && 'toc-debug')}>
|
||||
{child?.title}
|
||||
</span>
|
||||
),
|
||||
})),
|
||||
});
|
||||
|
||||
const anchorItems = useMemo(
|
||||
() =>
|
||||
(tab?.toc || meta.toc).reduce<AnchorItem[]>((result, item) => {
|
||||
if (item.depth === 2) {
|
||||
result.push({ ...item });
|
||||
} else if (item.depth === 3) {
|
||||
const parent = result[result.length - 1];
|
||||
if (parent) {
|
||||
parent.children = parent.children || [];
|
||||
parent.children.push({ ...item });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, []),
|
||||
[tab?.toc, meta.toc],
|
||||
);
|
||||
|
||||
if (!meta.frontmatter.toc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.tocWrapper}>
|
||||
<Anchor
|
||||
className={styles.toc}
|
||||
affix={false}
|
||||
targetOffset={token.anchorTop}
|
||||
showInkInFixed
|
||||
items={anchorItems.map(renderAnchorItem)}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocAnchor;
|
79
.dumi/theme/slots/Content/DocMeta.tsx
Normal file
79
.dumi/theme/slots/Content/DocMeta.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import React, { useState, useLayoutEffect, useMemo } from 'react';
|
||||
import { Typography, Space, Skeleton, Avatar } from 'antd';
|
||||
import { useRouteMeta } from 'dumi';
|
||||
import DayJS from 'dayjs';
|
||||
import { CalendarOutlined } from '@ant-design/icons';
|
||||
|
||||
const AuthorAvatar: React.FC<{ name: string; avatar: string }> = ({ name, avatar }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
useLayoutEffect(() => {
|
||||
const img = new Image();
|
||||
img.src = avatar;
|
||||
img.onload = () => setLoading(false);
|
||||
img.onerror = () => setError(true);
|
||||
}, []);
|
||||
if (error) {
|
||||
return null;
|
||||
}
|
||||
if (loading) {
|
||||
return <Skeleton.Avatar size="small" active />;
|
||||
}
|
||||
return (
|
||||
<Avatar size="small" src={avatar} alt={name}>
|
||||
{name}
|
||||
</Avatar>
|
||||
);
|
||||
};
|
||||
|
||||
const DocMeta: React.FC<{}> = () => {
|
||||
const meta = useRouteMeta();
|
||||
|
||||
const mergedAuthorInfos = useMemo(() => {
|
||||
const { author } = meta.frontmatter;
|
||||
if (!author) {
|
||||
return [];
|
||||
}
|
||||
if (typeof author === 'string') {
|
||||
return author.split(',').map((item) => ({
|
||||
name: item,
|
||||
avatar: `https://github.com/${item}.png`,
|
||||
}));
|
||||
}
|
||||
if (Array.isArray(author)) {
|
||||
return author;
|
||||
}
|
||||
return [];
|
||||
}, [meta.frontmatter.author]);
|
||||
|
||||
if (!meta.frontmatter.date && !meta.frontmatter.author) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography.Paragraph>
|
||||
<Space>
|
||||
{meta.frontmatter.date && (
|
||||
<span style={{ opacity: 0.65 }}>
|
||||
<CalendarOutlined /> {DayJS(meta.frontmatter.date).format('YYYY-MM-DD')}
|
||||
</span>
|
||||
)}
|
||||
{mergedAuthorInfos.map((info) => (
|
||||
<a
|
||||
href={`https://github.com/${info.name}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key={info.name}
|
||||
>
|
||||
<Space size={3}>
|
||||
<AuthorAvatar name={info.name} avatar={info.avatar} />
|
||||
<span style={{ opacity: 0.65 }}>@{info.name}</span>
|
||||
</Space>
|
||||
</a>
|
||||
))}
|
||||
</Space>
|
||||
</Typography.Paragraph>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocMeta;
|
@ -1,93 +1,25 @@
|
||||
import { CalendarOutlined } from '@ant-design/icons';
|
||||
import { createStyles, useTheme } from 'antd-style';
|
||||
import ContributorsList from '@qixian.cs/github-contributors-list';
|
||||
import classNames from 'classnames';
|
||||
import DayJS from 'dayjs';
|
||||
import { FormattedMessage, useIntl, useRouteMeta, useTabMeta } from 'dumi';
|
||||
import { FormattedMessage, useRouteMeta } from 'dumi';
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useContext, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { Anchor, Avatar, Col, Skeleton, Space, Tooltip, Typography } from 'antd';
|
||||
import React, { Suspense, useContext, useLayoutEffect, useMemo } from 'react';
|
||||
import { Col, Space, Typography, Skeleton } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import useLayoutState from '../../../hooks/useLayoutState';
|
||||
import useLocation from '../../../hooks/useLocation';
|
||||
import EditButton from '../../common/EditButton';
|
||||
import PrevAndNext from '../../common/PrevAndNext';
|
||||
import ComponentChangelog from '../../common/ComponentChangelog';
|
||||
import type { DemoContextProps } from '../DemoContext';
|
||||
import DemoContext from '../DemoContext';
|
||||
import Footer from '../Footer';
|
||||
import SiteContext from '../SiteContext';
|
||||
import ColumnCard from './ColumnCard';
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => {
|
||||
const { antCls } = token;
|
||||
const Contributors = React.lazy(() => import('./Contributors'));
|
||||
const ColumnCard = React.lazy(() => import('./ColumnCard'));
|
||||
const DocAnchor = React.lazy(() => import('./DocAnchor'));
|
||||
const DocMeta = React.lazy(() => import('./DocMeta'));
|
||||
const Footer = React.lazy(() => import('../Footer'));
|
||||
const PrevAndNext = React.lazy(() => import('../../common/PrevAndNext'));
|
||||
const ComponentChangelog = React.lazy(() => import('../../common/ComponentChangelog'));
|
||||
const EditButton = React.lazy(() => import('../../common/EditButton'));
|
||||
|
||||
return {
|
||||
contributorsList: css`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 120px !important;
|
||||
clear: both;
|
||||
|
||||
li {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
li,
|
||||
${antCls}-avatar + ${antCls}-avatar {
|
||||
transition: all ${token.motionDurationSlow};
|
||||
margin-inline-end: -8px;
|
||||
}
|
||||
&:hover {
|
||||
li,
|
||||
${antCls}-avatar {
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
}
|
||||
`,
|
||||
listMobile: css`
|
||||
margin: 1em 0 !important;
|
||||
`,
|
||||
toc: css`
|
||||
${antCls}-anchor {
|
||||
${antCls}-anchor-link-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
tocWrapper: css`
|
||||
position: fixed;
|
||||
top: ${token.headerHeight + token.contentMarginTop}px;
|
||||
inset-inline-end: 0;
|
||||
width: 160px;
|
||||
margin: 0 0 12px 0;
|
||||
padding: 8px 0;
|
||||
padding-inline: 4px 8px;
|
||||
backdrop-filter: blur(8px);
|
||||
border-radius: ${token.borderRadius}px;
|
||||
box-sizing: border-box;
|
||||
z-index: 1000;
|
||||
|
||||
.toc-debug {
|
||||
color: ${token.purple6};
|
||||
|
||||
&:hover {
|
||||
color: ${token.purple5};
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-height: calc(100vh - 40px) !important;
|
||||
margin: 0 auto;
|
||||
overflow: auto;
|
||||
padding-inline: 4px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${token.screenLG}px) {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
const useStyle = createStyles(({ token, css }) => ({
|
||||
articleWrapper: css`
|
||||
padding: 0 170px 32px 64px;
|
||||
|
||||
@ -102,53 +34,13 @@ const useStyle = createStyles(({ token, css }) => {
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
type AnchorItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
children?: AnchorItem[];
|
||||
};
|
||||
|
||||
const AvatarPlaceholder: React.FC<{ num?: number }> = ({ num = 3 }) => (
|
||||
<li>
|
||||
{Array.from({ length: num }).map((_, i) => (
|
||||
<Skeleton.Avatar size="small" active key={i} style={{ marginLeft: i === 0 ? 0 : -8 }} />
|
||||
))}
|
||||
</li>
|
||||
);
|
||||
|
||||
const AuthorAvatar: React.FC<{ name: string; avatar: string }> = ({ name, avatar }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
useLayoutEffect(() => {
|
||||
const img = new Image();
|
||||
img.src = avatar;
|
||||
img.onload = () => setLoading(false);
|
||||
img.onerror = () => setError(true);
|
||||
}, []);
|
||||
if (error) {
|
||||
return null;
|
||||
}
|
||||
if (loading) {
|
||||
return <Skeleton.Avatar size="small" active />;
|
||||
}
|
||||
return (
|
||||
<Avatar size="small" src={avatar} alt={name}>
|
||||
{name}
|
||||
</Avatar>
|
||||
);
|
||||
};
|
||||
}));
|
||||
|
||||
const Content: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const meta = useRouteMeta();
|
||||
const tab = useTabMeta();
|
||||
const { pathname, hash } = useLocation();
|
||||
const { formatMessage } = useIntl();
|
||||
const { direction } = useContext(SiteContext);
|
||||
const { styles } = useStyle();
|
||||
const token = useTheme();
|
||||
const { direction, isMobile } = useContext(SiteContext);
|
||||
|
||||
const [showDebug, setShowDebug] = useLayoutState(false);
|
||||
const debugDemos = useMemo(
|
||||
@ -167,71 +59,14 @@ const Content: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
[showDebug, debugDemos],
|
||||
);
|
||||
|
||||
const anchorItems = useMemo(
|
||||
() =>
|
||||
(tab?.toc || meta.toc).reduce<AnchorItem[]>((result, item) => {
|
||||
if (item.depth === 2) {
|
||||
result.push({ ...item });
|
||||
} else if (item.depth === 3) {
|
||||
const parent = result[result.length - 1];
|
||||
if (parent) {
|
||||
parent.children = parent.children || [];
|
||||
parent.children.push({ ...item });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, []),
|
||||
[tab?.toc, meta.toc],
|
||||
);
|
||||
|
||||
const isRTL = direction === 'rtl';
|
||||
|
||||
const mergedAuthorInfos = useMemo(() => {
|
||||
const { author } = meta.frontmatter;
|
||||
if (!author) {
|
||||
return [];
|
||||
}
|
||||
if (typeof author === 'string') {
|
||||
return author.split(',').map((item) => ({
|
||||
name: item,
|
||||
avatar: `https://github.com/${item}.png`,
|
||||
}));
|
||||
}
|
||||
if (Array.isArray(author)) {
|
||||
return author;
|
||||
}
|
||||
return [];
|
||||
}, [meta.frontmatter.author]);
|
||||
|
||||
return (
|
||||
<DemoContext.Provider value={contextValue}>
|
||||
<Col xxl={20} xl={19} lg={18} md={18} sm={24} xs={24}>
|
||||
{!!meta.frontmatter.toc && (
|
||||
<section className={styles.tocWrapper}>
|
||||
<Anchor
|
||||
className={styles.toc}
|
||||
affix={false}
|
||||
targetOffset={token.anchorTop}
|
||||
showInkInFixed
|
||||
items={anchorItems.map((item) => ({
|
||||
href: `#${item.id}`,
|
||||
title: item.title,
|
||||
key: item.id,
|
||||
children: item.children
|
||||
?.filter((child) => showDebug || !debugDemos.includes(child.id))
|
||||
.map((child) => ({
|
||||
key: child.id,
|
||||
href: `#${child.id}`,
|
||||
title: (
|
||||
<span className={classNames(debugDemos.includes(child.id) && 'toc-debug')}>
|
||||
{child?.title}
|
||||
</span>
|
||||
),
|
||||
})),
|
||||
}))}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
<Suspense fallback={<Skeleton.Input active size="small" />}>
|
||||
<DocAnchor showDebug={showDebug} debugDemos={debugDemos} />
|
||||
</Suspense>
|
||||
<article className={classNames(styles.articleWrapper, { rtl: isRTL })}>
|
||||
{meta.frontmatter?.title ? (
|
||||
<Typography.Title style={{ fontSize: 30, position: 'relative' }}>
|
||||
@ -240,90 +75,43 @@ const Content: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
{meta.frontmatter?.subtitle}
|
||||
|
||||
{!pathname.startsWith('/components/overview') && (
|
||||
<Suspense fallback={null}>
|
||||
<EditButton
|
||||
title={<FormattedMessage id="app.content.edit-page" />}
|
||||
filename={meta.frontmatter.filename}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</Space>
|
||||
{pathname.startsWith('/components/') && <ComponentChangelog pathname={pathname} />}
|
||||
{pathname.startsWith('/components/') && (
|
||||
<Suspense fallback={null}>
|
||||
<ComponentChangelog pathname={pathname} />
|
||||
</Suspense>
|
||||
)}
|
||||
</Typography.Title>
|
||||
) : null}
|
||||
{/* 添加作者、时间等信息 */}
|
||||
{meta.frontmatter.date || meta.frontmatter.author ? (
|
||||
<Typography.Paragraph>
|
||||
<Space>
|
||||
{meta.frontmatter.date && (
|
||||
<span style={{ opacity: 0.65 }}>
|
||||
<CalendarOutlined /> {DayJS(meta.frontmatter.date).format('YYYY-MM-DD')}
|
||||
</span>
|
||||
)}
|
||||
{mergedAuthorInfos.map((info) => (
|
||||
<a
|
||||
href={`https://github.com/${info.name}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key={info.name}
|
||||
>
|
||||
<Space size={3}>
|
||||
<AuthorAvatar name={info.name} avatar={info.avatar} />
|
||||
<span style={{ opacity: 0.65 }}>@{info.name}</span>
|
||||
</Space>
|
||||
</a>
|
||||
))}
|
||||
</Space>
|
||||
</Typography.Paragraph>
|
||||
) : null}
|
||||
<Suspense fallback={<Skeleton.Input active size="small" />}>
|
||||
<DocMeta />
|
||||
</Suspense>
|
||||
{!meta.frontmatter.__autoDescription && meta.frontmatter.description}
|
||||
<div style={{ minHeight: 'calc(100vh - 64px)' }}>{children}</div>
|
||||
{(meta.frontmatter?.zhihu_url ||
|
||||
meta.frontmatter?.yuque_url ||
|
||||
meta.frontmatter?.juejin_url) && (
|
||||
<Suspense fallback={<Skeleton.Input active size="small" />}>
|
||||
<ColumnCard
|
||||
zhihuLink={meta.frontmatter.zhihu_url}
|
||||
yuqueLink={meta.frontmatter.yuque_url}
|
||||
juejinLink={meta.frontmatter.juejin_url}
|
||||
/>
|
||||
)}
|
||||
{meta.frontmatter.filename && (
|
||||
<ContributorsList
|
||||
cache
|
||||
repo="ant-design"
|
||||
owner="ant-design"
|
||||
className={classNames(styles.contributorsList, { [styles.listMobile]: isMobile })}
|
||||
fileName={meta.frontmatter.filename}
|
||||
renderItem={(item, loading) => {
|
||||
if (!item || loading) {
|
||||
return <AvatarPlaceholder />;
|
||||
}
|
||||
if (item.username?.includes('github-actions')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Tooltip
|
||||
mouseEnterDelay={0.3}
|
||||
title={`${formatMessage({ id: 'app.content.contributors' })}: ${item.username}`}
|
||||
key={item.username}
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
href={`https://github.com/${item.username}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Avatar size="small" src={item.url} alt={item.username}>
|
||||
{item.username}
|
||||
</Avatar>
|
||||
</a>
|
||||
</li>
|
||||
</Tooltip>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
<Suspense fallback={<Skeleton.Input active size="small" />}>
|
||||
<Contributors filename={meta.frontmatter.filename} />
|
||||
</Suspense>
|
||||
</article>
|
||||
<Suspense fallback={<Skeleton.Input active size="small" />}>
|
||||
<PrevAndNext rtl={isRTL} />
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<Footer />
|
||||
</Suspense>
|
||||
</Col>
|
||||
</DemoContext.Provider>
|
||||
);
|
||||
|
@ -223,9 +223,11 @@ const Header: React.FC = () => {
|
||||
// Mirror url must have `/`, we add this for compatible
|
||||
const urlObj = new URL(currentUrl.replace(window.location.origin, url));
|
||||
if (urlObj.host.includes('antgroup')) {
|
||||
window.location.href = `${urlObj.href.replace(/\/$/, '')}/`;
|
||||
}
|
||||
urlObj.pathname = `${urlObj.pathname.replace(/\/$/, '')}/`;
|
||||
window.location.href = urlObj.toString();
|
||||
} else {
|
||||
window.location.href = urlObj.href.replace(/\/$/, '');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onLangChange = useCallback(() => {
|
||||
|
@ -7,7 +7,7 @@
|
||||
name: 👀 Visual Regression Diff Start
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
branches: [master, feature]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
|
@ -5,7 +5,7 @@ import { genPresetColor, genSubStyleComponent } from '../../theme/internal';
|
||||
// ============================== Preset ==============================
|
||||
const genPresetStyle = (token: TagToken) =>
|
||||
genPresetColor(token, (colorKey, { textColor, lightBorderColor, lightColor, darkColor }) => ({
|
||||
[`${token.componentCls}-${colorKey}`]: {
|
||||
[`${token.componentCls}${token.componentCls}-${colorKey}`]: {
|
||||
color: textColor,
|
||||
background: lightColor,
|
||||
borderColor: lightBorderColor,
|
||||
|
@ -15,7 +15,7 @@ const genTagStatusStyle = (
|
||||
): CSSInterpolation => {
|
||||
const capitalizedCssVariableType = capitalize<CssVariableType>(cssVariableType);
|
||||
return {
|
||||
[`${token.componentCls}-${status}`]: {
|
||||
[`${token.componentCls}${token.componentCls}-${status}`]: {
|
||||
color: token[`color${cssVariableType}`],
|
||||
background: token[`color${capitalizedCssVariableType}Bg`],
|
||||
borderColor: token[`color${capitalizedCssVariableType}Border`],
|
||||
|
Loading…
Reference in New Issue
Block a user