mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-09 19:09:21 +08:00
Compare commits
10 Commits
guidelines
...
5.29.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dd5a234de | ||
|
|
2a6d0b837e | ||
|
|
081e9a6311 | ||
|
|
a811b7a702 | ||
|
|
34b32bb389 | ||
|
|
2a823ea79c | ||
|
|
6339b669e7 | ||
|
|
2e771318d0 | ||
|
|
bd10127ad0 | ||
|
|
5ebd3cb42f |
@@ -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'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)',
|
||||
},
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)'};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable compat/compat */
|
||||
import { css } from 'antd-style';
|
||||
import useSWR from 'swr';
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
|
||||
@@ -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),
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 && {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
`,
|
||||
}));
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
@@ -20,7 +20,7 @@ const locales = {
|
||||
},
|
||||
};
|
||||
|
||||
const BezierVisualizer: React.FC<BezierVisualizerProps> = (props) => {
|
||||
const BezierVisualizer = (props: BezierVisualizerProps) => {
|
||||
const { value } = props;
|
||||
const [locale] = useLocale(locales);
|
||||
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 />
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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!
|
||||
@@ -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;
|
||||
@@ -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)** 也同步发布,欢迎探索!
|
||||
@@ -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,
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -26,7 +26,6 @@ const GlobalStyle: React.FC = () => {
|
||||
.markdown img {
|
||||
max-width: calc(100% - 32px);
|
||||
max-height: 100%;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.markdown > a > img,
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "精彩推荐",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
|
||||
@@ -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' }}>
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user