mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-18 15:22:28 +08:00
Compare commits
17 Commits
6.3.0
...
docs/theme
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aadae9bf6 | ||
|
|
7868764b90 | ||
|
|
bf8822b817 | ||
|
|
276a8921cf | ||
|
|
fde6fa9795 | ||
|
|
3dbba0e639 | ||
|
|
23e2c4a472 | ||
|
|
73c6168e3f | ||
|
|
87f59266e5 | ||
|
|
caf62f4e56 | ||
|
|
8f95529432 | ||
|
|
5a07a38c4b | ||
|
|
cd9e9aecf5 | ||
|
|
bf370c4af5 | ||
|
|
79eb03d314 | ||
|
|
0cfcd0873a | ||
|
|
2ab7472d97 |
@@ -46,6 +46,8 @@ export interface GroupProps {
|
||||
decoration?: React.ReactNode;
|
||||
/** 预加载的背景图片列表 */
|
||||
backgroundPrefetchList?: string[];
|
||||
/** 标题右侧的操作按钮 */
|
||||
extra?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Group: React.FC<React.PropsWithChildren<GroupProps>> = (props) => {
|
||||
@@ -59,6 +61,7 @@ const Group: React.FC<React.PropsWithChildren<GroupProps>> = (props) => {
|
||||
background,
|
||||
collapse,
|
||||
backgroundPrefetchList,
|
||||
extra,
|
||||
} = props;
|
||||
|
||||
// 预加载背景图片
|
||||
@@ -87,18 +90,29 @@ const Group: React.FC<React.PropsWithChildren<GroupProps>> = (props) => {
|
||||
<div className={styles.container}>{decoration}</div>
|
||||
<GroupMaskLayer style={{ paddingBlock: token.marginFarSM }}>
|
||||
<div className={styles.typographyWrapper}>
|
||||
<Typography.Title
|
||||
id={id}
|
||||
level={1}
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 900,
|
||||
color: titleColor,
|
||||
// Special for the title
|
||||
fontSize: isMobile ? token.fontSizeHeading2 : token.fontSizeHeading1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: token.paddingXS,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography.Title>
|
||||
<Typography.Title
|
||||
id={id}
|
||||
level={1}
|
||||
style={{
|
||||
fontWeight: 900,
|
||||
color: titleColor,
|
||||
margin: 0,
|
||||
// Special for the title
|
||||
fontSize: isMobile ? token.fontSizeHeading2 : token.fontSizeHeading1,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography.Title>
|
||||
{extra}
|
||||
</div>
|
||||
<Typography.Paragraph
|
||||
style={{
|
||||
color: titleColor,
|
||||
|
||||
@@ -4,15 +4,16 @@ import {
|
||||
Alert,
|
||||
App,
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
ColorPicker,
|
||||
ConfigProvider,
|
||||
DatePicker,
|
||||
Dropdown,
|
||||
Flex,
|
||||
Modal,
|
||||
Progress,
|
||||
Radio,
|
||||
Segmented,
|
||||
Select,
|
||||
Slider,
|
||||
Space,
|
||||
@@ -26,6 +27,7 @@ import clsx from 'clsx';
|
||||
import useLocale from '../../../../hooks/useLocale';
|
||||
|
||||
const { _InternalPanelDoNotUseOrYouWillBeFired: ModalPanel } = Modal;
|
||||
const { Group: RadioButtonGroup, Button: RadioButton } = Radio;
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
@@ -48,6 +50,9 @@ const locales = {
|
||||
icon: '图标按钮',
|
||||
hello: '你好,Ant Design!',
|
||||
release: 'Ant Design 6.0 正式发布!',
|
||||
segmentedDaily: '每日',
|
||||
segmentedWeekly: '每周',
|
||||
segmentedMonthly: '每月',
|
||||
},
|
||||
en: {
|
||||
range: 'Set Range',
|
||||
@@ -69,6 +74,9 @@ const locales = {
|
||||
icon: 'Icon',
|
||||
hello: 'Hello, Ant Design!',
|
||||
release: 'Ant Design 6.0 is released!',
|
||||
segmentedDaily: 'Daily',
|
||||
segmentedWeekly: 'Weekly',
|
||||
segmentedMonthly: 'Monthly',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -95,7 +103,7 @@ const ComponentsBlock: React.FC<ComponentsBlockProps> = (props) => {
|
||||
|
||||
return (
|
||||
<ConfigProvider {...config}>
|
||||
<Card className={clsx(containerClassName, styles.container)}>
|
||||
<div className={clsx(containerClassName, styles.container)}>
|
||||
<App>
|
||||
<Flex vertical gap="middle" style={style} className={className}>
|
||||
<ModalPanel title="Ant Design" width="100%">
|
||||
@@ -120,13 +128,32 @@ const ComponentsBlock: React.FC<ComponentsBlockProps> = (props) => {
|
||||
</Space.Compact>
|
||||
</div>
|
||||
|
||||
<ColorPicker style={{ flex: 'none' }} />
|
||||
<ColorPicker showText defaultValue="#1677ff" style={{ flex: 'none' }} />
|
||||
|
||||
<Select
|
||||
style={{ flex: 'auto' }}
|
||||
mode="multiple"
|
||||
maxTagCount="responsive"
|
||||
defaultValue={[{ value: 'apple' }, { value: 'banana' }]}
|
||||
defaultValue={['apple', 'banana']}
|
||||
options={[
|
||||
{ value: 'apple', label: locale.apple },
|
||||
{ value: 'banana', label: locale.banana },
|
||||
{ value: 'orange', label: locale.orange },
|
||||
{ value: 'watermelon', label: locale.watermelon },
|
||||
]}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
{/* Filled variants */}
|
||||
<Flex gap="middle">
|
||||
<DatePicker variant="filled" />
|
||||
|
||||
<Select
|
||||
variant="filled"
|
||||
style={{ flex: 'auto' }}
|
||||
mode="multiple"
|
||||
maxTagCount="responsive"
|
||||
defaultValue={['apple', 'banana']}
|
||||
options={[
|
||||
{ value: 'apple', label: locale.apple },
|
||||
{ value: 'banana', label: locale.banana },
|
||||
@@ -147,20 +174,7 @@ const ComponentsBlock: React.FC<ComponentsBlockProps> = (props) => {
|
||||
]}
|
||||
/>
|
||||
{/* Line */}
|
||||
<Slider
|
||||
style={{ marginInline: 20 }}
|
||||
range
|
||||
marks={{
|
||||
0: '0°C',
|
||||
26: '26°C',
|
||||
37: '37°C',
|
||||
100: {
|
||||
style: { color: '#f50' },
|
||||
label: <strong>100°C</strong>,
|
||||
},
|
||||
}}
|
||||
defaultValue={[26, 37]}
|
||||
/>
|
||||
<Slider defaultValue={50} />
|
||||
{/* Line */}
|
||||
<Flex gap="middle">
|
||||
<Button type="primary" className={styles.flexAuto}>
|
||||
@@ -188,9 +202,20 @@ const ComponentsBlock: React.FC<ComponentsBlockProps> = (props) => {
|
||||
/>
|
||||
<Radio.Group defaultValue={locale.apple} options={[locale.apple, locale.banana]} />
|
||||
</Flex>
|
||||
<Flex gap="middle" align="center">
|
||||
<RadioButtonGroup defaultValue="a">
|
||||
<RadioButton value="a">A</RadioButton>
|
||||
<RadioButton value="b">B</RadioButton>
|
||||
<RadioButton value="c">C</RadioButton>
|
||||
</RadioButtonGroup>
|
||||
<Segmented
|
||||
defaultValue={locale.segmentedDaily}
|
||||
options={[locale.segmentedDaily, locale.segmentedWeekly, locale.segmentedMonthly]}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</App>
|
||||
</Card>
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,11 +13,15 @@ const locales = {
|
||||
cn: {
|
||||
themeTitle: '定制主题,随心所欲',
|
||||
themeDesc: '开放样式算法与语义化结构,让你与 AI 一起轻松定制主题',
|
||||
aiGenerate: 'AI 生成主题',
|
||||
aiGenerateDesc: '用一句话描述你想要的风格',
|
||||
},
|
||||
en: {
|
||||
themeTitle: 'Flexible theme customization',
|
||||
themeDesc:
|
||||
'Open style algorithms and semantic structures make it easy for you and AI to customize themes',
|
||||
aiGenerate: 'AI Generate Theme',
|
||||
aiGenerateDesc: 'Describe your desired style',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,7 +44,7 @@ const useStyles = createStyles(({ css, cssVar }) => ({
|
||||
listStyleType: 'none',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: cssVar.paddingMD,
|
||||
gap: cssVar.paddingSM,
|
||||
}),
|
||||
listItem: css({
|
||||
margin: 0,
|
||||
@@ -53,7 +57,7 @@ const useStyles = createStyles(({ css, cssVar }) => ({
|
||||
borderColor: 'transparent',
|
||||
transition: `all ${cssVar.motionDurationMid} ${cssVar.motionEaseInOut}`,
|
||||
|
||||
'&:hover:not(.active)': {
|
||||
'&:hover:not(.active):not(.ai-generate-item)': {
|
||||
borderColor: cssVar.colorPrimaryBorder,
|
||||
backgroundColor: cssVar.colorPrimaryBg,
|
||||
cursor: 'pointer',
|
||||
@@ -82,6 +86,39 @@ const useStyles = createStyles(({ css, cssVar }) => ({
|
||||
},
|
||||
}),
|
||||
|
||||
// AI Generate Item
|
||||
aiGenerateItem: css({
|
||||
borderStyle: 'dashed',
|
||||
opacity: 0.7,
|
||||
cursor: 'pointer',
|
||||
color: cssVar.colorTextSecondary,
|
||||
paddingInline: cssVar.padding,
|
||||
|
||||
'&:hover': {
|
||||
borderColor: cssVar.colorPrimary,
|
||||
color: cssVar.colorPrimary,
|
||||
opacity: 1,
|
||||
},
|
||||
}),
|
||||
|
||||
aiGenerateContent: css({
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
}),
|
||||
|
||||
aiGenerateIcon: css({
|
||||
fontSize: 14,
|
||||
marginRight: 6,
|
||||
opacity: 0.6,
|
||||
}),
|
||||
|
||||
aiGenerateDesc: css({
|
||||
fontSize: cssVar.fontSizeSM,
|
||||
opacity: 0.5,
|
||||
marginTop: 2,
|
||||
fontWeight: 400,
|
||||
}),
|
||||
|
||||
// Components
|
||||
componentsBlockContainer: css({
|
||||
flex: 'auto',
|
||||
@@ -98,7 +135,12 @@ const useStyles = createStyles(({ css, cssVar }) => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
export default function ThemePreview() {
|
||||
export interface ThemePreviewProps {
|
||||
onOpenPromptDrawer?: () => void;
|
||||
}
|
||||
|
||||
export default function ThemePreview(props: ThemePreviewProps = {}) {
|
||||
const { onOpenPromptDrawer } = props;
|
||||
const [locale] = useLocale(locales);
|
||||
const { styles } = useStyles();
|
||||
const isDark = React.use(DarkContext);
|
||||
@@ -111,10 +153,7 @@ export default function ThemePreview() {
|
||||
const defaultThemeName = isDark ? 'dark' : 'light';
|
||||
|
||||
const targetTheme =
|
||||
process.env.NODE_ENV !== 'production'
|
||||
? previewThemes[previewThemes.length - 1].name
|
||||
: previewThemes.find((theme) => theme.key === defaultThemeName)?.name ||
|
||||
previewThemes[0].name;
|
||||
previewThemes.find((theme) => theme.key === defaultThemeName)?.name || previewThemes[0].name;
|
||||
|
||||
setActiveName(targetTheme);
|
||||
}, [isDark]);
|
||||
@@ -148,24 +187,50 @@ export default function ThemePreview() {
|
||||
backgroundPrefetchList={backgroundPrefetchList}
|
||||
>
|
||||
<Flex className={styles.container} gap="large">
|
||||
<div className={styles.list} role="tablist" aria-label="Theme selection">
|
||||
{previewThemes.map((theme) => (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<div className={styles.list} role="tablist" aria-label="Theme selection">
|
||||
{previewThemes.map((theme) => (
|
||||
<div
|
||||
className={clsx(
|
||||
styles.listItem,
|
||||
activeName === theme.name && 'active',
|
||||
activeTheme?.bgImgDark && 'dark',
|
||||
)}
|
||||
key={theme.name}
|
||||
role="tab"
|
||||
tabIndex={activeName === theme.name ? 0 : -1}
|
||||
aria-selected={activeName === theme.name}
|
||||
onClick={() => handleThemeClick(theme.name)}
|
||||
onKeyDown={(event) => handleKeyDown(event, theme.name)}
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
{theme.name}
|
||||
</div>
|
||||
))}
|
||||
{/* AI 生成主题 - 最后一个选项 */}
|
||||
<div
|
||||
className={clsx(
|
||||
styles.listItem,
|
||||
activeName === theme.name && 'active',
|
||||
activeTheme?.bgImgDark && 'dark',
|
||||
)}
|
||||
key={theme.name}
|
||||
className={clsx(styles.listItem, styles.aiGenerateItem, 'ai-generate-item')}
|
||||
role="tab"
|
||||
tabIndex={activeName === theme.name ? 0 : -1}
|
||||
aria-selected={activeName === theme.name}
|
||||
onClick={() => handleThemeClick(theme.name)}
|
||||
onKeyDown={(event) => handleKeyDown(event, theme.name)}
|
||||
tabIndex={0}
|
||||
onClick={onOpenPromptDrawer}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
onOpenPromptDrawer?.();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{theme.name}
|
||||
<div className={styles.aiGenerateContent}>
|
||||
<span className={styles.aiGenerateIcon}>🎨</span>
|
||||
<span>{locale.aiGenerate}</span>
|
||||
</div>
|
||||
<div className={styles.aiGenerateDesc}>{locale.aiGenerateDesc}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<ComponentsBlock
|
||||
key={activeName}
|
||||
|
||||
@@ -8,7 +8,6 @@ import type { UseTheme } from '.';
|
||||
|
||||
const useStyles = createStyles(({ css, cssVar }) => {
|
||||
const glassBorder = {
|
||||
border: `${cssVar.lineWidth} solid rgba(255,255,255,0.3)`,
|
||||
boxShadow: [
|
||||
`${cssVar.boxShadowSecondary}`,
|
||||
`inset 0 0 5px 2px rgba(255, 255, 255, 0.3)`,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import React, { Suspense, useState } from 'react';
|
||||
import { theme } from 'antd';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
|
||||
@@ -8,6 +8,9 @@ import BannerRecommends from './components/BannerRecommends';
|
||||
import Group from './components/Group';
|
||||
import PreviewBanner from './components/PreviewBanner';
|
||||
import ThemePreview from './components/ThemePreview';
|
||||
import PromptDrawer from '../../theme/common/ThemeSwitch/PromptDrawer';
|
||||
import SiteContext from '../../theme/slots/SiteContext';
|
||||
import type { SiteContextProps } from '../../theme/slots/SiteContext';
|
||||
|
||||
const ComponentsList = React.lazy(() => import('./components/ComponentsList'));
|
||||
const DesignFramework = React.lazy(() => import('./components/DesignFramework'));
|
||||
@@ -42,6 +45,16 @@ const Homepage: React.FC = () => {
|
||||
const { token } = theme.useToken();
|
||||
|
||||
const isDark = React.use(DarkContext);
|
||||
const [promptDrawerOpen, setPromptDrawerOpen] = useState(false);
|
||||
const siteContext = React.use(SiteContext);
|
||||
|
||||
const handlePromptDrawerOpen = () => setPromptDrawerOpen(true);
|
||||
const handlePromptDrawerClose = () => setPromptDrawerOpen(false);
|
||||
const handleThemeChange = (themeConfig: SiteContextProps['dynamicTheme']) => {
|
||||
if (siteContext?.updateSiteConfig) {
|
||||
siteContext.updateSiteConfig({ dynamicTheme: themeConfig });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
@@ -49,13 +62,14 @@ const Homepage: React.FC = () => {
|
||||
<BannerRecommends />
|
||||
</PreviewBanner>
|
||||
|
||||
{/* 定制主题 */}
|
||||
{/* <ConfigProvider theme={{ algorithm: theme.defaultAlgorithm }}>
|
||||
<Suspense fallback={null}>
|
||||
<Theme />
|
||||
</Suspense>
|
||||
</ConfigProvider> */}
|
||||
<ThemePreview />
|
||||
<ThemePreview onOpenPromptDrawer={handlePromptDrawerOpen} />
|
||||
|
||||
{/* AI 生成主题抽屉 */}
|
||||
<PromptDrawer
|
||||
open={promptDrawerOpen}
|
||||
onClose={handlePromptDrawerClose}
|
||||
onThemeChange={handleThemeChange}
|
||||
/>
|
||||
|
||||
{/* 组件列表 */}
|
||||
<Group
|
||||
@@ -90,55 +104,6 @@ const Homepage: React.FC = () => {
|
||||
</Group>
|
||||
</section>
|
||||
);
|
||||
|
||||
// return (
|
||||
// <section>
|
||||
// <PreviewBanner>
|
||||
// <BannerRecommends />
|
||||
// </PreviewBanner>
|
||||
|
||||
// <div>
|
||||
// {/* 定制主题 */}
|
||||
// <ConfigProvider theme={{ algorithm: theme.defaultAlgorithm }}>
|
||||
// <Suspense fallback={null}>
|
||||
// <Theme />
|
||||
// </Suspense>
|
||||
// </ConfigProvider>
|
||||
|
||||
// {/* 组件列表 */}
|
||||
// <Group
|
||||
// background={token.colorBgElevated}
|
||||
// collapse
|
||||
// title={locale.assetsTitle}
|
||||
// description={locale.assetsDesc}
|
||||
// id="design"
|
||||
// >
|
||||
// <Suspense fallback={null}>
|
||||
// <ComponentsList />
|
||||
// </Suspense>
|
||||
// </Group>
|
||||
|
||||
// {/* 设计语言 */}
|
||||
// <Group
|
||||
// title={locale.designTitle}
|
||||
// description={locale.designDesc}
|
||||
// background={isDark ? '#393F4A' : '#F5F8FF'}
|
||||
// decoration={
|
||||
// <img
|
||||
// draggable={false}
|
||||
// className={classNames.image}
|
||||
// src="https://gw.alipayobjects.com/zos/bmw-prod/ba37a413-28e6-4be4-b1c5-01be1a0ebb1c.svg"
|
||||
// alt="bg"
|
||||
// />
|
||||
// }
|
||||
// >
|
||||
// <Suspense fallback={null}>
|
||||
// <DesignFramework />
|
||||
// </Suspense>
|
||||
// </Group>
|
||||
// </div>
|
||||
// </section>
|
||||
// );
|
||||
};
|
||||
|
||||
export default Homepage;
|
||||
|
||||
@@ -1,22 +1,46 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { AntDesignOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { Bubble, Sender } from '@ant-design/x';
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
import { Bubble, Prompts, Sender, Welcome } from '@ant-design/x';
|
||||
import type { BubbleItemType } from '@ant-design/x/es/bubble/interface';
|
||||
import type { PromptsItemType } 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 { Button, Divider, Drawer, Flex, Skeleton, Splitter, Typography } from 'antd';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import type { SiteContextProps } from '../../../theme/slots/SiteContext';
|
||||
import SiteContext from '../../../theme/slots/SiteContext';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import ComponentsBlock from '../../../pages/index/components/ThemePreview/ComponentsBlock';
|
||||
import usePromptTheme from './usePromptTheme';
|
||||
import usePromptRecommend from './usePromptRecommend';
|
||||
|
||||
const antdLogoSrc = 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg';
|
||||
|
||||
const THEME_EMOJIS = ['🌅', '🌊', '🌿', '🍂', '🌸', '🌌', '🎨', '⚡', '🔮', '🪐'];
|
||||
|
||||
const getEmojiForTheme = (index: number) => THEME_EMOJIS[index % THEME_EMOJIS.length];
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
title: 'AI 生成主题',
|
||||
finishTips: '生成完成,对话以重新生成。',
|
||||
title: '🎨 AI 生成主题',
|
||||
finishTips: '生成主题完成,已应用',
|
||||
placeholder: '描述你想要的主题风格,如:温暖阳光、清新自然、科技感...',
|
||||
welcomeTitle: 'AI 主题生成器',
|
||||
welcomeDescription: '描述你想要的风格,我会为你生成专属主题',
|
||||
recommendTitle: '推荐主题',
|
||||
loading: '加载中...',
|
||||
refresh: '换一换',
|
||||
resetToDefault: '恢复默认主题',
|
||||
},
|
||||
en: {
|
||||
title: 'AI Theme Generator',
|
||||
finishTips: 'Completed. Regenerate by start a new conversation.',
|
||||
title: '🎨 AI Theme Generator',
|
||||
finishTips: 'Theme generated and applied',
|
||||
placeholder: 'Describe your desired theme style, e.g., warm sunny, fresh natural, tech feel...',
|
||||
welcomeTitle: 'AI Theme Generator',
|
||||
welcomeDescription: 'Describe your desired style and I will generate a custom theme for you',
|
||||
recommendTitle: 'Recommended Themes',
|
||||
loading: 'Loading...',
|
||||
refresh: 'Refresh',
|
||||
resetToDefault: 'Reset to default theme',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -26,17 +50,40 @@ export interface PromptDrawerProps {
|
||||
onThemeChange?: (themeConfig: SiteContextProps['dynamicTheme']) => void;
|
||||
}
|
||||
|
||||
// Extended type for Prompts items with additional properties
|
||||
interface ExtendedPromptsItemType extends PromptsItemType {
|
||||
originalDescription?: string;
|
||||
isRefresh?: boolean;
|
||||
}
|
||||
|
||||
const PromptDrawer: React.FC<PromptDrawerProps> = ({ open, onClose, onThemeChange }) => {
|
||||
const [locale] = useLocale(locales);
|
||||
const { updateSiteConfig, isDark } = React.use(SiteContext) as SiteContextProps;
|
||||
const [locale, localeKey] = useLocale(locales);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const senderRef = useRef<SenderRef>(null);
|
||||
|
||||
const [submitPrompt, loading, prompt, resText, cancelRequest] = usePromptTheme(onThemeChange);
|
||||
const {
|
||||
recommendations,
|
||||
loading: recommendLoading,
|
||||
fetch: fetchRecommendations,
|
||||
} = usePromptRecommend(localeKey);
|
||||
|
||||
const handleSubmit = (value: string) => {
|
||||
submitPrompt(value);
|
||||
setInputValue('');
|
||||
const handleSubmit = React.useCallback(
|
||||
(value: string) => {
|
||||
submitPrompt(value);
|
||||
setInputValue('');
|
||||
},
|
||||
[submitPrompt],
|
||||
);
|
||||
|
||||
const handleRefreshRecommendations = React.useCallback(() => {
|
||||
fetchRecommendations(`prompt-drawer-refresh-${Date.now()}`);
|
||||
}, [fetchRecommendations]);
|
||||
|
||||
const handleResetToDefaultTheme = () => {
|
||||
updateSiteConfig({ dynamicTheme: undefined });
|
||||
};
|
||||
|
||||
const handleAfterOpenChange = (isOpen: boolean) => {
|
||||
@@ -44,14 +91,18 @@ const PromptDrawer: React.FC<PromptDrawerProps> = ({ open, onClose, onThemeChang
|
||||
// Focus the Sender component when drawer opens
|
||||
senderRef.current.focus?.();
|
||||
}
|
||||
// Fetch AI recommendations when drawer opens
|
||||
if (isOpen) {
|
||||
fetchRecommendations('prompt-drawer-init');
|
||||
}
|
||||
};
|
||||
|
||||
const items = React.useMemo<GetProp<typeof Bubble.List, 'items'>>(() => {
|
||||
const items = React.useMemo<BubbleItemType[]>(() => {
|
||||
if (!prompt) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const nextItems: GetProp<typeof Bubble.List, 'items'> = [
|
||||
const nextItems: BubbleItemType[] = [
|
||||
{
|
||||
key: 1,
|
||||
role: 'user',
|
||||
@@ -62,54 +113,274 @@ const PromptDrawer: React.FC<PromptDrawerProps> = ({ open, onClose, onThemeChang
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
role: 'system',
|
||||
role: 'ai',
|
||||
placement: 'start',
|
||||
content: resText,
|
||||
avatar: <AntDesignOutlined />,
|
||||
avatar: <img src={antdLogoSrc} alt="Ant Design" style={{ width: 28, height: 28 }} />,
|
||||
loading: !resText,
|
||||
contentRender: (content: string) => (
|
||||
<Typography>
|
||||
<pre style={{ margin: 0 }}>{content}</pre>
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: '16px',
|
||||
borderRadius: 8,
|
||||
background: isDark
|
||||
? 'linear-gradient(135deg, rgba(90,196,255,0.08) 0%, rgba(174,136,255,0.08) 100%)'
|
||||
: 'linear-gradient(135deg, #f2f9fe 0%, #f7f3ff 100%)',
|
||||
fontSize: 13,
|
||||
lineHeight: 1.6,
|
||||
border: 'none',
|
||||
color: isDark ? 'rgba(255,255,255,0.85)' : 'rgba(0,0,0,0.85)',
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</pre>
|
||||
</Typography>
|
||||
),
|
||||
styles: {
|
||||
content: {
|
||||
background: 'transparent',
|
||||
padding: 0,
|
||||
border: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (!loading) {
|
||||
nextItems.push({
|
||||
key: 3,
|
||||
role: 'divider',
|
||||
role: 'system',
|
||||
placement: 'start',
|
||||
shape: 'round',
|
||||
content: locale.finishTips,
|
||||
avatar: <AntDesignOutlined />,
|
||||
shape: 'corner',
|
||||
});
|
||||
|
||||
// Add recommended themes prompts
|
||||
const recommendedPrompts: ExtendedPromptsItemType[] = recommendations
|
||||
.slice(0, 4)
|
||||
.map((text, index) => ({
|
||||
key: `rec-${text}`,
|
||||
description: `${getEmojiForTheme(index)} ${text}`,
|
||||
originalDescription: text,
|
||||
}));
|
||||
|
||||
// Add refresh button
|
||||
recommendedPrompts.push({
|
||||
key: 'refresh',
|
||||
description: `🔄 ${locale.refresh}`,
|
||||
isRefresh: true,
|
||||
});
|
||||
|
||||
nextItems.push({
|
||||
key: 4,
|
||||
role: 'ai',
|
||||
placement: 'start',
|
||||
content: '',
|
||||
avatar: <img src={antdLogoSrc} alt="Ant Design" style={{ width: 28, height: 28 }} />,
|
||||
contentRender: () =>
|
||||
recommendLoading ? (
|
||||
<Flex gap={8} wrap style={{ justifyContent: 'center' }}>
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<Skeleton.Input
|
||||
key={index}
|
||||
active
|
||||
size="small"
|
||||
style={{ width: 140, borderRadius: 8 }}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
) : (
|
||||
<Prompts
|
||||
wrap
|
||||
items={recommendedPrompts}
|
||||
onItemClick={({ data }) => {
|
||||
if ('isRefresh' in data && data.isRefresh) {
|
||||
handleRefreshRecommendations();
|
||||
} else {
|
||||
handleSubmit(
|
||||
String(
|
||||
(data as ExtendedPromptsItemType).originalDescription ?? data.description,
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
styles={{ root: { marginTop: 8 } }}
|
||||
/>
|
||||
),
|
||||
styles: { content: { padding: 0, background: 'transparent' } },
|
||||
});
|
||||
}
|
||||
|
||||
return nextItems;
|
||||
}, [prompt, resText, loading, locale.finishTips]);
|
||||
}, [
|
||||
prompt,
|
||||
resText,
|
||||
loading,
|
||||
recommendLoading,
|
||||
locale.finishTips,
|
||||
isDark,
|
||||
recommendations,
|
||||
handleSubmit,
|
||||
handleRefreshRecommendations,
|
||||
locale.refresh,
|
||||
]);
|
||||
|
||||
// Limit to 3 recommendations for Prompts component + refresh button
|
||||
const prompts: ExtendedPromptsItemType[] = React.useMemo(() => {
|
||||
const themePrompts: ExtendedPromptsItemType[] = recommendations
|
||||
.slice(0, 3)
|
||||
.map((text, index) => ({
|
||||
key: text,
|
||||
description: `${getEmojiForTheme(index)} ${text}`,
|
||||
originalDescription: text,
|
||||
}));
|
||||
|
||||
// Add refresh button as last item only when we have recommendations
|
||||
if (themePrompts.length > 0) {
|
||||
themePrompts.push({
|
||||
key: 'refresh',
|
||||
description: `🔄 ${locale.refresh}`,
|
||||
isRefresh: true,
|
||||
});
|
||||
}
|
||||
|
||||
return themePrompts;
|
||||
}, [recommendations, locale.refresh]);
|
||||
|
||||
const renderedWelcome = React.useMemo(
|
||||
() => (
|
||||
<div style={{ padding: '0 0 16px' }}>
|
||||
<Welcome
|
||||
icon={<img src={antdLogoSrc} alt="Ant Design" style={{ width: 48, height: 48 }} />}
|
||||
title={locale.welcomeTitle}
|
||||
description={locale.welcomeDescription}
|
||||
styles={{
|
||||
root: {
|
||||
background: isDark
|
||||
? 'linear-gradient(97deg, rgba(90,196,255,0.12) 0%, rgba(174,136,255,0.12) 100%)'
|
||||
: 'linear-gradient(97deg, #f2f9fe 0%, #f7f3ff 100%)',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
[locale.welcomeTitle, locale.welcomeDescription, isDark],
|
||||
);
|
||||
|
||||
const renderedPrompts = React.useMemo(
|
||||
() => (
|
||||
<div style={{ padding: '0 0 32px' }}>
|
||||
<Flex vertical gap={12} align="center">
|
||||
<Divider titlePlacement="center" style={{ margin: 0, fontSize: 12 }}>
|
||||
{locale.recommendTitle}
|
||||
</Divider>
|
||||
{recommendLoading ? (
|
||||
<Flex gap={8} wrap style={{ justifyContent: 'center' }}>
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<Skeleton.Input
|
||||
key={index}
|
||||
active
|
||||
size="small"
|
||||
style={{ width: 140, borderRadius: 8 }}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
) : (
|
||||
prompts.length > 0 && (
|
||||
<Prompts
|
||||
wrap
|
||||
items={prompts}
|
||||
onItemClick={({ data }) => {
|
||||
if ('isRefresh' in data && data.isRefresh) {
|
||||
handleRefreshRecommendations();
|
||||
} else {
|
||||
handleSubmit(
|
||||
String(
|
||||
(data as ExtendedPromptsItemType).originalDescription ?? data.description,
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
styles={{
|
||||
root: {
|
||||
marginTop: 4,
|
||||
},
|
||||
item: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Flex>
|
||||
</div>
|
||||
),
|
||||
[locale.recommendTitle, recommendLoading, prompts, handleSubmit, handleRefreshRecommendations],
|
||||
);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title={locale.title}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
size={480}
|
||||
width="80vw"
|
||||
placement="right"
|
||||
afterOpenChange={handleAfterOpenChange}
|
||||
extra={
|
||||
<Button type="text" size="small" onClick={handleResetToDefaultTheme}>
|
||||
{locale.resetToDefault}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<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>
|
||||
<Splitter style={{ height: '100%' }}>
|
||||
{/* 左侧预览区域 */}
|
||||
<Splitter.Panel defaultSize="50%" min="30%" max="70%">
|
||||
<Flex vertical style={{ height: '100%', padding: '0 8px' }}>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: '16px 8px',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<ComponentsBlock className="prompt-drawer-preview" />
|
||||
</div>
|
||||
</Flex>
|
||||
</Splitter.Panel>
|
||||
|
||||
{/* 右侧对话区域 */}
|
||||
<Splitter.Panel defaultSize="50%" min="30%" max="70%">
|
||||
<Flex vertical gap={0} style={{ height: '100%', padding: '0 8px' }}>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: 0,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{!prompt ? (
|
||||
<>
|
||||
{renderedWelcome}
|
||||
{renderedPrompts}
|
||||
</>
|
||||
) : (
|
||||
<Bubble.List items={items} />
|
||||
)}
|
||||
</div>
|
||||
<Sender
|
||||
ref={senderRef}
|
||||
value={inputValue}
|
||||
onChange={setInputValue}
|
||||
onSubmit={handleSubmit}
|
||||
loading={loading}
|
||||
onCancel={cancelRequest}
|
||||
placeholder={locale.placeholder}
|
||||
/>
|
||||
</Flex>
|
||||
</Splitter.Panel>
|
||||
</Splitter>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
158
.dumi/theme/common/ThemeSwitch/usePromptRecommend.ts
Normal file
158
.dumi/theme/common/ThemeSwitch/usePromptRecommend.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { XStream } from '@ant-design/x-sdk';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
recommendPrompt:
|
||||
'请生成 4 个 Tailwindcss 主题描述词,需要多样化的 UI 设计风格,用于 Ant Design 主题生成器推荐。参考官方内建风格:暗黑风格、类 MUI 风格、类 shadcn 风格、卡通风格、插画风格、类 Bootstrap 拟物化风格、玻璃风格、极客风格。回复格式:用逗号分隔。',
|
||||
},
|
||||
en: {
|
||||
recommendPrompt:
|
||||
'Generate 4 Tailwind CSS theme names featuring diverse UI design styles for an Ant Design theme generator recommendation. Reference the official built-in styles: Dark, MUI-like, shadcn-like, Cartoon, Illustration, Bootstrap-like Skeuomorphic, Glassmorphism, and Geek. Output format: comma-separated.',
|
||||
},
|
||||
};
|
||||
|
||||
const FALLBACK_THEMES = {
|
||||
cn: [
|
||||
'温暖阳光的橙色调,营造活力积极的氛围',
|
||||
'专业稳重的深海蓝商务风格',
|
||||
'清新自然的森林绿环保主题',
|
||||
'极客紫霓虹感的科技前沿风格',
|
||||
'柔和粉紫的樱花春日浪漫主题',
|
||||
'高对比度的赛博朋克深色科技风',
|
||||
'莫兰迪灰色调,简约优雅的现代感',
|
||||
'青花瓷蓝白配色,东方雅韵',
|
||||
'马卡龙多彩配色,活泼童趣',
|
||||
'水墨黑白灰,传统韵味',
|
||||
],
|
||||
en: [
|
||||
'Warm sunny orange tones for energetic positive vibes',
|
||||
'Professional deep ocean blue business style',
|
||||
'Fresh natural forest green eco-friendly theme',
|
||||
'Geek purple neon tech cutting-edge style',
|
||||
'Soft pink-purple cherry blossom spring romantic theme',
|
||||
'High contrast cyberpunk dark tech style',
|
||||
'Morandi gray tones, minimalist elegant modern feel',
|
||||
'Blue and white porcelain colors, Eastern elegance',
|
||||
'Colorful macaron, lively and playful',
|
||||
'Ink black white gray, traditional charm',
|
||||
],
|
||||
};
|
||||
|
||||
const fetchRecommendations = async (
|
||||
localeKey: keyof typeof locales,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<string[]> => {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: locales[localeKey].recommendPrompt,
|
||||
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') {
|
||||
try {
|
||||
const data = JSON.parse(chunk.data) as {
|
||||
lane: string;
|
||||
payload: string;
|
||||
};
|
||||
|
||||
const payload = JSON.parse(data.payload) as {
|
||||
text: string;
|
||||
};
|
||||
|
||||
fullContent += payload.text || '';
|
||||
} catch {
|
||||
// Skip malformed chunks and continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse theme names from response - separated by commas
|
||||
const text = fullContent.trim();
|
||||
const items = text
|
||||
.split(/[,,\n]/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
const result = items.slice(0, 4);
|
||||
|
||||
// If parsing failed or got no results, use fallback
|
||||
if (result.length === 0) {
|
||||
return FALLBACK_THEMES[localeKey];
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
// Don't log AbortError - it's expected when cancelling requests
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
throw error; // Re-throw AbortError to be handled by caller
|
||||
}
|
||||
console.error('Error in fetchRecommendations:', error);
|
||||
// Return fallback themes
|
||||
return FALLBACK_THEMES[localeKey];
|
||||
}
|
||||
};
|
||||
|
||||
export default function usePromptRecommend(localeKey: keyof typeof locales = 'cn') {
|
||||
const [recommendations, setRecommendations] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
const fetchedKeyRef = useRef<string>('');
|
||||
|
||||
const fetch = async (key: string) => {
|
||||
if (fetchedKeyRef.current === key) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel previous request if it exists
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
|
||||
const abortController = new AbortController();
|
||||
abortControllerRef.current = abortController;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const items = await fetchRecommendations(localeKey, abortController.signal);
|
||||
setRecommendations(items);
|
||||
fetchedKeyRef.current = key;
|
||||
} catch (error) {
|
||||
if (!(error instanceof Error && error.name === 'AbortError')) {
|
||||
console.error('Failed to fetch recommendations:', error);
|
||||
// Use fallback on error
|
||||
setRecommendations(FALLBACK_THEMES[localeKey]);
|
||||
fetchedKeyRef.current = key;
|
||||
}
|
||||
} finally {
|
||||
// Only clear loading and controller if this is still the active request
|
||||
if (abortControllerRef.current === abortController) {
|
||||
setLoading(false);
|
||||
abortControllerRef.current = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { recommendations, loading, fetch };
|
||||
}
|
||||
669
AGENTS.md
669
AGENTS.md
@@ -11,14 +11,17 @@
|
||||
- [命名规范](#命名规范)
|
||||
- [TypeScript 规范](#typescript-规范)
|
||||
- [样式规范](#样式规范)
|
||||
- [代码格式化](#代码格式化)
|
||||
- [开发指南](#开发指南)
|
||||
- [测试指南](#测试指南)
|
||||
- [演示代码规范](#演示代码规范)
|
||||
- [国际化规范](#国际化规范)
|
||||
- [组件开发模板](#组件开发模板)
|
||||
- [文档和 Changelog](#文档和-changelog-规范)
|
||||
- [Git 和 Pull Request](#git-和-pull-request-规范)
|
||||
- [质量保证](#质量保证)
|
||||
- [工具链和环境](#工具链和环境)
|
||||
- [常见问题和故障排查](#常见问题和故障排查)
|
||||
|
||||
---
|
||||
|
||||
@@ -29,12 +32,15 @@
|
||||
### 核心特性
|
||||
|
||||
- 使用 TypeScript 和 React 开发
|
||||
- 兼容 React 18 ~ 19 版本
|
||||
- 组件库设计精美,功能完善,广泛应用于企业级中后台产品
|
||||
- 遵循 Ant Design 设计规范
|
||||
- 支持国际化(i18n)
|
||||
- 支持主题定制和暗色模式
|
||||
- 兼容 React 18+ 版本(peerDependencies: `>=18.0.0`)
|
||||
- 包含 **84+ 个组件**,涵盖通用、输入、数据展示、反馈、导航、布局等类型
|
||||
- 采用完整的 CSS-in-JS 架构(基于 `@ant-design/cssinjs`)
|
||||
- 支持 Design Token 主题系统和动态主题切换
|
||||
- 支持国际化(i18n),包含 150+ 语言 locales
|
||||
- 支持暗色模式和自定义主题
|
||||
- 支持 RTL(从右到左)布局
|
||||
- 支持服务端渲染(SSR)
|
||||
- 提供完整的 TypeScript 类型定义
|
||||
|
||||
---
|
||||
|
||||
@@ -42,9 +48,9 @@
|
||||
|
||||
### 开发环境要求
|
||||
|
||||
- **Node.js**: >= 16
|
||||
- **包管理器**: npm 或 utoo
|
||||
- **浏览器兼容性**: Chrome 80+
|
||||
- **Node.js**: >= 18.12.0(推荐使用 LTS 版本)
|
||||
- **包管理器**: npm 或 ut(内部包管理器)
|
||||
- **浏览器兼容性**: 现代浏览器(Chrome 80+、Edge、Firefox、Safari)
|
||||
- **编辑器**: VS Code(推荐)或其他支持 TypeScript 的编辑器
|
||||
|
||||
### 安装依赖
|
||||
@@ -58,52 +64,53 @@ utoo install
|
||||
### 常用开发命令
|
||||
|
||||
```bash
|
||||
# 启动开发服务器(访问 http://127.0.0.1:8001)
|
||||
npm start
|
||||
|
||||
# 编译 TypeScript 代码到 lib 和 es 目录
|
||||
npm run compile
|
||||
|
||||
# 构建 UMD 格式的构建产物
|
||||
npm run build
|
||||
|
||||
# 运行所有测试
|
||||
npm test
|
||||
|
||||
# 监听模式运行测试
|
||||
npm test -- --watch
|
||||
|
||||
# 生成测试覆盖率报告
|
||||
npm run test:coverage
|
||||
|
||||
# 代码检查(包括 TypeScript、ESLint、Biome、Markdown、Changelog)
|
||||
npm run lint
|
||||
|
||||
# 格式化代码
|
||||
npm run format
|
||||
|
||||
# 生成 Changelog(交互式)
|
||||
npm run changelog
|
||||
|
||||
# 清理构建产物
|
||||
npm run clean
|
||||
npm start # 启动开发服务器(http://127.0.0.1:8001)
|
||||
npm run build # 完整构建
|
||||
npm test # 运行测试
|
||||
npm run lint # 代码检查
|
||||
npm run format # 代码格式化
|
||||
npm run version # 生成版本信息
|
||||
npm run clean # 清理构建产物
|
||||
```
|
||||
|
||||
### 项目结构
|
||||
|
||||
```
|
||||
ant-design/
|
||||
├── components/ # 组件源代码
|
||||
│ └── [component]/ # 单个组件目录
|
||||
│ ├── demo/ # 演示代码
|
||||
│ ├── style/ # 样式文件
|
||||
│ ├── index.tsx # 组件入口
|
||||
│ └── index.zh-CN.md # 组件文档
|
||||
├── scripts/ # 构建和工具脚本
|
||||
├── tests/ # 测试文件
|
||||
├── CHANGELOG.zh-CN.md # 中文更新日志
|
||||
├── CHANGELOG.en-US.md # 英文更新日志
|
||||
└── package.json # 项目配置
|
||||
├── components/ # 组件源代码(84+ 组件)
|
||||
│ ├── component-name/ # 单个组件目录
|
||||
│ │ ├── ComponentName.tsx # 主组件实现
|
||||
│ │ ├── SubComponent.tsx # 子组件(如有)
|
||||
│ │ ├── helpers.ts # 辅助函数
|
||||
│ │ ├── hooks/ # 组件专属 hooks
|
||||
│ │ ├── demo/ # 演示代码(*.tsx 和 *.md)
|
||||
│ │ ├── style/ # 样式系统
|
||||
│ │ │ ├── index.ts # 样式钩子生成器
|
||||
│ │ │ ├── token.ts # 主题 token 定义
|
||||
│ │ │ └── variant.ts # 变体样式
|
||||
│ │ ├── __tests__/ # 单元测试
|
||||
│ │ ├── index.en-US.md # 英文文档
|
||||
│ │ ├── index.zh-CN.md # 中文文档
|
||||
│ │ └── index.tsx # 导出入口
|
||||
│ ├── _util/ # 社会工具函数库
|
||||
│ ├── theme/ # 主题系统
|
||||
│ ├── locale/ # 国际化文本(150+ 文件)
|
||||
│ └── index.ts # 组件总入口
|
||||
├── scripts/ # 构建和工具脚本(26+ 脚本)
|
||||
├── tests/ # 测试文件和工具
|
||||
│ ├── __mocks__/ # Jest mocks
|
||||
│ ├── shared/ # 共享测试工具
|
||||
│ └── setup.ts # 测试环境设置
|
||||
├── docs/ # 站点文档
|
||||
├── CHANGELOG.zh-CN.md # 中文更新日志
|
||||
├── CHANGELOG.en-US.md # 英文更新日志
|
||||
├── package.json # 项目配置
|
||||
├── tsconfig.json # TypeScript 配置
|
||||
├── eslint.config.mjs # ESLint 配置
|
||||
├── biome.json # Biome 配置
|
||||
├── .jest.js # Jest 配置
|
||||
├── .dumirc.ts # Dumi 文档配置
|
||||
└── webpack.config.js # Webpack 构建配置
|
||||
```
|
||||
|
||||
---
|
||||
@@ -114,14 +121,20 @@ ant-design/
|
||||
|
||||
- ✅ 使用 TypeScript 和 React 书写
|
||||
- ✅ 使用函数式组件和 Hooks,**避免类组件**
|
||||
- ✅ 使用 `forwardRef` 实现组件 ref 传递
|
||||
- ✅ 使用提前返回(early returns)提高代码可读性
|
||||
- ✅ 避免引入新依赖,严控打包体积
|
||||
- ✅ 兼容 Chrome 80+ 浏览器
|
||||
- ✅ 兼容现代浏览器
|
||||
- ✅ 支持服务端渲染(SSR)
|
||||
- ✅ 保持向下兼容,避免 breaking change
|
||||
- ✅ 组件名使用大驼峰(PascalCase),如 `Button`、`DatePicker`
|
||||
- ✅ 属性名使用小驼峰(camelCase),如 `onClick`、`defaultValue`
|
||||
- ✅ 合理使用 `useLayoutEffect` 处理性能敏感操作(如 loading 延迟)
|
||||
- ✅ 合理使用 `React.memo`、`useMemo` 和 `useCallback` 优化性能
|
||||
- ✅ 使用 `clsx` 处理类名拼接
|
||||
- ✅ 使用 `devUseWarning` 提供开发时 API 过期警告
|
||||
- ✅ 使用 `displayName` 设置组件调试名称
|
||||
- ✅ 支持 Semantic 样式系统(`classNames` 和 `styles` 属性)
|
||||
|
||||
#### Props 命名
|
||||
|
||||
@@ -140,6 +153,37 @@ ant-design/
|
||||
| 图标 | `icon` | `icon`、`prefixIcon` |
|
||||
| 触发器 | `trigger` | `trigger` |
|
||||
| 类名 | `className` | `className` |
|
||||
| 样式对象 | `style` | `style` |
|
||||
|
||||
#### 组件引用 (Ref)
|
||||
|
||||
组件应支持 `classNames` 和 `styles` 属性,用于精细化样式定制:
|
||||
|
||||
```tsx
|
||||
// classNames 属性类型定义
|
||||
export type ComponentClassNamesType = {
|
||||
root?: string;
|
||||
icon?: string;
|
||||
content?: string;
|
||||
// ... 其他元素
|
||||
};
|
||||
|
||||
// styles 属性类型定义
|
||||
export type ComponentStylesType = {
|
||||
root?: React.CSSProperties;
|
||||
icon?: React.CSSProperties;
|
||||
content?: React.CSSProperties;
|
||||
// ... 其他元素
|
||||
};
|
||||
|
||||
// 使用示例
|
||||
<Button
|
||||
classNames={{ root: 'custom-btn', icon: 'custom-icon' }}
|
||||
styles={{ root: { width: 200 }, icon: { color: 'red' } }}
|
||||
>
|
||||
Button
|
||||
</Button>;
|
||||
```
|
||||
|
||||
#### 事件命名
|
||||
|
||||
@@ -164,27 +208,19 @@ interface ComponentRef {
|
||||
}
|
||||
```
|
||||
|
||||
#### 组件 Token 命名
|
||||
|
||||
格式:`variant (optional)` + `semantic part` + `semantic part variant (optional)` + `css property` + `size/disabled (optional)`
|
||||
|
||||
示例:
|
||||
|
||||
- `buttonPrimaryColor` - Button 主色
|
||||
- `inputPaddingBlock` - Input 垂直内边距
|
||||
- `menuItemActiveBg` - Menu 激活项背景色
|
||||
|
||||
### API 文档规范
|
||||
|
||||
#### API 表格格式
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| --------- | --------------- | ------------------------------------------------------ | --------- |
|
||||
| htmlType | Button 原生类型 | string | `button` |
|
||||
| type | 按钮类型 | `primary` \| `default` \| `dashed` \| `link` \| `text` | `default` |
|
||||
| disabled | 是否禁用 | boolean | false |
|
||||
| minLength | 最小长度 | number | 0 |
|
||||
| style | 自定义样式 | CSSProperties | - |
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| htmlType | Button 原生类型 | string | `button` | - |
|
||||
| type | 按钮类型 | `primary` \| `default` \| `dashed` \| `link` \| `text` | `default` | - |
|
||||
| disabled | 是否禁用 | boolean | false | - |
|
||||
| minLength | 最小长度 | number | 0 | - |
|
||||
| style | 自定义样式 | CSSProperties | - | - |
|
||||
| classNames | 自定义类名 | ComponentClassNamesType | - | 5.0.0 |
|
||||
| styles | 自定义内联样式 | ComponentStylesType | - | 5.0.0 |
|
||||
|
||||
#### API 文档要求
|
||||
|
||||
@@ -195,6 +231,7 @@ interface ComponentRef {
|
||||
- ✅ 无默认值使用 `-`
|
||||
- ✅ 描述首字母大写,结尾无句号
|
||||
- ✅ API 按字母顺序排列
|
||||
- ✅ 新增属性需要声明可用版本号,如 `5.0.0`
|
||||
|
||||
---
|
||||
|
||||
@@ -264,40 +301,117 @@ enum ButtonType {
|
||||
|
||||
## 样式规范
|
||||
|
||||
### 样式方案
|
||||
### 样式架构
|
||||
|
||||
Ant Design 6.x 采用完整的 **CSS-in-JS** 架构,基于 `@ant-design/cssinjs` 实现:
|
||||
|
||||
- 使用 `@ant-design/cssinjs` 作为样式解决方案
|
||||
- 每个组件的样式应该放在 `style/` 目录下
|
||||
- 样式文件应该与组件结构保持一致
|
||||
- 使用 CSS-in-JS 时应当注意性能影响,避免不必要的样式重计算
|
||||
- 样式生成函数应遵循 `gen[ComponentName]Style` 的命名规范
|
||||
- 样式覆盖应使用类选择器而非标签选择器,提高样式特异性
|
||||
- 使用 `@ant-design/cssinjs-utils` 提供额外样式工具
|
||||
- 支持动态样式和主题切换
|
||||
- 样式独立注入,避免 CSS 污染
|
||||
- 支持 Server-Side Rendering (SSR)
|
||||
|
||||
### 组件样式结构
|
||||
|
||||
每个组件的样式应该放在 `style/` 目录下,建议结构:
|
||||
|
||||
```
|
||||
style/
|
||||
├── index.ts # 样式钩生成器(导出点)
|
||||
├── token.ts # 组件 token 定义
|
||||
├── variant.ts # 变体样式(solid/outlined/text 等)
|
||||
├── compact.ts # 紧凑布局样式(如需要)
|
||||
└── group.ts | 组合样式(如需要)
|
||||
```
|
||||
|
||||
### 样式生成函数规范
|
||||
|
||||
```typescript
|
||||
// 1. Token 准备函数
|
||||
const prepareToken = (token: GlobalToken): ComponentToken => {
|
||||
return mergeToken(token, {
|
||||
// 组件特定 token
|
||||
controlHeightLG: 40,
|
||||
});
|
||||
};
|
||||
|
||||
// 2. Component Token 准备函数
|
||||
export const prepareComponentToken: GetDefaultToken<'ComponentName'> = (token) => ({
|
||||
componentBg: token.colorBgContainer,
|
||||
componentBorder: token.colorBorder,
|
||||
// ...
|
||||
});
|
||||
|
||||
// 3. 样式生成函数
|
||||
const genComponentStyle: GenerateStyle<ComponentToken> = (token) => {
|
||||
const { componentCls } = token;
|
||||
return {
|
||||
[componentCls]: {
|
||||
// 基础样式
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// 4. 导出样式钩子(使用 genStyleHooks)
|
||||
export default genStyleHooks(
|
||||
'ComponentName', // 组件名称
|
||||
(token) => [genComponentStyle(token)],
|
||||
prepareComponentToken, // Component Token 准备函数
|
||||
{
|
||||
unitless: {
|
||||
// 无单位 token
|
||||
fontWeight: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### Token 系统
|
||||
|
||||
- 使用 Ant Design 的设计 Token 系统
|
||||
使用 Ant Design 的 Design Token 系统:
|
||||
|
||||
- 避免硬编码颜色、尺寸、间距等值
|
||||
- 组件样式应基于全局 Token 和组件级 Token
|
||||
- 自定义样式应尽可能使用现有的 Token,保持一致性
|
||||
- 组件级 Token 命名规范:`Component` + 属性名,如 `buttonPrimaryColor`
|
||||
- 对 Token 的修改应当向下传递,确保设计系统的一致性
|
||||
- 自定义样式应尽可能使用现有的 Token
|
||||
- 组件级 Token 命名规范:`Component` + `semantic part` + `css property`
|
||||
- 使用 `mergeToken` 合并 token
|
||||
- 使用 `calc` 处理 CSS 计算值
|
||||
|
||||
### Token 命名规范
|
||||
|
||||
格式:`variant (optional)` + `semantic part` + `semantic part variant (optional)` + `css property` + `size/disabled (optional)`
|
||||
|
||||
示例:
|
||||
|
||||
- `buttonPrimaryColor` - Button 主色
|
||||
- `inputPaddingBlock` - Input 垂直内边距
|
||||
- `menuItemActiveBg` - Menu 激活项背景色
|
||||
|
||||
### 响应式和主题支持
|
||||
|
||||
- ✅ 组件应支持在不同屏幕尺寸下良好展示
|
||||
- ✅ 组件应支持不同屏幕尺寸(使用 CSS 媒体查询)
|
||||
- ✅ 所有组件必须支持暗色模式
|
||||
- ✅ 组件应支持从右到左(RTL)的阅读方向
|
||||
- ✅ 使用 CSS 逻辑属性(如 `margin-inline-start`)替代方向性属性(如 `margin-left`)
|
||||
- ✅ 组件应支持 RTL(从右到左)布局
|
||||
- ✅ 使用 CSS 逻辑属性(如 `margin-inline-start`)替代方向性属性
|
||||
- ✅ 支持通过 `ConfigProvider` 进行主题定制
|
||||
- ✅ 使用 CSS 变量 (`cssVarCls`) 支持动态主题切换
|
||||
|
||||
### 动画效果
|
||||
|
||||
- 使用 CSS 过渡实现简单动画
|
||||
- 复杂动画使用 `@rc-component/motion` 实现
|
||||
- 尊重用户的减少动画设置(`prefers-reduced-motion`)
|
||||
- 动画时长和缓动函数应保持一致性
|
||||
- 动画时长和缓动函数应使用 Token:`motionDurationMid`、`motionEaseInOut`
|
||||
- 动画不应干扰用户的操作和阅读体验
|
||||
|
||||
### CSS-in-JS 注意事项
|
||||
|
||||
- 样式生成函数应遵循 `gen[ComponentName]Style` 的命名规范
|
||||
- 样式覆盖应使用类选择器而非标签选择器
|
||||
- 避免在 render 过程中重复创建样式对象
|
||||
- 使用 `hashId` 确保样式唯一性
|
||||
- 使用 `cssVarCls` 支持 CSS 变量
|
||||
|
||||
### 可访问性样式
|
||||
|
||||
- 遵循 WCAG 2.1 AA 级别标准
|
||||
@@ -309,32 +423,132 @@ enum ButtonType {
|
||||
|
||||
---
|
||||
|
||||
## 代码格式化
|
||||
|
||||
### 工具配置
|
||||
|
||||
项目使用多种代码格式化工具组合使用:
|
||||
|
||||
| 工具 | 用途 | 配置文件 |
|
||||
| -------- | ------------------------ | ------------------- |
|
||||
| Biome | 代码检查和格式化(主要) | `biome.json` |
|
||||
| ESLint | 代码质量检查 | `eslint.config.mjs` |
|
||||
| Prettier | 补充格式化 | `.prettierrc` |
|
||||
|
||||
### 格式化规范
|
||||
|
||||
配置文件:`biome.json`、`.prettierrc`
|
||||
|
||||
- **缩进**: 2 空格
|
||||
- **行宽**: 100 字符
|
||||
- **引号**: JavaScript 使用单引号,JSX 属性使用双引号
|
||||
- **尾随逗号**: 强制添加(`all`)
|
||||
- **分号**: 不强制使用
|
||||
|
||||
### 格式化命令
|
||||
|
||||
```bash
|
||||
# 使用 Biome 格式化
|
||||
npm run format
|
||||
|
||||
# 使用 Biome 检查
|
||||
npm run lint:biome
|
||||
|
||||
# 使用 Prettier 格式化(补充)
|
||||
npm run prettier
|
||||
```
|
||||
|
||||
### 导入顺序
|
||||
|
||||
使用 `@ianvs/prettier-plugin-sort-imports` 插件自动排序导入:
|
||||
|
||||
```typescript
|
||||
// 1. React 导入
|
||||
import React, { forwardRef, useState } from 'react';
|
||||
import RcComponent from '@rc-component/component';
|
||||
// 2. 第三方库导入
|
||||
import clsx from 'clsx';
|
||||
|
||||
// 3. Ant Design 内部导入
|
||||
import { useToken } from '../theme/internal';
|
||||
// 4. 相对路径导入
|
||||
import { helperFunction } from './helpers';
|
||||
// 5. 类型导入
|
||||
import type { RefType } from './types';
|
||||
// 6. 样式导入(如果有)
|
||||
import './custom.css';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 测试指南
|
||||
|
||||
#### 测试框架和工具
|
||||
|
||||
- 使用 Jest 和 React Testing Library 编写单元测试
|
||||
- 对 UI 组件使用快照测试(Snapshot Testing)
|
||||
- 使用 **Jest 30+** 和 **React Testing Library** 编写单元测试
|
||||
- 使用 **jest-axe** 进行可访问性测试
|
||||
- 使用 **jest-image-snapshot** 进行视觉回归测试
|
||||
- 测试覆盖率要求 **100%**
|
||||
- 测试文件放在 `tests/` 目录,命名格式为:`index.test.tsx` 或 `xxx.test.tsx`
|
||||
- 测试文件放在组件目录下的 `__tests__/` 目录
|
||||
|
||||
#### 测试文件类型
|
||||
|
||||
| 测试类型 | 文件名 | 用途 |
|
||||
| ------------- | ------------------------ | ------------------------- |
|
||||
| 主测试 | `index.test.tsx` | 组件功能测试 |
|
||||
| 无障碍测试 | `a11y.test.ts` | WCAG 可访问性标准测试 |
|
||||
| 类型测试 | `type.test.tsx` | TypeScript 类型完整性测试 |
|
||||
| Semantic 测试 | `demo-semantic.test.tsx` | Demo 语义化测试 |
|
||||
| Demo 测试 | `demo.test.ts` | Demo 代码测试 |
|
||||
|
||||
#### 测试辅助函数
|
||||
|
||||
项目提供了多个测试辅助函数:
|
||||
|
||||
```typescript
|
||||
// mountTest - 测试组件挂载/卸载
|
||||
import mountTest from 'tests/shared/mountTest';
|
||||
// rtlTest - 测试 RTL 布局渲染
|
||||
import rtlTest from 'tests/shared/rtlTest';
|
||||
|
||||
mountTest(Button);
|
||||
|
||||
rtlTest(Button);
|
||||
```
|
||||
|
||||
#### 运行测试
|
||||
|
||||
```bash
|
||||
npm test # 运行所有测试
|
||||
npm test -- --watch # 监听模式
|
||||
npm run test:coverage # 生成覆盖率报告
|
||||
npm run test:image # UI 快照测试(需要 Docker)
|
||||
# 运行单元测试
|
||||
npm test
|
||||
|
||||
# 更新测试快照
|
||||
npm run test:update
|
||||
|
||||
# 运行视觉回归测试(需要 Puppeteer/Docker)
|
||||
npm run test:image
|
||||
|
||||
# 运行所有测试套件
|
||||
npm run test:all
|
||||
|
||||
# 运行 Node.js 环境测试
|
||||
npm run test:node
|
||||
|
||||
# 运行站点文档测试
|
||||
npm run test:site
|
||||
```
|
||||
|
||||
#### 测试最佳实践
|
||||
|
||||
- ✅ 测试用户行为而非实现细节
|
||||
- ✅ 使用有意义的测试描述
|
||||
- ✅ 使用有意义的测试描述(`describe` 和 `it`)
|
||||
- ✅ 每个测试用例应该独立,不依赖其他测试
|
||||
- ✅ 测试边界情况和错误处理
|
||||
- ✅ 组件应同时包含 `mountTest` 和 `rtlTest`
|
||||
- ✅ 新增功能必须有对应的测试用例
|
||||
- ✅ 使用 `toHaveBeenCalledTimes` 而非 `toHaveBeenCalledExactTimes`
|
||||
|
||||
### 演示代码规范
|
||||
|
||||
@@ -424,6 +638,147 @@ export function TestComp(props) {
|
||||
}
|
||||
```
|
||||
|
||||
### 组件开发模板
|
||||
|
||||
#### 标准组件目录结构
|
||||
|
||||
```
|
||||
[component-name]/
|
||||
├── ComponentName.tsx # 主组件实现
|
||||
├── index.tsx # 导出入口
|
||||
├── demo/ # 演示代码
|
||||
│ ├── basic.tsx
|
||||
│ └── basic.md
|
||||
├── style/ # 样式系统
|
||||
│ ├── index.ts
|
||||
│ └── token.ts
|
||||
├── __tests__/ # 测试文件
|
||||
│ ├── index.test.tsx
|
||||
│ └── a11y.test.ts
|
||||
├── index.en-US.md # 英文文档
|
||||
└── index.zh-CN.md # 中文文档
|
||||
```
|
||||
|
||||
#### 主组件模板
|
||||
|
||||
```tsx
|
||||
import React, { forwardRef, useContext, useRef } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
|
||||
import { useComposeRef } from '../_util/hooks';
|
||||
import { useComponentConfig } from '../_util/hooks/useComponentConfig';
|
||||
import { devUseWarning } from '../_util/warning';
|
||||
import { ConfigProviderContext } from '../../config-provider';
|
||||
import useStyle from './style';
|
||||
|
||||
export interface ComponentNameProps {
|
||||
// ... 其他 props
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
classNames?: ComponentClassNames;
|
||||
styles?: ComponentStyles;
|
||||
}
|
||||
|
||||
export interface ComponentRef {
|
||||
nativeElement: HTMLElement;
|
||||
focus: VoidFunction;
|
||||
blur: VoidFunction;
|
||||
}
|
||||
|
||||
export type ComponentClassNames = {
|
||||
root?: string;
|
||||
// ...
|
||||
};
|
||||
|
||||
export type ComponentStyles = {
|
||||
root?: React.CSSProperties;
|
||||
// ...
|
||||
};
|
||||
|
||||
const InternalComponent = React.forwardRef<ComponentRef, ComponentNameProps>((props, ref) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
style,
|
||||
classNames,
|
||||
styles,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const { getPrefixCls, direction } = useContext(ConfigProviderContext);
|
||||
const componentConfig = useComponentConfig('ComponentName');
|
||||
const prefixCls = getPrefixCls('component-name', customizePrefixCls);
|
||||
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
||||
const domRef = useRef<HTMLElement>(null);
|
||||
const mergedRef = useComposeRef(ref, domRef);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warning = devUseWarning('ComponentName');
|
||||
warning.deprecated(!deprecatedProp, 'deprecatedProp', 'newProp');
|
||||
}
|
||||
|
||||
return wrapCSSVar(
|
||||
<div
|
||||
ref={mergedRef}
|
||||
className={clsx(
|
||||
prefixCls,
|
||||
hashId,
|
||||
cssVarCls,
|
||||
className,
|
||||
classNames?.root,
|
||||
componentConfig.className,
|
||||
)}
|
||||
style={{ ...style, ...styles?.root, ...componentConfig.style }}
|
||||
dir={direction}
|
||||
{...restProps}
|
||||
>
|
||||
{/* 子内容 */}
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
|
||||
const Component = InternalComponent as typeof InternalComponent & {
|
||||
displayName?: string;
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Component.displayName = 'ComponentName';
|
||||
}
|
||||
|
||||
export default Component;
|
||||
```
|
||||
|
||||
#### 样式模板
|
||||
|
||||
```typescript
|
||||
// style/token.ts
|
||||
import type { TokenType } from '../../theme/internal';
|
||||
// style/index.ts
|
||||
import { genStyleHooks } from '../../theme/internal';
|
||||
import { prepareComponentToken } from './token';
|
||||
|
||||
export interface ComponentToken {
|
||||
componentFontSize?: number;
|
||||
componentPadding?: number;
|
||||
}
|
||||
|
||||
export const prepareComponentToken: GetDefaultToken<'ComponentName'> = (token) => ({
|
||||
componentFontSize: token.fontSize,
|
||||
componentPadding: token.paddingXS,
|
||||
});
|
||||
|
||||
const genComponentStyle: GenerateStyle<ComponentToken> = (token) => {
|
||||
const { componentCls, fontSize, padding } = token;
|
||||
return { [componentCls]: { fontSize, padding } };
|
||||
};
|
||||
|
||||
export default genStyleHooks(
|
||||
'ComponentName',
|
||||
(token) => [genComponentStyle(token)],
|
||||
prepareComponentToken,
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文档和 Changelog 规范
|
||||
@@ -514,6 +869,8 @@ export function TestComp(props) {
|
||||
|
||||
#### 💡 输出示例参考
|
||||
|
||||
需要同时提供中英文两个版本,格式如下:
|
||||
|
||||
**中文版**(Emoji 在前、无冒号、每条含组件名、属性用反引号):
|
||||
|
||||
```markdown
|
||||
@@ -668,17 +1025,27 @@ export function TestComp(props) {
|
||||
|
||||
### 开发工具
|
||||
|
||||
- 推荐使用 VS Code 或其他支持 TypeScript 的编辑器
|
||||
- 配置 ESLint 和 Prettier
|
||||
- 使用 TypeScript 严格模式
|
||||
- 配置 Git hooks 进行代码检查
|
||||
- **编辑器**: 推荐使用 VS Code 或其他支持 TypeScript 的编辑器
|
||||
- **代码检查**: ESLint (@antfu/eslint-config) + Biome
|
||||
- **格式化**: Biome + Prettier
|
||||
- **类型检查**: TypeScript 5.9+ 严格模式
|
||||
- **Git hooks**: Husky + lint-staged
|
||||
|
||||
### 构建工具
|
||||
|
||||
- 使用 webpack 进行构建
|
||||
- 支持 ES modules 和 CommonJS
|
||||
- 提供 UMD 格式的构建产物
|
||||
- 支持按需加载
|
||||
| 工具 | 用途 |
|
||||
| ------- | ---------------------- |
|
||||
| Father | 组件编译(lib/es) |
|
||||
| Webpack | dist 构建和产物分析 |
|
||||
| Dumi | 文档站点构建 |
|
||||
| Mako | SSR 构建器(生产环境) |
|
||||
|
||||
### 构建产物
|
||||
|
||||
- **lib/**: CommonJS 格式
|
||||
- **es/**: ES Modules 格式
|
||||
- **dist/**: UMD 格式(包含 dist/antd.min.js)
|
||||
- **locale/**: 国际化配置
|
||||
|
||||
### CI/CD
|
||||
|
||||
@@ -686,6 +1053,113 @@ export function TestComp(props) {
|
||||
- 包括单元测试、集成测试、类型检查、代码风格检查
|
||||
- 自动化发布流程
|
||||
- 支持多环境部署
|
||||
- 支持视觉回归测试
|
||||
|
||||
### 相关配置文件
|
||||
|
||||
| 配置文件 | 说明 |
|
||||
| ------------------- | ---------------- |
|
||||
| `package.json` | 项目配置和脚本 |
|
||||
| `tsconfig.json` | TypeScript 配置 |
|
||||
| `eslint.config.mjs` | ESLint 配置 |
|
||||
| `biome.json` | Biome 配置 |
|
||||
| `.prettierrc` | Prettier 配置 |
|
||||
| `.jest.js` | Jest 测试配置 |
|
||||
| `.dumirc.ts` | Dumi 文档配置 |
|
||||
| `webpack.config.js` | Webpack 构建配置 |
|
||||
|
||||
---
|
||||
|
||||
## 常见问题和故障排查
|
||||
|
||||
### 开发相关问题
|
||||
|
||||
#### 启动开发服务器失败
|
||||
|
||||
```bash
|
||||
# 确认 Node.js 版本
|
||||
node -v # 应该 >= 18
|
||||
|
||||
# 尝试清理 node_modules 和重新安装
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
|
||||
# 重新生成版本信息
|
||||
npm run version
|
||||
```
|
||||
|
||||
#### 样式不生效
|
||||
|
||||
- 确保已运行 `npm run style` 生成样式文件
|
||||
- 检查 `useStyle` hook 是否正确调用
|
||||
- 确认 `hashId` 和 `cssVarCls` 是否正确应用到类名
|
||||
|
||||
#### TypeScript 类型错误
|
||||
|
||||
```bash
|
||||
# 运行 TypeScript 类型检查
|
||||
npm run tsc
|
||||
|
||||
# 清理构建产物后重新编译
|
||||
npm run clean && npm run compile
|
||||
```
|
||||
|
||||
### 测试相关问题
|
||||
|
||||
#### 快照测试失败
|
||||
|
||||
```bash
|
||||
# 更新快照
|
||||
npm run test:update
|
||||
|
||||
# 按组件更新快照
|
||||
npm test -- --updateSnapshot components/button/__tests__
|
||||
```
|
||||
|
||||
#### 视觉回归测试问题
|
||||
|
||||
```bash
|
||||
# 本地运行视觉回归测试
|
||||
npm run test:visual-regression:local
|
||||
|
||||
# 需要确保 Puppeteer 和相关依赖已正确安装
|
||||
```
|
||||
|
||||
### 构建相关问题
|
||||
|
||||
#### 构建产物体积过大
|
||||
|
||||
```bash
|
||||
# 运行包体积分析
|
||||
npm run size-limit
|
||||
|
||||
# 检查是否有重复依赖包(production 构建)
|
||||
npm run dist
|
||||
|
||||
# 分析 bundle
|
||||
ANALYZER=true npm run dist
|
||||
```
|
||||
|
||||
#### Token 相关问题
|
||||
|
||||
```bash
|
||||
# 重新生成 Token 元数据
|
||||
npm run token:meta
|
||||
|
||||
# 收集 Token 统计
|
||||
npm run token:statistic
|
||||
|
||||
# 重新构建样式
|
||||
npm run style
|
||||
```
|
||||
|
||||
### 国际化问题
|
||||
|
||||
#### 新增多语言配置
|
||||
|
||||
1. 在 `components/locale/` 下添加对应的语言文件
|
||||
2. 更新 `components/locale/index.tsx` 的类型定义
|
||||
3. 确保所有语言配置保持同步
|
||||
|
||||
---
|
||||
|
||||
@@ -695,3 +1169,6 @@ export function TestComp(props) {
|
||||
- [#16048](https://github.com/ant-design/ant-design/issues/16048) - Current listing api & Chinese version
|
||||
- [#25066](https://github.com/ant-design/ant-design/issues/25066) - API standard in the document
|
||||
- [Development Guide](https://github.com/ant-design/ant-design/wiki/Development)
|
||||
- [@ant-design/cssinjs](https://github.com/ant-design/cssinjs) - CSS-in-JS 解决方案
|
||||
- [React 文档](https://react.dev)
|
||||
- [TypeScript 文档](https://www.typescriptlang.org/docs/)
|
||||
|
||||
@@ -187,7 +187,7 @@ $ npm start
|
||||
</table>
|
||||
|
||||
<a href="https://openomy.app/github/ant-design/ant-design" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://openomy.app/svg?repo=ant-design/ant-design&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
<img src="https://openomy.app/svg?repo=ant-design/ant-design&chart=bubble&latestMonth=3" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
|
||||
请参考[贡献指南](https://ant.design/docs/react/contributing-cn).
|
||||
|
||||
14
SECURITY.md
14
SECURITY.md
@@ -2,7 +2,7 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are currently being supported with security updates.
|
||||
Versions of ant-design that are currently supported with security updates:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
@@ -12,6 +12,14 @@ Use this section to tell people about which versions of your project are current
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
Security vulnerabilities in ant-design are handled by the ant-design team.
|
||||
|
||||
Tell them where to go, how often they can expect to get an update on a reported vulnerability, what to expect if the vulnerability is accepted or declined, etc.
|
||||
### How to Report
|
||||
|
||||
If you find a security vulnerability, please **do not** open a public issue. Instead, please send an email to **security@ant.design**. Include details about:
|
||||
|
||||
- The affected version(s)
|
||||
- Steps to reproduce the vulnerability
|
||||
- Potential impact of the vulnerability
|
||||
|
||||
Our team will review your report and respond as soon as possible. We appreciate your help in reporting security issues responsibly.
|
||||
|
||||
@@ -19,7 +19,7 @@ Please note that Affix should not cover other content on the page, especially wh
|
||||
|
||||
> Notes for developers
|
||||
>
|
||||
> After version `5.10.0`, we rewrite Affix use FC, Some methods of obtaining `ref` and calling internal instance methods will invalid.
|
||||
> After version `5.10.0`, we rewrite Affix use FC, Some methods of obtaining `ref` and calling internal instance methods will be invalid.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -4,35 +4,7 @@ exports[`renders components/back-top/demo/basic.tsx correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="css-var-test-id ant-back-top"
|
||||
>
|
||||
<div
|
||||
class="ant-back-top-content"
|
||||
>
|
||||
<div
|
||||
class="ant-back-top-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="vertical-align-top"
|
||||
class="anticon anticon-vertical-align-top"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="vertical-align-top"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M859.9 168H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM518.3 355a8 8 0 00-12.6 0l-112 141.7a7.98 7.98 0 006.3 12.9h73.9V848c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V509.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 355z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
/>,
|
||||
Scroll down to see the bottom-right.,
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -3777,7 +3777,6 @@ exports[`renders components/color-picker/demo/line-gradient.tsx extend context c
|
||||
class="ant-segmented-item"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
@@ -3793,6 +3792,7 @@ exports[`renders components/color-picker/demo/line-gradient.tsx extend context c
|
||||
class="ant-segmented-item ant-segmented-item-selected"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-segmented-item-input"
|
||||
name="test-id"
|
||||
type="radio"
|
||||
|
||||
@@ -110,12 +110,10 @@ const ColorTrigger = forwardRef<HTMLDivElement, ColorTriggerProps>((props, ref)
|
||||
prefixCls={prefixCls}
|
||||
color={color.toCssString()}
|
||||
className={classNames.body}
|
||||
innerClassName={classNames.content}
|
||||
style={styles.body}
|
||||
innerStyle={styles.content}
|
||||
/>
|
||||
),
|
||||
[color, prefixCls, classNames.body, classNames.content, styles.body, styles.content],
|
||||
[color, prefixCls, classNames.body, styles.body],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -59,7 +59,7 @@ Some components use dynamic style to support wave effect. You can config `csp` p
|
||||
| getPopupContainer | To set the container of the popup element. The default is to create a `div` element in `body` | `(trigger?: HTMLElement) => HTMLElement \| ShadowRoot` | () => document.body | |
|
||||
| getTargetContainer | Config Affix, Anchor scroll target container | `() => HTMLElement \| Window \| ShadowRoot` | () => window | 4.2.0 |
|
||||
| iconPrefixCls | Set icon prefix className | string | `anticon` | 4.11.0 |
|
||||
| locale | Language package setting, you can find the packages in [antd/locale](http://unpkg.com/antd/locale/) | object | - | |
|
||||
| locale | Language package setting, you can find the packages in [antd/locale](https://unpkg.com/antd/locale/) | object | - | |
|
||||
| popupMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | - | 5.5.0 |
|
||||
| popupOverflow | Select like component popup logic. Can set to show in viewport or follow window scroll | 'viewport' \| 'scroll' <InlinePopover previewURL="https://user-images.githubusercontent.com/5378891/230344474-5b9f7e09-0a5d-49e8-bae8-7d2abed6c837.png"></InlinePopover> | 'viewport' | 5.5.0 |
|
||||
| prefixCls | Set prefix className | string | `ant` | |
|
||||
|
||||
@@ -60,7 +60,7 @@ export default Demo;
|
||||
| getPopupContainer | 弹出框(Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | `(trigger?: HTMLElement) => HTMLElement \| ShadowRoot` | () => document.body | |
|
||||
| getTargetContainer | 配置 Affix、Anchor 滚动监听容器。 | `() => HTMLElement \| Window \| ShadowRoot` | () => window | 4.2.0 |
|
||||
| iconPrefixCls | 设置图标统一样式前缀 | string | `anticon` | 4.11.0 |
|
||||
| locale | 语言包配置,语言包可到 [antd/locale](http://unpkg.com/antd/locale/) 目录下寻找 | object | - | |
|
||||
| locale | 语言包配置,语言包可到 [antd/locale](https://unpkg.com/antd/locale/) 目录下寻找 | object | - | |
|
||||
| popupMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。`false` 时会关闭虚拟滚动 | boolean \| number | - | 5.5.0 |
|
||||
| popupOverflow | Select 类组件弹层展示逻辑,默认为可视区域滚动,可配置成滚动区域滚动 | 'viewport' \| 'scroll' <InlinePopover previewURL="https://user-images.githubusercontent.com/5378891/230344474-5b9f7e09-0a5d-49e8-bae8-7d2abed6c837.png"></InlinePopover> | 'viewport' | 5.5.0 |
|
||||
| prefixCls | 设置统一样式前缀 | string | `ant` | |
|
||||
|
||||
@@ -317,34 +317,6 @@ Array [
|
||||
</sup>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="vertical-align-top"
|
||||
class="anticon anticon-vertical-align-top"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="vertical-align-top"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M859.9 168H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM518.3 355a8 8 0 00-12.6 0l-112 141.7a7.98 7.98 0 006.3 12.9h73.9V848c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V509.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 355z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
@@ -559,103 +531,6 @@ Array [
|
||||
class="ant-float-btn-group css-var-test-id ant-float-btn-css-var ant-float-btn-group-individual ant-float-btn-group-top ant-float-btn-group-menu-mode"
|
||||
style="inset-inline-end:24px"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-group-list ant-flex css-var-test-id ant-flex-align-stretch ant-flex-vertical"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="comment"
|
||||
class="anticon anticon-comment"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="comment"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<defs>
|
||||
<style />
|
||||
</defs>
|
||||
<path
|
||||
d="M573 421c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40zm-280 0c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40z"
|
||||
/>
|
||||
<path
|
||||
d="M894 345a343.92 343.92 0 00-189-130v.1c-17.1-19-36.4-36.5-58-52.1-163.7-119-393.5-82.7-513 81-96.3 133-92.2 311.9 6 439l.8 132.6c0 3.2.5 6.4 1.5 9.4a31.95 31.95 0 0040.1 20.9L309 806c33.5 11.9 68.1 18.7 102.5 20.6l-.5.4c89.1 64.9 205.9 84.4 313 49l127.1 41.4c3.2 1 6.5 1.6 9.9 1.6 17.7 0 32-14.3 32-32V753c88.1-119.6 90.4-284.9 1-408zM323 735l-12-5-99 31-1-104-8-9c-84.6-103.2-90.2-251.9-11-361 96.4-132.2 281.2-161.4 413-66 132.2 96.1 161.5 280.6 66 412-80.1 109.9-223.5 150.5-348 102zm505-17l-8 10 1 104-98-33-12 5c-56 20.8-115.7 22.5-171 7l-.2-.1A367.31 367.31 0 00729 676c76.4-105.3 88.8-237.6 44.4-350.4l.6.4c23 16.5 44.1 37.1 62 62 72.6 99.6 68.5 235.2-8 330z"
|
||||
/>
|
||||
<path
|
||||
d="M433 421c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-group-trigger ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
@@ -690,103 +565,6 @@ Array [
|
||||
class="ant-float-btn-group css-var-test-id ant-float-btn-css-var ant-float-btn-group-top ant-float-btn-group-menu-mode"
|
||||
style="inset-inline-end:88px"
|
||||
>
|
||||
<div
|
||||
class="ant-space-compact ant-space-compact-vertical ant-float-btn-group-list"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg ant-btn-compact-vertical-item ant-btn-compact-vertical-first-item css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-square ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg ant-btn-compact-vertical-item css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-square ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg ant-btn-compact-vertical-item ant-btn-compact-vertical-last-item css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-square ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="comment"
|
||||
class="anticon anticon-comment"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="comment"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<defs>
|
||||
<style />
|
||||
</defs>
|
||||
<path
|
||||
d="M573 421c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40zm-280 0c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40z"
|
||||
/>
|
||||
<path
|
||||
d="M894 345a343.92 343.92 0 00-189-130v.1c-17.1-19-36.4-36.5-58-52.1-163.7-119-393.5-82.7-513 81-96.3 133-92.2 311.9 6 439l.8 132.6c0 3.2.5 6.4 1.5 9.4a31.95 31.95 0 0040.1 20.9L309 806c33.5 11.9 68.1 18.7 102.5 20.6l-.5.4c89.1 64.9 205.9 84.4 313 49l127.1 41.4c3.2 1 6.5 1.6 9.9 1.6 17.7 0 32-14.3 32-32V753c88.1-119.6 90.4-284.9 1-408zM323 735l-12-5-99 31-1-104-8-9c-84.6-103.2-90.2-251.9-11-361 96.4-132.2 281.2-161.4 413-66 132.2 96.1 161.5 280.6 66 412-80.1 109.9-223.5 150.5-348 102zm505-17l-8 10 1 104-98-33-12 5c-56 20.8-115.7 22.5-171 7l-.2-.1A367.31 367.31 0 00729 676c76.4-105.3 88.8-237.6 44.4-350.4l.6.4c23 16.5 44.1 37.1 62 62 72.6 99.6 68.5 235.2-8 330z"
|
||||
/>
|
||||
<path
|
||||
d="M433 421c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-group-trigger ant-float-btn-default ant-float-btn-square ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
@@ -888,34 +666,6 @@ Array [
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="vertical-align-top"
|
||||
class="anticon anticon-vertical-align-top"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="vertical-align-top"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M859.9 168H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM518.3 355a8 8 0 00-12.6 0l-112 141.7a7.98 7.98 0 006.3 12.9h73.9V848c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V509.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 355z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
@@ -1012,34 +762,6 @@ Array [
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg ant-btn-compact-vertical-item ant-btn-compact-vertical-last-item css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-square ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="vertical-align-top"
|
||||
class="anticon anticon-vertical-align-top"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="vertical-align-top"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M859.9 168H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM518.3 355a8 8 0 00-12.6 0l-112 141.7a7.98 7.98 0 006.3 12.9h73.9V848c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V509.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 355z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
@@ -1264,34 +986,6 @@ exports[`renders components/float-button/demo/render-panel.tsx correctly 1`] = `
|
||||
<div
|
||||
style="display:flex;column-gap:16px;align-items:center"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-pure ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="vertical-align-top"
|
||||
class="anticon anticon-vertical-align-top"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="vertical-align-top"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M859.9 168H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM518.3 355a8 8 0 00-12.6 0l-112 141.7a7.98 7.98 0 006.3 12.9h73.9V848c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V509.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 355z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-pure ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
@@ -1454,97 +1148,6 @@ exports[`renders components/float-button/demo/render-panel.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-float-btn-group css-var-test-id ant-float-btn-css-var ant-float-btn-pure ant-float-btn-group-individual ant-float-btn-group-top ant-float-btn-group-menu-mode"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-group-list ant-flex css-var-test-id ant-flex-align-stretch ant-flex-vertical"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="question-circle"
|
||||
class="anticon anticon-question-circle"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="question-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"
|
||||
/>
|
||||
<path
|
||||
d="M623.6 316.7C593.6 290.4 554 276 512 276s-81.6 14.5-111.6 40.7C369.2 344 352 380.7 352 420v7.6c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V420c0-44.1 43.1-80 96-80s96 35.9 96 80c0 31.1-22 59.6-56.1 72.7-21.2 8.1-39.2 22.3-52.1 40.9-13.1 19-19.9 41.8-19.9 64.9V620c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-22.7a48.3 48.3 0 0130.9-44.8c59-22.7 97.1-74.7 97.1-132.5.1-39.3-17.1-76-48.3-103.3zM472 732a40 40 0 1080 0 40 40 0 10-80 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="customer-service"
|
||||
class="anticon anticon-customer-service"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="customer-service"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 128c-212.1 0-384 171.9-384 384v360c0 13.3 10.7 24 24 24h184c35.3 0 64-28.7 64-64V624c0-35.3-28.7-64-64-64H200v-48c0-172.3 139.7-312 312-312s312 139.7 312 312v48H688c-35.3 0-64 28.7-64 64v208c0 35.3 28.7 64 64 64h184c13.3 0 24-10.7 24-24V512c0-212.1-171.9-384-384-384zM328 632v192H200V632h128zm496 192H696V632h128v192z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="sync"
|
||||
class="anticon anticon-sync"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="sync"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M168 504.2c1-43.7 10-86.1 26.9-126 17.3-41 42.1-77.7 73.7-109.4S337 212.3 378 195c42.4-17.9 87.4-27 133.9-27s91.5 9.1 133.8 27A341.5 341.5 0 01755 268.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 003 14.1l175.7 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c0-6.7-7.7-10.5-12.9-6.3l-56.4 44.1C765.8 155.1 646.2 92 511.8 92 282.7 92 96.3 275.6 92 503.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8zm756 7.8h-60c-4.4 0-7.9 3.5-8 7.8-1 43.7-10 86.1-26.9 126-17.3 41-42.1 77.8-73.7 109.4A342.45 342.45 0 01512.1 856a342.24 342.24 0 01-243.2-100.8c-9.9-9.9-19.2-20.4-27.8-31.4l60.2-47a8 8 0 00-3-14.1l-175.7-43c-5-1.2-9.9 2.6-9.9 7.7l-.7 181c0 6.7 7.7 10.5 12.9 6.3l56.4-44.1C258.2 868.9 377.8 932 512.2 932c229.2 0 415.5-183.7 419.8-411.8a8 8 0 00-8-8.2z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-circle ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-lg css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-group-trigger ant-float-btn-default ant-float-btn-circle ant-float-btn-individual ant-float-btn-icon-only"
|
||||
type="button"
|
||||
|
||||
@@ -55,7 +55,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
|
||||
If the Ant Design grid layout component does not meet your needs, you can use the excellent layout components of the community:
|
||||
|
||||
- [react-flexbox-grid](http://roylee0704.github.io/react-flexbox-grid/)
|
||||
- [react-flexbox-grid](https://roylee0704.github.io/react-flexbox-grid/)
|
||||
- [react-blocks](https://github.com/whoisandy/react-blocks/)
|
||||
|
||||
### Row
|
||||
|
||||
@@ -54,7 +54,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*DLUwQ4B2_zQAAA
|
||||
|
||||
Ant Design 的布局组件若不能满足你的需求,你也可以直接使用社区的优秀布局组件:
|
||||
|
||||
- [react-flexbox-grid](http://roylee0704.github.io/react-flexbox-grid/)
|
||||
- [react-flexbox-grid](https://roylee0704.github.io/react-flexbox-grid/)
|
||||
- [react-blocks](https://github.com/whoisandy/react-blocks/)
|
||||
|
||||
### Row
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
对于使用 [iconfont.cn](http://iconfont.cn/) 的用户,通过设置 `createFromIconfontCN` 方法参数对象中的 `scriptUrl` 字段, 即可轻松地使用已有项目中的图标。
|
||||
对于使用 [iconfont.cn](https://iconfont.cn/) 的用户,通过设置 `createFromIconfontCN` 方法参数对象中的 `scriptUrl` 字段, 即可轻松地使用已有项目中的图标。
|
||||
|
||||
## en-US
|
||||
|
||||
If you are using [iconfont.cn](http://iconfont.cn/), you can use the icons in your project gracefully.
|
||||
If you are using [iconfont.cn](https://iconfont.cn/), you can use the icons in your project gracefully.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
`@ant-design/icons@4.1.0` 以后,`scriptUrl` 可引用多个资源,用户可灵活的管理 [iconfont.cn](http://iconfont.cn/) 图标。如果资源的图标出现重名,会按照数组顺序进行覆盖。
|
||||
`@ant-design/icons@4.1.0` 以后,`scriptUrl` 可引用多个资源,用户可灵活的管理 [iconfont.cn](https://iconfont.cn/) 图标。如果资源的图标出现重名,会按照数组顺序进行覆盖。
|
||||
|
||||
## en-US
|
||||
|
||||
You can use `scriptUrl` as an array after `@ant-design/icons@4.1.0`, to manage icons in one `<Icon />` from multiple [iconfont.cn](http://iconfont.cn/) resources. If an icon with a duplicate name is in resources, it will be overridden in array order.
|
||||
You can use `scriptUrl` as an array after `@ant-design/icons@4.1.0`, to manage icons in one `<Icon />` from multiple [iconfont.cn](https://iconfont.cn/) resources. If an icon with a duplicate name is in resources, it will be overridden in array order.
|
||||
|
||||
@@ -104,9 +104,9 @@ getTwoToneColor(); // #eb2f96
|
||||
|
||||
### Custom Font Icon
|
||||
|
||||
We added a `createFromIconfontCN` function to help developer use their own icons deployed at [iconfont.cn](http://iconfont.cn/) in a convenient way.
|
||||
We added a `createFromIconfontCN` function to help developer use their own icons deployed at [iconfont.cn](https://iconfont.cn/) in a convenient way.
|
||||
|
||||
> This method is specified for [iconfont.cn](http://iconfont.cn/).
|
||||
> This method is specified for [iconfont.cn](https://iconfont.cn/).
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
@@ -127,11 +127,11 @@ The following options are available:
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| extraCommonProps | Define extra properties to the component | { \[key: string]: any } | {} | |
|
||||
| scriptUrl | The URL generated by [iconfont.cn](http://iconfont.cn/) project. Support `string[]` after `@ant-design/icons@4.1.0` | string \| string\[] | - | |
|
||||
| scriptUrl | The URL generated by [iconfont.cn](https://iconfont.cn/) project. Support `string[]` after `@ant-design/icons@4.1.0` | string \| string\[] | - | |
|
||||
|
||||
The property `scriptUrl` should be set to import the SVG sprite symbols.
|
||||
|
||||
See [iconfont.cn documents](http://iconfont.cn/help/detail?spm=a313x.7781069.1998910419.15&helptype=code) to learn about how to generate `scriptUrl`.
|
||||
See [iconfont.cn documents](https://iconfont.cn/help/detail?spm=a313x.7781069.1998910419.15&helptype=code) to learn about how to generate `scriptUrl`.
|
||||
|
||||
### Custom SVG Icon
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ getTwoToneColor(); // #eb2f96
|
||||
|
||||
### 自定义 font 图标 {#custom-font-icon}
|
||||
|
||||
在 `3.9.0` 之后,我们提供了一个 `createFromIconfontCN` 方法,方便开发者调用在 [iconfont.cn](http://iconfont.cn/) 上自行管理的图标。
|
||||
在 `3.9.0` 之后,我们提供了一个 `createFromIconfontCN` 方法,方便开发者调用在 [iconfont.cn](https://iconfont.cn/) 上自行管理的图标。
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
@@ -122,11 +122,11 @@ options 的配置项如下:
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| extraCommonProps | 给所有的 `svg` 图标 `<Icon />` 组件设置额外的属性 | { \[key: string]: any } | {} | |
|
||||
| scriptUrl | [iconfont.cn](http://iconfont.cn/) 项目在线生成的 js 地址,`@ant-design/icons@4.1.0` 之后支持 `string[]` 类型 | string \| string\[] | - | |
|
||||
| scriptUrl | [iconfont.cn](https://iconfont.cn/) 项目在线生成的 js 地址,`@ant-design/icons@4.1.0` 之后支持 `string[]` 类型 | string \| string\[] | - | |
|
||||
|
||||
在 `scriptUrl` 都设置有效的情况下,组件在渲染前会自动引入 [iconfont.cn](http://iconfont.cn/) 项目中的图标符号集,无需手动引入。
|
||||
在 `scriptUrl` 都设置有效的情况下,组件在渲染前会自动引入 [iconfont.cn](https://iconfont.cn/) 项目中的图标符号集,无需手动引入。
|
||||
|
||||
见 [iconfont.cn 使用帮助](http://iconfont.cn/help/detail?spm=a313x.7781069.1998910419.15&helptype=code) 查看如何生成 js 地址。
|
||||
见 [iconfont.cn 使用帮助](https://iconfont.cn/help/detail?spm=a313x.7781069.1998910419.15&helptype=code) 查看如何生成 js 地址。
|
||||
|
||||
### 自定义 SVG 图标 {#custom-svg-icon}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ Added in `5.16.0`.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Why Input lose focus when change `prefix/suffix/showCount` {#faq-lose-focus}
|
||||
### Why Input loses focus when change `prefix/suffix/showCount` {#faq-lose-focus}
|
||||
|
||||
When Input dynamic add or remove `prefix/suffix/showCount` will make React recreate the dom structure and new input will be not focused. You can set an empty `<span />` element to keep the dom structure:
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| showTitle | Show page item's title | boolean | true | |
|
||||
| showTotal | To display the total number and range | function(total, range) | - | |
|
||||
| simple | Whether to use simple mode | boolean \| { readOnly?: boolean } | - | |
|
||||
| size | Component size | `large` \| `middle` \| `small` | `middle` | |
|
||||
| size | Component size | `large` \| `medium` \| `small` | `medium` | |
|
||||
| styles | Customize inline style for each semantic structure inside the component. Supports object or function | Record<[SemanticDOM](#semantic-dom), CSSProperties> \| (info: { props }) => Record<[SemanticDOM](#semantic-dom), CSSProperties> | - | |
|
||||
| total | Total number of data items | number | 0 | |
|
||||
| onChange | Called when the page number or `pageSize` is changed, and it takes the resulting page number and pageSize as its arguments | function(page, pageSize) | - | |
|
||||
|
||||
@@ -59,7 +59,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*WM86SrBC8TsAAA
|
||||
| showTitle | 是否显示原生 tooltip 页码提示 | boolean | true | |
|
||||
| showTotal | 用于显示数据总量和当前数据顺序 | function(total, range) | - | |
|
||||
| simple | 当添加该属性时,显示为简单分页 | boolean \| { readOnly?: boolean } | - | |
|
||||
| size | 组件尺寸 | `large` \| `middle` \| `small` | `middle` | |
|
||||
| size | 组件尺寸 | `large` \| `medium` \| `small` | `medium` | |
|
||||
| styles | 自定义组件内部各语义化结构的内联样式。支持对象或函数 | Record<[SemanticDOM](#semantic-dom), CSSProperties> \| (info: { props }) => Record<[SemanticDOM](#semantic-dom), CSSProperties> | - | |
|
||||
| total | 数据总数 | number | 0 | |
|
||||
| onChange | 页码或 `pageSize` 改变的回调,参数是改变后的页码及每页条数 | function(page, pageSize) | - | |
|
||||
|
||||
@@ -297,6 +297,10 @@ const genSelectInputStyle: GenerateStyle<SelectToken> = (token) => {
|
||||
|
||||
[`&${componentCls}-open ${componentCls}-content`]: {
|
||||
color: token.colorTextPlaceholder,
|
||||
|
||||
'&-has-search-value': {
|
||||
color: 'transparent',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -56,7 +56,7 @@ dayjs.extend(customParseFormat)
|
||||
| changeOnScroll | Trigger selection when scroll the column | boolean | false | 5.14.0 |
|
||||
| className | The className of picker | string | - | |
|
||||
| classNames | Customize class for each semantic structure inside the component. Supports object or function. | Record<[SemanticDOM](#semantic-dom), string> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), string> | - | |
|
||||
| defaultValue | To set default time | [dayjs](http://day.js.org/) | - | |
|
||||
| defaultValue | To set default time | [dayjs](https://day.js.org/) | - | |
|
||||
| disabled | Determine whether the TimePicker is disabled | boolean | false | |
|
||||
| disabledTime | To specify the time that cannot be selected | [DisabledTime](#disabledtime) | - | 4.19.0 |
|
||||
| format | To set the time format | string | `HH:mm:ss` | |
|
||||
@@ -81,7 +81,7 @@ dayjs.extend(customParseFormat)
|
||||
| styles | Customize inline style for each semantic structure inside the component. Supports object or function. | Record<[SemanticDOM](#semantic-dom), CSSProperties> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), CSSProperties> | - | |
|
||||
| suffixIcon | The custom suffix icon | ReactNode | - | |
|
||||
| use12Hours | Display as 12 hours format, with default format `h:mm:ss a` | boolean | false | |
|
||||
| value | To set time | [dayjs](http://day.js.org/) | - | |
|
||||
| value | To set time | [dayjs](https://day.js.org/) | - | |
|
||||
| variant | Variants of picker | `outlined` \| `borderless` \| `filled` \| `underlined` | `outlined` | 5.13.0 \| `underlined`: 5.24.0 |
|
||||
| onCalendarChange | Callback function, can be executed when the start time or the end time of the range is changing. `info` argument is added in 4.4.0 | function(dates: \[dayjs, dayjs], dateStrings: \[string, string], info: { range:`start`\|`end` }) | - | |
|
||||
| onChange | A callback function, can be executed when the selected time is changing | function(time: dayjs, timeString: string): void | - | |
|
||||
|
||||
@@ -11,7 +11,7 @@ This is due to the implementation of `@rc-component/trigger`. `@rc-component/tri
|
||||
|
||||
Similar issues: [#15909](https://github.com/ant-design/ant-design/issues/15909), [#12812](https://github.com/ant-design/ant-design/issues/12812).
|
||||
|
||||
Please ensure that the child node to accept `onMouseEnter`, `onMouseLeave`, `onPointerEnter`, `onPointerLeave`, `onFocus`, and `onClick` events, If you create your own component and do not explicitly add these mouse and pointer events as props, the tooltip will never appear. [See Example](http://ant.design/components/tooltip#tooltip-demo-wrap-custom-component).
|
||||
Please ensure that the child node to accept `onMouseEnter`, `onMouseLeave`, `onPointerEnter`, `onPointerLeave`, `onFocus`, and `onClick` events, If you create your own component and do not explicitly add these mouse and pointer events as props, the tooltip will never appear. [See Example](/components/tooltip#tooltip-demo-wrap-custom-component).
|
||||
|
||||
### What's the placement logic?
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ const genPictureCardStyle: GenerateStyle<UploadToken> = (token) => {
|
||||
[`${listCls}${listCls}-picture-card, ${listCls}${listCls}-picture-circle`]: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
height: uploadPictureCardSize,
|
||||
minHeight: uploadPictureCardSize,
|
||||
|
||||
'@supports not (gap: 1px)': {
|
||||
'& > *': {
|
||||
|
||||
@@ -78,11 +78,11 @@ Runs Ant Design website locally.
|
||||
|
||||
<InstallDependencies npm='$ npm start' yarn='$ yarn start'></InstallDependencies>
|
||||
|
||||
### Checks the code style
|
||||
### Check the code style
|
||||
|
||||
<InstallDependencies npm='$ npm run lint' yarn='$ yarn lint'></InstallDependencies>
|
||||
|
||||
### Run test
|
||||
### Run tests
|
||||
|
||||
runs the complete test suite. (Make sure the `NODE_ENV` environment variable is unset, or it may causing some problems.)
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ If you encounter build errors during the upgrade, please verify that your `@ant-
|
||||
|
||||
- `Breadcrumb`
|
||||
- `routes` is deprecated and replaced by `items`.
|
||||
- `Breadcrumb.Item` 和 `Breadcrumb.Separator` is deprecated and replaced by `items`.
|
||||
- `Breadcrumb.Item` and `Breadcrumb.Separator` are deprecated and replaced by `items`.
|
||||
|
||||
- `Button.Group`
|
||||
- `Button.Group` is deprecated and replaced by `Space.Compact`.
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
"@rc-component/input-number": "~1.6.2",
|
||||
"@rc-component/mentions": "~1.6.0",
|
||||
"@rc-component/menu": "~1.2.0",
|
||||
"@rc-component/motion": "~1.1.6",
|
||||
"@rc-component/motion": "^1.3.1",
|
||||
"@rc-component/mutate-observer": "^2.0.1",
|
||||
"@rc-component/notification": "~1.2.0",
|
||||
"@rc-component/pagination": "~1.2.0",
|
||||
@@ -173,7 +173,7 @@
|
||||
"@emotion/css": "^11.13.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@eslint-react/eslint-plugin": "2.12.2",
|
||||
"@eslint-react/eslint-plugin": "2.12.4",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
||||
"@inquirer/prompts": "^8.0.2",
|
||||
"@madccc/duplicate-package-checker-webpack-plugin": "^1.0.0",
|
||||
|
||||
Reference in New Issue
Block a user