Compare commits

..

10 Commits

Author SHA1 Message Date
afc163
6dd5a234de chore: add 5.x-stable branch to github workflows (#56222) 2025-12-15 17:36:20 +08:00
afc163
2a6d0b837e chore: update branch references from master to 5.x-stable in scripts (#56218) 2025-12-15 16:02:26 +08:00
Wanpan
081e9a6311 fix(V5): Splitter ptg not fully cover (#56217)
Co-authored-by: 二货爱吃白萝卜 <smith3816@gmail.com>
fix: Splitter ptg not fully cover (#56025)
2025-12-15 12:08:23 +08:00
afc163
a811b7a702 docs: bump version to 5.29.2 and update changelogs (#56190)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-15 11:45:47 +08:00
afc163
34b32bb389 fix: notification wrong background color when cssVar is false (#56133) 2025-12-10 13:25:19 +08:00
Guo Yunhe
2a823ea79c fix(Breadcrumb): lift link style priority for v5 (#56139) 2025-12-10 13:21:16 +08:00
Copilot
6339b669e7 chore: cherry-pick changelog route fix from PR #56038 to 5.x-stable docs (#56138)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: 𝑾𝒖𝒙𝒉 <wxh1220@gmail.com>
2025-12-10 13:20:49 +08:00
𝑾𝒖𝒙𝒉
2e771318d0 docs: Add the link corresponding to the next major version of the cur… (#55946)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-11-27 15:50:04 +08:00
𝑾𝒖𝒙𝒉
bd10127ad0 docs(theme-editor): revert useLocalStorage to fix state update issue in Theme Editor (#55848) 2025-11-25 10:37:48 +08:00
二货爱吃白萝卜
5ebd3cb42f fix: Input.Search should not warning for addonAfter (#55806)
* docs: demo of Injector

* fix: Search should not warning
2025-11-22 12:56:13 +08:00
2294 changed files with 186856 additions and 299138 deletions

View File

@@ -2,21 +2,18 @@ const fs = require('fs');
const path = require('path');
const restCssPath = path.join(process.cwd(), 'components', 'style', 'reset.css');
const antdCssPath = path.join(process.cwd(), 'components', 'style', 'antd.css');
const tokenStatisticPath = path.join(process.cwd(), 'components', 'version', 'token.json');
const tokenMetaPath = path.join(process.cwd(), 'components', 'version', 'token-meta.json');
function finalizeCompile() {
if (fs.existsSync(path.join(__dirname, './es'))) {
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'es', 'style', 'reset.css'));
fs.copyFileSync(antdCssPath, path.join(process.cwd(), 'es', 'style', 'antd.css'));
fs.copyFileSync(tokenStatisticPath, path.join(process.cwd(), 'es', 'version', 'token.json'));
fs.copyFileSync(tokenMetaPath, path.join(process.cwd(), 'es', 'version', 'token-meta.json'));
}
if (fs.existsSync(path.join(__dirname, './lib'))) {
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'lib', 'style', 'reset.css'));
fs.copyFileSync(antdCssPath, path.join(process.cwd(), 'lib', 'style', 'antd.css'));
fs.copyFileSync(tokenStatisticPath, path.join(process.cwd(), 'lib', 'version', 'token.json'));
fs.copyFileSync(tokenMetaPath, path.join(process.cwd(), 'lib', 'version', 'token-meta.json'));
}
@@ -25,7 +22,6 @@ function finalizeCompile() {
function finalizeDist() {
if (fs.existsSync(path.join(__dirname, './dist'))) {
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'dist', 'reset.css'));
fs.copyFileSync(antdCssPath, path.join(process.cwd(), 'dist', 'antd.css'));
}
}

View File

@@ -7,6 +7,7 @@ const isNumber = (value: any): value is number => {
};
const fetcher = async (url: string): Promise<number> => {
// eslint-disable-next-line compat/compat
const res = await fetch(url, { headers: { Accept: 'application/vnd.github+json' } });
const data = await res.json();
const totalCount = isNumber(data?.total_count) ? data.total_count : 0;

View File

@@ -1,39 +1,34 @@
import React, { useMemo } from 'react';
import type { MenuProps } from 'antd';
import { Flex, Tag, version } from 'antd';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles } from 'antd-style';
import classnames from 'classnames';
import { useFullSidebarData, useSidebarData } from 'dumi';
import Link from '../theme/common/Link';
import useLocale from './useLocale';
import useLocation from './useLocation';
const locales = {
cn: {
deprecated: '废弃',
updated: '更新',
new: '新增',
},
en: {
deprecated: 'DEPRECATED',
updated: 'UPDATED',
new: 'NEW',
},
};
function isVersionNumber(value?: string) {
return value && /^\d+\.\d+\.\d+$/.test(value);
}
const getTagColor = (val?: string) => {
switch (val?.toUpperCase()) {
case 'UPDATED':
return 'processing';
case 'DEPRECATED':
return 'red';
default:
return 'success';
if (isVersionNumber(val)) {
return 'success';
}
if (val?.toUpperCase() === 'NEW') {
return 'success';
}
if (val?.toUpperCase() === 'UPDATED') {
return 'processing';
}
if (val?.toUpperCase() === 'DEPRECATED') {
return 'red';
}
return 'success';
};
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ css, token }) => ({
link: css`
display: flex;
align-items: center;
@@ -44,9 +39,8 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
`,
subtitle: css`
font-weight: normal;
font-size: ${cssVar.fontSizeSM};
font-size: ${token.fontSizeSM}px;
opacity: 0.8;
margin-inline-start: ${cssVar.marginSM};
`,
}));
@@ -62,24 +56,19 @@ interface MenuItemLabelProps {
}
const MenuItemLabelWithTag: React.FC<MenuItemLabelProps> = (props) => {
const { styles } = useStyle();
const { before, after, link, title, subtitle, search, tag, className } = props;
const [locale] = useLocale(locales);
const getLocale = (name: string) => {
return (locale as any)[name.toLowerCase()] ?? name;
};
if (!before && !after) {
return (
<Link to={`${link}${search}`} className={clsx(className, { [styles.link]: tag })}>
<Flex justify="flex-start" align="center">
<Link to={`${link}${search}`} className={classnames(className, { [styles.link]: tag })}>
<Flex justify="flex-start" align="center" gap="small">
<span>{title}</span>
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
</Flex>
{tag && (
<Tag variant="filled" className={clsx(styles.tag)} color={getTagColor(tag)}>
{getLocale(tag.replace(/VERSION/i, version))}
<Tag bordered={false} className={classnames(styles.tag)} color={getTagColor(tag)}>
{tag.replace(/VERSION/i, version)}
</Tag>
)}
</Link>

View File

@@ -1,16 +1,12 @@
import { useEffect, useRef } from 'react';
import { removeCSS, updateCSS } from '@rc-component/util/lib/Dom/dynamicCSS';
import { removeCSS, updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
import theme from '../../components/theme';
const duration = 0.5;
const viewTransitionStyle = `
@keyframes keepAlive {100% { z-index: -1 }}
::view-transition-old(root),
::view-transition-new(root) {
animation: keepAlive ${duration}s linear;
animation-fill-mode: forwards;
animation: none;
mix-blend-mode: normal;
}
@@ -54,7 +50,7 @@ const useThemeAnimation = () => {
clipPath: isDark ? [...clipPath].reverse() : clipPath,
},
{
duration: duration * 1000,
duration: 500,
easing: 'ease-in',
pseudoElement: isDark ? '::view-transition-old(root)' : '::view-transition-new(root)',
},

View File

@@ -1,14 +1,14 @@
import React from 'react';
import { Alert, Badge, Carousel, Flex, Skeleton, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { clsx } from 'clsx';
import classNames from 'classnames';
import useLocale from '../../../hooks/useLocale';
import SiteContext from '../../../theme/slots/SiteContext';
import type { Extra, Icon } from './util';
import { getCarouselStyle, useAntdSiteConfig } from './util';
const useStyle = createStyles(({ cssVar, css, cx }) => {
const useStyle = createStyles(({ token, css, cx }) => {
const { carousel } = getCarouselStyle();
const itemBase = css`
@@ -17,12 +17,12 @@ const useStyle = createStyles(({ cssVar, css, cx }) => {
flex-direction: column;
align-items: stretch;
text-decoration: none;
background: ${cssVar.colorBgContainer};
border: ${cssVar.lineWidth} solid ${cssVar.colorBorderSecondary};
border-radius: ${cssVar.borderRadiusLG};
transition: all ${cssVar.motionDurationSlow};
padding-block: ${cssVar.paddingMD};
padding-inline: ${cssVar.paddingLG};
background: ${token.colorBgContainer};
border: ${token.lineWidth}px solid ${token.colorBorderSecondary};
border-radius: ${token.borderRadiusLG}px;
transition: all ${token.motionDurationSlow};
padding-block: ${token.paddingMD}px;
padding-inline: ${token.paddingLG}px;
box-sizing: border-box;
`;
@@ -35,12 +35,12 @@ const useStyle = createStyles(({ cssVar, css, cx }) => {
`,
cardItem: css`
&:hover {
box-shadow: ${cssVar.boxShadowCard};
box-shadow: ${token.boxShadowCard};
border-color: transparent;
}
`,
sliderItem: css`
margin: 0 ${cssVar.margin};
margin: 0 ${token.margin}px;
text-align: start;
`,
container: css`
@@ -49,17 +49,17 @@ const useStyle = createStyles(({ cssVar, css, cx }) => {
max-width: 100%;
margin-inline: auto;
box-sizing: border-box;
column-gap: calc(${cssVar.paddingMD} * 2);
column-gap: ${token.paddingMD * 2}px;
align-items: stretch;
text-align: start;
min-height: 178px;
> * {
width: calc((100% - calc(${cssVar.marginXXL} * 2)) / 3);
width: calc((100% - ${token.marginXXL * 2}px) / 3);
}
`,
carousel,
bannerBg: css`
height: ${cssVar.fontSize};
height: ${token.fontSize}px;
`,
};
});
@@ -87,7 +87,7 @@ const RecommendItem: React.FC<RecommendItemProps> = (props) => {
key={extra?.title}
href={extra.href}
target="_blank"
className={clsx(styles.itemBase, className)}
className={classNames(styles.itemBase, className)}
rel="noreferrer"
>
<Typography.Title level={5}>{extra?.title}</Typography.Title>
@@ -155,7 +155,7 @@ const BannerRecommends: React.FC = () => {
<Alert
showIcon
type="error"
title={error.message}
message={error.message}
description={process.env.NODE_ENV !== 'production' ? error.stack : undefined}
/>
);

View File

@@ -1,19 +1,19 @@
import React from 'react';
import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
import {
Card,
Alert,
Carousel,
DatePicker,
Flex,
FloatButton,
Masonry,
Splitter,
Modal,
Progress,
Tag,
Tour,
Typography,
} from 'antd';
import { createStyles, css } from 'antd-style';
import { clsx } from 'clsx';
import classNames from 'classnames';
import dayjs from 'dayjs';
import useLocale from '../../../hooks/useLocale';
@@ -21,15 +21,16 @@ import SiteContext from '../../../theme/slots/SiteContext';
import { DarkContext } from './../../../hooks/useDark';
import { getCarouselStyle } from './util';
const { _InternalPanelDoNotUseOrYouWillBeFired: ModalDoNotUseOrYouWillBeFired } = Modal;
const { _InternalPanelDoNotUseOrYouWillBeFired: DatePickerDoNotUseOrYouWillBeFired } = DatePicker;
const { _InternalPanelDoNotUseOrYouWillBeFired: TourDoNotUseOrYouWillBeFired } = Tour;
const { _InternalPanelDoNotUseOrYouWillBeFired: FloatButtonDoNotUseOrYouWillBeFired } = FloatButton;
const SAMPLE_CONTENT_EN =
'Ant Design use CSS-in-JS technology to provide dynamic & mix theme ability. And which use component level CSS-in-JS solution get your application a better performance.';
'Ant Design 5.0 use CSS-in-JS technology to provide dynamic & mix theme ability. And which use component level CSS-in-JS solution get your application a better performance.';
const SAMPLE_CONTENT_CN =
'Ant Design 使用 CSS-in-JS 技术以提供动态与混合主题的能力。与此同时,我们使用组件级别的 CSS-in-JS 解决方案,让你的应用获得更好的性能。';
'Ant Design 5.0 使用 CSS-in-JS 技术以提供动态与混合主题的能力。与此同时,我们使用组件级别的 CSS-in-JS 解决方案,让你的应用获得更好的性能。';
const locales = {
cn: {
@@ -60,14 +61,14 @@ const locales = {
},
};
const useStyle = createStyles(({ cssVar }, isDark: boolean) => {
const useStyle = createStyles(({ token }, isDark: boolean) => {
const { carousel } = getCarouselStyle();
return {
card: css`
border-radius: ${cssVar.borderRadius};
border: 1px solid ${isDark ? cssVar.colorBorder : 'transparent'};
background-color: ${isDark ? cssVar.colorBgContainer : '#f5f8ff'};
padding: ${cssVar.paddingXL};
border-radius: ${token.borderRadius}px;
border: 1px solid ${isDark ? token.colorBorder : 'transparent'};
background-color: ${isDark ? token.colorBgContainer : '#f5f8ff'};
padding: ${token.paddingXL}px;
flex: none;
overflow: hidden;
position: relative;
@@ -92,7 +93,7 @@ const useStyle = createStyles(({ cssVar }, isDark: boolean) => {
height: 395px;
`,
nodeWrap: css`
margin-top: ${cssVar.paddingLG};
margin-top: ${token.paddingLG}px;
flex: auto;
display: flex;
align-items: center;
@@ -104,7 +105,7 @@ const useStyle = createStyles(({ cssVar }, isDark: boolean) => {
overflow: hidden;
`,
mobileComponentsList: css`
margin: 0 ${cssVar.margin};
margin: 0 ${token.margin}px;
`,
};
});
@@ -117,7 +118,7 @@ const ComponentItem: React.FC<ComponentItemProps> = ({ title, node, type, index
const { isMobile } = React.use(SiteContext);
const { styles } = useStyle(isDark);
return (
<div className={clsx(styles.card, isMobile && styles.mobileCard)}>
<div className={classNames(styles.card, isMobile && styles.mobileCard)}>
{/* Decorator */}
<div
className={styles.cardCircle}
@@ -147,26 +148,24 @@ const ComponentsList: React.FC = () => {
const { styles } = useStyle();
const [locale] = useLocale(locales);
const { isMobile } = React.use(SiteContext);
const isDark = React.use(DarkContext);
const COMPONENTS = React.useMemo<Omit<ComponentItemProps, 'index'>[]>(
() => [
// {
// title: 'Modal',
// type: 'update',
// node: (
// <ModalDoNotUseOrYouWillBeFired title="Ant Design" width={300}>
// {locale.sampleContent}
// </ModalDoNotUseOrYouWillBeFired>
// ),
// },
{
title: 'Modal',
type: 'update',
node: (
<ModalDoNotUseOrYouWillBeFired title="Ant Design 5.0" width={300}>
{locale.sampleContent}
</ModalDoNotUseOrYouWillBeFired>
),
},
{
title: 'DatePicker',
type: 'update',
node: (
<DatePickerDoNotUseOrYouWillBeFired
value={dayjs('2025-11-22 00:00:00')}
// defaultValue={dayjs('2025-11-22 00:00:00')}
value={dayjs('2022-11-18 14:00:00')}
showToday={false}
presets={
isMobile
@@ -181,33 +180,33 @@ const ComponentsList: React.FC = () => {
/>
),
},
// {
// title: 'Progress',
// type: 'update',
// node: (
// <Flex gap="small" vertical>
// <Flex gap="small" align="center">
// <Progress type="circle" railColor="#e6f4ff" percent={60} size={14} />
// {locale.inProgress}
// </Flex>
// <Flex gap="small" align="center">
// <Progress type="circle" percent={100} size={14} />
// {locale.success}
// </Flex>
// <Flex gap="small" align="center">
// <Progress type="circle" status="exception" percent={88} size={14} />
// {locale.taskFailed}
// </Flex>
// </Flex>
// ),
// },
{
title: 'Tour',
title: 'Progress',
type: 'update',
node: (
<Flex gap="small" vertical>
<Flex gap="small" align="center">
<Progress type="circle" trailColor="#e6f4ff" percent={60} size={14} />
{locale.inProgress}
</Flex>
<Flex gap="small" align="center">
<Progress type="circle" percent={100} size={14} />
{locale.success}
</Flex>
<Flex gap="small" align="center">
<Progress type="circle" status="exception" percent={88} size={14} />
{locale.taskFailed}
</Flex>
</Flex>
),
},
{
title: 'Tour',
type: 'new',
node: (
<TourDoNotUseOrYouWillBeFired
title="Ant Design"
title="Ant Design 5.0"
description={locale.tour}
style={{ width: isMobile ? 'auto' : 350 }}
current={3}
@@ -215,10 +214,9 @@ const ComponentsList: React.FC = () => {
/>
),
},
{
title: 'FloatButton',
type: 'update',
type: 'new',
node: (
<Flex align="center" gap="large">
<FloatButtonDoNotUseOrYouWillBeFired
@@ -248,83 +246,19 @@ const ComponentsList: React.FC = () => {
// },
{
title: 'Splitter',
type: 'new',
title: 'Alert',
type: 'update',
node: (
<Splitter
orientation="vertical"
style={{
height: 320,
width: 200,
background: isDark ? '#1f1f1f' : '#ffffff',
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
}}
>
<Splitter.Panel defaultSize="40%" min="20%" max="70%">
<Flex justify="center" align="center" style={{ height: '100%' }}>
<Typography.Title type="secondary" level={5} style={{ whiteSpace: 'nowrap' }}>
First
</Typography.Title>
</Flex>
</Splitter.Panel>
<Splitter.Panel>
<Flex justify="center" align="center" style={{ height: '100%' }}>
<Typography.Title type="secondary" level={5} style={{ whiteSpace: 'nowrap' }}>
Second
</Typography.Title>
</Flex>
</Splitter.Panel>
</Splitter>
),
},
{
title: 'Masonry',
type: 'new',
node: (
<Masonry
columns={2}
gutter={8}
style={{
width: 300,
height: 320,
}}
items={[
{ key: '1', data: 80 },
{ key: '2', data: 60 },
{ key: '3', data: 40 },
{ key: '4', data: 120 },
{ key: '5', data: 90 },
{ key: '6', data: 40 },
{ key: '7', data: 60 },
{ key: '8', data: 70 },
{ key: '9', data: 120 },
]}
itemRender={({ data, index }) => (
<Card size="small" style={{ height: data }}>
{index + 1}
</Card>
)}
<Alert
style={{ width: 400 }}
message="Ant Design 5.0"
description={locale.sampleContent}
closable={{ closeIcon: true, disabled: true }}
/>
),
},
// {
// title: 'Alert',
// type: 'update',
// node: (
// <Alert
// style={{ width: 400 }}
// title="Ant Design"
// description={locale.sampleContent}
// closable={{ closeIcon: true, disabled: true }}
// />
// ),
// },
],
[
isDark,
isMobile,
locale.inProgress,
locale.lastMonth,

View File

@@ -64,12 +64,12 @@ const locales = {
},
};
const useStyle = createStyles(({ cssVar, css }, isDark: boolean) => {
const useStyle = createStyles(({ token, css }, isDark: boolean) => {
return {
card: css`
padding: ${cssVar.paddingSM};
border-radius: calc(${cssVar.borderRadius} * 2);
background: ${isDark ? 'rgba(0, 0, 0, 0.45)' : cssVar.colorBgElevated};
padding: ${token.paddingSM}px;
border-radius: ${token.borderRadius * 2}px;
background: ${isDark ? 'rgba(0, 0, 0, 0.45)' : token.colorBgElevated};
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.03),
0 1px 6px -1px rgba(0, 0, 0, 0.02),
@@ -78,14 +78,14 @@ const useStyle = createStyles(({ cssVar, css }, isDark: boolean) => {
img {
width: 100%;
vertical-align: top;
border-radius: ${cssVar.borderRadius};
border-radius: ${token.borderRadius}px;
}
`,
cardMini: css`
display: block;
border-radius: calc(${cssVar.borderRadius} * 2);
padding: ${cssVar.paddingMD} ${cssVar.paddingLG};
border-radius: ${token.borderRadius * 2}px;
padding: ${token.paddingMD}px ${token.paddingLG}px;
background: ${isDark ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.02)'};
border: 1px solid ${isDark ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 0.06)'};

View File

@@ -1,15 +1,15 @@
import * as React from 'react';
import { Typography } from 'antd';
import { createStaticStyles, useTheme } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles, useTheme } from 'antd-style';
import classNames from 'classnames';
import SiteContext from '../../../theme/slots/SiteContext';
import GroupMaskLayer from './GroupMaskLayer';
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ css, token }) => ({
box: css`
position: relative;
transition: all ${cssVar.motionDurationSlow};
transition: all ${token.motionDurationSlow};
`,
container: css`
position: absolute;
@@ -23,11 +23,11 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
max-width: 1208px;
margin-inline: auto;
box-sizing: border-box;
padding-inline: ${cssVar.marginXXL};
padding-inline: ${token.marginXXL}px;
`,
withoutChildren: css`
min-height: 300px;
border-radius: ${cssVar.borderRadiusLG};
border-radius: ${token.borderRadiusLG}px;
background-color: '#e9e9e9';
`,
}));
@@ -46,6 +46,7 @@ export interface GroupProps {
const Group: React.FC<React.PropsWithChildren<GroupProps>> = (props) => {
const { id, title, titleColor, description, children, decoration, background, collapse } = props;
const token = useTheme();
const { styles } = useStyle();
const { isMobile } = React.use(SiteContext);
return (
<div style={{ backgroundColor: background }} className={styles.box}>
@@ -73,7 +74,7 @@ const Group: React.FC<React.PropsWithChildren<GroupProps>> = (props) => {
{description}
</Typography.Paragraph>
</div>
<div className={clsx({ [styles.marginStyle]: !collapse })}>
<div className={classNames({ [styles.marginStyle]: !collapse })}>
{children ? <div>{children}</div> : <div className={styles.withoutChildren} />}
</div>
</GroupMaskLayer>

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
const classNames = createStaticStyles(({ css }) => ({
const useStyle = createStyles(({ css }) => ({
siteMask: css`
z-index: 1;
position: relative;
@@ -19,10 +19,11 @@ export interface GroupMaskLayerProps {
const GroupMaskLayer: React.FC<React.PropsWithChildren<GroupMaskLayerProps>> = (props) => {
const { children, className, style, onMouseMove, onMouseEnter, onMouseLeave } = props;
const { styles } = useStyle();
return (
<div
style={style}
className={clsx(className, classNames.siteMask)}
className={classNames(className, styles.siteMask)}
onMouseMove={onMouseMove}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { AntDesignOutlined, CheckOutlined, CloseOutlined, DownOutlined } from '@ant-design/icons';
import { AntDesignOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons';
import {
Alert,
Button,
@@ -12,12 +12,11 @@ import {
Progress,
Select,
Slider,
Space,
Steps,
Switch,
Tooltip,
} from 'antd';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
import useLocale from '../../../../hooks/useLocale';
import Tilt from './Tilt';
@@ -29,7 +28,7 @@ const { _InternalPanelDoNotUseOrYouWillBeFired: InternalMessage } = message;
const locales = {
cn: {
range: '设置范围',
text: 'Ant Design 使用 CSS-in-JS 技术以提供动态与混合主题的能力。与此同时,我们使用组件级别的 CSS-in-JS 解决方案,让你的应用获得更好的性能。',
text: 'Ant Design 5.0 使用 CSS-in-JS 技术以提供动态与混合主题的能力。与此同时,我们使用组件级别的 CSS-in-JS 解决方案,让你的应用获得更好的性能。',
infoText: '信息内容展示',
dropdown: '下拉菜单',
finished: '已完成',
@@ -46,11 +45,11 @@ const locales = {
dashed: '虚线按钮',
icon: '图标按钮',
hello: '你好Ant Design!',
release: 'Ant Design 6.0 正式发布!',
release: 'Ant Design 5.0 正式发布!',
},
en: {
range: 'Set Range',
text: 'Ant Design use CSS-in-JS technology to provide dynamic & mix theme ability. And which use component level CSS-in-JS solution get your application a better performance.',
text: 'Ant Design 5.0 use CSS-in-JS technology to provide dynamic & mix theme ability. And which use component level CSS-in-JS solution get your application a better performance.',
infoText: 'Info Text',
dropdown: 'Dropdown',
finished: 'Finished',
@@ -67,24 +66,24 @@ const locales = {
dashed: 'Dashed',
icon: 'Icon',
hello: 'Hello, Ant Design!',
release: 'Ant Design 6.0 is released!',
release: 'Ant Design 5.0 is released!',
},
};
const styles = createStaticStyles(({ cssVar, css }) => {
const gap = cssVar.padding;
const useStyle = createStyles(({ token, css }) => {
const gap = token.padding;
return {
holder: css`
width: 500px;
display: flex;
flex-direction: column;
row-gap: ${gap};
row-gap: ${gap}px;
opacity: 0.8;
`,
flex: css`
display: flex;
flex-wrap: nowrap;
column-gap: ${gap};
column-gap: ${gap}px;
`,
ptg_20: css`
flex: 0 1 20%;
@@ -93,10 +92,10 @@ const styles = createStaticStyles(({ cssVar, css }) => {
flex: none;
`,
block: css`
background-color: ${cssVar.colorBgContainer};
padding: ${cssVar.paddingXS} ${cssVar.paddingSM};
border-radius: ${cssVar.borderRadius};
border: 1px solid ${cssVar.colorBorder};
background-color: ${token.colorBgContainer};
padding: ${token.paddingXS}px ${token.paddingSM}px;
border-radius: ${token.borderRadius}px;
border: 1px solid ${token.colorBorder};
`,
noMargin: css`
margin: 0;
@@ -106,30 +105,28 @@ const styles = createStaticStyles(({ cssVar, css }) => {
const ComponentsBlock: React.FC = () => {
const [locale] = useLocale(locales);
const { styles } = useStyle();
return (
<Tilt options={{ max: 4, glare: false, scale: 0.98 }} className={styles.holder}>
<ModalPanel title="Ant Design" width="100%">
<ModalPanel title="Ant Design 5.0" width="100%">
{locale.text}
</ModalPanel>
<Alert title={locale.infoText} type="info" />
<Alert message={locale.infoText} type="info" />
{/* Line */}
<div className={styles.flex}>
<ColorPicker style={{ flex: 'none' }} />
<div style={{ flex: 'none' }}>
<Space.Compact>
<Button>{locale.dropdown}</Button>
<Dropdown
menu={{
items: Array.from({ length: 5 }).map((_, index) => ({
key: `opt${index}`,
label: `${locale.option} ${index}`,
})),
}}
>
<Button icon={<DownOutlined />} />
</Dropdown>
</Space.Compact>
<Dropdown.Button
menu={{
items: Array.from({ length: 5 }).map((_, index) => ({
key: `opt${index}`,
label: `${locale.option} ${index}`,
})),
}}
>
{locale.dropdown}
</Dropdown.Button>
</div>
<Select
style={{ flex: 'auto' }}
@@ -212,7 +209,7 @@ const ComponentsBlock: React.FC = () => {
<InternalMessage content={locale.release} type="success" />
</div>
<InternalTooltip title={locale.hello} placement="topLeft" className={styles.noMargin} />
<Alert title="Ant Design love you!" type="success" />
<Alert message="Ant Design love you!" type="success" />
</Tilt>
);
};

View File

@@ -1,7 +1,7 @@
import React, { Suspense, use } from 'react';
import { Flex, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { clsx } from 'clsx';
import classNames from 'classnames';
import { useLocation } from 'dumi';
import useLocale from '../../../../hooks/useLocale';
@@ -29,8 +29,8 @@ const locales = {
},
};
const useStyle = createStyles(({ cssVar, css, cx }, siteConfig: SiteContextProps) => {
const textShadow = `0 0 4px ${cssVar.colorBgContainer}`;
const useStyle = createStyles(({ token, css, cx }, siteConfig: SiteContextProps) => {
const textShadow = `0 0 4px ${token.colorBgContainer}`;
const mask = cx(css`
position: absolute;
inset: 0;
@@ -63,7 +63,7 @@ const useStyle = createStyles(({ cssVar, css, cx }, siteConfig: SiteContextProps
perspective: 800px;
/* fix safari bug by removing blur style */
transform: translateZ(1000px);
row-gap: ${cssVar.marginXL};
row-gap: ${token.marginXL}px;
&:hover {
.${mask} {
@@ -82,16 +82,16 @@ const useStyle = createStyles(({ cssVar, css, cx }, siteConfig: SiteContextProps
text-align: center;
position: relative;
z-index: 1;
padding-inline: ${cssVar.paddingXL};
padding-inline: ${token.paddingXL}px;
text-shadow: ${Array.from({ length: 5 }, () => textShadow).join(', ')};
h1 {
font-weight: 900 !important;
font-size: calc(${cssVar.fontSizeHeading2} * 2) !important;
line-height: ${cssVar.lineHeightHeading2} !important;
font-size: ${token.fontSizeHeading2 * 2}px !important;
line-height: ${token.lineHeightHeading2} !important;
}
p {
font-size: ${cssVar.fontSizeLG} !important;
font-size: ${token.fontSizeLG}px !important;
font-weight: normal !important;
margin-bottom: 0;
}
@@ -105,7 +105,7 @@ const useStyle = createStyles(({ cssVar, css, cx }, siteConfig: SiteContextProps
z-index: 1;
`,
btnWrap: css`
margin-bottom: ${cssVar.marginXL};
margin-bottom: ${token.marginXL}px;
`,
bgImg: css`
position: absolute;
@@ -137,14 +137,14 @@ const PreviewBanner: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
alt="bg"
src="https://gw.alipayobjects.com/zos/bmw-prod/49f963db-b2a8-4f15-857a-270d771a1204.svg"
draggable={false}
className={clsx(styles.bgImg, styles.bgImgTop)}
className={classNames(styles.bgImg, styles.bgImgTop)}
/>
{/* Image Right Top */}
<img
alt="bg"
src="https://gw.alipayobjects.com/zos/bmw-prod/e152223c-bcae-4913-8938-54fda9efe330.svg"
draggable={false}
className={clsx(styles.bgImg, styles.bgImgBottom)}
className={classNames(styles.bgImg, styles.bgImgBottom)}
/>
<div className={styles.holder}>
@@ -158,7 +158,7 @@ const PreviewBanner: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
</Suspense>
<div className={styles.mask} />
<Typography className={styles.typography}>
<h1>Ant Design</h1>
<h1>Ant Design 5.0</h1>
<p>{locale.slogan}</p>
</Typography>
<Flex gap="middle" className={styles.btnWrap}>

View File

@@ -1,7 +1,7 @@
import React, { useMemo, useState } from 'react';
import { CSSMotionList } from '@rc-component/motion';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
import { CSSMotionList } from 'rc-motion';
import { COLOR_IMAGES, getClosetColor } from './colorUtil';
@@ -10,9 +10,9 @@ export interface BackgroundImageProps {
isLight?: boolean;
}
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ token }) => ({
image: css`
transition: all ${cssVar.motionDurationSlow};
transition: all ${token.motionDurationSlow};
position: absolute;
inset-inline-start: 0;
top: 0;
@@ -29,6 +29,7 @@ const onHide = () => ({ opacity: 0 });
const BackgroundImage: React.FC<BackgroundImageProps> = ({ colorPrimary, isLight }) => {
const activeColor = useMemo(() => getClosetColor(colorPrimary), [colorPrimary]);
const { styles } = useStyle();
const [keyList, setKeyList] = useState<string[]>([]);
@@ -49,7 +50,7 @@ const BackgroundImage: React.FC<BackgroundImageProps> = ({ colorPrimary, isLight
motionDeadline={500}
>
{({ key: color, className, style }) => {
const cls = clsx(styles.image, className);
const cls = classNames(styles.image, className);
const entity = COLOR_IMAGES.find((ent) => ent.color === color);
if (!entity || !entity.url) {

View File

@@ -1,21 +1,21 @@
import React, { useEffect, useState } from 'react';
import { ColorPicker, Flex, Input } from 'antd';
import type { ColorPickerProps, GetProp } from 'antd';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
import { generateColor } from 'antd/es/color-picker/util';
import { clsx } from 'clsx';
import classNames from 'classnames';
import { PRESET_COLORS } from './colorUtil';
type Color = Extract<GetProp<ColorPickerProps, 'value'>, string | { cleared: any }>;
const styles = createStaticStyles(({ cssVar, css }) => ({
const useStyle = createStyles(({ token, css }) => ({
color: css`
width: calc(${cssVar.controlHeightLG} / 2);
height: calc(${cssVar.controlHeightLG} / 2);
width: ${token.controlHeightLG / 2}px;
height: ${token.controlHeightLG / 2}px;
border-radius: 100%;
cursor: pointer;
transition: all ${cssVar.motionDurationFast};
transition: all ${token.motionDurationFast};
display: inline-block;
& > input[type='radio'] {
@@ -31,8 +31,8 @@ const styles = createStaticStyles(({ cssVar, css }) => ({
colorActive: css`
box-shadow:
0 0 0 1px ${cssVar.colorBgContainer},
0 0 0 calc(${cssVar.controlOutlineWidth} * 2 + 1) ${cssVar.colorPrimary};
0 0 0 1px ${token.colorBgContainer},
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
`,
}));
@@ -69,6 +69,8 @@ const DebouncedColorPicker: React.FC<React.PropsWithChildren<ThemeColorPickerPro
};
const ThemeColorPicker: React.FC<ThemeColorPickerProps> = ({ value, onChange, id }) => {
const { styles } = useStyle();
const matchColors = React.useMemo(() => {
const valueStr = generateColor(value || '').toRgbString();
const colors = PRESET_COLORS.map((color) => {
@@ -101,7 +103,7 @@ const ThemeColorPicker: React.FC<ThemeColorPickerProps> = ({ value, onChange, id
const colorNode = (
<label
key={color}
className={clsx(styles.color, { [styles.colorActive]: active })}
className={classNames(styles.color, { [styles.colorActive]: active })}
style={{ background: color }}
onClick={() => {
if (!picker) {

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { Flex } from 'antd';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import useLocale from '../../../../hooks/useLocale';
@@ -32,11 +32,11 @@ const locales = {
},
};
const styles = createStaticStyles(({ cssVar, css }) => ({
const useStyle = createStyles(({ token, css }) => ({
themeCard: css`
border-radius: ${cssVar.borderRadius};
border-radius: ${token.borderRadius}px;
cursor: pointer;
transition: all ${cssVar.motionDurationSlow};
transition: all ${token.motionDurationSlow};
overflow: hidden;
display: inline-block;
@@ -63,8 +63,8 @@ const styles = createStaticStyles(({ cssVar, css }) => ({
themeCardActive: css`
box-shadow:
0 0 0 1px ${cssVar.colorBgContainer},
0 0 0 calc(${cssVar.controlOutlineWidth} * 2 + 1px) ${cssVar.colorPrimary};
0 0 0 1px ${token.colorBgContainer},
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
&,
&:hover:not(:focus-within) {
transform: scale(1);
@@ -80,6 +80,7 @@ export interface ThemePickerProps {
const ThemePicker: React.FC<ThemePickerProps> = (props) => {
const { value, id, onChange } = props;
const { styles } = useStyle();
const [locale] = useLocale(locales);
return (
<Flex gap="large" wrap>
@@ -87,7 +88,9 @@ const ThemePicker: React.FC<ThemePickerProps> = (props) => {
<Flex vertical gap="small" justify="center" align="center" key={theme}>
<label
onClick={() => onChange?.(theme)}
className={clsx(styles.themeCard, { [styles.themeCardActive]: value === theme })}
className={classNames(styles.themeCard, {
[styles.themeCardActive]: value === theme,
})}
>
<input type="radio" name="theme" id={index === 0 ? id : undefined} />
<img draggable={false} src={THEMES[theme]} alt={theme} />

View File

@@ -20,9 +20,9 @@ import {
theme,
Typography,
} from 'antd';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
import { generateColor } from 'antd/es/color-picker/util';
import { clsx } from 'clsx';
import classNames from 'classnames';
import { useLocation } from 'dumi';
import useLocale from '../../../../hooks/useLocale';
@@ -57,7 +57,7 @@ const TokenChecker: React.FC = () => {
const locales = {
cn: {
themeTitle: '定制主题,随心所欲',
themeDesc: 'Ant Design 开放更多样式算法,让你定制主题更简单',
themeDesc: 'Ant Design 5.0 开放更多样式算法,让你定制主题更简单',
customizeTheme: '定制主题',
myTheme: '我的主题',
@@ -74,7 +74,7 @@ const locales = {
},
en: {
themeTitle: 'Flexible theme customization',
themeDesc: 'Ant Design enable extendable algorithm, make custom theme easier',
themeDesc: 'Ant Design 5.0 enable extendable algorithm, make custom theme easier',
customizeTheme: 'Customize Theme',
myTheme: 'My Theme',
@@ -92,14 +92,14 @@ const locales = {
};
// ============================= Style =============================
const styles = createStaticStyles(({ cssVar, css, cx }) => {
const useStyle = createStyles(({ token, css, cx }) => {
const { carousel } = getCarouselStyle();
const demo = css`
overflow: hidden;
background: rgba(240, 242, 245, 0.25);
backdrop-filter: blur(50px);
box-shadow: 0 2px 10px 2px rgba(0, 0, 0, 0.1);
transition: all ${cssVar.motionDurationSlow};
transition: all ${token.motionDurationSlow};
`;
return {
@@ -138,10 +138,10 @@ const styles = createStaticStyles(({ cssVar, css, cx }) => {
header: css`
display: flex;
align-items: center;
border-bottom: 1px solid ${cssVar.colorSplit};
padding-inline: ${cssVar.paddingLG} !important;
height: calc(${cssVar.controlHeightLG} * 1.2);
line-height: calc(${cssVar.controlHeightLG} * 1.2);
border-bottom: 1px solid ${token.colorSplit};
padding-inline: ${token.paddingLG}px !important;
height: ${token.controlHeightLG * 1.2}px;
line-height: ${token.controlHeightLG * 1.2}px;
`,
headerDark: css`
@@ -149,8 +149,8 @@ const styles = createStaticStyles(({ cssVar, css, cx }) => {
`,
avatar: css`
width: ${cssVar.controlHeight};
height: ${cssVar.controlHeight};
width: ${token.controlHeight}px;
height: ${token.controlHeight}px;
border-radius: 100%;
background: rgba(240, 240, 240, 0.75);
background-size: cover;
@@ -164,11 +164,11 @@ const styles = createStaticStyles(({ cssVar, css, cx }) => {
logo: css`
display: flex;
align-items: center;
column-gap: ${cssVar.padding};
column-gap: ${token.padding}px;
h1 {
font-weight: 400;
font-size: ${cssVar.fontSizeLG};
font-size: ${token.fontSizeLG}px;
line-height: 1.5;
}
`,
@@ -219,7 +219,7 @@ const styles = createStaticStyles(({ cssVar, css, cx }) => {
height: 287px;
`,
motion: css`
transition: all ${cssVar.motionDurationSlow};
transition: all ${token.motionDurationSlow};
`,
op1: css`
opacity: 1;
@@ -345,6 +345,7 @@ function rgbToColorMatrix(color: string) {
}
const Theme: React.FC = () => {
const { styles } = useStyle();
const [locale, lang] = useLocale(locales);
const isZhCN = lang === 'cn';
const { search } = useLocation();
@@ -446,14 +447,16 @@ const Theme: React.FC = () => {
<ConfigProvider theme={memoTheme}>
<TokenChecker />
<div
className={clsx(styles.demo, {
className={classNames(styles.demo, {
[styles.otherDemo]: isLight && closestColor !== DEFAULT_COLOR && styles.otherDemo,
[styles.darkDemo]: !isLight,
})}
style={{ borderRadius: themeData.borderRadius }}
>
<Layout className={styles.transBg}>
<Header className={clsx(styles.header, styles.transBg, !isLight && styles.headerDark)}>
<Header
className={classNames(styles.header, styles.transBg, !isLight && styles.headerDark)}
>
{/* Logo */}
<div className={styles.logo}>
<div className={styles.logoImg}>
@@ -467,13 +470,13 @@ const Theme: React.FC = () => {
alt="antd logo"
/>
</div>
<h1>Ant Design</h1>
<h1>Ant Design 5.0</h1>
</div>
<Flex className={styles.menu} gap="middle">
<BellOutlined />
<QuestionCircleOutlined />
<div
className={clsx(styles.avatar, { [styles.avatarDark]: themeType === 'dark' })}
className={classNames(styles.avatar, { [styles.avatarDark]: themeType === 'dark' })}
style={{
backgroundColor: avatarColor,
backgroundImage: `url(${getAvatarURL(closestColor)})`,
@@ -482,10 +485,10 @@ const Theme: React.FC = () => {
</Flex>
</Header>
<Layout className={styles.transBg} hasSider>
<Sider className={clsx(styles.transBg)} width={200}>
<Sider className={classNames(styles.transBg)} width={200}>
<Menu
mode="inline"
className={clsx(styles.transBg)}
className={classNames(styles.transBg)}
selectedKeys={['Themes']}
openKeys={['Design']}
style={{ height: '100%', borderInlineEnd: 0 }}
@@ -569,7 +572,7 @@ const Theme: React.FC = () => {
<>
{/* >>>>>> Default <<<<<< */}
<div
className={clsx(
className={classNames(
styles.motion,
isLight && closestColor === DEFAULT_COLOR ? styles.op1 : styles.op0,
)}
@@ -577,31 +580,36 @@ const Theme: React.FC = () => {
{/* Image Left Top */}
<img
draggable={false}
className={clsx(styles.pos, styles.leftTopImage)}
className={classNames(styles.pos, styles.leftTopImage)}
src="https://gw.alipayobjects.com/zos/bmw-prod/bd71b0c6-f93a-4e52-9c8a-f01a9b8fe22b.svg"
alt="image-left-top"
/>
{/* Image Right Bottom */}
<img
draggable={false}
className={clsx(styles.pos, styles.rightBottomImage)}
className={classNames(styles.pos, styles.rightBottomImage)}
src="https://gw.alipayobjects.com/zos/bmw-prod/84ad805a-74cb-4916-b7ba-9cdc2bdec23a.svg"
alt="image-right-bottom"
/>
</div>
{/* >>>>>> Dark <<<<<< */}
<div className={clsx(styles.motion, !isLight || !closestColor ? styles.op1 : styles.op0)}>
<div
className={classNames(
styles.motion,
!isLight || !closestColor ? styles.op1 : styles.op0,
)}
>
{/* Image Left Top */}
<img
draggable={false}
className={clsx(styles.pos, styles.leftTopImagePos)}
className={classNames(styles.pos, styles.leftTopImagePos)}
src="https://gw.alipayobjects.com/zos/bmw-prod/a213184a-f212-4afb-beec-1e8b36bb4b8a.svg"
alt="image-left-top"
/>
{/* Image Right Bottom */}
<img
draggable={false}
className={clsx(styles.pos, styles.rightBottomPos)}
className={classNames(styles.pos, styles.rightBottomPos)}
src="https://gw.alipayobjects.com/zos/bmw-prod/bb74a2fb-bff1-4d0d-8c2d-2ade0cd9bb0d.svg"
alt="image-right-bottom"
/>

View File

@@ -1,3 +1,4 @@
/* eslint-disable compat/compat */
import { css } from 'antd-style';
import useSWR from 'swr';

View File

@@ -1,6 +1,6 @@
import React, { Suspense } from 'react';
import { ConfigProvider, theme } from 'antd';
import { createStaticStyles } from 'antd-style';
import { createStyles, css } from 'antd-style';
import useLocale from '../../hooks/useLocale';
import { DarkContext } from './../../hooks/useDark';
@@ -12,7 +12,7 @@ const ComponentsList = React.lazy(() => import('./components/ComponentsList'));
const DesignFramework = React.lazy(() => import('./components/DesignFramework'));
const Theme = React.lazy(() => import('./components/Theme'));
const classNames = createStaticStyles(({ css }) => ({
const useStyle = createStyles(() => ({
image: css`
position: absolute;
inset-inline-start: 0;
@@ -38,6 +38,7 @@ const locales = {
const Homepage: React.FC = () => {
const [locale] = useLocale(locales);
const { styles } = useStyle();
const { token } = theme.useToken();
const isDark = React.use(DarkContext);
@@ -77,7 +78,7 @@ const Homepage: React.FC = () => {
decoration={
<img
draggable={false}
className={classNames.image}
className={styles.image}
src="https://gw.alipayobjects.com/zos/bmw-prod/ba37a413-28e6-4be4-b1c5-01be1a0ebb1c.svg"
alt="bg"
/>

View File

@@ -1,6 +1,5 @@
import React, { Suspense } from 'react';
import { App, Button, ConfigProvider, Skeleton, version } from 'antd';
import { createStaticStyles } from 'antd-style';
import { enUS, zhCN } from 'antd-token-previewer';
import type { ThemeConfig } from 'antd/es/config-provider/context';
import { Helmet } from 'dumi';
@@ -9,15 +8,6 @@ import useLocale from '../../hooks/useLocale';
const ThemeEditor = React.lazy(() => import('antd-token-previewer/lib/ThemeEditor'));
const classNames = createStaticStyles(({ css }) => ({
editor: css`
svg,
img {
display: inline;
}
`,
}));
const locales = {
cn: {
title: '主题编辑器',
@@ -77,7 +67,6 @@ const CustomTheme: React.FC = () => {
hideAdvancedSwitcher
theme={{ name: 'Custom Theme', key: 'test', config: theme }}
style={{ height: 'calc(100vh - 64px)' }}
className={classNames.editor}
onThemeChange={(newTheme) => {
setTheme(newTheme.config);
}}

View File

@@ -3,7 +3,7 @@ import { theme as antdTheme, ConfigProvider } from 'antd';
import type { ThemeConfig } from 'antd';
import type { ThemeProviderProps } from 'antd-style';
import { ThemeProvider } from 'antd-style';
import { updateCSS } from '@rc-component/util/lib/Dom/dynamicCSS';
import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
import SiteContext from './slots/SiteContext';
@@ -20,6 +20,7 @@ interface NewToken {
marginFarSM: number;
marginFar: number;
codeFamily: string;
contentMarginTop: number;
anchorTop: number;
}
@@ -79,6 +80,7 @@ const SiteThemeProvider: React.FC<ThemeProviderProps<any>> = ({ children, theme,
/** 96 */
marginFar: token.marginXXL * 2,
codeFamily: `'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace`,
contentMarginTop: 40,
anchorTop: headerHeight + token.margin + (bannerVisible ? bannerHeight : 0),
}}
>

View File

@@ -1,25 +1,27 @@
import React from 'react';
import { SoundOutlined } from '@ant-design/icons';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
const styles = createStaticStyles(({ css, cssVar }) => {
const useStyle = createStyles(({ css, token }) => {
const { paddingXXS, fontSizeXL, motionDurationSlow, colorLink, colorLinkHover, colorLinkActive } =
token;
return {
playBtn: css`
display: inline-flex;
justify-content: center;
align-items: center;
column-gap: ${cssVar.paddingXXS};
column-gap: ${paddingXXS}px;
margin: 0;
`,
icon: css`
font-size: ${cssVar.fontSizeXL};
color: ${cssVar.colorLink};
transition: all ${cssVar.motionDurationSlow};
font-size: ${fontSizeXL}px;
color: ${colorLink};
transition: all ${motionDurationSlow};
&:hover {
color: ${cssVar.colorLinkHover};
color: ${colorLinkHover};
}
&:active {
color: ${cssVar.colorLinkActive};
color: ${colorLinkActive};
}
`,
};
@@ -30,6 +32,7 @@ interface AudioProps {
}
const AudioControl: React.FC<React.PropsWithChildren<AudioProps>> = ({ id, children }) => {
const { styles } = useStyle();
const onClick: React.MouseEventHandler<HTMLAnchorElement> = () => {
const audio = document.querySelector<HTMLAudioElement>(`#${id}`);
audio?.play();

View File

@@ -16,7 +16,7 @@ const colorMap = {
export default ({ type = 'info', ...props }: BadgeProps) => (
<Tag
variant="filled"
bordered={false}
color={colorMap[type]}
{...props}
style={{ verticalAlign: 'top', ...props.style }}

View File

@@ -4,12 +4,12 @@ import type { ColorInput } from '@ant-design/fast-color';
import { Popover } from 'antd';
import { createStyles } from 'antd-style';
const useStyle = createStyles(({ css, cssVar, token }) => ({
const useStyle = createStyles(({ token, css }) => ({
codeSpan: css`
padding: 0.2em 0.4em;
font-size: 0.9em;
background: ${token.siteMarkdownCodeBg};
border-radius: ${cssVar.borderRadius};
border-radius: ${token.borderRadius}px;
font-family: monospace;
`,
dot: css`
@@ -17,8 +17,8 @@ const useStyle = createStyles(({ css, cssVar, token }) => ({
width: 6px;
height: 6px;
border-radius: 50%;
margin-inline-end: ${cssVar.marginXXS};
border: 1px solid ${cssVar.colorSplit};
margin-inline-end: ${token.marginXXS}px;
border: 1px solid ${token.colorSplit};
`,
}));
@@ -46,7 +46,7 @@ const ColorChunk: React.FC<React.PropsWithChildren<ColorChunkProps>> = (props) =
placement="left"
content={<div hidden />}
styles={{
container: {
body: {
backgroundColor: dotColor,
width: 120,
height: 120,

View File

@@ -11,8 +11,8 @@ import {
import type { GetProp } from 'antd';
import { Descriptions, Flex, theme, Tooltip, Typography } from 'antd';
import { createStyles, css } from 'antd-style';
import copy from 'antd/es/_util/copy';
import kebabCase from 'lodash/kebabCase';
import CopyToClipboard from 'react-copy-to-clipboard';
import useIssueCount from '../../../hooks/useIssueCount';
import useLocale from '../../../hooks/useLocale';
@@ -48,7 +48,7 @@ const locales = {
},
};
const branchUrl = (repo: string) => `https://github.com/${repo}/edit/master/`;
const branchUrl = (repo: string) => `https://github.com/${repo}/edit/5.x-stable/`;
function isVersionNumber(value?: string) {
return value && /^\d+\.\d+\.\d+$/.test(value);
@@ -61,20 +61,20 @@ const transformComponentName = (componentName: string) => {
return componentName;
};
const useStyle = createStyles(({ cssVar, token }) => ({
const useStyle = createStyles(({ token }) => ({
code: css`
cursor: pointer;
position: relative;
display: inline-flex;
align-items: center;
column-gap: ${cssVar.paddingXXS};
border-radius: ${cssVar.borderRadiusSM};
padding-inline: ${cssVar.paddingXXS} !important;
transition: all ${cssVar.motionDurationSlow} !important;
column-gap: ${token.paddingXXS}px;
border-radius: ${token.borderRadiusSM}px;
padding-inline: ${token.paddingXXS}px !important;
transition: all ${token.motionDurationSlow} !important;
font-family: ${token.codeFamily};
color: ${cssVar.colorTextSecondary} !important;
color: ${token.colorTextSecondary} !important;
&:hover {
background: ${cssVar.controlItemBgHover};
background: ${token.controlItemBgHover};
}
a&:hover {
text-decoration: underline !important;
@@ -111,13 +111,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
// ========================= Copy =========================
const [copied, setCopied] = React.useState(false);
const importCode =
component === 'Icon'
? `import { AntDesignOutlined } from '@ant-design/icons';`
: `import { ${transformComponentName(component)} } from 'antd';`;
const onCopy = async () => {
await copy(importCode);
const onCopy = () => {
setCopied(true);
};
@@ -132,7 +126,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
if (String(source) === 'true') {
const kebabComponent = kebabCase(component);
return [
`https://github.com/${repo}/blob/master/components/${kebabComponent}`,
`https://github.com/${repo}/blob/5.x-stable/components/${kebabComponent}`,
`components/${kebabComponent}`,
];
}
@@ -144,6 +138,12 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
return [source, source];
}, [component, repo, source]);
// ======================== Render ========================
const importCode =
component === 'Icon'
? `import { AntDesignOutlined } from '@ant-design/icons';`
: `import { ${transformComponentName(component)} } from 'antd';`;
return (
<Descriptions
size="small"
@@ -156,19 +156,17 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
{
label: locale.import,
children: (
<Tooltip
placement="right"
title={copied ? locale.copied : locale.copy}
onOpenChange={onOpenChange}
>
<Typography.Text
className={styles.code}
style={{ cursor: 'pointer' }}
onClick={onCopy}
<CopyToClipboard text={importCode} onCopy={onCopy}>
<Tooltip
placement="right"
title={copied ? locale.copied : locale.copy}
onOpenChange={onOpenChange}
>
{importCode}
</Typography.Text>
</Tooltip>
<Typography.Text className={styles.code} onClick={onCopy}>
{importCode}
</Typography.Text>
</Tooltip>
</CopyToClipboard>
),
},
filledSource && {

View File

@@ -2,7 +2,7 @@ import React, { memo, useMemo, useRef, useState } from 'react';
import type { CSSProperties } from 'react';
import { SearchOutlined } from '@ant-design/icons';
import { Affix, Card, Col, Divider, Flex, Input, Row, Tag, Typography } from 'antd';
import { createStaticStyles, useTheme } from 'antd-style';
import { createStyles, useTheme } from 'antd-style';
import { useIntl, useLocation, useSidebarData } from 'dumi';
import debounce from 'lodash/debounce';
import scrollIntoView from 'scroll-into-view-if-needed';
@@ -12,13 +12,13 @@ import SiteContext from '../../slots/SiteContext';
import type { Component } from './ProComponentsList';
import proComponentsList from './ProComponentsList';
const styles = createStaticStyles(({ cssVar, css }) => ({
const useStyle = createStyles(({ token, css }) => ({
componentsOverviewGroupTitle: css`
margin-bottom: ${cssVar.marginLG} !important;
margin-bottom: ${token.marginLG}px !important;
`,
componentsOverviewTitle: css`
overflow: hidden;
color: ${cssVar.colorTextHeading};
color: ${token.colorTextHeading};
text-overflow: ellipsis;
`,
componentsOverviewImg: css`
@@ -39,23 +39,23 @@ const styles = createStaticStyles(({ cssVar, css }) => ({
`,
componentsOverviewAffix: css`
display: flex;
transition: all ${cssVar.motionDurationSlow};
transition: all ${token.motionDurationSlow};
justify-content: space-between;
`,
componentsOverviewSearch: css`
padding: 0;
box-shadow: none !important;
.anticon-search {
color: ${cssVar.colorTextDisabled};
color: ${token.colorTextDisabled};
}
`,
componentsOverviewContent: css`
&:empty:after {
display: block;
padding: ${cssVar.padding} 0 calc(${cssVar.paddingMD} * 2);
color: ${cssVar.colorTextDisabled};
padding: ${token.padding}px 0 ${token.paddingMD * 2}px;
color: ${token.colorTextDisabled};
text-align: center;
border-bottom: 1px solid ${cssVar.colorSplit};
border-bottom: 1px solid ${token.colorSplit};
content: 'Not Found';
}
`,
@@ -78,7 +78,8 @@ const reportSearch = debounce<(value: string) => void>((value) => {
const { Title } = Typography;
const Overview: React.FC = () => {
const { isDark } = React.use(SiteContext);
const { styles } = useStyle();
const { theme } = React.use(SiteContext);
const data = useSidebarData();
const [searchBarAffixed, setSearchBarAffixed] = useState<boolean>(false);
@@ -224,9 +225,11 @@ const Overview: React.FC = () => {
<img
draggable={false}
src={
isDark && component.coverDark ? component.coverDark : component.cover
theme.includes('dark') && component.coverDark
? component.coverDark
: component.cover
}
alt=""
alt={component.title}
/>
</div>
</Card>

View File

@@ -1,7 +1,7 @@
import React, { useMemo, useState } from 'react';
import { LinkOutlined, QuestionCircleOutlined, RightOutlined } from '@ant-design/icons';
import { ConfigProvider, Flex, Popover, Table, Typography } from 'antd';
import { createStaticStyles, css, useTheme } from 'antd-style';
import { createStyles, css, useTheme } from 'antd-style';
import { getDesignToken } from 'antd-token-previewer';
import tokenMeta from 'antd/es/version/token-meta.json';
import tokenData from 'antd/es/version/token.json';
@@ -53,7 +53,7 @@ const locales = {
},
};
const styles = createStaticStyles(({ cssVar }) => ({
const useStyle = createStyles(({ token }) => ({
tableTitle: css`
cursor: pointer;
position: relative;
@@ -61,16 +61,16 @@ const styles = createStaticStyles(({ cssVar }) => ({
align-items: center;
justify-content: flex-start;
line-height: 40px;
gap: ${cssVar.marginXS};
gap: ${token.marginXS}px;
`,
arrowIcon: css`
font-size: ${cssVar.fontSizeLG};
font-size: ${token.fontSizeLG}px;
& svg {
transition: all ${cssVar.motionDurationSlow};
transition: all ${token.motionDurationSlow};
}
`,
help: css`
font-size: ${cssVar.fontSizeSM};
font-size: ${token.fontSizeSM}px;
font-weight: normal;
color: #999;
a {
@@ -78,7 +78,7 @@ const styles = createStaticStyles(({ cssVar }) => ({
}
`,
tokenTitle: css`
font-size: ${cssVar.fontSizeLG};
font-size: ${token.fontSizeLG}px;
font-weight: bold;
`,
}));
@@ -104,6 +104,8 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
const [open, setOpen] = useState<boolean>(defaultOpen ?? process.env.NODE_ENV !== 'production');
const { styles } = useStyle();
if (!tokens.length) {
return null;
}

View File

@@ -22,7 +22,7 @@ const Container: React.FC<React.PropsWithChildren<ContainerProps>> = ({
<Alert
showIcon
type={type}
title={title || type.toUpperCase()}
message={title || type.toUpperCase()}
description={
<div
className={cx(

View File

@@ -1,17 +1,31 @@
import React, { Suspense } from 'react';
import { BugOutlined, CodeOutlined } from '@ant-design/icons';
import { BugOutlined, CodeOutlined, ExperimentOutlined } from '@ant-design/icons';
import { css, Global } from '@emotion/react';
import { Button, Tooltip } from 'antd';
import { Button, ConfigProvider, Tooltip } from 'antd';
import { DumiDemo, DumiDemoGrid, FormattedMessage } from 'dumi';
import useLayoutState from '../../../hooks/useLayoutState';
import useLocale from '../../../hooks/useLocale';
import DemoContext from '../../slots/DemoContext';
import DemoFallback from '../Previewer/DemoFallback';
const locales = {
cn: {
enableCssVar: '启用 CSS 变量',
disableCssVar: '禁用 CSS 变量',
},
en: {
enableCssVar: 'Enable CSS Var',
disableCssVar: 'Disable CSS Var',
},
};
const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
const { showDebug, setShowDebug } = React.use(DemoContext);
const [locale] = useLocale(locales);
const [expandAll, setExpandAll] = useLayoutState(false);
const [enableCssVar, setEnableCssVar] = useLayoutState(true);
const handleVisibleToggle = () => {
setShowDebug?.(!showDebug);
@@ -21,6 +35,10 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
setExpandAll(!expandAll);
};
const handleCssVarToggle = () => {
setEnableCssVar((v) => !v);
};
const demos = React.useMemo(
() =>
items.reduce<typeof items>((acc, item) => {
@@ -83,15 +101,26 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
className={showDebug ? 'icon-enabled' : ''}
/>
</Tooltip>
<Tooltip title={enableCssVar ? locale.disableCssVar : locale.enableCssVar}>
<Button
type="text"
size="small"
icon={<ExperimentOutlined />}
onClick={handleCssVarToggle}
className={enableCssVar ? 'icon-enabled' : ''}
/>
</Tooltip>
</span>
<DumiDemoGrid
items={demos}
demoRender={(item) => (
<Suspense key={item.demo.id} fallback={<DemoFallback />}>
<DumiDemo {...item} />
</Suspense>
)}
/>
<ConfigProvider theme={{ cssVar: enableCssVar, hashed: !enableCssVar }}>
<DumiDemoGrid
items={demos}
demoRender={(item) => (
<Suspense key={item.demo.id} fallback={<DemoFallback />}>
<DumiDemo {...item} />
</Suspense>
)}
/>
</ConfigProvider>
</div>
);
};

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { Flex } from 'antd';
import type { FlexProps } from 'antd';
import { createStyles } from 'antd-style';
import { clsx } from 'clsx';
import classNames from 'classnames';
import ImagePreview from '../ImagePreview';
import type { ImagePreviewProps } from '../ImagePreview';
@@ -49,7 +49,7 @@ const FlexWithImagePreview: React.FC<
return <ImagePreview {...imagePreviewProps}>{children}</ImagePreview>;
}
return (
<Flex className={clsx(styles.wrapper, className)} style={style} {...rest}>
<Flex className={classNames(styles.wrapper, className)} style={style} {...rest}>
<Flex align="flex-start" justify="flex-start" vertical>
{isNonNullable(title) && <div className={styles.title}>{title}</div>}
{isNonNullable(description) && <div className={styles.description}>{description}</div>}

View File

@@ -1,28 +1,28 @@
import * as React from 'react';
import { App } from 'antd';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
import { useIntl } from 'dumi';
import CopyableIcon from './CopyableIcon';
import type { CategoriesKeys } from './fields';
import type { ThemeType } from './IconSearch';
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ token, css }) => ({
anticonsList: css`
margin: ${cssVar.margin} 0;
margin: ${token.margin}px 0;
overflow: hidden;
direction: ltr;
list-style: none;
display: grid;
grid-gap: ${cssVar.margin};
grid-gap: ${token.margin}px;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
padding: 0;
`,
copiedCode: css`
padding: 0 ${cssVar.paddingXXS};
font-size: ${cssVar.fontSizeSM};
background-color: ${cssVar.colorBgLayout};
border-radius: ${cssVar.borderRadiusXS};
padding: 0 ${token.paddingXXS}px;
font-size: ${token.fontSizeSM}px;
background-color: ${token.colorBgLayout};
border-radius: ${token.borderRadiusXS}px;
`,
}));
@@ -36,6 +36,7 @@ interface CategoryProps {
const Category: React.FC<CategoryProps> = (props) => {
const { message } = App.useApp();
const { icons, title, newIcons, theme } = props;
const { styles } = useStyle();
const intl = useIntl();
const [justCopied, setJustCopied] = React.useState<string | null>(null);
const copyId = React.useRef<ReturnType<typeof setTimeout> | null>(null);

View File

@@ -2,15 +2,15 @@ import React from 'react';
import * as AntdIcons from '@ant-design/icons';
import { App, Badge } from 'antd';
import { createStyles } from 'antd-style';
import copy from 'antd/es/_util/copy';
import { clsx } from 'clsx';
import classNames from 'classnames';
import CopyToClipboard from 'react-copy-to-clipboard';
import useLocale from '../../../hooks/useLocale';
import type { ThemeType } from './IconSearch';
const allIcons: { [key: PropertyKey]: any } = AntdIcons;
const useStyle = createStyles(({ cssVar, token, css }) => {
const useStyle = createStyles(({ token, css }) => {
const { antCls, iconCls } = token;
return {
iconItem: css`
@@ -30,23 +30,23 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
text-align: center;
list-style: none;
background-color: inherit;
border-radius: ${cssVar.borderRadiusSM};
border-radius: ${token.borderRadiusSM}px;
cursor: pointer;
transition: all ${cssVar.motionDurationSlow} ease-in-out;
transition: all ${token.motionDurationSlow} ease-in-out;
${token.iconCls} {
margin: ${cssVar.marginXS} 0;
margin: ${token.marginXS}px 0;
font-size: 36px;
transition: transform ${cssVar.motionDurationSlow} ease-in-out;
transition: transform ${token.motionDurationSlow} ease-in-out;
will-change: transform;
}
&:hover {
color: ${cssVar.colorWhite};
background-color: ${cssVar.colorPrimary};
color: ${token.colorWhite};
background-color: ${token.colorPrimary};
${iconCls} {
transform: scale(1.3);
}
${antCls}-badge {
color: ${cssVar.colorWhite};
color: ${token.colorWhite};
}
}
&.TwoTone:hover {
@@ -63,11 +63,11 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
width: 100%;
height: 100%;
line-height: 100px;
color: ${cssVar.colorTextLightSolid};
color: ${token.colorTextLightSolid};
text-align: center;
background-color: ${cssVar.colorPrimary};
background-color: ${token.colorPrimary};
opacity: 0;
transition: all ${cssVar.motionDurationSlow} cubic-bezier(0.18, 0.89, 0.32, 1.28);
transition: all ${token.motionDurationSlow} cubic-bezier(0.18, 0.89, 0.32, 1.28);
}
&.copied::after {
opacity: 1;
@@ -80,7 +80,7 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
text-align: center;
transform: scale(0.8);
${antCls}-badge {
transition: color ${cssVar.motionDurationSlow} ease-in-out;
transition: color ${token.motionDurationSlow} ease-in-out;
}
`,
};
@@ -108,9 +108,7 @@ const CopyableIcon: React.FC<CopyableIconProps> = (props) => {
const { name, isNew, justCopied, theme, onCopied } = props;
const [locale] = useLocale(locales);
const { styles } = useStyle();
const onCopy = async (text: string) => {
const result = await copy(text);
const onCopy = (text: string, result: boolean) => {
if (result) {
onCopied(name, text);
} else {
@@ -118,16 +116,14 @@ const CopyableIcon: React.FC<CopyableIconProps> = (props) => {
}
};
return (
<li
className={clsx(theme, styles.iconItem, { copied: justCopied === name })}
onClick={() => onCopy(`<${name} />`)}
style={{ cursor: 'pointer' }}
>
{React.createElement(allIcons[name])}
<span className={styles.anticonCls}>
<Badge dot={isNew}>{name}</Badge>
</span>
</li>
<CopyToClipboard text={`<${name} />`} onCopy={onCopy}>
<li className={classNames(theme, styles.iconItem, { copied: justCopied === name })}>
{React.createElement(allIcons[name])}
<span className={styles.anticonCls}>
<Badge dot={isNew}>{name}</Badge>
</span>
</li>
</CopyToClipboard>
);
};

View File

@@ -2,7 +2,7 @@ import type { CSSProperties } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import Icon, * as AntdIcons from '@ant-design/icons';
import { Affix, Empty, Input, Segmented } from 'antd';
import { createStaticStyles, useTheme } from 'antd-style';
import { createStyles, useTheme } from 'antd-style';
import type { SegmentedOptions } from 'antd/es/segmented';
import { useIntl } from 'dumi';
import debounce from 'lodash/debounce';
@@ -22,10 +22,10 @@ export enum ThemeType {
const allIcons: { [key: string]: any } = AntdIcons;
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ token, css }) => ({
iconSearchAffix: css`
display: flex;
transition: all ${cssVar.motionDurationSlow};
transition: all ${token.motionDurationSlow};
justify-content: space-between;
`,
}));
@@ -39,6 +39,7 @@ const NEW_ICON_NAMES: ReadonlyArray<string> = [];
const IconSearch: React.FC = () => {
const intl = useIntl();
const { styles } = useStyle();
const [displayState, setDisplayState] = useState<IconSearchState>({
searchKey: '',
theme: ThemeType.Outlined,

View File

@@ -1,13 +1,13 @@
import React, { Suspense } from 'react';
import { Skeleton } from 'antd';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
const IconSearch = React.lazy(() => import('./IconSearch'));
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ token, css }) => ({
searchWrapper: css`
display: flex;
gap: ${cssVar.padding};
gap: ${token.padding}px;
> *:first-child {
flex: 0 0 328px;
}
@@ -21,7 +21,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
justify-content: space-between;
> * {
flex: 0 0 15%;
margin: ${cssVar.marginXXS} 0;
margin: ${token.marginXXS}px 0;
}
`,
skeletonWrapper: css`
@@ -34,6 +34,8 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
}));
const IconSearchFallback: React.FC = () => {
const { styles } = useStyle();
return (
<>
<div className={styles.searchWrapper}>

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { toArray } from '@rc-component/util';
import { Image } from 'antd';
import { clsx } from 'clsx';
import classNames from 'classnames';
import toArray from 'rc-util/lib/Children/toArray';
export interface ImagePreviewProps {
className?: string;
@@ -81,7 +81,7 @@ const ImagePreview: React.FC<React.PropsWithChildren<ImagePreviewProps>> = (prop
const hasCarousel = imgs.length > 1 && !comparable;
const previewClassName = clsx(rootClassName, 'clearfix', 'preview-image-boxes', {
const previewClassName = classNames(rootClassName, 'clearfix', 'preview-image-boxes', {
'preview-image-boxes-compare': comparable,
'preview-image-boxes-with-carousel': hasCarousel,
});
@@ -105,7 +105,7 @@ const ImagePreview: React.FC<React.PropsWithChildren<ImagePreviewProps>> = (prop
return null;
}
const coverMeta = imgsMeta[index];
const imageWrapperClassName = clsx(imgWrapperCls, {
const imageWrapperClassName = classNames(imgWrapperCls, {
good: coverMeta.isGood,
bad: coverMeta.isBad,
});

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
interface IconProps {
className?: string;
style?: React.CSSProperties;
}
const classNames = createStaticStyles(({ css }) => ({
const useStyle = createStyles(() => ({
iconWrap: css`
display: inline-flex;
align-items: center;
@@ -19,8 +19,9 @@ const classNames = createStaticStyles(({ css }) => ({
const BunIcon: React.FC<IconProps> = (props) => {
const { className, style } = props;
const { styles } = useStyle();
return (
<span className={clsx(classNames.iconWrap, className)} style={style}>
<span className={classNames(styles.iconWrap, className)} style={style}>
<svg id="Bun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 70" width="1em" height="1em">
<title>Bun Logo</title>
<path

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { ConfigProvider, Tabs } from 'antd';
import SourceCode from 'dumi/theme-default/builtins/SourceCode';
import type { Tab } from '@rc-component/tabs/lib/interface';
import type { Tab } from 'rc-tabs/lib/interface';
import BunLogo from './bun';
import NpmLogo from './npm';

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
interface IconProps {
className?: string;
style?: React.CSSProperties;
}
const classNames = createStaticStyles(({ css }) => ({
const useStyle = createStyles(() => ({
iconWrap: css`
display: inline-flex;
align-items: center;
@@ -19,8 +19,9 @@ const classNames = createStaticStyles(({ css }) => ({
const NpmIcon: React.FC<IconProps> = (props) => {
const { className, style } = props;
const { styles } = useStyle();
return (
<span className={clsx(classNames.iconWrap, className)} style={style}>
<span className={classNames(styles.iconWrap, className)} style={style}>
<svg
fill="#E53E3E"
focusable="false"

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
interface IconProps {
className?: string;
style?: React.CSSProperties;
}
const classNames = createStaticStyles(({ css }) => ({
const useStyle = createStyles(() => ({
iconWrap: css`
display: inline-flex;
align-items: center;
@@ -19,8 +19,9 @@ const classNames = createStaticStyles(({ css }) => ({
const PnpmIcon: React.FC<IconProps> = (props) => {
const { className, style } = props;
const { styles } = useStyle();
return (
<span className={clsx(classNames.iconWrap, className)} style={style}>
<span className={classNames(styles.iconWrap, className)} style={style}>
<svg
aria-hidden="true"
fill="#F69220"

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
interface IconProps {
className?: string;
style?: React.CSSProperties;
}
const classNames = createStaticStyles(({ css }) => ({
const useStyle = createStyles(() => ({
iconWrap: css`
display: inline-flex;
align-items: center;
@@ -19,8 +19,9 @@ const classNames = createStaticStyles(({ css }) => ({
const YarnIcon: React.FC<IconProps> = (props) => {
const { className, style } = props;
const { styles } = useStyle();
return (
<span className={clsx(classNames.iconWrap, className)} style={style}>
<span className={classNames(styles.iconWrap, className)} style={style}>
<svg
aria-hidden="true"
fill="#2C8EBB"

View File

@@ -1,12 +1,12 @@
import React, { Suspense, useRef } from 'react';
import { BugOutlined, ThunderboltOutlined } from '@ant-design/icons';
import { LinkOutlined, ThunderboltOutlined } from '@ant-design/icons';
import stackblitzSdk from '@stackblitz/sdk';
import type { MenuProps } from 'antd';
import { Button, Dropdown, Flex, Tooltip } from 'antd';
import type { Project } from '@stackblitz/sdk';
import { Flex, Tooltip } from 'antd';
import { FormattedMessage, useSiteData } from 'dumi';
import LZString from 'lz-string';
import { dependencies, devDependencies } from '../../../../package.json';
import packageJson from '../../../../package.json';
import useLocale from '../../../hooks/useLocale';
import ClientOnly from '../../common/ClientOnly';
import CodePenIcon from '../../icons/CodePenIcon';
@@ -29,6 +29,8 @@ function compress(string: string): string {
}
interface ActionsProps {
showOnlineUrl: boolean;
docsOnlineUrl?: string;
assetId: string;
title?: string;
pkgDependencyList: Record<PropertyKey, string>;
@@ -38,10 +40,11 @@ interface ActionsProps {
onCodeExpand: () => void;
entryCode: string;
styleCode: string;
debugOptions?: MenuProps['items'];
}
const Actions: React.FC<ActionsProps> = ({
showOnlineUrl,
docsOnlineUrl,
assetId,
title,
jsx,
@@ -51,7 +54,6 @@ const Actions: React.FC<ActionsProps> = ({
pkgDependencyList,
entryCode,
styleCode,
debugOptions,
}) => {
const [, lang] = useLocale();
const isZhCN = lang === 'cn';
@@ -81,7 +83,7 @@ const Actions: React.FC<ActionsProps> = ({
const suffix = codeType === 'tsx' ? 'tsx' : 'js';
const runtimeDependencies = (jsx as string).split('\n').reduce<Record<PropertyKey, string>>(
const dependencies = (jsx as string).split('\n').reduce<Record<PropertyKey, string>>(
(acc, line) => {
const matches = line.match(/import .+? from '(.+)';$/);
if (matches?.[1]) {
@@ -94,19 +96,18 @@ const Actions: React.FC<ActionsProps> = ({
{ antd: pkg.version },
);
runtimeDependencies.react = '^19.0.0';
runtimeDependencies['react-dom'] = '^19.0.0';
runtimeDependencies['@ant-design/icons'] = dependencies['@ant-design/icons'] || 'latest';
const runtimeDevDependencies: Record<PropertyKey, string> = {};
dependencies['@ant-design/icons'] = packageJson.dependencies['@ant-design/icons'] || 'latest';
if (suffix === 'tsx') {
runtimeDevDependencies['@types/react'] = devDependencies['@types/react'] || '^19.0.0';
runtimeDevDependencies['@types/react-dom'] = devDependencies['@types/react-dom'] || '^19.0.0';
dependencies['@types/react'] = '^18.0.0';
dependencies['@types/react-dom'] = '^18.0.0';
}
dependencies.react = '^18.0.0';
dependencies['react-dom'] = '^18.0.0';
const codepenPrefillConfig = {
title: `${title} - antd@${runtimeDependencies.antd}`,
title: `${title} - antd@${dependencies.antd}`,
html,
js: `const { createRoot } = ReactDOM;\n${jsx
.replace(/import\s+(?:React,\s+)?{(\s+[^}]*\s+)}\s+from\s+'react'/, `const { $1 } = React;`)
@@ -127,8 +128,8 @@ const Actions: React.FC<ActionsProps> = ({
editors: '001',
css: '',
js_external: [
'react@18/umd/react.production.min.js',
'react-dom@18/umd/react-dom.production.min.js',
'react@18/umd/react.development.js',
'react-dom@18/umd/react-dom.development.js',
'dayjs@1/dayjs.min.js',
`antd@${pkg.version}/dist/antd-with-locales.min.js`,
`@ant-design/icons/dist/index.umd.js`,
@@ -170,14 +171,16 @@ createRoot(document.getElementById('container')).render(<Demo />);
`;
const codesandboxPackage = {
title: `${title} - antd@${runtimeDependencies.antd}`,
title: `${title} - antd@${dependencies.antd}`,
main: 'index.js',
dependencies: {
...runtimeDependencies,
...dependencies,
'rc-util': pkgDependencyList['rc-util'],
react: '^18.0.0',
'react-dom': '^18.0.0',
'react-scripts': '^5.0.0',
},
devDependencies: {
...runtimeDevDependencies,
'@types/node': '^24.0.0',
typescript: '^5.0.2',
},
scripts: {
@@ -201,10 +204,16 @@ createRoot(document.getElementById('container')).render(<Demo />);
},
};
const stackblitzPrefillConfig = getStackblitzConfig({
title: `${title} - antd@${runtimeDependencies.antd}`,
dependencies: runtimeDependencies,
devDependencies: runtimeDevDependencies,
const stackblitzPrefillConfig: Project = getStackblitzConfig({
title: `${title} - antd@${dependencies.antd}`,
dependencies: {
...dependencies,
react: '^19.0.0',
'react-dom': '^19.0.0',
'@types/react': '^19.0.0',
'@types/react-dom': '^19.0.0',
'@ant-design/v5-patch-for-react-19': '^1.0.3',
},
demoJsContent,
indexCssContent,
suffix,
@@ -212,15 +221,21 @@ createRoot(document.getElementById('container')).render(<Demo />);
});
return (
<Flex wrap gap="middle" className="code-box-actions" align="center">
{
// 调试选项
debugOptions?.length ? (
<Dropdown menu={{ items: debugOptions }} arrow={{ pointAtCenter: true }}>
<Button icon={<BugOutlined />} color="purple" variant="filled" size="small" />
</Dropdown>
) : null
}
<Flex wrap gap="middle" className="code-box-actions">
{/* 在线文档按钮 */}
{showOnlineUrl && (
<Tooltip title={<FormattedMessage id="app.demo.online" />}>
<a
className="code-box-code-action"
aria-label="open in new tab"
target="_blank"
rel="noreferrer"
href={docsOnlineUrl || ''}
>
<LinkOutlined className="code-box-online" />
</a>
</Tooltip>
)}
{/* CodeSandbox 按钮 */}
<form
className="code-box-code-action"
@@ -244,7 +259,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
</Tooltip>
</form>
{/* 代码块复制按钮 */}
<CodeBlockButton title={title} dependencies={runtimeDependencies} jsx={jsx} />
<CodeBlockButton title={title} dependencies={dependencies} jsx={jsx} />
{/* StackBlitz 按钮 */}
<Tooltip title={<FormattedMessage id="app.demo.stackblitz" />}>
<span

View File

@@ -1,11 +1,10 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { UpOutlined } from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { Badge, Tag, Tooltip } from 'antd';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { FormattedMessage, useLiveDemo, useSiteData } from 'dumi';
import { major, minVersion } from 'semver';
import { Badge, Tooltip } from 'antd';
import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
import { FormattedMessage, useLiveDemo } from 'dumi';
import type { AntdPreviewerProps } from '.';
import useLocation from '../../../hooks/useLocation';
import BrowserFrame from '../../common/BrowserFrame';
@@ -13,11 +12,10 @@ import ClientOnly from '../../common/ClientOnly';
import CodePreview from '../../common/CodePreview';
import EditButton from '../../common/EditButton';
import SiteContext from '../../slots/SiteContext';
import DemoContext from '../../slots/DemoContext';
import { isOfficialHost } from '../../utils';
import Actions from './Actions';
const styles = createStaticStyles(({ cssVar, css }) => {
const useStyle = createStyles(({ token }) => {
const { borderRadius } = token;
return {
codeHideBtn: css`
position: sticky;
@@ -28,17 +26,17 @@ const styles = createStaticStyles(({ cssVar, css }) => {
display: flex;
justify-content: center;
align-items: center;
border-radius: 0 0 ${cssVar.borderRadius} ${cssVar.borderRadius};
border-top: 1px solid ${cssVar.colorSplit};
color: ${cssVar.colorTextSecondary};
transition: all ${cssVar.motionDurationMid} ease-in-out;
background-color: ${cssVar.colorBgElevated};
border-radius: 0 0 ${borderRadius}px ${borderRadius}px;
border-top: 1px solid ${token.colorSplit};
color: ${token.colorTextSecondary};
transition: all ${token.motionDurationMid} ease-in-out;
background-color: ${token.colorBgElevated};
cursor: pointer;
&:hover {
color: ${cssVar.colorPrimary};
color: ${token.colorPrimary};
}
span {
margin-inline-end: ${cssVar.marginXXS};
margin-inline-end: ${token.marginXXS}px;
}
`,
};
@@ -64,14 +62,13 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
clientOnly,
pkgDependencyList,
} = props;
const { showDebug } = React.use(DemoContext);
const { pkg } = useSiteData();
const location = useLocation();
const { styles } = useStyle();
const entryName = 'index.tsx';
const entryCode = asset.dependencies[entryName].value;
const previewDemo = useRef<React.ReactNode>(null);
const demoContainer = useRef<HTMLElement>(null);
const {
node: liveDemoNode,
@@ -83,16 +80,18 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
});
const anchorRef = useRef<HTMLAnchorElement>(null);
const [codeExpand, setCodeExpand] = useState<boolean>(false);
const { isDark } = React.use(SiteContext);
const { hash, pathname, search } = location;
const { theme } = React.use(SiteContext);
const { hash, pathname, search } = location;
const docsOnlineUrl = `https://ant.design${pathname ?? ''}${search ?? ''}#${asset.id}`;
const [showOnlineUrl, setShowOnlineUrl] = useState<boolean>(false);
/**
* Record whether it is deployed on the official domain name.
* Note that window.location.hostname is not available on the server side due to hydration issues
*/
const [deployedOnOfficialHost, setDeployedOnOfficialHost] = useState<boolean>(true);
useEffect(() => {
setDeployedOnOfficialHost(isOfficialHost(window.location.hostname));
const regexp = /preview-(\d+)-ant-design/; // matching PR preview addresses
setShowOnlineUrl(
process.env.NODE_ENV === 'development' || regexp.test(window.location.hostname),
);
}, []);
useEffect(() => {
@@ -105,43 +104,11 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
setCodeExpand(expand);
}, [expand]);
const generateDocUrl = (domain = 'https://ant.design') =>
`${domain}${pathname ?? ''}${search ?? ''}#${asset.id}`;
// Enable "Go Online Docs" only when deployed on non-official domains
const enableDocsOnlineUrl = process.env.NODE_ENV === 'development' || !deployedOnOfficialHost;
// Previous version demos are only available during the maintenance window
const [supportsPreviousVersionDemo, previousVersionDomain, previousVersion] = useMemo(() => {
const maintenanceDeadline = new Date('2026/12/31');
if (new Date() > maintenanceDeadline) {
return [false, undefined, -1] as const;
}
const currentMajor = major(pkg.version);
const previousMajor = Math.min(currentMajor - 1, 5);
let enabled = true;
// If the demo specifies a version, perform an additional check;
if (version) {
const minVer = minVersion(version);
enabled = minVer?.major ? minVer.major < currentMajor : true;
}
return [enabled, `https://${previousMajor}x.ant.design`, previousMajor];
}, [version, pkg.version]);
const mergedChildren = !iframe && clientOnly ? <ClientOnly>{children}</ClientOnly> : children;
const demoUrlWithTheme = useMemo(() => {
return `${demoUrl}${isDark ? '?theme=dark' : ''}`;
}, [demoUrl, isDark]);
const demoUrlWithTheme = `${demoUrl}${theme.includes('dark') ? '?theme=dark' : ''}`;
const iframePreview = useMemo(() => {
if (!iframe) {
return null;
}
return (
if (!previewDemo.current) {
previewDemo.current = iframe ? (
<BrowserFrame>
<iframe
src={demoUrlWithTheme}
@@ -150,22 +117,22 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
className="iframe-demo"
/>
</BrowserFrame>
) : (
mergedChildren
);
}, [demoUrlWithTheme, iframe]);
}
const previewContent = iframePreview ?? mergedChildren;
const codeBoxClass = clsx('code-box', {
const codeBoxClass = classNames('code-box', {
expand: codeExpand,
'code-box-debug': originDebug,
'code-box-simplify': simplify && !iframe,
'code-box-simplify': simplify,
});
const highlightClass = clsx('highlight-wrapper', {
const highlightClass = classNames('highlight-wrapper', {
'highlight-wrapper-expand': codeExpand,
});
const backgroundGrey = isDark ? '#303030' : '#f0f2f5';
const backgroundGrey = theme.includes('dark') ? '#303030' : '#f0f2f5';
const codeBoxDemoStyle: React.CSSProperties = {
padding: iframe || compact ? 0 : undefined,
@@ -173,47 +140,6 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
backgroundColor: background === 'grey' ? backgroundGrey : undefined,
};
const debugOptions: MenuProps['items'] = [
{
key: 'online',
label: (
<a
aria-label="Go to online documentation"
href={generateDocUrl()}
target="_blank"
rel="noreferrer"
>
<FormattedMessage id="app.demo.online" />
</a>
),
icon: (
<Tag variant="filled" color="blue">
ant.design
</Tag>
),
enabled: enableDocsOnlineUrl,
},
{
key: 'previousVersion',
label: (
<a
aria-label="Go to previous version documentation"
href={generateDocUrl(previousVersionDomain)}
target="_blank"
rel="noreferrer"
>
<FormattedMessage id="app.demo.previousVersion" values={{ version: previousVersion }} />
</a>
),
icon: (
<Tag variant="filled" color="purple">
v{previousVersion}
</Tag>
),
enabled: supportsPreviousVersionDemo,
},
].filter(({ enabled }) => showDebug && enabled);
const codeBox: React.ReactNode = (
<section className={codeBoxClass} id={asset.id}>
<section
@@ -222,7 +148,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
style={codeBoxDemoStyle}
ref={demoContainer}
>
{liveDemoNode || <React.StrictMode>{previewContent}</React.StrictMode>}
{liveDemoNode || <React.StrictMode>{previewDemo.current}</React.StrictMode>}
</section>
{!simplify && (
<section className="code-box-meta markdown">
@@ -245,7 +171,8 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
/>
)}
<Actions
debugOptions={debugOptions}
showOnlineUrl={showOnlineUrl}
docsOnlineUrl={docsOnlineUrl}
entryCode={entryCode}
styleCode={style}
pkgDependencyList={pkgDependencyList}

View File

@@ -1,17 +1,18 @@
import React from 'react';
import { Skeleton } from 'antd';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ token, css }) => ({
skeletonWrapper: css`
width: 100% !important;
height: 250px;
margin-bottom: ${cssVar.margin};
border-radius: ${cssVar.borderRadiusLG};
margin-bottom: ${token.margin}px;
border-radius: ${token.borderRadiusLG}px;
`,
}));
const DemoFallback = () => {
const { styles } = useStyle();
return (
<Skeleton.Node
active

View File

@@ -2,8 +2,8 @@ import type { FC } from 'react';
import React, { useEffect, useRef } from 'react';
import { CheckOutlined, SketchOutlined } from '@ant-design/icons';
import { App } from 'antd';
import { createStaticStyles } from 'antd-style';
import copy from '../../../../components/_util/copy';
import { createStyles } from 'antd-style';
import copy from 'copy-to-clipboard';
import { nodeToGroup } from 'html2sketch';
import type { AntdPreviewerProps } from '.';
@@ -22,39 +22,39 @@ const locales = {
},
};
const styles = createStaticStyles(({ cssVar, css }) => ({
const useStyle = createStyles(({ token, css }) => ({
wrapper: css`
position: relative;
border: 1px solid ${cssVar.colorBorderSecondary};
border-radius: ${cssVar.borderRadius};
padding: ${cssVar.paddingMD} ${cssVar.paddingLG} ${cssVar.paddingMD * 2};
margin-bottom: ${cssVar.marginLG};
border: 1px solid ${token.colorBorderSecondary};
border-radius: ${token.borderRadius}px;
padding: ${token.paddingMD}px ${token.paddingLG}px ${token.paddingMD * 2}px;
margin-bottom: ${token.marginLG}px;
`,
title: css`
font-size: ${cssVar.fontSizeLG};
font-weight: ${cssVar.fontWeightStrong};
color: ${cssVar.colorTextHeading};
font-size: ${token.fontSizeLG}px;
font-weight: ${token.fontWeightStrong};
color: ${token.colorTextHeading};
&:hover {
color: ${cssVar.colorTextHeading};
color: ${token.colorTextHeading};
}
`,
description: css`
margin-top: ${cssVar.margin};
margin-top: ${token.margin}px;
`,
demo: css`
margin-top: ${cssVar.marginLG};
margin-top: ${token.marginLG}px;
display: flex;
justify-content: center;
`,
copy: css`
position: absolute;
inset-inline-end: ${cssVar.paddingMD};
inset-block-start: ${cssVar.paddingMD};
inset-inline-end: ${token.paddingMD}px;
inset-block-start: ${token.paddingMD}px;
cursor: pointer;
`,
copyTip: css`
color: ${cssVar.colorTextTertiary};
color: ${token.colorTextTertiary};
border: none;
background: transparent;
padding: 0;
@@ -62,16 +62,17 @@ const styles = createStaticStyles(({ cssVar, css }) => ({
`,
copiedTip: css`
.anticon {
color: ${cssVar.colorSuccess};
color: ${token.colorSuccess};
}
`,
tip: css`
color: ${cssVar.colorTextTertiary};
margin-top: ${cssVar.marginMD * 2};
color: ${token.colorTextTertiary};
margin-top: ${token.marginMD * 2}px;
`,
}));
const DesignPreviewer: FC<AntdPreviewerProps> = ({ children, title, description, tip, asset }) => {
const { styles } = useStyle();
const demoRef = useRef<HTMLDivElement>(null);
const [copied, setCopied] = React.useState<boolean>(false);
const { message } = App.useApp();
@@ -82,7 +83,7 @@ const DesignPreviewer: FC<AntdPreviewerProps> = ({ children, title, description,
try {
if (demoRef.current) {
const group = await nodeToGroup(demoRef.current);
await copy(JSON.stringify(group.toSketchJSON()));
copy(JSON.stringify(group.toSketchJSON()));
setCopied(true);
timerRef.current = setTimeout(() => {
setCopied(false);

View File

@@ -1,25 +1,20 @@
import type { Project, ProjectFiles } from '@stackblitz/sdk';
interface StackblitzConfigOptions {
const getStackblitzConfig = ({
title = '',
dependencies,
indexCssContent = '',
demoJsContent = '',
suffix = '',
isZhCN = false,
}: {
title?: string;
dependencies: Record<PropertyKey, string>;
devDependencies: Record<PropertyKey, string>;
dependencies: Record<string, string>;
indexCssContent?: string;
demoJsContent?: string;
suffix?: string;
isZhCN?: boolean;
}
const getStackblitzConfig = (options: StackblitzConfigOptions) => {
const {
title = '',
dependencies,
devDependencies,
indexCssContent = '',
demoJsContent = '',
suffix = '',
isZhCN = false,
} = options;
}) => {
const _suffix = suffix === 'tsx' ? suffix : 'jsx';
const packageJSON = {
name: 'vite-react-typescript-starter',
@@ -35,7 +30,6 @@ const getStackblitzConfig = (options: StackblitzConfigOptions) => {
dependencies,
devDependencies: {
'@eslint/js': '^9.32.0',
'@types/node': '^24.0.0',
'@types/react': '^19.1.9',
'@types/react-dom': '^19.1.7',
'@vitejs/plugin-react': '^4.7.0',
@@ -46,7 +40,6 @@ const getStackblitzConfig = (options: StackblitzConfigOptions) => {
typescript: '~5.8.3',
'typescript-eslint': '^8.39.0',
vite: '^7.0.6',
...devDependencies,
},
};
@@ -129,6 +122,7 @@ const getStackblitzConfig = (options: StackblitzConfigOptions) => {
// main.tsx
[`src/main.${_suffix}`]: `import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import '@ant-design/v5-patch-for-react-19';
import Demo from './demo';
createRoot(document.getElementById('container')${suffix === 'tsx' ? '!' : ''}).render(

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { BugOutlined } from '@ant-design/icons';
import { Button, Flex, Popover, theme } from 'antd';
import { createStaticStyles, cx } from 'antd-style';
import { createStyles } from 'antd-style';
import dayjs from 'dayjs';
import type { Dayjs } from 'dayjs';
@@ -33,13 +33,13 @@ const locales = {
},
};
const styles = createStaticStyles(({ cssVar, css }) => ({
const useStyle = createStyles(({ token, css }) => ({
container: css`
margin-block: ${cssVar.margin};
padding: ${cssVar.padding};
margin-block: ${token.margin}px;
padding: ${token.padding}px;
.changelog-version {
line-height: ${cssVar.lineHeight} !important;
line-height: ${token.lineHeight} !important;
margin: 0 !important;
}
`,
@@ -49,6 +49,8 @@ const styles = createStaticStyles(({ cssVar, css }) => ({
const RefinedChangelog: React.FC<React.PropsWithChildren<RefinedChangelogProps>> = (props) => {
const { version, date, children } = props;
const { styles, cx } = useStyle();
const memoizedValue = React.useMemo<ContextProps>(() => {
const realVersion = version || '0.0.0';
const bugVersionInfo = matchDeprecated(realVersion);

View File

@@ -8,7 +8,7 @@ import useLocale from '../../../hooks/useLocale';
import type { Article, Authors, SiteData } from '../../../pages/index/components/util';
import { useAntdSiteConfig } from '../../../pages/index/components/util';
const useStyle = createStyles(({ cssVar, token, css }) => {
const useStyle = createStyles(({ token, css }) => {
const { antCls } = token;
return {
@@ -16,7 +16,7 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
h4 {
margin: 40px 0 24px;
font-weight: 500;
font-size: ${cssVar.fontSizeXL};
font-size: ${token.fontSizeXL}px;
}
${antCls}-skeleton {
@@ -44,7 +44,7 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
li {
margin: 1em 0;
padding: 0;
font-size: ${cssVar.fontSize};
font-size: ${token.fontSize}px;
list-style: none;
overflow: hidden;
text-overflow: ellipsis;
@@ -79,7 +79,7 @@ const ArticleList: React.FC<ArticleListProps> = ({ name, data = [], authors = []
<a href={author?.href} target="_blank" rel="noreferrer">
<Avatar size="small" src={author?.avatar} />
</a>
<Divider vertical />
<Divider type="vertical" />
<a href={article.href} target="_blank" rel="noreferrer">
{article?.title}
</a>
@@ -156,7 +156,7 @@ const ResourceArticles: React.FC = () => {
<Alert
showIcon
type="error"
title={error.message}
message={error.message}
description={process.env.NODE_ENV !== 'production' ? error.stack : undefined}
/>
);

View File

@@ -7,7 +7,7 @@ import useLocale from '../../../hooks/useLocale';
const { Paragraph } = Typography;
const useStyle = createStyles(({ cssVar, token, css }) => ({
const useStyle = createStyles(({ token, css }) => ({
card: css`
position: relative;
overflow: hidden;
@@ -16,7 +16,7 @@ const useStyle = createStyles(({ cssVar, token, css }) => ({
}
img {
display: block;
transition: all ${cssVar.motionDurationSlow} ease-out;
transition: all ${token.motionDurationSlow} ease-out;
}
&:hover img {
transform: scale(1.3);
@@ -26,15 +26,15 @@ const useStyle = createStyles(({ cssVar, token, css }) => ({
position: absolute;
top: 8px;
inset-inline-end: 8px;
padding: ${cssVar.paddingXXS} ${cssVar.paddingXS};
padding: ${token.paddingXXS}px ${token.paddingXS}px;
color: #fff;
font-size: ${cssVar.fontSizeSM};
font-size: ${token.fontSizeSM}px;
line-height: 1;
background: rgba(0, 0, 0, 0.65);
border-radius: ${cssVar.borderRadiusLG};
border-radius: ${token.borderRadiusLG}px;
box-shadow: 0 0 2px rgba(255, 255, 255, 0.2);
display: inline-flex;
column-gap: ${cssVar.paddingXXS};
column-gap: ${token.paddingXXS}px;
`,
}));

View File

@@ -1,11 +1,8 @@
import React, { Suspense } from 'react';
import type { SandpackSetup } from '@codesandbox/sandpack-react';
import { Skeleton } from 'antd';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
import { useSearchParams } from 'dumi';
import { version } from '../../../../package.json';
const OriginSandpack = React.lazy(() => import('./Sandpack'));
const indexContent = `import React from 'react';
@@ -17,21 +14,23 @@ const root = createRoot(document.getElementById("root"));
root.render(<App />);
`;
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ token, css }) => ({
fallback: css`
width: 100%;
> * {
width: 100% !important;
border-radius: ${cssVar.borderRadiusLG};
border-radius: ${token.borderRadiusLG}px;
}
`,
placeholder: css`
color: ${cssVar.colorTextDescription};
font-size: ${cssVar.fontSizeLG};
color: ${token.colorTextDescription};
font-size: ${token.fontSizeLG}px;
`,
}));
const SandpackFallback: React.FC = () => {
const SandpackFallback = () => {
const { styles } = useStyle();
return (
<div className={styles.fallback}>
<Skeleton.Node active style={{ height: 500, width: '100%' }}>
@@ -47,23 +46,25 @@ interface SandpackProps {
dependencies?: string;
}
const Sandpack: React.FC<React.PropsWithChildren<SandpackProps>> = (props) => {
const { children, dark, dependencies, autorun = false } = props;
const Sandpack: React.FC<React.PropsWithChildren<SandpackProps>> = ({
children,
dark,
dependencies: extraDeps,
autorun = false,
}) => {
const [searchParams] = useSearchParams();
const dependencies = extraDeps && JSON.parse(extraDeps);
const extraDependencies = dependencies ? JSON.parse(dependencies) : {};
const setup: SandpackSetup = {
const setup = {
dependencies: {
react: '^19.0.0',
'react-dom': '^19.0.0',
antd: version,
...extraDependencies,
react: '^18.0.0',
'react-dom': '^18.0.0',
antd: '^5.0.0',
...dependencies,
},
devDependencies: {
'@types/react': '^19.0.0',
'@types/react-dom': '^19.0.0',
'@types/react': '^18.0.0',
'@types/react-dom': '^18.0.0',
typescript: '^5',
},
entry: 'index.tsx',
@@ -82,14 +83,13 @@ const Sandpack: React.FC<React.PropsWithChildren<SandpackProps>> = (props) => {
<OriginSandpack
theme={searchParams.getAll('theme').includes('dark') ? 'dark' : undefined}
customSetup={setup}
template="vite-react-ts"
options={options}
files={{
'index.tsx': indexContent,
'index.css': `html, body {
padding: 0;
margin: 0;
background-color: ${dark ? '#000' : '#fff'};
background: ${dark ? '#000' : '#fff'};
}
#root {

View File

@@ -2,20 +2,20 @@
import React from 'react';
import { FastColor } from '@ant-design/fast-color';
import { Flex, theme } from 'antd';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
import tokenMeta from 'antd/es/version/token-meta.json';
import { clsx } from 'clsx';
import classNames from 'classnames';
import useLocale from '../../../hooks/useLocale';
const styles = createStaticStyles(({ cssVar, css }) => {
const height = cssVar.controlHeightLG;
const useStyle = createStyles(({ token, css }) => {
const height = token.controlHeightLG;
const dotSize = height / 5;
return {
container: css`
background: #fff;
border-radius: ${cssVar.borderRadiusLG};
border-radius: ${token.borderRadiusLG}px;
overflow: hidden;
`,
@@ -48,7 +48,7 @@ const styles = createStaticStyles(({ cssVar, css }) => {
`,
dotColor: css`
width: calc(${cssVar.fontSize} * 6);
width: ${token.fontSize * 6}px;
white-space: nowrap;
`,
};
@@ -63,6 +63,7 @@ interface ColorCircleProps {
}
const ColorCircle: React.FC<ColorCircleProps> = ({ color }) => {
const { styles } = useStyle();
return (
<Flex align="center" gap={4}>
<div className={styles.dot} style={{ background: color }} />
@@ -78,6 +79,7 @@ export interface TokenCompareProps {
const TokenCompare: React.FC<TokenCompareProps> = (props) => {
const { tokenNames = '' } = props;
const [, lang] = useLocale();
const { styles } = useStyle();
const tokenList = React.useMemo(() => {
const list = tokenNames.split('|');
@@ -104,7 +106,7 @@ const TokenCompare: React.FC<TokenCompareProps> = (props) => {
<div className={styles.col}>
<ColorCircle color={data.light} />
</div>
<div className={clsx(styles.col, styles.colDark)}>
<div className={classNames(styles.col, styles.colDark)}>
<ColorCircle color={data.dark} />
</div>
</div>

View File

@@ -39,14 +39,14 @@ const locales = {
},
};
const useStyle = createStyles(({ css, cssVar, token }) => ({
const useStyle = createStyles(({ token, css }) => ({
codeSpan: css`
margin: 0 1px;
padding: 0.2em 0.4em;
font-size: 0.9em;
background: ${token.siteMarkdownCodeBg};
border: 1px solid ${cssVar.colorSplit};
border-radius: ${cssVar.borderRadiusSM};
border: 1px solid ${token.colorSplit};
border-radius: ${token.borderRadiusSM}px;
font-family: monospace;
`,
}));
@@ -117,15 +117,7 @@ const TokenTable: FC<TokenTableProps> = ({ type }) => {
[type, lang],
);
return (
<Table<TokenData>
bordered
rowKey={(record) => record.name}
dataSource={data}
columns={columns}
pagination={false}
/>
);
return <Table dataSource={data} columns={columns} pagination={false} bordered />;
};
export default TokenTable;

View File

@@ -1,20 +1,20 @@
import React from 'react';
import { PauseCircleFilled, PlayCircleFilled } from '@ant-design/icons';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
const styles = createStaticStyles(({ css, cx, cssVar }) => {
const useStyles = createStyles(({ cx, token }) => {
const play = css`
position: absolute;
inset-inline-end: ${cssVar.paddingLG};
bottom: ${cssVar.paddingLG};
inset-inline-end: ${token.paddingLG}px;
bottom: ${token.paddingLG}px;
font-size: 64px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(0, 0, 0, 0.65);
opacity: 0;
transition: opacity ${cssVar.motionDurationSlow};
transition: opacity ${token.motionDurationSlow};
`;
return {
@@ -45,6 +45,7 @@ const VideoPlayer: React.FC<React.HtmlHTMLAttributes<HTMLVideoElement>> = ({
className,
...restProps
}) => {
const { styles } = useStyles();
const videoRef = React.useRef<HTMLVideoElement>(null);
const [playing, setPlaying] = React.useState(false);
@@ -58,7 +59,7 @@ const VideoPlayer: React.FC<React.HtmlHTMLAttributes<HTMLVideoElement>> = ({
return (
<div
className={clsx(styles.container, className)}
className={classNames(styles.container, className)}
tabIndex={0}
role="button"
title="play or pause"
@@ -66,7 +67,7 @@ const VideoPlayer: React.FC<React.HtmlHTMLAttributes<HTMLVideoElement>> = ({
setPlaying(!playing);
}}
>
<div className={clsx(styles.holder)}>
<div className={classNames(styles.holder)}>
<video ref={videoRef} className={styles.video} muted loop {...restProps} />
<div className={styles.play}>{playing ? <PauseCircleFilled /> : <PlayCircleFilled />}</div>
</div>

View File

@@ -1,71 +1,78 @@
import React, { useEffect, useRef } from 'react';
import { createStaticStyles } from 'antd-style';
import { RightCircleOutlined } from '@ant-design/icons';
import type { TreeGraph } from '@antv/g6';
import { Flex } from 'antd';
import { createStyles, css } from 'antd-style';
import { useRouteMeta } from 'dumi';
import useLocale from '../../../hooks/useLocale';
import { useMermaidCode } from './useMermaidCode';
import { renderReactToHTMLString } from '../../../theme/utils/renderReactToHTML';
export interface BehaviorMapItem {
interface BehaviorMapItem {
id: string;
label: string;
targetType?: 'mvp' | 'extension';
children?: BehaviorMapItem[];
link?: string;
collapsed?: boolean;
type?: 'behavior-start-node' | 'behavior-sub-node';
}
export interface BehaviorMapProps {
data: BehaviorMapItem;
}
const dataTransform = (rootData: BehaviorMapItem) => {
const changeData = (data: BehaviorMapItem, level = 0) => {
const clonedData: BehaviorMapItem = { ...data };
switch (level) {
case 0:
clonedData.type = 'behavior-start-node';
break;
case 1:
clonedData.type = 'behavior-sub-node';
clonedData.collapsed = true;
break;
default:
clonedData.type = 'behavior-sub-node';
break;
}
if (Array.isArray(data.children)) {
clonedData.children = data.children.map((child) => changeData(child, level + 1));
}
return clonedData;
};
return changeData(rootData);
};
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ token }) => ({
container: css`
width: 100%;
min-height: 600px;
height: fit-content;
background-color: ${cssVar.colorBgLayout};
height: 600px;
background-color: #f5f5f5;
border: 1px solid #e8e8e8;
border-radius: ${cssVar.borderRadiusLG};
border-radius: ${token.borderRadiusLG}px;
overflow: hidden;
position: relative;
display: flex;
justify-content: center;
align-items: center;
`,
chartContainer: css`
width: 100%;
height: 100%;
overflow: auto;
display: flex;
> svg {
margin: auto;
}
`,
title: css`
position: absolute;
top: 20px;
inset-inline-start: 20px;
font-size: ${cssVar.fontSizeLG};
z-index: 10;
font-size: ${token.fontSizeLG}px;
`,
tips: css`
display: flex;
position: absolute;
bottom: 20px;
inset-inline-end: 20px;
z-index: 10;
border-radius: 4px;
font-size: ${cssVar.fontSize};
`,
mvp: css`
margin-inline-end: ${cssVar.marginMD};
margin-inline-end: ${token.marginMD}px;
display: flex;
align-items: center;
&::before {
display: block;
width: 8px;
height: 8px;
margin-inline-end: ${cssVar.marginXS};
background-color: rgb(22, 119, 255);
margin-inline-end: ${token.marginXS}px;
background-color: #1677ff;
border-radius: 50%;
content: '';
}
@@ -77,8 +84,8 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
display: block;
width: 8px;
height: 8px;
margin-inline-end: ${cssVar.marginXS};
background-color: rgb(160, 160, 160);
margin-inline-end: ${token.marginXS}px;
background-color: #a0a0a0;
border-radius: 50%;
content: '';
}
@@ -98,67 +105,219 @@ const locales = {
},
};
export interface BehaviorMapProps {
data: BehaviorMapItem;
}
const BehaviorMap: React.FC<BehaviorMapProps> = ({ data }) => {
const chartRef = useRef<HTMLDivElement>(null);
const ref = useRef<HTMLDivElement>(null);
const { styles } = useStyle();
const [locale] = useLocale(locales);
const meta = useRouteMeta();
const mermaidCode = useMermaidCode(data);
const cancelledRef = useRef<boolean>(false);
const graphRef = useRef<TreeGraph>(null);
useEffect(() => {
cancelledRef.current = false;
import('@antv/g6').then((G6) => {
G6.registerNode('behavior-start-node', {
draw: (cfg, group) => {
const textWidth = G6.Util.getTextSize(cfg!.label, 16)[0];
const size = [textWidth + 20 * 2, 48];
const keyShape = group!.addShape('rect', {
name: 'start-node',
attrs: {
width: size[0],
height: size[1],
y: -size[1] / 2,
radius: 8,
fill: '#fff',
},
});
group!.addShape('text', {
attrs: {
text: `${cfg!.label}`,
fill: 'rgba(0, 0, 0, 0.88)',
fontSize: 16,
fontWeight: 500,
x: 20,
textBaseline: 'middle',
},
name: 'start-node-text',
});
return keyShape;
},
getAnchorPoints() {
return [
[0, 0.5],
[1, 0.5],
];
},
});
const renderChart = async () => {
if (!chartRef.current || !mermaidCode) {
return;
}
try {
const mermaid = (await import('mermaid')).default;
if (cancelledRef.current) {
return;
}
mermaid.initialize({
startOnLoad: false,
theme: 'base',
securityLevel: 'strict',
flowchart: {
htmlLabels: true,
curve: 'linear',
rankSpacing: 150,
nodeSpacing: 10,
G6.registerNode(
'behavior-sub-node',
{
draw: (cfg, group) => {
const textWidth = G6.Util.getTextSize(cfg!.label, 14)[0];
const padding = 16;
const size = [
textWidth + 16 * 2 + (cfg!.targetType ? 12 : 0) + (cfg!.link ? 20 : 0),
40,
];
const keyShape = group!.addShape('rect', {
name: 'sub-node',
attrs: {
width: size[0],
height: size[1],
y: -size[1] / 2,
radius: 8,
fill: '#fff',
cursor: 'pointer',
},
});
group!.addShape('text', {
attrs: {
text: `${cfg!.label}`,
x: cfg!.targetType ? 12 + 16 : padding,
fill: 'rgba(0, 0, 0, 0.88)',
fontSize: 14,
textBaseline: 'middle',
cursor: 'pointer',
},
name: 'sub-node-text',
});
if (cfg!.targetType) {
group!.addShape('rect', {
name: 'sub-node-type',
attrs: {
width: 8,
height: 8,
radius: 4,
y: -4,
x: 12,
fill: cfg!.targetType === 'mvp' ? '#1677ff' : '#A0A0A0',
cursor: 'pointer',
},
});
}
if (cfg!.children) {
const { length } = cfg!.children as any;
group!.addShape('rect', {
name: 'sub-node-children-length',
attrs: {
width: 20,
height: 20,
radius: 10,
y: -10,
x: size[0] - 4,
fill: '#404040',
cursor: 'pointer',
},
});
group!.addShape('text', {
name: 'sub-node-children-length-text',
attrs: {
text: `${length}`,
x: size[0] + 6 - G6.Util.getTextSize(`${length}`, 12)[0] / 2,
textBaseline: 'middle',
fill: '#fff',
fontSize: 12,
cursor: 'pointer',
},
});
}
if (cfg!.link) {
group!.addShape('dom', {
attrs: {
width: 16,
height: 16,
x: size[0] - 12 - 16,
y: -8,
cursor: 'pointer',
// DOM's html
html: renderReactToHTMLString(
<Flex align="center" justify="center">
<RightCircleOutlined style={{ color: '#BFBFBF' }} />
</Flex>,
),
},
// 在 G6 3.3 及之后的版本中,必须指定 name可以是任意字符串但需要在同一个自定义元素类型中保持唯一性
name: 'sub-node-link',
});
}
return keyShape;
},
});
getAnchorPoints() {
return [
[0, 0.5],
[1, 0.5],
];
},
options: {
stateStyles: {
hover: {
stroke: '#1677ff',
'sub-node-link': {
html: renderReactToHTMLString(
<Flex align="center" justify="center">
<RightCircleOutlined style={{ color: '#1677ff' }} />
</Flex>,
),
},
},
},
},
},
'rect',
);
graphRef.current = new G6.TreeGraph({
container: ref.current!,
width: ref.current!.scrollWidth,
height: ref.current!.scrollHeight,
renderer: 'svg',
modes: {
default: ['collapse-expand', 'drag-canvas'],
},
defaultEdge: {
type: 'cubic-horizontal',
style: { lineWidth: 1, stroke: '#BFBFBF' },
},
layout: {
type: 'mindmap',
direction: 'LR',
getHeight: () => 48,
getWidth: (node: any) => G6.Util.getTextSize(node.label, 16)[0] + 20 * 2,
getVGap: () => 10,
getHGap: () => 60,
getSide: (node: any) => node.data.direction,
},
});
const id = `mermaid-${Date.now()}`;
const { svg } = await mermaid.render(id, mermaidCode);
if (!cancelledRef.current && chartRef.current) {
chartRef.current.innerHTML = svg;
graphRef.current?.on('node:mouseenter', (e) => {
graphRef.current?.setItemState(e.item!, 'hover', true);
});
graphRef.current?.on('node:mouseleave', (e) => {
graphRef.current?.setItemState(e.item!, 'hover', false);
});
graphRef.current?.on('node:click', (e) => {
const { link } = e.item!.getModel();
if (link) {
window.location.hash = link as string;
}
} catch {
if (!cancelledRef.current && chartRef.current) {
chartRef.current.innerHTML = 'Render Error';
}
}
};
renderChart();
});
graphRef.current?.data(dataTransform(data));
graphRef.current?.render();
graphRef.current?.fitCenter();
});
return () => {
cancelledRef.current = true;
graphRef.current?.destroy();
};
}, [mermaidCode]);
}, [data]);
return (
<div className={styles.container}>
<div ref={ref} className={styles.container}>
<div className={styles.title}>{`${meta.frontmatter.title} ${locale.behaviorMap}`}</div>
<div ref={chartRef} className={styles.chartContainer} />
<div className={styles.tips}>
<div className={styles.mvp}>{locale.MVPPurpose}</div>
<div className={styles.extension}>{locale.extensionPurpose}</div>

View File

@@ -1,24 +1,24 @@
import type { FC } from 'react';
import React, { Suspense } from 'react';
import { Skeleton } from 'antd';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
import useLocale from '../../../hooks/useLocale';
import type { BehaviorMapProps } from './BehaviorMap';
const InternalBehaviorMap = React.lazy(() => import('./BehaviorMap'));
const styles = createStaticStyles(({ cssVar, css }) => ({
const useStyle = createStyles(({ token, css }) => ({
fallback: css`
width: 100%;
> * {
width: 100% !important;
border-radius: ${cssVar.borderRadiusLG};
border-radius: ${token.borderRadiusLG}px;
}
`,
placeholder: css`
color: ${cssVar.colorTextDescription};
font-size: ${cssVar.fontSizeLG};
color: ${token.colorTextDescription};
font-size: ${token.fontSizeLG}px;
`,
}));
@@ -32,6 +32,7 @@ const locales = {
};
const BehaviorMapFallback: React.FC = () => {
const { styles } = useStyle();
const [locale] = useLocale(locales);
return (
<div className={styles.fallback}>

View File

@@ -1,46 +0,0 @@
import { useMemo } from 'react';
import type { BehaviorMapItem } from './BehaviorMap';
const generateMermaidCode = (root: BehaviorMapItem) => {
const lines: string[] = [];
lines.push('graph LR');
lines.push(`classDef baseNode fill:#fff,stroke:none,stroke-width:0px,rx:5,ry:5,font-size:14px`);
const traverse = (node: BehaviorMapItem, parentId?: string) => {
const safeId = `node_${node.id.replace(/[^a-z0-9]/gi, '_')}`;
let labelText = node.label.replace(/"/g, "'");
if (!parentId) {
lines.push(`style ${safeId} font-size:16px`);
labelText = `**${labelText}**`;
} else if (node.targetType === 'mvp') {
const blueDot = `<span style="display:inline-block;width:8px;height:8px;background-color:rgb(22, 119, 255);border-radius:50%;margin-inline-end:8px;vertical-align:middle;"></span>`;
labelText = `${blueDot}${labelText}`;
} else if (node.targetType === 'extension') {
const grayDot = `<span style="display:inline-block;width:8px;height:8px;background-color:rgb(160, 160, 160);border-radius:50%;margin-inline-end:8px;vertical-align:middle;"></span>`;
labelText = `${grayDot}${labelText}`;
}
lines.push(`${safeId}["${labelText}"]:::baseNode`);
if (node.link) {
lines.push(`click ${safeId} "#${node.link}"`);
}
if (parentId) {
lines.push(`${parentId} --> ${safeId}`);
}
if (node.children && node.children.length > 0) {
node.children.forEach((child) => traverse(child, safeId));
}
};
traverse(root);
return lines.join('\n');
};
export const useMermaidCode = (data: BehaviorMapItem) => {
return useMemo(() => generateMermaidCode(data), [data]);
};

View File

@@ -20,7 +20,7 @@ const locales = {
},
};
const BezierVisualizer: React.FC<BezierVisualizerProps> = (props) => {
const BezierVisualizer = (props: BezierVisualizerProps) => {
const { value } = props;
const [locale] = useLocale(locales);

View File

@@ -1,11 +1,11 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
const styles = createStaticStyles(({ cssVar, css }) => ({
const useStyle = createStyles(({ token, css }) => ({
browserMockup: css`
position: relative;
border-top: 2em solid rgba(230, 230, 230, 0.7);
border-radius: ${cssVar.borderRadiusSM} ${cssVar.borderRadiusSM} 0 0;
border-radius: ${token.borderRadiusSM}px ${token.borderRadiusSM}px 0 0;
box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.28);
&::before {
position: absolute;
@@ -31,7 +31,7 @@ const styles = createStaticStyles(({ cssVar, css }) => ({
width: calc(100% - 6em);
height: 1.2em;
background-color: #fff;
border-radius: ${cssVar.borderRadiusSM};
border-radius: ${token.borderRadiusSM}px;
}
& > * {
display: block;
@@ -49,6 +49,7 @@ const styles = createStaticStyles(({ cssVar, css }) => ({
}));
const BrowserFrame: React.FC<React.PropsWithChildren> = ({ children }) => {
const { styles } = useStyle();
return <div className={styles.browserMockup}>{children}</div>;
};

View File

@@ -9,21 +9,21 @@ import Prism from 'prismjs';
import DemoContext from '../slots/DemoContext';
import LiveCode from './LiveCode';
const useStyle = createStyles(({ cssVar, token, css }) => {
const { antCls } = token;
const useStyle = createStyles(({ token, css }) => {
const { colorIcon, antCls } = token;
return {
code: css`
position: relative;
margin-top: calc(-1 * ${cssVar.margin});
margin-top: -${token.margin}px;
`,
copyButton: css`
color: ${cssVar.colorIcon};
color: ${colorIcon};
position: absolute;
z-index: 2;
top: 16px;
inset-inline-end: ${cssVar.padding};
inset-inline-end: ${token.padding}px;
width: 32px;
text-align: center;
padding: 0;
@@ -46,10 +46,10 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
}
}
${antCls}-typography-copy:not(${antCls}-typography-copy-success) {
color: ${cssVar.colorIcon};
color: ${colorIcon};
&:hover {
color: ${cssVar.colorIcon};
color: ${colorIcon};
}
}
`,

View File

@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { App } from 'antd';
import copy from 'antd/es/_util/copy';
import CopyToClipboard from 'react-copy-to-clipboard';
interface ColorBlockProps {
color: string;
@@ -20,17 +20,13 @@ const ColorBlock: React.FC<ColorBlockProps> = (props) => {
fontWeight: index === 6 ? 'bold' : 'normal',
};
}, [color, dark, index]);
const onCopy = async () => {
await copy(color);
message.success(`Copied: ${color}`);
};
return (
<div className="main-color-item" style={{ ...textStyle, cursor: 'pointer' }} onClick={onCopy}>
color-{index}
<span className="main-color-value">{color.toLowerCase()}</span>
</div>
<CopyToClipboard text={color} onCopy={() => message.success(`Copied: ${color}`)}>
<div className="main-color-item" style={textStyle}>
color-{index}
<span className="main-color-value">{color.toLowerCase()}</span>
</div>
</CopyToClipboard>
);
};

View File

@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import { clsx } from 'clsx';
import classNames from 'classnames';
import useLocale from '../../../hooks/useLocale';
import Palette from './Palette';
@@ -107,7 +107,7 @@ const ColorPalettes: React.FC<{ dark?: boolean }> = (props) => {
}));
}, [locale]);
return (
<div className={clsx('color-palettes', { 'color-palettes-dark': dark })}>
<div className={classNames('color-palettes', { 'color-palettes-dark': dark })}>
{memoizedColors.map((color) => (
<Palette key={`item-${color.name}`} color={color} dark={dark} showTitle />
))}

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { presetDarkPalettes } from '@ant-design/colors';
import { App } from 'antd';
import copy from 'antd/es/_util/copy';
import CopyToClipboard from 'react-copy-to-clipboard';
const rgbToHex = (rgbString: string): string => {
const rgb = rgbString.match(/\d+/g);
@@ -51,45 +51,42 @@ const Palette: React.FC<PaletteProps> = (props) => {
setHexColors(colors);
}, []);
const onCopy = async (colorText: string, colorKey: string) => {
await copy(hexColors[colorKey]);
message.success(`@${colorText} copied: ${hexColors[colorKey]}`);
};
const className = direction === 'horizontal' ? 'color-palette-horizontal' : 'color-palette';
const colorPaletteMap = {
dark: ['#fff', 'unset'],
default: ['rgba(0, 0, 0, 0.85)', '#fff'],
};
const [lastColor, firstColor] = dark ? colorPaletteMap.dark : colorPaletteMap.default;
const colors = Array.from<any, React.ReactNode>({ length: count }, (_, i) => {
const colorText = `${name}-${i}`;
const colorKey = `${name}-${i + 1}`;
const defaultBgStyle = dark && name ? presetDarkPalettes[name][i - 1] : '';
const colors: React.ReactNode[] = Array.from({ length: count }, (_, i) => {
const colorText = `${name}-${i + 1}`;
const defaultBgStyle = dark && name ? presetDarkPalettes[name][i] : '';
return (
<div
key={i}
ref={(node) => {
if (node) {
colorNodesRef.current[colorKey] = node;
}
}}
className={`main-color-item palette-${name}-${i + 1}`}
style={{
color: (name === 'yellow' ? i > 6 : i > 5) ? firstColor : lastColor,
fontWeight: i === 6 ? 'bold' : 'normal',
backgroundColor: defaultBgStyle,
cursor: 'pointer',
}}
title="click to copy color"
onClick={() => onCopy(colorText, colorKey)}
<CopyToClipboard
text={hexColors[colorText]}
onCopy={() => message.success(`@${colorText} copied: ${hexColors[colorText]}`)}
key={colorText}
>
<span className="main-color-text">{colorText}</span>
<span className="main-color-value">{hexColors[colorKey]}</span>
</div>
<div
key={i}
ref={(node) => {
if (node) {
colorNodesRef.current[`${name}-${i + 1}`] = node;
}
}}
className={`main-color-item palette-${name}-${i + 1}`}
style={{
color: (name === 'yellow' ? i > 6 : i > 5) ? firstColor : lastColor,
fontWeight: i === 6 ? 'bold' : 'normal',
backgroundColor: defaultBgStyle,
}}
title="click to copy color"
>
<span className="main-color-text">{colorText}</span>
<span className="main-color-value">{hexColors[colorText]}</span>
</div>
</CopyToClipboard>
);
});

View File

@@ -1,6 +1,6 @@
import React, { cloneElement, isValidElement } from 'react';
import { BugOutlined } from '@ant-design/icons';
import { Button, Drawer, Flex, Popover, Tag, Timeline, Typography } from 'antd';
import { Button, Drawer, Flex, Grid, Popover, Tag, Timeline, Typography } from 'antd';
import type { TimelineItemProps } from 'antd';
import { createStyles } from 'antd-style';
import useSWR from 'swr';
@@ -18,19 +18,19 @@ interface ChangelogInfo {
releaseDate: string;
}
const useStyle = createStyles(({ cssVar, token, css }) => ({
const useStyle = createStyles(({ token, css }) => ({
listWrap: css`
> li {
line-height: 2;
}
`,
linkRef: css`
margin-inline-start: ${cssVar.marginXS};
margin-inline-start: ${token.marginXS}px;
`,
bug: css`
font-size: ${cssVar.fontSize};
font-size: ${token.fontSize}px;
color: #aaa;
margin-inline-start: ${cssVar.marginXS};
margin-inline-start: ${token.marginXS}px;
display: inline-block;
vertical-align: inherit;
cursor: pointer;
@@ -39,22 +39,22 @@ const useStyle = createStyles(({ cssVar, token, css }) => ({
}
`,
bugReasonTitle: css`
padding: ${cssVar.paddingXXS} ${cssVar.paddingXS};
padding: ${token.paddingXXS}px ${token.paddingXS}px;
`,
bugReasonList: css`
width: 100%;
max-width: 100%;
li {
padding: ${cssVar.paddingXXS} ${cssVar.paddingXS};
padding: ${token.paddingXXS}px ${token.paddingXS}px;
a {
display: flex;
align-items: center;
gap: ${cssVar.marginXXS};
gap: ${token.marginXXS}px;
}
}
`,
extraLink: css`
font-size: ${cssVar.fontSize};
font-size: ${token.fontSize}px;
`,
drawerContent: {
position: 'relative',
@@ -72,7 +72,6 @@ const useStyle = createStyles(({ cssVar, token, css }) => ({
font-weight: 600;
font-size: 20px;
margin: 0 !important;
padding: 0;
`,
versionTag: css`
user-select: none;
@@ -293,7 +292,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
</Popover>
)}
</Button>
<Tag className={styles.versionTag} variant="filled" color="blue">
<Tag className={styles.versionTag} bordered={false} color="blue">
{changelogList[0]?.releaseDate}
</Tag>
</Flex>
@@ -314,14 +313,17 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
styles.versionWrap,
]);
const screens = Grid.useBreakpoint();
const width = screens.md ? '48vw' : '90vw';
if (!pathname.startsWith('/components/') || !list || !list.length) {
return null;
}
return (
<>
{isValidElement<React.HTMLAttributes<HTMLElement>>(children) &&
cloneElement(children, {
{isValidElement(children) &&
cloneElement(children as React.ReactElement<any>, {
onClick: () => setShow(true),
})}
<Drawer
@@ -334,7 +336,7 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
</Link>
}
open={show}
size="large"
width={width}
onClose={() => setShow(false)}
>
<Timeline items={timelineItems} />

View File

@@ -3,14 +3,14 @@ import { EditOutlined } from '@ant-design/icons';
import { Tooltip } from 'antd';
import { createStyles } from 'antd-style';
const branchUrl = 'https://github.com/ant-design/ant-design/edit/master/';
const branchUrl = 'https://github.com/ant-design/ant-design/edit/5.x-stable/';
export interface EditButtonProps {
title: React.ReactNode;
filename?: string;
}
const useStyle = createStyles(({ cssVar, token, css }) => {
const useStyle = createStyles(({ token, css }) => {
const { colorIcon, colorText, iconCls } = token;
return {
@@ -21,12 +21,12 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
display: inline-block;
text-decoration: none;
vertical-align: middle;
margin-inline-start: ${cssVar.marginXS};
margin-inline-start: ${token.marginXS}px;
${iconCls} {
display: block;
color: ${colorIcon};
font-size: ${cssVar.fontSizeLG};
transition: all ${cssVar.motionDurationSlow};
font-size: ${token.fontSizeLG}px;
transition: all ${token.motionDurationSlow};
&:hover {
color: ${colorText};
}

View File

@@ -1,5 +1,5 @@
import type { MouseEvent, MouseEventHandler } from 'react';
import React, { useMemo } from 'react';
import React, { forwardRef, useMemo } from 'react';
import { Link as DumiLink, useAppData, useLocation, useNavigate } from 'dumi';
export interface LinkProps {
@@ -8,48 +8,49 @@ export interface LinkProps {
className?: string;
onClick?: MouseEventHandler;
component?: React.ComponentType<any>;
ref?: React.Ref<HTMLAnchorElement>;
children?: React.ReactNode;
}
const Link: React.FC<React.PropsWithChildren<LinkProps>> = (props) => {
const { component, children, to, ref, ...rest } = props;
const { pathname } = useLocation();
const { preloadRoute } = useAppData();
const navigate = useNavigate();
const href = useMemo<string>(() => {
if (typeof to === 'object') {
return `${to.pathname || pathname}${to.search || ''}${to.hash || ''}`;
}
return to;
}, [pathname, to]);
const onClick = (e: MouseEvent<HTMLAnchorElement>) => {
rest.onClick?.(e);
if (!href?.startsWith('http')) {
// Should support open in new tab
if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
e.preventDefault();
navigate(href);
const Link = forwardRef<HTMLAnchorElement, React.PropsWithChildren<LinkProps>>(
({ component, children, to, ...rest }, ref) => {
const { pathname } = useLocation();
const { preloadRoute } = useAppData();
const navigate = useNavigate();
const href = useMemo<string>(() => {
if (typeof to === 'object') {
return `${to.pathname || pathname}${to.search || ''}${to.hash || ''}`;
}
return to;
}, [pathname, to]);
const onClick = (e: MouseEvent<HTMLAnchorElement>) => {
rest.onClick?.(e);
if (!href?.startsWith('http')) {
// Should support open in new tab
if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
e.preventDefault();
navigate(href);
}
}
};
if (component) {
return React.createElement(
component,
{
...rest,
ref,
href,
onClick,
onMouseEnter: () => preloadRoute?.(href),
},
children,
);
}
};
if (component) {
return React.createElement(
component,
{
...rest,
ref,
href,
onClick,
onMouseEnter: () => preloadRoute?.(href),
},
children,
return (
<DumiLink ref={ref} {...rest} to={href} prefetch>
{children}
</DumiLink>
);
}
return (
<DumiLink ref={ref} {...rest} to={href} prefetch>
{children}
</DumiLink>
);
};
},
);
export default Link;

View File

@@ -1,17 +1,18 @@
import type { ComponentProps, FC } from 'react';
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
import SourceCodeEditor from 'dumi/theme-default/slots/SourceCodeEditor';
import LiveError from '../slots/LiveError';
const styles = createStaticStyles(({ cssVar, css }) => {
const useStyle = createStyles(({ token, css }) => {
const { colorBgContainer } = token;
return {
editor: css`
// override dumi editor styles
.dumi-default-source-code-editor {
.dumi-default-source-code {
background: ${cssVar.colorBgContainer};
background: ${colorBgContainer};
&-scroll-container {
scrollbar-width: thin;
scrollbar-gutter: stable;
@@ -20,12 +21,12 @@ const styles = createStaticStyles(({ cssVar, css }) => {
.dumi-default-source-code > pre,
.dumi-default-source-code-scroll-content > pre,
.dumi-default-source-code-editor-textarea {
padding: ${cssVar.paddingSM} ${cssVar.padding};
padding: ${token.paddingSM}px ${token.padding}px;
}
.dumi-default-source-code > pre,
.dumi-default-source-code-scroll-content > pre {
font-size: ${cssVar.fontSize};
font-size: ${token.fontSize}px;
line-height: 2;
font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
}
@@ -41,7 +42,7 @@ const styles = createStaticStyles(({ cssVar, css }) => {
&:hover:not(:focus-within) {
&::after {
box-shadow: 0 0 0 1px ${cssVar.colorPrimaryBorderHover} inset;
box-shadow: 0 0 0 1px ${token.colorPrimaryBorderHover} inset;
}
}
}
@@ -54,6 +55,7 @@ const LiveCode: FC<
error: Error | null;
} & Pick<ComponentProps<typeof SourceCodeEditor>, 'lang' | 'initialValue' | 'onChange'>
> = (props) => {
const { styles } = useStyle();
return (
<div className={styles.editor}>
<SourceCodeEditor

View File

@@ -1,75 +1,34 @@
import React from 'react';
import { StyleProvider } from '@ant-design/cssinjs';
import { ConfigProvider, Flex, Skeleton, Spin } from 'antd';
import { createStaticStyles } from 'antd-style';
import { Flex, Skeleton, Spin } from 'antd';
import { useLocation } from 'dumi';
import { Common } from './styles';
const styles = createStaticStyles(({ css, cssVar }) => {
return {
skeletonWrapper: css`
width: 100%;
max-width: 70vw;
margin: 80px auto 0;
text-align: center;
`,
img: css`
display: block;
margin-inline: auto;
margin-bottom: ${cssVar.marginLG};
filter: grayscale(1);
opacity: 0.33;
`,
};
});
const Loading: React.FC = () => {
const { pathname } = useLocation();
let loadingNode: React.ReactNode = null;
if (
pathname.startsWith('/components') ||
pathname.startsWith('/docs') ||
pathname.startsWith('/changelog')
) {
loadingNode = (
<div className={styles.skeletonWrapper}>
return (
<div style={{ maxWidth: '70vw', width: '100%', margin: '80px auto 0', textAlign: 'center' }}>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
width={40}
height={40}
alt="loading"
draggable={false}
className={styles.img}
style={{ marginBottom: 24, filter: 'grayscale(1)', opacity: 0.33 }}
/>
<Skeleton active paragraph={{ rows: 3 }} />
<Skeleton active paragraph={{ rows: 4 }} style={{ marginTop: 32 }} />
</div>
);
} else {
loadingNode = (
<Flex
justify="center"
align="center"
gap="small"
style={{ width: '100%', margin: '120px 0' }}
>
<Spin size="large" />
</Flex>
);
}
// Loading 组件独立于 GlobalLayout而它也会影响站点样式。
// 所以我们这边需要 hardcode 一下启动 layer。
return (
<StyleProvider layer>
<ConfigProvider theme={{ zeroRuntime: process.env.NODE_ENV === 'production' }}>
<Common />
{loadingNode}
</ConfigProvider>
</StyleProvider>
<Flex justify="center" align="center" gap="small" style={{ width: '100%', margin: '120px 0' }}>
<Spin size="large" />
</Flex>
);
};

View File

@@ -1,14 +1,14 @@
import * as React from 'react';
import { createStyles, css } from 'antd-style';
import { clsx } from 'clsx';
import classNames from 'classnames';
const useStyle = createStyles(({ cssVar, cx }) => {
const duration = cssVar.motionDurationSlow;
const useStyle = createStyles(({ token, cx }) => {
const duration = token.motionDurationSlow;
const marker = css`
--mark-border-size: 1px;
position: absolute;
border: var(--mark-border-size) solid ${cssVar.colorWarning};
border: var(--mark-border-size) solid ${token.colorWarning};
box-sizing: border-box;
z-index: 999999;
pointer-events: none;
@@ -73,7 +73,7 @@ const Marker = React.memo<MarkerProps>((props) => {
return (
<div
className={clsx(
className={classNames(
styles.marker,
visible && styles.markerActive,
primary && styles.markerPrimary,

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import isVisible from '@rc-component/util/lib/Dom/isVisible';
import isVisible from 'rc-util/lib/Dom/isVisible';
import Marker from './Marker';
@@ -23,34 +23,14 @@ const Markers: React.FC<MarkersProps> = (props) => {
// ======================== Effect =========================
React.useEffect(() => {
const allElements = targetClassName
const targetElements = targetClassName
? Array.from(containerRef.current?.querySelectorAll<HTMLElement>(`.${targetClassName}`) || [])
: [];
const targetElements = allElements.filter((element) => {
let currentElement: HTMLElement | null = element;
let count = 0;
while (currentElement && count <= 5) {
const computedStyle = window.getComputedStyle(currentElement);
const opacity = Number.parseFloat(computedStyle.opacity);
if (opacity === 0) {
return false;
}
currentElement = currentElement.parentElement;
count++;
}
return true;
});
const containerRect = containerRef.current?.getBoundingClientRect() || ({} as DOMRect);
const targetRectList = targetElements.map<RectType>((targetElement) => {
const rect = targetElement.getBoundingClientRect();
return {
left: rect.left - (containerRect.left || 0),
top: rect.top - (containerRect.top || 0),

View File

@@ -3,16 +3,15 @@ import React, { useMemo } from 'react';
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import type { GetProp, MenuProps } from 'antd';
import { createStyles } from 'antd-style';
import { clsx } from 'clsx';
import classNames from 'classnames';
import useMenu from '../../hooks/useMenu';
import SiteContext from '../slots/SiteContext';
type MenuItemType = Extract<GetProp<MenuProps, 'items'>[number], { type?: 'item' }>;
const useStyle = createStyles(({ cssVar, token, css }) => {
const { iconCls } = token;
const { colorSplit, fontSizeIcon } = cssVar;
const useStyle = createStyles(({ token, css }) => {
const { colorSplit, iconCls, fontSizeIcon } = token;
return {
prevNextNav: css`
@@ -20,7 +19,7 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
margin-inline-end: 170px;
margin-inline-start: 64px;
overflow: hidden;
font-size: ${cssVar.fontSize};
font-size: ${token.fontSize}px;
border-top: 1px solid ${colorSplit};
display: flex;
`,
@@ -32,12 +31,12 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
${iconCls} {
color: #999;
font-size: ${fontSizeIcon};
transition: all ${cssVar.motionDurationSlow};
font-size: ${fontSizeIcon}px;
transition: all ${token.motionDurationSlow};
}
.chinese {
margin-inline-start: ${cssVar.marginXXS};
margin-inline-start: ${token.marginXXS}px;
}
`,
prevNav: css`
@@ -54,7 +53,7 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
position: relative;
line-height: 0;
vertical-align: middle;
transition: inset-inline-end ${cssVar.motionDurationSlow};
transition: inset-inline-end ${token.motionDurationSlow};
margin-inline-end: 1em;
inset-inline-end: 0;
}
@@ -78,7 +77,7 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
margin-bottom: 1px;
line-height: 0;
vertical-align: middle;
transition: inset-inline-start ${cssVar.motionDurationSlow};
transition: inset-inline-start ${token.motionDurationSlow};
margin-inline-start: 1em;
inset-inline-start: 0;
}
@@ -141,13 +140,23 @@ const PrevAndNext: React.FC<{ rtl?: boolean }> = ({ rtl }) => {
return (
<section className={styles.prevNextNav}>
{prev &&
React.cloneElement(prev.label as ReactElement<{ className: string }>, {
className: clsx(styles.pageNav, styles.prevNav, prev.className),
})}
React.cloneElement(
prev.label as ReactElement<{
className: string;
}>,
{
className: classNames(styles.pageNav, styles.prevNav, prev.className),
},
)}
{next &&
React.cloneElement(next.label as ReactElement<{ className: string }>, {
className: clsx(styles.pageNav, styles.nextNav, next.className),
})}
React.cloneElement(
next.label as ReactElement<{
className: string;
}>,
{
className: classNames(styles.pageNav, styles.nextNav, next.className),
},
)}
</section>
);
};

View File

@@ -1,46 +1,16 @@
import React from 'react';
import { Flex, Segmented } from 'antd';
import useLocale from '../../hooks/useLocale';
import SemanticPreview from './SemanticPreview';
export const locales = {
cn: {
root: '根元素,包含相对定位、行内 flex 布局、光标样式、过渡动画、边框等选择器容器的基础样式',
prefix: '前缀元素,包含前缀内容的布局和样式',
suffix: '后缀元素,包含后缀内容的布局和样式,如清除按钮、箭头图标等',
input: '输入框元素,包含搜索输入框的样式、光标控制、字体继承等搜索相关样式,去除了边框样式',
content: '多选容器,包含已选项的布局、间距、换行相关样式',
clear: '清除按钮元素,包含清除按钮的布局、样式和交互效果',
item: '多选项元素,包含边框、背景、内边距、外边距样式',
itemContent: '多选项内容区域,包含文字的省略样式',
itemRemove: '多选项移除按钮,包含字体相关样式',
placeholder: '占位符元素,包含占位符文本的字体样式和颜色',
'popup.root': '弹出菜单元素,包含弹出层的定位、层级、背景、边框、阴影等弹出容器样式',
'popup.list': '弹出菜单列表元素,包含选项列表的布局、滚动、最大高度等列表容器样式',
'popup.listItem':
'弹出菜单条目元素,包含选项项的内边距、悬浮效果、选中状态、禁用状态等选项交互样式',
root: '根元素',
'popup.root': '弹出菜单元素',
},
en: {
root: 'Root element with relative positioning, inline-flex layout, cursor styles, transitions, border and other basic selector container styles',
prefix: 'Prefix element with layout and styling for prefix content',
suffix:
'Suffix element with layout and styling for suffix content like clear button, arrow icon, etc.',
input:
'Input element with search input styling, cursor control, font inheritance and other search-related styles. Remove border styles',
content:
'Multiple selection container with layout, spacing, and wrapping styles for selected items',
clear: 'Clear button element with layout, styling and interactive effects for clear button',
item: 'Multiple selection item element with border, background, padding, and margin styles',
itemContent: 'Multiple selection item content area with text ellipsis styles',
itemRemove: 'Multiple selection item remove button with font-related styles',
placeholder: 'Placeholder element with font styles and colors for placeholder text',
'popup.root':
'Popup element with popup layer positioning, z-index, background, border, box-shadow and other popup container styles',
'popup.list':
'Popup list element with option list layout, scrolling, max-height and other list container styles',
'popup.listItem':
'Popup item element with option item padding, hover effects, selected states, disabled states and other option interactive styles',
root: 'Root element',
'popup.root': 'Popup element',
},
};
@@ -49,80 +19,35 @@ interface BlockProps {
options?: { value: string; label: string }[];
defaultValue?: string;
style?: React.CSSProperties;
mode: 'single' | 'multiple';
onModeChange: (mode: 'single' | 'multiple') => void;
multipleProps?: object;
singleOnly?: boolean;
[key: string]: any;
}
const Block: React.FC<BlockProps> = ({
component: Component,
options,
defaultValue,
mode,
onModeChange,
multipleProps,
singleOnly,
...props
}) => {
const Block: React.FC<BlockProps> = ({ component: Component, options, defaultValue, ...props }) => {
const divRef = React.useRef<HTMLDivElement>(null);
// 多选模式下,优先使用 multipleProps 中的 defaultValue
const multipleDefaultValue = (multipleProps as any)?.defaultValue;
const initialValue = mode === 'single' ? defaultValue : multipleDefaultValue;
const [value, setValue] = React.useState(initialValue);
React.useEffect(() => {
setValue(mode === 'single' ? defaultValue : multipleDefaultValue);
}, [mode, defaultValue, multipleDefaultValue]);
return (
<Flex
ref={divRef}
style={{ position: 'absolute', marginBottom: 80 }}
vertical
gap="middle"
align="center"
>
{!singleOnly && (
<Segmented<'single' | 'multiple'>
options={[
{ label: 'Single', value: 'single' },
{ label: 'Multiple', value: 'multiple' },
]}
value={mode}
onChange={onModeChange}
/>
)}
<div ref={divRef} style={{ position: 'absolute', marginBottom: 80 }}>
<Component
{...props}
open
placement="bottomLeft"
value={value}
onChange={setValue}
defaultValue={defaultValue}
getPopupContainer={() => divRef.current}
options={options}
{...(mode === 'multiple' ? multipleProps : {})}
styles={{ popup: { zIndex: 1 } }}
maxTagCount={process.env.NODE_ENV === 'test' ? 1 : 'responsive'}
placeholder="Please select"
allowClear
/>
</Flex>
</div>
);
};
export interface SelectSemanticTemplateProps {
component: React.ComponentType<any>;
componentName: string;
defaultValue?: string;
options?: { value: string; label: string }[];
height?: number;
onSearch?: (text: string) => void;
placeholder?: string;
style?: React.CSSProperties;
ignoreSemantics?: string[];
multipleProps?: object;
singleOnly?: boolean;
[key: string]: any;
}
@@ -133,60 +58,24 @@ const SelectSemanticTemplate: React.FC<SelectSemanticTemplateProps> = ({
height,
style,
componentName,
ignoreSemantics = [],
singleOnly = false,
...restProps
}) => {
const [locale] = useLocale(locales);
const [mode, setMode] = React.useState<'single' | 'multiple'>(singleOnly ? 'single' : 'single');
const semanticList =
mode === 'single'
? [
{ name: 'root', desc: locale.root },
{ name: 'prefix', desc: locale.prefix },
{ name: 'content', desc: locale.content },
{ name: 'placeholder', desc: locale.placeholder },
{ name: 'clear', desc: locale.clear },
{ name: 'input', desc: locale.input },
{ name: 'suffix', desc: locale.suffix },
{ name: 'popup.root', desc: locale['popup.root'] },
{ name: 'popup.list', desc: locale['popup.list'] },
{ name: 'popup.listItem', desc: locale['popup.listItem'] },
].filter((semantic) => !ignoreSemantics.includes(semantic.name))
: [
{ name: 'root', desc: locale.root },
{ name: 'prefix', desc: locale.prefix },
{ name: 'content', desc: locale.content },
{ name: 'placeholder', desc: locale.placeholder },
{ name: 'clear', desc: locale.clear },
{ name: 'item', desc: locale.item },
{ name: 'itemContent', desc: locale.itemContent },
{ name: 'itemRemove', desc: locale.itemRemove },
{ name: 'input', desc: locale.input },
{ name: 'suffix', desc: locale.suffix },
{ name: 'popup.root', desc: locale['popup.root'] },
{ name: 'popup.list', desc: locale['popup.list'] },
{ name: 'popup.listItem', desc: locale['popup.listItem'] },
].filter((semantic) => !ignoreSemantics.includes(semantic.name));
return (
<SemanticPreview
componentName={componentName}
semantics={semanticList}
semantics={[
{ name: 'root', desc: locale.root, version: '5.25.0' },
{ name: 'popup.root', desc: locale['popup.root'], version: '5.25.0' },
]}
height={height}
style={{
alignItems: 'flex-start',
}}
>
<Block
component={component}
defaultValue={defaultValue}
options={options}
style={style}
mode={mode}
onModeChange={singleOnly ? () => {} : setMode}
singleOnly={singleOnly}
{...restProps}
/>
</SemanticPreview>

View File

@@ -1,10 +1,11 @@
import React from 'react';
import { InfoCircleOutlined, PushpinOutlined } from '@ant-design/icons';
import { get, set } from '@rc-component/util';
import { Button, Col, ConfigProvider, Flex, Popover, Row, Tag, theme, Typography } from 'antd';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles, css } from 'antd-style';
import classnames from 'classnames';
import Prism from 'prismjs';
import get from 'rc-util/lib/utils/get';
import set from 'rc-util/lib/utils/set';
import Markers from './Markers';
@@ -12,20 +13,18 @@ export interface SemanticPreviewInjectionProps {
classNames?: Record<string, string>;
}
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ token }) => ({
container: css`
position: relative;
z-index: 0;
`,
colWrap: css`
border-inline-end: 1px solid ${cssVar.colorBorderSecondary};
border-inline-end: 1px solid ${token.colorBorderSecondary};
display: flex;
justify-content: center;
align-items: center;
padding: ${cssVar.paddingMD};
padding: ${token.paddingMD}px;
overflow: hidden;
position: relative;
z-index: 0;
`,
colWrapPaddingLess: css`
padding: 0;
@@ -40,13 +39,13 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
`,
listItem: css`
cursor: pointer;
padding: ${cssVar.paddingSM};
transition: background-color ${cssVar.motionDurationFast} ease;
padding: ${token.paddingSM}px;
transition: background-color ${token.motionDurationFast} ease;
&:hover {
background-color: ${cssVar.controlItemBgHover};
background-color: ${token.controlItemBgHover};
}
&:not(:first-of-type) {
border-top: 1px solid ${cssVar.colorBorderSecondary};
border-top: 1px solid ${token.colorBorderSecondary};
}
`,
}));
@@ -121,7 +120,6 @@ export interface SemanticPreviewProps {
children: React.ReactElement<any>;
height?: number;
padding?: false;
style?: React.CSSProperties;
}
const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
@@ -130,7 +128,6 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
children,
height,
padding,
style,
componentName = 'Component',
itemsAPI,
} = props;
@@ -155,6 +152,8 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
const mergedSemantic = pinSemantic || hoverSemantic;
const { styles } = useStyle();
const hoveredSemanticClassNames = React.useMemo(() => {
if (!mergedSemantic) {
return semanticClassNames;
@@ -164,7 +163,7 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
const clone = set(
semanticClassNames,
hoverCell,
clsx(get(semanticClassNames, hoverCell), getMarkClassName('active')),
classnames(get(semanticClassNames, hoverCell), getMarkClassName('active')),
);
return clone;
@@ -176,21 +175,20 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
});
return (
<div className={clsx(styles.container)} ref={containerRef}>
<div className={classnames(styles.container)} ref={containerRef}>
<Row style={{ minHeight: height }}>
<Col
span={16}
className={clsx(styles.colWrap, padding === false && styles.colWrapPaddingLess)}
style={style}
className={classnames(styles.colWrap, padding === false && styles.colWrapPaddingLess)}
>
<ConfigProvider theme={{ token: { motion: false } }}>{cloneNode}</ConfigProvider>
</Col>
<Col span={8}>
<ul className={clsx(styles.listWrap)}>
<ul className={classnames(styles.listWrap)}>
{semantics.map<React.ReactNode>((semantic) => (
<li
key={semantic.name}
className={clsx(styles.listItem)}
className={classnames(styles.listItem)}
onMouseEnter={() => setHoverSemantic(semantic.name)}
onMouseLeave={() => setHoverSemantic(null)}
>

View File

@@ -1,117 +0,0 @@
import React, { useRef, useState } from 'react';
import { AntDesignOutlined, UserOutlined } from '@ant-design/icons';
import { Bubble, Sender } from '@ant-design/x';
import type { SenderRef } from '@ant-design/x/es/sender';
import { Drawer, Flex, Typography } from 'antd';
import type { GetProp } from 'antd';
import useLocale from '../../../hooks/useLocale';
import type { SiteContextProps } from '../../../theme/slots/SiteContext';
import usePromptTheme from './usePromptTheme';
const locales = {
cn: {
title: 'AI 生成主题',
finishTips: '生成完成,对话以重新生成。',
},
en: {
title: 'AI Theme Generator',
finishTips: 'Completed. Regenerate by start a new conversation.',
},
};
export interface PromptDrawerProps {
open: boolean;
onClose: () => void;
onThemeChange?: (themeConfig: SiteContextProps['dynamicTheme']) => void;
}
const PromptDrawer: React.FC<PromptDrawerProps> = ({ open, onClose, onThemeChange }) => {
const [locale] = useLocale(locales);
const [inputValue, setInputValue] = useState('');
const senderRef = useRef<SenderRef>(null);
const [submitPrompt, loading, prompt, resText, cancelRequest] = usePromptTheme(onThemeChange);
const handleSubmit = (value: string) => {
submitPrompt(value);
setInputValue('');
};
const handleAfterOpenChange = (isOpen: boolean) => {
if (isOpen && senderRef.current) {
// Focus the Sender component when drawer opens
senderRef.current.focus?.();
}
};
const items = React.useMemo<GetProp<typeof Bubble.List, 'items'>>(() => {
if (!prompt) {
return [];
}
const nextItems: GetProp<typeof Bubble.List, 'items'> = [
{
key: 1,
role: 'user',
placement: 'end',
content: prompt,
avatar: <UserOutlined />,
shape: 'corner',
},
{
key: 2,
role: 'system',
placement: 'start',
content: resText,
avatar: <AntDesignOutlined />,
loading: !resText,
contentRender: (content: string) => (
<Typography>
<pre style={{ margin: 0 }}>{content}</pre>
</Typography>
),
},
];
if (!loading) {
nextItems.push({
key: 3,
role: 'divider',
placement: 'start',
content: locale.finishTips,
avatar: <AntDesignOutlined />,
shape: 'corner',
});
}
return nextItems;
}, [prompt, resText, loading, locale.finishTips]);
return (
<Drawer
title={locale.title}
open={open}
onClose={onClose}
size={480}
placement="right"
afterOpenChange={handleAfterOpenChange}
>
<Flex vertical style={{ height: '100%' }}>
<Bubble.List style={{ flex: 1, overflow: 'auto' }} items={items} />
<Sender
ref={senderRef}
style={{ flex: 0 }}
value={inputValue}
onChange={setInputValue}
onSubmit={handleSubmit}
loading={loading}
onCancel={cancelRequest}
/>
</Flex>
</Drawer>
);
};
export default PromptDrawer;

View File

@@ -1,8 +1,7 @@
import React, { use, useRef, useState } from 'react';
import React, { use, useRef } from 'react';
import {
BgColorsOutlined,
LinkOutlined,
ShopOutlined,
SmileOutlined,
SunOutlined,
SyncOutlined,
@@ -18,7 +17,6 @@ import type { SiteContextProps } from '../../slots/SiteContext';
import SiteContext from '../../slots/SiteContext';
import { getLocalizedPathname, isZhCN } from '../../utils';
import Link from '../Link';
import PromptDrawer from './PromptDrawer';
import ThemeIcon from './ThemeIcon';
export type ThemeName = 'light' | 'dark' | 'auto' | 'compact' | 'motion-off' | 'happy-work';
@@ -31,10 +29,9 @@ export interface ThemeSwitchProps {
const ThemeSwitch: React.FC<ThemeSwitchProps> = () => {
const { pathname, search } = useLocation();
const { theme, updateSiteConfig, dynamicTheme } = use<SiteContextProps>(SiteContext);
const { theme, updateSiteConfig } = use<SiteContextProps>(SiteContext);
const toggleAnimationTheme = useThemeAnimation();
const lastThemeKey = useRef<string>(theme.includes('dark') ? 'dark' : 'light');
const [isMarketDrawerOpen, setIsMarketDrawerOpen] = useState(false);
const [, setTheme] = useLocalStorage<ThemeName>(ANT_DESIGN_SITE_THEME, {
defaultValue: undefined,
@@ -83,12 +80,6 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = () => {
{
type: 'divider',
},
{
id: 'app.theme.switch.market',
icon: <ShopOutlined />,
key: 'market',
showBadge: () => !!dynamicTheme,
},
{
id: 'app.footer.theme',
icon: <BgColorsOutlined />,
@@ -123,25 +114,8 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = () => {
// 处理主题切换
const handleThemeChange = (key: string, domEvent: React.MouseEvent<HTMLElement, MouseEvent>) => {
// 查找对应的选项配置
const option = themeOptions.find((opt) => opt.key === key);
// 链接类型的菜单项特殊处理,不执行主题切换逻辑
if (option?.isLink) {
return;
}
// Market 选项特殊处理
if (key === 'market') {
// 如果已经有动态主题,点击时清除动态主题
if (dynamicTheme) {
updateSiteConfig({
dynamicTheme: undefined,
});
} else {
// 否则打开 Drawer 生成新主题
setIsMarketDrawerOpen(true);
}
// 主题编辑器特殊处理
if (key === 'theme-editor') {
return;
}
@@ -180,21 +154,9 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = () => {
};
return (
<>
<Dropdown menu={{ items, onClick }} arrow={{ pointAtCenter: true }} placement="bottomRight">
<Button type="text" icon={<ThemeIcon />} style={{ fontSize: 16 }} />
</Dropdown>
<PromptDrawer
open={isMarketDrawerOpen}
onClose={() => setIsMarketDrawerOpen(false)}
onThemeChange={(nextTheme) => {
updateSiteConfig({
dynamicTheme: nextTheme,
});
}}
/>
</>
<Dropdown menu={{ items, onClick }} arrow={{ pointAtCenter: true }} placement="bottomRight">
<Button type="text" icon={<ThemeIcon />} style={{ fontSize: 16 }} />
</Dropdown>
);
};

View File

@@ -1,130 +0,0 @@
import { useRef, useState } from 'react';
import { XStream } from '@ant-design/x-sdk';
import type { SiteContextProps } from '../../../theme/slots/SiteContext';
const fetchTheme = async (
prompt: string,
update: (currentFullContent: string) => void,
abortSignal?: AbortSignal,
) => {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: prompt,
userId: 'AntDesignSite',
}),
signal: abortSignal,
};
try {
const response = await fetch('https://api.x.ant.design/api/agent_tbox_antd', options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
if (!response.body) {
throw new Error('Response body is null');
}
let fullContent = '';
for await (const chunk of XStream({
readableStream: response.body,
})) {
if (chunk.event === 'message') {
const data = JSON.parse(chunk.data) as {
lane: string;
payload: string;
};
const payload = JSON.parse(data.payload) as {
text: string;
};
fullContent += payload.text || '';
update(fullContent);
}
}
return fullContent;
} catch (error) {
console.error('Error in fetchTheme:', error);
throw error;
}
};
// Remove '```json' code block from the response
function getJsonText(raw: string, rmComment = false): string {
const replaced = raw.trim().replace(/^```json\s*|\s*```$/g, '');
return rmComment ? replaced.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, '').trim() : replaced;
}
export default function usePromptTheme(
onThemeChange?: (themeConfig: SiteContextProps['dynamicTheme']) => void,
) {
const [prompt, setPrompt] = useState('');
const [loading, setLoading] = useState(false);
const [resText, setResText] = useState('');
const abortControllerRef = useRef<AbortController | null>(null);
const submitPrompt = async (nextPrompt: string) => {
if (!nextPrompt.trim()) {
return;
}
setPrompt(nextPrompt);
// Cancel previous request if it exists
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
// Create new AbortController for this request
const abortController = new AbortController();
abortControllerRef.current = abortController;
setLoading(true);
setResText('');
try {
const data = await fetchTheme(
nextPrompt,
(currentContent) => {
setResText(currentContent);
},
abortController.signal,
);
// Handle the response
if (data && onThemeChange) {
const nextConfig = JSON.parse(getJsonText(data, true));
onThemeChange(nextConfig);
}
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
console.warn('Request was aborted');
} else {
console.error('Failed to generate theme:', error);
}
} finally {
setLoading(false);
abortControllerRef.current = null;
}
};
const cancelRequest = () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
abortControllerRef.current = null;
setLoading(false);
}
};
return [submitPrompt, loading, prompt, getJsonText(resText), cancelRequest] as const;
}

View File

@@ -1,181 +0,0 @@
import React from 'react';
import { Flex, Segmented } from 'antd';
import useLocale from '../../hooks/useLocale';
import SemanticPreview from './SemanticPreview';
export const locales = {
cn: {
root: '根元素,设置树选择器的基础样式、边框、圆角容器样式',
prefix: '前缀元素,设置前缀内容的布局和样式',
input: '输入框元素,设置文本输入、搜索、选择值显示等输入框的核心交互样式',
suffix: '后缀元素,设置后缀内容、清除按钮、下拉箭头等后缀区域的样式',
content: '多选容器,包含已选项的布局、间距、换行相关样式',
item: '多选项元素,包含边框、背景、内边距、外边距样式',
itemContent: '多选项内容区域,包含文字的省略样式',
itemRemove: '多选项移除按钮,包含字体相关样式',
placeholder: '占位符元素,包含占位符文本的字体样式和颜色',
'popup.root': '弹出菜单元素,设置下拉树形选择面板的定位、层级、背景、边框、阴影等弹层样式',
'popup.item': '弹出菜单条目元素,设置树节点选项的样式、悬停态、选中态等交互状态',
'popup.itemTitle': '弹出菜单标题元素,设置树节点标题文字的显示样式',
},
en: {
root: 'Root element with tree selector base styles, border, border radius container styles',
prefix: 'Prefix element with prefix content layout and styles',
input:
'Input element with text input, search, selected value display and other input core interaction styles',
suffix:
'Suffix element with suffix content, clear button, dropdown arrow and other suffix area styles',
content:
'Multiple selection container with layout, spacing, and wrapping styles for selected items',
item: 'Multiple selection item element with border, background, padding, and margin styles',
itemContent: 'Multiple selection item content area with text ellipsis styles',
itemRemove: 'Multiple selection item remove button with font-related styles',
placeholder: 'Placeholder element with font styles and colors for placeholder text',
'popup.root':
'Popup element with dropdown tree selection panel positioning, z-index, background, border, shadow and other popup layer styles',
'popup.item':
'Popup item element with tree node option styles, hover state, selected state and other interaction states',
'popup.itemTitle': 'Popup title element with tree node title text display styles',
},
};
interface BlockProps {
component: React.ComponentType<any>;
treeData?: any[];
defaultValue?: string | string[];
style?: React.CSSProperties;
mode: 'single' | 'multiple';
onModeChange: (mode: 'single' | 'multiple') => void;
multipleProps?: object;
[key: string]: any;
}
const Block: React.FC<BlockProps> = ({
component: Component,
treeData,
defaultValue,
mode,
onModeChange,
multipleProps,
...props
}) => {
const divRef = React.useRef<HTMLDivElement>(null);
// 多选模式下,优先使用 multipleProps 中的 defaultValue
const multipleDefaultValue = (multipleProps as any)?.defaultValue;
const initialValue = mode === 'single' ? defaultValue : multipleDefaultValue;
const [value, setValue] = React.useState(initialValue);
React.useEffect(() => {
setValue(mode === 'single' ? defaultValue : multipleDefaultValue);
}, [mode, defaultValue, multipleDefaultValue]);
return (
<Flex
ref={divRef}
style={{ position: 'absolute', marginBottom: 80 }}
vertical
gap="middle"
align="center"
>
<Segmented
options={[
{ label: 'Single', value: 'single' },
{ label: 'Multiple', value: 'multiple' },
]}
value={mode}
onChange={(value) => onModeChange(value as 'single' | 'multiple')}
/>
<Component
{...props}
open
placement="bottomLeft"
value={value}
onChange={setValue}
getPopupContainer={() => divRef.current}
treeData={treeData}
{...(mode === 'multiple' ? multipleProps : {})}
styles={{ popup: { zIndex: 1 } }}
maxTagCount={process.env.NODE_ENV === 'test' ? 1 : 'responsive'}
placeholder="Please select"
/>
</Flex>
);
};
export interface TreeSelectSemanticTemplateProps {
component: React.ComponentType<any>;
componentName: string;
treeData?: any[];
height?: number;
onSearch?: (text: string) => void;
placeholder?: string;
style?: React.CSSProperties;
ignoreSemantics?: string[];
multipleProps?: object;
[key: string]: any;
}
const TreeSelectSemanticTemplate: React.FC<TreeSelectSemanticTemplateProps> = ({
component,
defaultValue,
treeData,
height,
style,
componentName,
ignoreSemantics = [],
...restProps
}) => {
const [locale] = useLocale(locales);
const [mode, setMode] = React.useState<'single' | 'multiple'>('single');
const semanticList =
mode === 'single'
? [
{ name: 'root', desc: locale.root },
{ name: 'prefix', desc: locale.prefix },
{ name: 'placeholder', desc: locale.placeholder },
{ name: 'input', desc: locale.input },
{ name: 'suffix', desc: locale.suffix },
{ name: 'popup.root', desc: locale['popup.root'] },
{ name: 'popup.item', desc: locale['popup.item'] },
{ name: 'popup.itemTitle', desc: locale['popup.itemTitle'] },
].filter((semantic) => !ignoreSemantics.includes(semantic.name))
: [
{ name: 'root', desc: locale.root },
{ name: 'prefix', desc: locale.prefix },
{ name: 'content', desc: locale.content },
{ name: 'item', desc: locale.item },
{ name: 'itemContent', desc: locale.itemContent },
{ name: 'itemRemove', desc: locale.itemRemove },
{ name: 'input', desc: locale.input },
{ name: 'placeholder', desc: locale.placeholder },
{ name: 'suffix', desc: locale.suffix },
{ name: 'popup.root', desc: locale['popup.root'] },
{ name: 'popup.item', desc: locale['popup.item'] },
{ name: 'popup.itemTitle', desc: locale['popup.itemTitle'] },
].filter((semantic) => !ignoreSemantics.includes(semantic.name));
return (
<SemanticPreview
componentName={componentName}
semantics={semanticList}
height={height}
style={{
alignItems: 'flex-start',
}}
>
<Block
component={component}
defaultValue={defaultValue}
treeData={treeData}
style={style}
mode={mode}
onModeChange={setMode}
{...restProps}
/>
</SemanticPreview>
);
};
export default TreeSelectSemanticTemplate;

View File

@@ -1,38 +0,0 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import useLocale from '../../../hooks/useLocale';
import EN from './en-US.md';
import CN from './zh-CN.md';
const changeLog = { cn: CN, en: EN };
const classNames = createStaticStyles(({ css }) => ({
container: css`
max-height: max(62vh, 500px);
overflow-y: scroll;
scrollbar-width: thin;
scrollbar-color: #eaeaea transparent;
/* 图片铺满 */
&& img {
display: block;
width: 100%;
max-width: 100%;
}
`,
}));
const ChangeLog = () => {
const [, lang] = useLocale();
const validatedLanguage = Object.keys(changeLog).includes(lang) ? lang : 'en';
const C = changeLog[validatedLanguage];
return (
<div className={classNames.container}>
<C />
</div>
);
};
export default ChangeLog;

View File

@@ -1,12 +0,0 @@
<p align="center">
<img src="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*_DMIQaxDuXsAAAAAgDAAAAgAegCCAQ/fmt.webp" alt="Ant Design 6.0">
</p>
After extensive refinement, v6 is officially released! This upgrade focuses on deep technical optimizations for better performance and developer experience:
- **Technical Upgrades**: Minimum React 18 support; defaults to Pure CSS Variables mode, supporting zero-runtime styles and real-time theme switching.
- **Semantic Structure**: All components now feature semantic DOM structure, enabling flexible customization via `classNames`.
- **New Features**: Added Masonry component; Tooltip panning; InputNumber spinner mode; Resizable Drawer; default blur mask for overlays.
- **Smooth Migration**: Direct upgrade from v5 without codemod tools. For v5 documentation, please visit [5x.ant.design](https://5x.ant.design).
**[Ant Design X 2.0](https://github.com/ant-design/x/issues/1357)** for AI scenarios is also released simultaneously. Explore now!

View File

@@ -1,98 +0,0 @@
import React from 'react';
import { Button, Flex, Modal, version } from 'antd';
import { useLocation } from 'dumi';
import useLocale from '../../../hooks/useLocale';
import * as utils from '../../utils';
import ChangeLog from './ChangeLog';
const [major] = version.split('.');
const STORAGE_KEY = `antd${major}-version-upgrade-notify`;
// 弹窗截止日期
const NOTIFICATION_DEADLINE = new Date('2026/02/01').getTime();
const locales = {
cn: {
title: 'Ant Design 6.0 现已发布 🎉',
releasePost: '发布公告 🚀',
fullChangelog: '完整更新日志 📝',
},
en: {
title: 'Ant Design 6.0 has been released 🎉',
releasePost: 'Release Post 🚀',
fullChangelog: 'Full Changelog 📝',
},
};
const VersionUpgradeModal = () => {
const [locale, lang] = useLocale(locales);
const { pathname } = useLocation();
const [open, updateOpen] = React.useState(false);
const isCN = lang === 'cn' || utils.isZhCN(pathname);
function handleClose() {
localStorage.setItem(STORAGE_KEY, Date.now().toString());
updateOpen(false);
}
React.useEffect(() => {
const lastTime = localStorage.getItem(STORAGE_KEY);
const now = Date.now();
if (now > NOTIFICATION_DEADLINE) {
return;
}
if (!lastTime) {
const timer = setTimeout(() => {
updateOpen(true);
}, 1000);
return () => {
clearTimeout(timer);
};
}
}, []);
const fullChangelogUrl = utils.getLocalizedPathname('/changelog', isCN).pathname;
const releasePostUrl = `https://github.com/ant-design/ant-design/issues/${isCN ? '55805' : '55804'}`;
return (
<Modal
title={locale.title}
open={open}
width={`min(90vw, 800px)`}
centered
onCancel={handleClose}
styles={{
body: {
padding: 0,
},
}}
footer={() => (
<Flex align="center" gap="middle" justify="flex-end">
<Button href={fullChangelogUrl} onClick={handleClose}>
{locale.fullChangelog}
</Button>
<Button
color="primary"
variant="solid"
href={releasePostUrl}
target="_blank"
onClick={handleClose}
>
{locale.releasePost}
</Button>
</Flex>
)}
>
<ChangeLog />
</Modal>
);
};
export default VersionUpgradeModal;

View File

@@ -1,12 +0,0 @@
<p align="center">
<img src="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*_DMIQaxDuXsAAAAAgDAAAAgAegCCAQ/fmt.webp" alt="Ant Design 6.0">
</p>
经过大量打磨v6 版本现已正式发布!本次升级专注于技术深度优化,带来更佳的性能与开发体验:
- **技术升级**:最低支持 React 18移除历史包袱默认启用纯 CSS 变量模式,支持零运行时样式与实时主题切换。
- **语义化结构**:全量组件完成 DOM 语义化改造,配合 `classNames` 属性实现更灵活的样式定制。
- **新特性**:新增 Masonry 瀑布流组件Tooltip 支持平移InputNumber 新增按钮模式Drawer 支持拖拽;弹层默认开启模糊背景。
- **平滑迁移**v5 项目可直接升级,无需 codemod 工具。如需查看 v5 文档,请访问 [5x.ant.design](https://5x.ant.design)。
同时,面向 AI 场景的 **[Ant Design X 2.0](https://github.com/ant-design/x/issues/1358)** 也同步发布,欢迎探索!

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { css, Global } from '@emotion/react';
import { updateCSS } from '@rc-component/util/lib/Dom/dynamicCSS';
import { useTheme } from 'antd-style';
import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
export default () => {
const { anchorTop } = useTheme();
React.useInsertionEffect(() => {
updateCSS(`@layer theme, base, global, antd, components, utilities;`, 'site-global', {
updateCSS(`@layer global, antd;`, 'site-global', {
prepend: true,
});
}, []);

View File

@@ -26,7 +26,6 @@ const GlobalStyle: React.FC = () => {
.markdown img {
max-width: calc(100% - 32px);
max-height: 100%;
display: inline;
}
.markdown > a > img,

View File

@@ -58,7 +58,6 @@ export default () => {
body {
overflow-x: hidden;
scrollbar-width: thin;
color: ${token.colorText};
font-size: ${token.fontSize}px;
font-family: ${token.fontFamily};

View File

@@ -8,15 +8,8 @@ const SVGIcon: React.FC = () => (
</svg>
);
interface SvgIconProps {
className?: string;
style?: React.CSSProperties;
ref?: React.Ref<HTMLSpanElement>;
}
const CodePenIcon: React.FC<SvgIconProps> = (props) => {
const { ref, ...rest } = props;
return <Icon component={SVGIcon} ref={ref} {...rest} />;
};
const CodePenIcon = React.forwardRef<HTMLSpanElement, { className?: string }>((props, ref) => (
<Icon component={SVGIcon} ref={ref} {...props} />
));
export default CodePenIcon;

View File

@@ -8,15 +8,8 @@ const SVGIcon: React.FC = () => (
</svg>
);
interface SvgIconProps {
className?: string;
style?: React.CSSProperties;
ref?: React.Ref<HTMLSpanElement>;
}
const CodeSandboxIcon: React.FC<SvgIconProps> = (props) => {
const { ref, ...rest } = props;
return <Icon component={SVGIcon} ref={ref} {...rest} />;
};
const CodeSandboxIcon = React.forwardRef<HTMLSpanElement, { className?: string }>((props, ref) => (
<Icon component={SVGIcon} ref={ref} {...props} />
));
export default CodeSandboxIcon;

View File

@@ -2,14 +2,12 @@ import React from 'react';
import Icon from '@ant-design/icons';
import type { DirectionType } from 'antd/es/config-provider';
interface SvgIconProps {
interface DirectionIconProps {
className?: string;
style?: React.CSSProperties;
ref?: React.Ref<HTMLSpanElement>;
direction?: DirectionType;
}
const DirectionSvg: React.FC<SvgIconProps> = ({ direction }) => (
const DirectionSvg: React.FC<DirectionIconProps> = ({ direction }) => (
<svg
viewBox="0 0 20 20"
width="20"
@@ -22,9 +20,8 @@ const DirectionSvg: React.FC<SvgIconProps> = ({ direction }) => (
</svg>
);
const DirectionIcon: React.FC<SvgIconProps> = (props) => {
const { ref, direction, ...rest } = props;
return <Icon component={() => <DirectionSvg direction={direction} />} ref={ref} {...rest} />;
};
const DirectionIcon = React.forwardRef<HTMLSpanElement, DirectionIconProps>((props, ref) => (
<Icon ref={ref} component={() => <DirectionSvg direction={props.direction} />} {...props} />
));
export default DirectionIcon;

View File

@@ -1,14 +1,11 @@
import React from 'react';
import Icon from '@ant-design/icons';
interface SvgIconProps {
className?: string;
style?: React.CSSProperties;
ref?: React.Ref<HTMLSpanElement>;
interface SVGIconProps {
expanded?: boolean;
}
const SVGIcon: React.FC<SvgIconProps> = ({ expanded }) => (
const SVGIcon: React.FC<SVGIconProps> = ({ expanded }) => (
<svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor">
<title>Expand Icon</title>
{expanded ? (
@@ -27,9 +24,8 @@ const SVGIcon: React.FC<SvgIconProps> = ({ expanded }) => (
</svg>
);
const ExpandIcon: React.FC<SvgIconProps> = (props) => {
const { ref, expanded, ...rest } = props;
return <Icon component={() => <SVGIcon expanded={expanded} />} ref={ref} {...rest} />;
};
const ExpandIcon = React.forwardRef<HTMLSpanElement, { className?: string; expanded?: boolean }>(
({ expanded }, ref) => <Icon component={() => SVGIcon({ expanded })} ref={ref} />,
);
export default ExpandIcon;

View File

@@ -1,14 +1,12 @@
import React from 'react';
import Icon from '@ant-design/icons';
interface SvgIconProps {
interface ExternalIconProps {
className?: string;
style?: React.CSSProperties;
ref?: React.Ref<HTMLSpanElement>;
color?: string;
}
const SVGIcon: React.FC<SvgIconProps> = ({ color = 'currentColor' }) => (
const SVGIcon: React.FC<{ color?: string }> = ({ color = 'currentColor' }) => (
<svg viewBox="0 0 1024 1024" width="1em" height="1em" fill={color}>
<title>External Link Icon</title>
<path d="M853.333 469.333A42.667 42.667 0 0 0 810.667 512v256A42.667 42.667 0 0 1 768 810.667H256A42.667 42.667 0 0 1 213.333 768V256A42.667 42.667 0 0 1 256 213.333h256A42.667 42.667 0 0 0 512 128H256a128 128 0 0 0-128 128v512a128 128 0 0 0 128 128h512a128 128 0 0 0 128-128V512a42.667 42.667 0 0 0-42.667-42.667z" />
@@ -16,9 +14,8 @@ const SVGIcon: React.FC<SvgIconProps> = ({ color = 'currentColor' }) => (
</svg>
);
const ExternalLinkIcon: React.FC<SvgIconProps> = (props) => {
const { ref, color, ...rest } = props;
return <Icon component={() => <SVGIcon color={color} />} ref={ref} {...rest} />;
};
const ExternalLinkIcon = React.forwardRef<HTMLSpanElement, ExternalIconProps>((props, ref) => (
<Icon component={() => <SVGIcon color={props.color} />} ref={ref} {...props} />
));
export default ExternalLinkIcon;

View File

@@ -1,4 +1,4 @@
import { clsx } from 'clsx';
import classNames from 'classnames';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
@@ -16,7 +16,6 @@ import SiteContext from '../../slots/SiteContext';
import IndexLayout from '../IndexLayout';
import ResourceLayout from '../ResourceLayout';
import SidebarLayout from '../SidebarLayout';
import VersionUpgrade from '../../common/VersionUpgrade';
const locales = {
cn: {
@@ -83,7 +82,7 @@ const DocLayout: React.FC = () => {
if (pathname.startsWith('/docs/resource')) {
return <ResourceLayout>{outlet}</ResourceLayout>;
}
if (pathname.startsWith('/theme-editor') || pathname.startsWith('/theme-market')) {
if (pathname.startsWith('/theme-editor')) {
return outlet;
}
return <SidebarLayout>{outlet}</SidebarLayout>;
@@ -95,7 +94,7 @@ const DocLayout: React.FC = () => {
<html
lang={lang === 'cn' ? 'zh-CN' : lang}
data-direction={direction}
className={clsx({ rtl: direction === 'rtl' })}
className={classNames({ rtl: direction === 'rtl' })}
/>
<link
sizes="144x144"
@@ -115,7 +114,6 @@ const DocLayout: React.FC = () => {
>
<GlobalStyles />
{!hideLayout && <Header />}
<VersionUpgrade />
{content}
</ConfigProvider>
</>

View File

@@ -9,7 +9,7 @@ import {
} from '@ant-design/cssinjs';
import { HappyProvider } from '@ant-design/happy-work-theme';
import { getSandpackCssText } from '@codesandbox/sandpack-react';
import { theme as antdTheme, App, ConfigProvider } from 'antd';
import { theme as antdTheme, App } from 'antd';
import type { MappingAlgorithm } from 'antd';
import type { DirectionType, ThemeConfig } from 'antd/es/config-provider';
import dayjs from 'dayjs';
@@ -22,9 +22,12 @@ import { getBannerData } from '../../pages/index/components/util';
import { ANT_DESIGN_SITE_THEME } from '../common/ThemeSwitch';
import type { ThemeName } from '../common/ThemeSwitch';
import SiteThemeProvider from '../SiteThemeProvider';
import type { SimpleComponentClassNames, SiteContextProps } from '../slots/SiteContext';
import type { SiteContextProps } from '../slots/SiteContext';
import SiteContext from '../slots/SiteContext';
import '@ant-design/v5-patch-for-react-19';
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][];
type SiteState = Partial<Omit<SiteContextProps, 'updateSiteConfig'>>;
const RESPONSIVE_MOBILE = 768;
@@ -65,24 +68,16 @@ const getSystemTheme = (): 'light' | 'dark' => {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};
const isThemeDark = (theme: ThemeName[], systemTheme: 'dark' | 'light') => {
return theme.includes('dark') || (theme.includes('auto') && systemTheme === 'dark');
};
const GlobalLayout: React.FC = () => {
const outlet = useOutlet();
const [searchParams, setSearchParams] = useSearchParams();
const [
{ theme = [], direction, isMobile, bannerVisible = false, dynamicTheme, isDark = false },
setSiteState,
] = useLayoutState<SiteState>({
isMobile: false,
direction: 'ltr',
theme: [],
isDark: false,
bannerVisible: false,
dynamicTheme: undefined,
});
const [{ theme = [], direction, isMobile, bannerVisible = false }, setSiteState] =
useLayoutState<SiteState>({
isMobile: false,
direction: 'ltr',
theme: [],
bannerVisible: false,
});
const [storedTheme] = useLocalStorage<ThemeName>(ANT_DESIGN_SITE_THEME, {
defaultValue: undefined,
@@ -110,16 +105,17 @@ const GlobalLayout: React.FC = () => {
const bannerData = getBannerData();
// TODO: This can be remove in v6
const useCssVar = searchParams.get('cssVar') !== 'false';
const updateSiteConfig = useCallback(
(props: SiteState) => {
setSiteState((prev) => ({ ...prev, ...props }));
const oldSearchStr = searchParams.toString();
let nextSearchParams: URLSearchParams = searchParams;
Object.entries(props).forEach((kv) => {
const [key, value] = kv as [string, string];
(Object.entries(props) as Entries<SiteContextProps>).forEach(([key, value]) => {
if (key === 'direction') {
if (value === 'rtl') {
nextSearchParams.set('direction', 'rtl');
@@ -143,7 +139,6 @@ const GlobalLayout: React.FC = () => {
setSearchParams(nextSearchParams);
}
},
[searchParams, setSearchParams],
);
@@ -151,7 +146,7 @@ const GlobalLayout: React.FC = () => {
updateSiteConfig({ isMobile: window.innerWidth < RESPONSIVE_MOBILE });
}, [updateSiteConfig]);
// 设置 data-prefers-color 属性和 isDark 状态
// 设置 data-prefers-color 属性
useEffect(() => {
const color = theme.find((t) => t === 'light' || t === 'dark');
const html = document.querySelector<HTMLHtmlElement>('html');
@@ -160,8 +155,6 @@ const GlobalLayout: React.FC = () => {
} else if (color) {
html?.setAttribute('data-prefers-color', color);
}
setSiteState((prev) => ({ ...prev, isDark: isThemeDark(theme, systemTheme) }));
}, [systemTheme, theme]);
// 监听系统主题变化
@@ -189,7 +182,6 @@ const GlobalLayout: React.FC = () => {
const urlTheme = searchParams.getAll('theme') as ThemeName[];
const finalTheme = getFinalTheme(urlTheme);
const _direction = searchParams.get('direction') as DirectionType;
const _isDark = isThemeDark(finalTheme, systemTheme);
const storedBannerVisible = bannerLastTime && dayjs().diff(dayjs(bannerLastTime), 'day') >= 1;
@@ -199,7 +191,6 @@ const GlobalLayout: React.FC = () => {
setSiteState({
theme: finalTheme,
isDark: _isDark,
direction: _direction === 'rtl' ? 'rtl' : 'ltr',
bannerVisible: hasBannerContent && (bannerLastTime ? !!storedBannerVisible : true),
});
@@ -224,51 +215,22 @@ const GlobalLayout: React.FC = () => {
direction,
updateSiteConfig,
theme: theme!,
isDark: isDark!,
isMobile: isMobile!,
bannerVisible,
dynamicTheme,
}),
[isMobile, direction, updateSiteConfig, theme, isDark, bannerVisible, dynamicTheme],
[isMobile, direction, updateSiteConfig, theme, bannerVisible],
);
const [themeConfig, componentsClassNames] = React.useMemo<
[ThemeConfig, SimpleComponentClassNames]
>(() => {
let mergedTheme = theme;
const {
algorithm: dynamicAlgorithm,
token: dynamicToken,
...rawComponentsClassNames
} = dynamicTheme || {};
if (dynamicAlgorithm) {
mergedTheme = mergedTheme.filter((c) => c !== 'dark' && c !== 'light');
mergedTheme.push(dynamicAlgorithm);
}
// Convert rawComponentsClassNames to nextComponentsClassNames
const nextComponentsClassNames: any = {};
Object.keys(rawComponentsClassNames).forEach((key) => {
nextComponentsClassNames[key] = {
classNames: (rawComponentsClassNames as any)[key],
};
});
return [
{
algorithm: getAlgorithm(mergedTheme, systemTheme),
token: {
motion: !theme.includes('motion-off'),
...dynamicToken,
// colorBgContainer: 'rgba(255,0,0,0.1)',
},
zeroRuntime: process.env.NODE_ENV === 'production',
},
nextComponentsClassNames,
];
}, [theme, dynamicTheme, systemTheme]);
const themeConfig = React.useMemo<ThemeConfig>(() => {
// 算法优先级auto 时用系统主题算法
const themeForAlgorithm = theme;
return {
algorithm: getAlgorithm(themeForAlgorithm, systemTheme),
token: { motion: !theme.includes('motion-off') },
cssVar: useCssVar,
hashed: !useCssVar,
};
}, [theme, useCssVar, systemTheme]);
const styleCache = React.useMemo(() => createCache(), []);
@@ -307,18 +269,17 @@ const GlobalLayout: React.FC = () => {
));
return (
<DarkContext value={isDark}>
<DarkContext
value={theme.includes('dark') || (theme.includes('auto') && systemTheme === 'dark')}
>
<StyleProvider
cache={styleCache}
layer
linters={[legacyNotSelectorLinter, parentSelectorLinter, NaNLinter]}
>
<SiteContext value={siteContextValue}>
<SiteThemeProvider theme={themeConfig}>
<HappyProvider disabled={!theme.includes('happy-work')}>
<ConfigProvider {...componentsClassNames}>
<App>{outlet}</App>
</ConfigProvider>
<App>{outlet}</App>
</HappyProvider>
</SiteThemeProvider>
</SiteContext>

View File

@@ -1,16 +1,15 @@
import * as React from 'react';
import { Tabs } from 'antd';
import { createStyles } from 'antd-style';
import { clsx } from 'clsx';
import classNames from 'classnames';
import throttle from 'lodash/throttle';
import scrollTo from '../../../../components/_util/scrollTo';
const listenerEvents: (keyof WindowEventMap)[] = ['scroll', 'resize'];
const useStyle = createStyles(({ cssVar, token, css }) => {
const { antCls } = token;
const { boxShadowSecondary } = cssVar;
const useStyle = createStyles(({ token, css }) => {
const { boxShadowSecondary, antCls } = token;
return {
affixTabs: css`
@@ -25,8 +24,8 @@ const useStyle = createStyles(({ cssVar, token, css }) => {
transform: translate3d(0, -100%, 0);
opacity: 0;
transition:
opacity ${cssVar.motionDurationSlow},
transform ${cssVar.motionDurationSlow};
opacity ${token.motionDurationSlow},
transform ${token.motionDurationSlow};
${antCls}-tabs {
max-width: 1208px;
@@ -120,7 +119,7 @@ const AffixTabs: React.FC = () => {
}, [onSyncAffix]);
return (
<div className={clsx(affixTabs, fixedId && affixTabsFixed)} ref={containerRef}>
<div className={classNames(affixTabs, fixedId && affixTabsFixed)} ref={containerRef}>
<Tabs
activeKey={fixedId}
centered

View File

@@ -16,7 +16,7 @@ const resourcePadding = 40;
const articleMaxWidth = 1208;
const resourcePaddingXS = 24;
const useStyle = createStyles(({ cssVar, token, css }, isDark: boolean) => {
const useStyle = createStyles(({ token, css }, isDark: boolean) => {
return {
resourcePage: css`
footer {
@@ -42,8 +42,8 @@ const useStyle = createStyles(({ cssVar, token, css }, isDark: boolean) => {
padding: 0 ${resourcePaddingXS}px;
}
${token.antCls}-col {
padding-top: ${cssVar.padding} !important;
padding-bottom: ${cssVar.padding} !important;
padding-top: ${token.padding}px !important;
padding-bottom: ${token.padding}px !important;
}
}
}
@@ -68,7 +68,7 @@ const useStyle = createStyles(({ cssVar, token, css }, isDark: boolean) => {
max-width: ${articleMaxWidth}px;
margin: 0 auto 56px;
font-weight: 200;
font-size: ${cssVar.fontSizeLG};
font-size: ${token.fontSizeLG}px;
line-height: 24px;
}

View File

@@ -1,22 +1,24 @@
import type { PropsWithChildren } from 'react';
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { useSearchParams } from 'dumi';
import { createStyles } from 'antd-style';
import CommonHelmet from '../../common/CommonHelmet';
import Content from '../../slots/Content';
import Sidebar from '../../slots/Sidebar';
import { useSearchParams } from 'dumi';
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ css, token }) => ({
main: css`
display: flex;
margin-top: ${cssVar.marginXL};
margin-top: ${token.contentMarginTop}px;
`,
}));
const SidebarLayout: React.FC<PropsWithChildren> = ({ children }) => {
const [searchParams] = useSearchParams();
const hideLayout = searchParams.get('layout') === 'false';
const { styles } = useStyle();
return (
<main className={styles.main}>
<CommonHelmet />

View File

@@ -7,7 +7,6 @@
"app.theme.switch.motion.on": "Motion On",
"app.theme.switch.motion.off": "Motion Off",
"app.theme.switch.happy-work": "Happy Work Effect",
"app.theme.switch.market": "AI Theme Generator",
"app.header.search": "Search...",
"app.header.menu.documentation": "Docs",
"app.header.menu.more": "More",
@@ -25,6 +24,8 @@
"app.component.examples.collapse": "Collapse all code",
"app.component.examples.visible": "Expand debug examples",
"app.component.examples.hide": "Collapse debug examples",
"app.component.examples.openDemoNotReact18": "Open Demo with React < 18",
"app.component.examples.openDemoWithReact18": "Open Demo with React 18",
"app.demo.debug": "Debug only, won't display at online",
"app.demo.copy": "Copy code",
"app.demo.copied": "Copied!",
@@ -36,8 +37,7 @@
"app.demo.stackblitz": "Open in Stackblitz",
"app.demo.codeblock": "Open in Hitu (This feature is only available in the internal network environment)",
"app.demo.separate": "Open in a new window",
"app.demo.online": "Official demo",
"app.demo.previousVersion": "Previous version",
"app.demo.online": "Online Address",
"app.home.introduce": "A design system for enterprise-level products. Create an efficient and enjoyable work experience.",
"app.home.pr-welcome": "💡 It is an alpha version and still in progress. Contribution from community is welcome!",
"app.home.recommend": "Recommended",

View File

@@ -7,7 +7,6 @@
"app.theme.switch.motion.on": "动画开启",
"app.theme.switch.motion.off": "动画关闭",
"app.theme.switch.happy-work": "快乐工作特效",
"app.theme.switch.market": "AI 生成主题",
"app.header.search": "全文本搜索...",
"app.header.menu.documentation": "文档",
"app.header.menu.more": "更多",
@@ -25,6 +24,8 @@
"app.component.examples.collapse": "收起全部代码",
"app.component.examples.visible": "显示调试专用演示",
"app.component.examples.hide": "隐藏调试专用演示",
"app.component.examples.openDemoNotReact18": "使用 React 18 以下版本打开 Demo",
"app.component.examples.openDemoWithReact18": "使用 React 18 打开 Demo",
"app.demo.debug": "此演示仅供调试,线上不会展示",
"app.demo.copy": "复制代码",
"app.demo.copied": "复制成功",
@@ -36,8 +37,7 @@
"app.demo.stackblitz": "在 Stackblitz 中打开",
"app.demo.codeblock": "在海兔中打开(此功能仅在内网环境可用)",
"app.demo.separate": "在新窗口打开",
"app.demo.online": "官网示例",
"app.demo.previousVersion": "历史版本",
"app.demo.online": "线上地址",
"app.home.introduce": "企业级产品设计体系,创造高效愉悦的工作体验",
"app.home.pr-welcome": "💡 当前为 alpha 版本,仍在开发中。欢迎社区一起共建,让 Ant Design 变得更好!",
"app.home.recommend": "精彩推荐",

View File

@@ -231,12 +231,6 @@ const RoutesPlugin = async (api: IApi) => {
return memo;
});
if (process.env.NODE_ENV === 'production') {
api.addEntryImportsAhead(() => ({
source: path.join(api.paths.cwd, 'components', 'style', 'antd.css'),
}));
}
};
export default RoutesPlugin;

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { RightOutlined, YuqueOutlined, ZhihuOutlined } from '@ant-design/icons';
import { Button, Card, Divider } from 'antd';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import useLocale from '../../../hooks/useLocale';
import JuejinIcon from '../../../theme/icons/JuejinIcon';
@@ -10,18 +10,18 @@ import JuejinIcon from '../../../theme/icons/JuejinIcon';
const ANTD_IMG_URL =
'https://picx.zhimg.com/v2-3b2bca09c2771e7a82a81562e806be4d.jpg?source=d16d100b';
const styles = createStaticStyles(({ cssVar, css }) => ({
const useStyle = createStyles(({ token, css }) => ({
card: css`
width: 100%;
margin: calc(${cssVar.marginMD} * 2) 0;
transition: all ${cssVar.motionDurationMid};
background-color: ${cssVar.colorFillQuaternary};
margin: ${token.marginMD * 2}px 0;
transition: all ${token.motionDurationMid};
background-color: ${token.colorFillQuaternary};
`,
bigTitle: css`
color: #121212;
font-size: ${cssVar.fontSizeLG};
margin-bottom: ${cssVar.marginLG};
font-weight: ${cssVar.fontWeightStrong};
font-size: ${token.fontSizeLG}px;
margin-bottom: ${token.marginLG}px;
font-weight: ${token.fontWeightStrong};
`,
cardBody: css`
display: flex;
@@ -35,14 +35,14 @@ const styles = createStaticStyles(({ cssVar, css }) => ({
img {
width: 200px;
overflow: hidden;
margin-inline-end: ${cssVar.marginLG};
border-radius: ${cssVar.borderRadiusLG};
margin-inline-end: ${token.marginLG}px;
border-radius: ${token.borderRadiusLG}px;
}
`,
title: css`
color: #444;
font-size: ${cssVar.fontSizeLG};
font-weight: ${cssVar.fontWeightStrong};
font-size: ${token.fontSizeLG}px;
font-weight: ${token.fontWeightStrong};
user-select: none;
`,
subTitle: css`
@@ -50,9 +50,9 @@ const styles = createStaticStyles(({ cssVar, css }) => ({
justify-content: flex-start;
align-items: center;
color: #646464;
font-size: ${cssVar.fontSize};
font-size: ${token.fontSize}px;
font-weight: 400;
margin-top: ${cssVar.marginXS};
margin-top: ${token.marginXS}px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -73,8 +73,8 @@ const styles = createStaticStyles(({ cssVar, css }) => ({
`,
arrowIcon: css`
color: #8a8f8d;
margin: 0 ${cssVar.marginXS};
font-size: ${cssVar.fontSizeSM};
margin: 0 ${token.marginXS}px;
font-size: ${token.fontSizeSM}px;
`,
zlBtn: css`
padding: 0;
@@ -113,17 +113,19 @@ interface Props {
const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
const [locale] = useLocale(locales);
const {
card,
bigTitle,
cardBody,
leftCard,
title,
subTitle,
logo,
arrowIcon,
zlBtn,
discussLogo,
} = styles;
styles: {
card,
bigTitle,
cardBody,
leftCard,
title,
subTitle,
logo,
arrowIcon,
zlBtn,
discussLogo,
},
} = useStyle();
if (!zhihuLink && !yuqueLink && !juejinLink) {
return null;
}
@@ -139,7 +141,7 @@ const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
<div>
<p className={title}>Ant Design</p>
<div className={subTitle}>
<ZhihuOutlined className={clsx(logo, 'zhihu-logo')} />
<ZhihuOutlined className={classNames(logo, 'zhihu-logo')} />
<RightOutlined className={arrowIcon} />
<Button
target="_blank"
@@ -173,7 +175,7 @@ const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
<div>
<p className={title}>Ant Design</p>
<div className={subTitle}>
<YuqueOutlined className={clsx(logo, 'yuque-logo')} />
<YuqueOutlined className={classNames(logo, 'yuque-logo')} />
<RightOutlined className={arrowIcon} />
<Button
target="_blank"
@@ -207,7 +209,7 @@ const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
<div>
<p className={title}>Ant Design</p>
<div className={subTitle}>
<JuejinIcon className={clsx(logo, 'juejin-logo')} />
<JuejinIcon className={classNames(logo, 'juejin-logo')} />
<RightOutlined className={arrowIcon} />
<Button
target="_blank"

View File

@@ -1,20 +1,20 @@
import React, { Suspense } from 'react';
import ContributorsList from '@qixian.cs/github-contributors-list';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import { useIntl } from 'dumi';
import SiteContext from '../SiteContext';
import ContributorAvatar from './ContributorAvatar';
const styles = createStaticStyles(({ cssVar, css }) => ({
const useStyle = createStyles(({ token, css }) => ({
listMobile: css`
margin: 1em 0 !important;
`,
title: css`
font-size: ${cssVar.fontSizeSM};
font-size: ${token.fontSizeSM}px;
opacity: 0.5;
margin-bottom: ${cssVar.marginXS};
margin-bottom: ${token.marginXS}px;
`,
list: css`
display: flex;
@@ -22,8 +22,8 @@ const styles = createStaticStyles(({ cssVar, css }) => ({
clear: both;
li {
height: 24px;
transition: all ${cssVar.motionDurationSlow};
margin-inline-end: calc(-1 * ${cssVar.marginXS});
transition: all ${token.motionDurationSlow};
margin-inline-end: -${token.marginXS}px;
}
&:hover {
li {
@@ -38,16 +38,11 @@ interface ContributorsProps {
}
// 这些机器人账号不需要展示
const blockList = [
'github-actions',
'copilot',
'renovate',
'dependabot',
'gemini-code-assist[bot]',
];
const blockList = ['github-actions', 'copilot', 'renovate', 'dependabot'];
const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
const { formatMessage } = useIntl();
const { styles } = useStyle();
const { isMobile } = React.use(SiteContext);
if (!filename) {
@@ -55,7 +50,7 @@ const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
}
return (
<div className={clsx({ [styles.listMobile]: isMobile })}>
<div className={classNames({ [styles.listMobile]: isMobile })}>
<div className={styles.title}>{formatMessage({ id: 'app.content.contributors' })}</div>
<ContributorsList
cache

View File

@@ -2,10 +2,10 @@ import React from 'react';
import { Anchor } from 'antd';
import { createStyles, useTheme } from 'antd-style';
import type { AnchorLinkItemProps } from 'antd/es/anchor/Anchor';
import { clsx } from 'clsx';
import classNames from 'classnames';
import { useRouteMeta, useTabMeta } from 'dumi';
export const useStyle = createStyles(({ cssVar, token, css }) => {
export const useStyle = createStyles(({ token, css }) => {
const { antCls } = token;
return {
anchorToc: css`
@@ -13,37 +13,37 @@ export const useStyle = createStyles(({ cssVar, token, css }) => {
scrollbar-gutter: stable;
${antCls}-anchor {
${antCls}-anchor-link-title {
font-size: ${cssVar.fontSizeSM};
font-size: ${token.fontSizeSM}px;
}
}
`,
tocWrapper: css`
position: fixed;
top: calc(${token.headerHeight}px + ${cssVar.marginXL} - 4px);
top: ${token.headerHeight + token.contentMarginTop - 4}px;
inset-inline-end: 0;
width: 148px;
padding: 0;
border-radius: ${cssVar.borderRadius};
border-radius: ${token.borderRadius}px;
box-sizing: border-box;
margin-inline-end: calc(8px - 100vw + 100%);
z-index: 10;
.toc-debug {
color: ${cssVar.purple6};
color: ${token.purple6};
&:hover {
color: ${cssVar.purple5};
color: ${token.purple5};
}
}
> div {
box-sizing: border-box;
width: 100%;
max-height: calc(100vh - ${token.headerHeight}px - ${cssVar.marginXL} - 24px) !important;
max-height: calc(100vh - ${token.headerHeight + token.contentMarginTop + 24}px) !important;
margin: auto;
overflow: auto;
padding: ${cssVar.paddingXXS};
padding: ${token.paddingXXS}px;
backdrop-filter: blur(8px);
}
@media only screen and (max-width: ${cssVar.screenLG}) {
@media only screen and (max-width: ${token.screenLG}px) {
display: none;
}
`,
@@ -51,9 +51,9 @@ export const useStyle = createStyles(({ cssVar, token, css }) => {
padding-inline: 48px 164px;
padding-block: 0 32px;
@media only screen and (max-width: ${cssVar.screenLG}) {
@media only screen and (max-width: ${token.screenLG}px) {
& {
padding: 0 calc(${cssVar.paddingLG} * 2);
padding: 0 ${token.paddingLG * 2}px;
}
}
`,
@@ -87,7 +87,7 @@ const DocAnchor: React.FC<DocAnchorProps> = ({ showDebug, debugDemos = [] }) =>
key: child.id,
href: `#${child.id}`,
title: (
<span className={clsx({ 'toc-debug': debugDemos.includes(child.id) })}>
<span className={classNames({ 'toc-debug': debugDemos.includes(child.id) })}>
{child?.title}
</span>
),

View File

@@ -1,6 +1,6 @@
import React, { Suspense, useLayoutEffect, useMemo, useState } from 'react';
import { Col, Flex, FloatButton, Skeleton, Space, Typography } from 'antd';
import { clsx } from 'clsx';
import classNames from 'classnames';
import { FormattedMessage, useRouteMeta } from 'dumi';
import useLayoutState from '../../../hooks/useLayoutState';
@@ -22,12 +22,7 @@ const AvatarPlaceholder: React.FC<{ num?: number }> = ({ num = 6 }) =>
<Skeleton.Avatar size="small" active key={i} style={{ marginInlineStart: i === 0 ? 0 : -8 }} />
));
export interface ContentProps {
children?: React.ReactNode;
className?: string;
}
const Content: React.FC<ContentProps> = ({ children, className }) => {
const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
const meta = useRouteMeta();
const { pathname, hash } = useLocation();
const { direction } = React.use(SiteContext);
@@ -56,9 +51,9 @@ const Content: React.FC<ContentProps> = ({ children, className }) => {
return (
<DemoContext value={contextValue}>
<Col xxl={20} xl={19} lg={18} md={18} sm={24} xs={24} className={className}>
<Col xxl={20} xl={19} lg={18} md={18} sm={24} xs={24}>
<DocAnchor showDebug={showDebug} debugDemos={debugDemos} />
<article className={clsx(styles.articleWrapper, { rtl: isRTL })}>
<article className={classNames(styles.articleWrapper, { rtl: isRTL })}>
{meta.frontmatter?.title ? (
<Flex justify="space-between">
<Typography.Title style={{ fontSize: 32, position: 'relative' }}>

View File

@@ -4,7 +4,7 @@ import { CodeOutlined, SkinOutlined } from '@ant-design/icons';
import { Tabs } from 'antd';
import { useRouteMeta } from 'dumi';
import type { IContentTabsProps } from 'dumi/theme-default/slots/ContentTabs';
import type { TabsProps } from '@rc-component/tabs';
import type { TabsProps } from 'rc-tabs';
import useLocale from '../../../hooks/useLocale';

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { removeCSS, updateCSS } from '@rc-component/util/lib/Dom/dynamicCSS';
import { createStaticStyles } from 'antd-style';
import { createStyles } from 'antd-style';
import { removeCSS, updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
import useLocale from '../../../hooks/useLocale';
@@ -20,7 +20,7 @@ const locales = {
},
};
const styles = createStaticStyles(({ css, cssVar }) => ({
const useStyle = createStyles(({ css, token }) => ({
container: css`
position: fixed;
inset-inline-start: 0;
@@ -28,22 +28,22 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
top: 0;
bottom: 0;
z-index: 99999999;
background-color: ${cssVar.colorTextSecondary};
background-color: ${token.colorTextSecondary};
display: flex;
justify-content: center;
align-items: center;
`,
alertBox: css`
border: 1px solid ${cssVar.colorWarningBorder};
background-color: ${cssVar.colorWarningBg};
color: ${cssVar.colorTextHeading};
padding: ${cssVar.paddingXS} ${cssVar.paddingSM};
border-radius: ${cssVar.borderRadiusLG};
border: 1px solid ${token.colorWarningBorder};
background-color: ${token.colorWarningBg};
color: ${token.colorTextHeading};
padding: ${token.paddingXS}px ${token.paddingSM}px;
border-radius: ${token.borderRadiusLG}px;
z-index: 9999999999;
line-height: 22px;
width: 520px;
a {
color: ${cssVar.colorPrimary};
color: ${token.colorPrimary};
text-decoration-line: none;
}
`,
@@ -80,6 +80,8 @@ const InfoNewVersion: React.FC = () => {
removeCSS(whereCls);
}, []);
const { styles } = useStyle();
if (supportWhere) {
return null;
}

View File

@@ -34,7 +34,7 @@ const locales = {
},
};
const useStyle = createStyles(({ cssVar, token, css }, isMobile: boolean) => {
const useStyle = createStyles(({ token, css }, isMobile: boolean) => {
const background = new FastColor(getAlphaColor('#f0f3fa', '#fff'))
.onBackground(token.colorBgContainer)
.toHexString();
@@ -45,7 +45,7 @@ const useStyle = createStyles(({ cssVar, token, css }, isMobile: boolean) => {
footer: css`
background: ${background};
color: ${cssVar.colorTextSecondary};
color: ${token.colorTextSecondary};
box-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
* {
@@ -54,7 +54,7 @@ const useStyle = createStyles(({ cssVar, token, css }, isMobile: boolean) => {
h2,
a {
color: ${cssVar.colorText};
color: ${token.colorText};
}
.rc-footer-column {
margin-bottom: ${isMobile ? 60 : 0}px;
@@ -68,12 +68,12 @@ const useStyle = createStyles(({ cssVar, token, css }, isMobile: boolean) => {
.rc-footer-container {
max-width: 1208px;
margin-inline: auto;
padding-inline: ${cssVar.marginXXL};
padding-inline: ${token.marginXXL}px;
}
.rc-footer-bottom {
box-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
.rc-footer-bottom-container {
font-size: ${cssVar.fontSize};
font-size: ${token.fontSize}px;
}
}
`,

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