mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-18 07:12:28 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
375463e21d | ||
|
|
3b52894cf3 | ||
|
|
ad9149ba24 | ||
|
|
95f032bc3d | ||
|
|
5dfaf14db7 | ||
|
|
5ad1ecd723 | ||
|
|
9b03f7ae6c | ||
|
|
852ed686d2 | ||
|
|
47159dd3b2 | ||
|
|
70f564603b | ||
|
|
d534dd8b17 | ||
|
|
d271104014 | ||
|
|
6915342818 | ||
|
|
956c1d1b51 | ||
|
|
6e993d2ef8 | ||
|
|
a46cd9d8ba | ||
|
|
75819fc3a7 | ||
|
|
a900301eac | ||
|
|
06c9af5e98 | ||
|
|
bf8822b817 | ||
|
|
5a07a38c4b | ||
|
|
cd9e9aecf5 | ||
|
|
bf370c4af5 | ||
|
|
79eb03d314 | ||
|
|
0cfcd0873a | ||
|
|
2ab7472d97 |
@@ -31,7 +31,7 @@ export const useIssueCount = (options: UseIssueCountOptions) => {
|
||||
|
||||
// Note: current query only filters by title keywords. Filtering by component name can be added later if needed.
|
||||
const searchUrl = useMemo(() => {
|
||||
const tokens = (titleKeywords || []).filter(Boolean).map((k) => encodeURIComponent(String(k)));
|
||||
const tokens = (titleKeywords || []).filter(Boolean).map<string>(encodeURIComponent);
|
||||
const orExpr = tokens.length > 0 ? tokens.join('%20OR%20') : '';
|
||||
const titlePart = orExpr ? `in:title+(${orExpr})` : 'in:title';
|
||||
const q = `repo:${repo}+is:issue+is:open+${titlePart}`;
|
||||
@@ -45,7 +45,7 @@ export const useIssueCount = (options: UseIssueCountOptions) => {
|
||||
const issueNewUrl = `https://github.com/${repo}/issues/new/choose`;
|
||||
|
||||
const issueSearchUrl = useMemo(() => {
|
||||
const keywords = (titleKeywords || []).filter(Boolean).map((k) => String(k));
|
||||
const keywords = (titleKeywords || []).filter(Boolean).map<string>(String);
|
||||
const groupExpr =
|
||||
keywords.length > 0 ? `(${keywords.map((k) => `is:issue in:title ${k}`).join(' OR ')})` : '';
|
||||
const qRaw = `is:open ${groupExpr}`.trim();
|
||||
|
||||
@@ -113,8 +113,10 @@ const RecommendItem: React.FC<RecommendItemProps> = (props) => {
|
||||
const [mousePosition, setMousePosition] = React.useState<[number, number]>([0, 0]);
|
||||
const [transMousePosition, setTransMousePosition] = React.useState<[number, number]>([0, 0]);
|
||||
|
||||
const onMouseMove = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
if (!cardRef.current) return;
|
||||
const onMouseMove: React.MouseEventHandler<HTMLAnchorElement> = (e) => {
|
||||
if (!cardRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = cardRef.current.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
|
||||
@@ -29,13 +29,13 @@ const VersionUpgradeModal = () => {
|
||||
const [locale, lang] = useLocale(locales);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const [open, updateOpen] = React.useState(false);
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const isCN = lang === 'cn' || utils.isZhCN(pathname);
|
||||
|
||||
function handleClose() {
|
||||
localStorage.setItem(STORAGE_KEY, Date.now().toString());
|
||||
updateOpen(false);
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -48,7 +48,7 @@ const VersionUpgradeModal = () => {
|
||||
|
||||
if (!lastTime) {
|
||||
const timer = setTimeout(() => {
|
||||
updateOpen(true);
|
||||
setOpen(true);
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -101,7 +101,9 @@ let TOKEN_CACHE: { meta: TokenMeta; data: TokenData } | null | undefined;
|
||||
*/
|
||||
function readJsonIfExists<T>(abs: string): T | null {
|
||||
try {
|
||||
if (!fs.existsSync(abs)) return null;
|
||||
if (!fs.existsSync(abs)) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(fs.readFileSync(abs, 'utf-8')) as T;
|
||||
} catch {
|
||||
return null;
|
||||
@@ -144,7 +146,9 @@ function replaceSemanticDomSection(md: string, context: ContentFilterContext) {
|
||||
// 从文档路径推断组件路径(用于生成链接)
|
||||
// 例如:components/card/index.en-US.md -> components/card/semantic.md
|
||||
const componentPathMatch = context.file.match(/components\/([^/]+)\//);
|
||||
if (!componentPathMatch) return md;
|
||||
if (!componentPathMatch) {
|
||||
return md;
|
||||
}
|
||||
|
||||
const componentName = componentPathMatch[1];
|
||||
const isZhCN = /-cn\.md$/i.test(context.file) || /\.zh-CN\.md$/i.test(context.file);
|
||||
@@ -154,10 +158,14 @@ function replaceSemanticDomSection(md: string, context: ContentFilterContext) {
|
||||
return md.replace(/<code[^>]*_semantic[^>]*>.*?<\/code>/g, (match) => {
|
||||
// 从匹配的标签中提取文件名
|
||||
const demoIndex = match.indexOf('./demo/');
|
||||
if (demoIndex === -1) return match;
|
||||
if (demoIndex === -1) {
|
||||
return match;
|
||||
}
|
||||
const start = demoIndex + './demo/'.length;
|
||||
const end = match.indexOf('"', start);
|
||||
if (end === -1) return match;
|
||||
if (end === -1) {
|
||||
return match;
|
||||
}
|
||||
const semanticFile = match.substring(start, end);
|
||||
// 生成对应的 semantic.md 文件名:_semantic.tsx -> semantic.md, _semantic_meta.tsx -> semantic_meta.md
|
||||
const semanticMdFileName = semanticFile
|
||||
@@ -180,7 +188,9 @@ function getMaxBacktickRun(text: string) {
|
||||
let m: RegExpExecArray | null = re.exec(text);
|
||||
|
||||
while (m) {
|
||||
if (m[0].length > max) max = m[0].length;
|
||||
if (m[0].length > max) {
|
||||
max = m[0].length;
|
||||
}
|
||||
m = re.exec(text);
|
||||
}
|
||||
return max;
|
||||
@@ -244,7 +254,9 @@ function antdCodeAppend(docFileAbs: string, src: string): string {
|
||||
'i',
|
||||
);
|
||||
const match = demoMd.match(re);
|
||||
if (!match) return demoMd.trim();
|
||||
if (!match) {
|
||||
return demoMd.trim();
|
||||
}
|
||||
return (match[2] ?? '').trim();
|
||||
}
|
||||
|
||||
@@ -255,7 +267,7 @@ function antdCodeAppend(docFileAbs: string, src: string): string {
|
||||
*
|
||||
* @param md - 原始 markdown 内容
|
||||
* @param docFileAbs - 文档文件的绝对路径,用于解析相对路径和检测语言
|
||||
* @param enablePickLocaleBlock - 是否启用多语言块提取,可以是布尔值或函数,默认为 true
|
||||
* @param codeAppend - 代码追加函数:在替换 <code src> 标签时,用于追加额外的内容(如 demo 描述信息)
|
||||
* @returns 替换后的 markdown 内容
|
||||
*/
|
||||
function replaceCodeSrcToMarkdown(
|
||||
@@ -305,8 +317,9 @@ function replaceCodeSrcToMarkdown(
|
||||
* @returns token 元数据和数据对象,如果文件不存在则返回 null
|
||||
*/
|
||||
function loadTokenFromRepo(api: IApi) {
|
||||
if (TOKEN_CACHE !== undefined) return TOKEN_CACHE;
|
||||
|
||||
if (TOKEN_CACHE !== undefined) {
|
||||
return TOKEN_CACHE;
|
||||
}
|
||||
const cwd = api.paths.cwd;
|
||||
const metaPath = path.join(cwd, 'components', 'version', 'token-meta.json');
|
||||
const dataPath = path.join(cwd, 'components', 'version', 'token.json');
|
||||
@@ -345,9 +358,15 @@ function escapeMdCell(v: unknown) {
|
||||
* @returns 规范化后的字符串,null/undefined 返回空字符串
|
||||
*/
|
||||
function normalizeValue(v: unknown) {
|
||||
if (v === undefined || v === null) return '';
|
||||
if (typeof v === 'string') return v.trim();
|
||||
if (typeof v === 'number' || typeof v === 'boolean') return String(v);
|
||||
if (v === undefined || v === null) {
|
||||
return '';
|
||||
}
|
||||
if (typeof v === 'string') {
|
||||
return v.trim();
|
||||
}
|
||||
if (typeof v === 'number' || typeof v === 'boolean') {
|
||||
return String(v);
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(v);
|
||||
} catch {
|
||||
@@ -369,7 +388,9 @@ function normalizeValue(v: unknown) {
|
||||
*/
|
||||
function replaceComponentTokenTable(md: string, context: ContentFilterContext) {
|
||||
const tokens = loadTokenFromRepo(context.api);
|
||||
if (!tokens) return md;
|
||||
if (!tokens) {
|
||||
return md;
|
||||
}
|
||||
|
||||
const { meta: tokenMeta, data: tokenData } = tokens;
|
||||
const locale = detectDocLocale(context.file);
|
||||
@@ -398,7 +419,9 @@ function replaceComponentTokenTable(md: string, context: ContentFilterContext) {
|
||||
|
||||
return md.replace(re, (full, componentProp) => {
|
||||
const comp = String(componentProp || '').trim();
|
||||
if (!comp) return full;
|
||||
if (!comp) {
|
||||
return full;
|
||||
}
|
||||
|
||||
const comps = comp
|
||||
.split(',')
|
||||
@@ -474,7 +497,9 @@ function replaceComponentTokenTable(md: string, context: ContentFilterContext) {
|
||||
}
|
||||
|
||||
// 如果没有生成任何内容,则保留原标签
|
||||
if (!out.length) return full;
|
||||
if (!out.length) {
|
||||
return full;
|
||||
}
|
||||
// 返回生成的 markdown 表格,前后添加换行确保格式正确
|
||||
return `\n\n${out.join('\n').trim()}\n\n`;
|
||||
});
|
||||
@@ -489,8 +514,12 @@ function replaceComponentTokenTable(md: string, context: ContentFilterContext) {
|
||||
* @param api - Dumi API 实例,用于获取输出路径等配置
|
||||
*/
|
||||
function emitRawMd(api: IApi) {
|
||||
if (process.env.NODE_ENV !== 'production') return;
|
||||
if (RAW_MD_EMITTED) return;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return;
|
||||
}
|
||||
if (RAW_MD_EMITTED) {
|
||||
return;
|
||||
}
|
||||
RAW_MD_EMITTED = true;
|
||||
|
||||
const outRoot = api.paths.absOutputPath;
|
||||
@@ -499,7 +528,9 @@ function emitRawMd(api: IApi) {
|
||||
try {
|
||||
const { absPath, file } = route;
|
||||
const relPath = absPath.replace(/^\//, '');
|
||||
if (!relPath || !fs.existsSync(file)) return;
|
||||
if (!relPath || !fs.existsSync(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 应用路由过滤器
|
||||
if (PLUGIN_OPTIONS.routeFilter && !PLUGIN_OPTIONS.routeFilter(route)) {
|
||||
@@ -556,7 +587,6 @@ function emitRawMd(api: IApi) {
|
||||
* 2. 在 HTML 文件导出阶段输出处理后的 raw markdown 文件
|
||||
*
|
||||
* @param api - Dumi API 实例
|
||||
* @param options - 插件配置选项
|
||||
*/
|
||||
export default function rawMdPlugin(api: IApi) {
|
||||
// 注册配置键,允许用户在配置中使用 rawMd 键
|
||||
|
||||
@@ -19,8 +19,8 @@ function extractSemantics(objContent: string): Record<string, string> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 _semantic*.tsx 文件中提取语义信息
|
||||
* @param semanticFile - _semantic*.tsx 文件的绝对路径
|
||||
* 从 _semantic*.tsx 文件内容中提取语义信息
|
||||
* @param content - _semantic*.tsx 文件的文件内容字符串
|
||||
* @returns 包含中文和英文语义描述的对象,失败返回 null
|
||||
*/
|
||||
function extractLocaleInfoFromContent(content: string): {
|
||||
@@ -29,14 +29,20 @@ function extractLocaleInfoFromContent(content: string): {
|
||||
} | null {
|
||||
// 匹配 locales 对象定义
|
||||
const localesMatch = content.match(/const locales = \{([\s\S]*?)\};/);
|
||||
if (!localesMatch) return null;
|
||||
if (!localesMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取中文和英文的语义描述
|
||||
const cnMatch = content.match(/cn:\s*\{([\s\S]*?)\},?\s*en:/);
|
||||
if (!cnMatch) return null;
|
||||
if (!cnMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const enMatch = content.match(/en:\s*\{([\s\S]*?)\}\s*[,;]/);
|
||||
if (!enMatch) return null;
|
||||
if (!enMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cnContent = cnMatch[1];
|
||||
const enContent = enMatch[1];
|
||||
@@ -44,7 +50,9 @@ function extractLocaleInfoFromContent(content: string): {
|
||||
const cnSemantics = extractSemantics(cnContent);
|
||||
const enSemantics = extractSemantics(enContent);
|
||||
|
||||
if (Object.keys(cnSemantics).length === 0) return null;
|
||||
if (Object.keys(cnSemantics).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { cn: cnSemantics, en: enSemantics };
|
||||
}
|
||||
@@ -60,7 +68,9 @@ function resolveTemplateFilePath(semanticFile: string, importPath: string): stri
|
||||
path.join(basePath, 'index.ts'),
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
if (fs.existsSync(candidate)) return candidate;
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -72,7 +82,9 @@ function parseTemplateUsage(content: string): Array<{ componentName: string; imp
|
||||
for (const match of content.matchAll(importRegex)) {
|
||||
const importClause = match[1].trim();
|
||||
const importPath = match[2].trim();
|
||||
if (!importPath.startsWith('.')) continue;
|
||||
if (!importPath.startsWith('.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const componentNames: string[] = [];
|
||||
if (importClause.startsWith('{')) {
|
||||
@@ -119,7 +131,9 @@ function parseTemplateUsage(content: string): Array<{ componentName: string; imp
|
||||
// 解析 ignoreSemantics 属性值
|
||||
function parseIgnoreSemantics(propsString: string): string[] {
|
||||
const ignoreMatch = propsString.match(/ignoreSemantics\s*=\s*\{([\s\S]*?)\}/);
|
||||
if (!ignoreMatch) return [];
|
||||
if (!ignoreMatch) {
|
||||
return [];
|
||||
}
|
||||
const ignoreContent = ignoreMatch[1];
|
||||
return Array.from(ignoreContent.matchAll(/['"]([^'"]+)['"]/g)).map((match) => match[1]);
|
||||
}
|
||||
@@ -127,8 +141,12 @@ function parseIgnoreSemantics(propsString: string): string[] {
|
||||
// 解析 singleOnly 属性值
|
||||
function parseSingleOnly(propsString: string): boolean {
|
||||
const singleOnlyMatch = propsString.match(/singleOnly(\s*=\s*\{?([^}\s]+)\}?)?/);
|
||||
if (!singleOnlyMatch) return false;
|
||||
if (!singleOnlyMatch[1]) return true;
|
||||
if (!singleOnlyMatch) {
|
||||
return false;
|
||||
}
|
||||
if (!singleOnlyMatch[1]) {
|
||||
return true;
|
||||
}
|
||||
const value = singleOnlyMatch[2];
|
||||
return value !== 'false';
|
||||
}
|
||||
@@ -136,7 +154,9 @@ function parseSingleOnly(propsString: string): boolean {
|
||||
// 抽取模板组件 JSX 的属性字符串
|
||||
function extractTemplateProps(content: string, componentName: string): string {
|
||||
const start = content.indexOf(`<${componentName}`);
|
||||
if (start === -1) return '';
|
||||
if (start === -1) {
|
||||
return '';
|
||||
}
|
||||
let index = start + componentName.length + 1;
|
||||
const propsStart = index;
|
||||
let braceDepth = 0;
|
||||
@@ -164,7 +184,9 @@ function extractTemplateProps(content: string, componentName: string): string {
|
||||
}
|
||||
|
||||
if (ch === '}') {
|
||||
if (braceDepth > 0) braceDepth -= 1;
|
||||
if (braceDepth > 0) {
|
||||
braceDepth -= 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -198,15 +220,21 @@ function extractSemanticInfoFromTemplate(
|
||||
content: string,
|
||||
): { cn: Record<string, string>; en: Record<string, string> } | null {
|
||||
const templates = parseTemplateUsage(content);
|
||||
if (templates.length === 0) return null;
|
||||
if (templates.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const template of templates) {
|
||||
const templatePath = resolveTemplateFilePath(semanticFile, template.importPath);
|
||||
if (!templatePath) continue;
|
||||
if (!templatePath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
||||
const templateLocales = extractLocaleInfoFromContent(templateContent);
|
||||
if (!templateLocales) continue;
|
||||
if (!templateLocales) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const propsString = extractTemplateProps(content, template.componentName);
|
||||
const ignoreSemantics = parseIgnoreSemantics(propsString);
|
||||
@@ -235,11 +263,15 @@ function extractSemanticInfo(semanticFile: string): {
|
||||
en: Record<string, string>;
|
||||
} | null {
|
||||
try {
|
||||
if (!fs.existsSync(semanticFile)) return null;
|
||||
if (!fs.existsSync(semanticFile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(semanticFile, 'utf-8');
|
||||
const localeInfo = extractLocaleInfoFromContent(content);
|
||||
if (localeInfo) return localeInfo;
|
||||
if (localeInfo) {
|
||||
return localeInfo;
|
||||
}
|
||||
|
||||
return extractSemanticInfoFromTemplate(semanticFile, content);
|
||||
} catch (error) {
|
||||
@@ -324,9 +356,12 @@ function getComponentHTMLSnapshot(semanticFile: string, cwd: string): string | n
|
||||
try {
|
||||
const relativePath = path.relative(cwd, semanticFile);
|
||||
const pathMatch = relativePath.match(/^components\/([^/]+)\/demo\/([^/]+)\.tsx$/);
|
||||
if (!pathMatch) return null;
|
||||
if (!pathMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [, componentName, fileName] = pathMatch;
|
||||
|
||||
const snapshotPath = path.join(
|
||||
cwd,
|
||||
'components',
|
||||
@@ -336,7 +371,9 @@ function getComponentHTMLSnapshot(semanticFile: string, cwd: string): string | n
|
||||
'demo-semantic.test.tsx.snap',
|
||||
);
|
||||
|
||||
if (!fs.existsSync(snapshotPath)) return null;
|
||||
if (!fs.existsSync(snapshotPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const snapshotContent = fs.readFileSync(snapshotPath, 'utf-8');
|
||||
// 匹配快照 key:exports[`renders components/button/demo/_semantic.tsx correctly 1`] = `...`;
|
||||
@@ -345,7 +382,9 @@ function getComponentHTMLSnapshot(semanticFile: string, cwd: string): string | n
|
||||
`exports\\[\\\`[^\\\`]*${snapshotKeyPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[^\\\`]*\\\`\\]\\s*=\\s*\\\`([\\s\\S]*?)\\\`;`,
|
||||
);
|
||||
const snapshotMatch = snapshotContent.match(regex);
|
||||
if (!snapshotMatch) return null;
|
||||
if (!snapshotMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let html = snapshotMatch[1].trim();
|
||||
|
||||
@@ -415,8 +454,12 @@ function getComponentHTMLSnapshot(semanticFile: string, cwd: string): string | n
|
||||
* @param api - Dumi API 实例
|
||||
*/
|
||||
function emitSemanticMd(api: IApi) {
|
||||
if (process.env.NODE_ENV !== 'production') return;
|
||||
if (SEMANTIC_MD_EMITTED) return;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return;
|
||||
}
|
||||
if (SEMANTIC_MD_EMITTED) {
|
||||
return;
|
||||
}
|
||||
SEMANTIC_MD_EMITTED = true;
|
||||
|
||||
const outRoot = api.paths.absOutputPath;
|
||||
|
||||
2
.github/workflows/pr-check-merge.yml
vendored
2
.github/workflows/pr-check-merge.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: (github.event.pull_request.head.ref == 'next' || github.event.pull_request.head.ref == 'feature' || github.event.pull_request.head.ref == 'master') && github.event.pull_request.head.user.login == 'ant-design'
|
||||
steps:
|
||||
- uses: actions-cool/issues-helper@e2ff99831a4f13625d35064e2b3dfe65c07a0396
|
||||
- uses: actions-cool/issues-helper@e361abf610221f09495ad510cb1e69328d839e1c
|
||||
with:
|
||||
actions: create-comment
|
||||
issue-number: ${{ github.event.number }}
|
||||
|
||||
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.
|
||||
|
||||
@@ -34,7 +34,7 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
||||
const [varName] = genCssVar(rootPrefixCls, 'wave');
|
||||
|
||||
// ===================== Effect =====================
|
||||
const [color, setWaveColor] = React.useState<string | null>(null);
|
||||
const [waveColor, setWaveColor] = React.useState<string | null>(null);
|
||||
const [borderRadius, setBorderRadius] = React.useState<number[]>([]);
|
||||
const [left, setLeft] = React.useState(0);
|
||||
const [top, setTop] = React.useState(0);
|
||||
@@ -50,8 +50,8 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
||||
borderRadius: borderRadius.map((radius) => `${radius}px`).join(' '),
|
||||
};
|
||||
|
||||
if (color) {
|
||||
waveStyle[varName('color')] = color;
|
||||
if (waveColor) {
|
||||
waveStyle[varName('color')] = waveColor;
|
||||
}
|
||||
|
||||
function syncPos() {
|
||||
|
||||
@@ -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.,
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -225,7 +225,7 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
|
||||
const loadingOrDelay = useMemo<LoadingConfigType>(() => getLoadingConfig(loading), [loading]);
|
||||
|
||||
const [innerLoading, setLoading] = useState<boolean>(loadingOrDelay.loading);
|
||||
const [innerLoading, setInnerLoading] = useState<boolean>(loadingOrDelay.loading);
|
||||
|
||||
const [hasTwoCNChar, setHasTwoCNChar] = useState<boolean>(false);
|
||||
|
||||
@@ -255,10 +255,10 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
if (loadingOrDelay.delay > 0) {
|
||||
delayTimer = setTimeout(() => {
|
||||
delayTimer = null;
|
||||
setLoading(true);
|
||||
setInnerLoading(true);
|
||||
}, loadingOrDelay.delay);
|
||||
} else {
|
||||
setLoading(loadingOrDelay.loading);
|
||||
setInnerLoading(loadingOrDelay.loading);
|
||||
}
|
||||
|
||||
function cleanupTimer() {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
|
||||
import { unit } from '@ant-design/cssinjs';
|
||||
|
||||
import { genFocusStyle, resetIcon } from '../../style';
|
||||
import { genNoMotionStyle } from '../../style/motion';
|
||||
import type { GenerateStyle } from '../../theme/internal';
|
||||
import { genStyleHooks, mergeToken } from '../../theme/internal';
|
||||
import genGroupStyle from './group';
|
||||
@@ -40,7 +41,7 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
|
||||
transition: `all ${token.motionDurationMid} ${token.motionEaseInOut}`,
|
||||
userSelect: 'none',
|
||||
touchAction: 'manipulation',
|
||||
|
||||
...genNoMotionStyle(),
|
||||
'&:disabled > *': {
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
@@ -80,7 +81,7 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
|
||||
|
||||
[`${componentCls}-loading-icon`]: {
|
||||
transition: ['width', 'opacity', 'margin']
|
||||
.map((transition) => `${transition} ${motionDurationSlow} ${motionEaseInOut}`)
|
||||
.map((prop) => `${prop} ${motionDurationSlow} ${motionEaseInOut}`)
|
||||
.join(','),
|
||||
},
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ const App: React.FC = () => {
|
||||
const { styles } = useStyle({ test: true });
|
||||
|
||||
const [selectDate, setSelectDate] = React.useState<Dayjs>(() => dayjs());
|
||||
const [panelDateDate, setPanelDate] = React.useState<Dayjs>(() => dayjs());
|
||||
const [panelDate, setPanelDate] = React.useState<Dayjs>(() => dayjs());
|
||||
|
||||
const onPanelChange = (value: Dayjs, mode: CalendarProps<Dayjs>['mode']) => {
|
||||
console.log(value.format('YYYY-MM-DD'), mode);
|
||||
@@ -131,7 +131,7 @@ const App: React.FC = () => {
|
||||
<span
|
||||
className={clsx({
|
||||
[styles.weekend]: isWeekend,
|
||||
gray: !panelDateDate.isSame(date, 'month'),
|
||||
gray: !panelDate.isSame(date, 'month'),
|
||||
})}
|
||||
>
|
||||
{date.get('date')}
|
||||
|
||||
@@ -44,12 +44,12 @@ const options: Option[] = [
|
||||
];
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [placement, SetPlacement] = useState<'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight'>(
|
||||
const [placement, setPlacement] = useState<'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight'>(
|
||||
'topLeft',
|
||||
);
|
||||
|
||||
const placementChange = (e: RadioChangeEvent) => {
|
||||
SetPlacement(e.target.value);
|
||||
setPlacement(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { unit } from '@ant-design/cssinjs';
|
||||
|
||||
import { genFocusOutline, resetComponent } from '../../style';
|
||||
import { genNoMotionStyle } from '../../style/motion';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genStyleHooks, mergeToken } from '../../theme/internal';
|
||||
|
||||
@@ -99,6 +100,7 @@ export const genCheckboxStyle: GenerateStyle<CheckboxToken> = (token) => {
|
||||
borderRadius: token.borderRadiusSM,
|
||||
borderCollapse: 'separate',
|
||||
transition: `all ${token.motionDurationSlow}`,
|
||||
...genNoMotionStyle(),
|
||||
|
||||
// Checkmark
|
||||
'&:after': {
|
||||
@@ -116,6 +118,7 @@ export const genCheckboxStyle: GenerateStyle<CheckboxToken> = (token) => {
|
||||
opacity: 0,
|
||||
content: '""',
|
||||
transition: `all ${token.motionDurationFast} ${token.motionEaseInBack}, opacity ${token.motionDurationFast}`,
|
||||
...genNoMotionStyle(),
|
||||
},
|
||||
|
||||
// Wrapper > Checkbox > input
|
||||
@@ -173,6 +176,7 @@ export const genCheckboxStyle: GenerateStyle<CheckboxToken> = (token) => {
|
||||
opacity: 1,
|
||||
transform: 'rotate(45deg) scale(1) translate(-50%,-50%)',
|
||||
transition: `all ${token.motionDurationMid} ${token.motionEaseOutBack} ${token.motionDurationFast}`,
|
||||
...genNoMotionStyle(),
|
||||
},
|
||||
|
||||
// Hover on checked checkbox directly
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -225,11 +225,11 @@ const Page: React.FC = () => {
|
||||
};
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [locale, setLocal] = useState<Locale>(enUS);
|
||||
const [locale, setLocale] = useState<Locale>(enUS);
|
||||
|
||||
const changeLocale = (e: RadioChangeEvent) => {
|
||||
const localeValue = e.target.value;
|
||||
setLocal(localeValue);
|
||||
setLocale(localeValue);
|
||||
if (!localeValue) {
|
||||
dayjs.locale('en');
|
||||
} else {
|
||||
|
||||
@@ -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` | |
|
||||
|
||||
@@ -5,10 +5,10 @@ import { DatePicker, Radio } from 'antd';
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [placement, SetPlacement] = useState<DatePickerProps['placement']>('topLeft');
|
||||
const [placement, setPlacement] = useState<DatePickerProps['placement']>('topLeft');
|
||||
|
||||
const placementChange = (e: RadioChangeEvent) => {
|
||||
SetPlacement(e.target.value);
|
||||
setPlacement(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -88,205 +88,6 @@ Array [
|
||||
|
||||
exports[`renders components/drawer/demo/basic-right.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/drawer/demo/classNames.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Open
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
ConfigProvider
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-drawer ant-drawer-right css-var-test-id ant-drawer-open ant-drawer-inline"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="ant-drawer-mask acss-c0hvaj"
|
||||
/>
|
||||
<div
|
||||
class="ant-drawer-content-wrapper"
|
||||
style="width: 378px;"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="test-id"
|
||||
aria-modal="true"
|
||||
class="ant-drawer-section acss-10412ne"
|
||||
role="dialog"
|
||||
style="box-shadow: -10px 0 10px #666;"
|
||||
>
|
||||
<div
|
||||
class="ant-drawer-header acss-1l0wu1y"
|
||||
style="border-bottom: 1px solid rgb(22, 119, 255);"
|
||||
>
|
||||
<div
|
||||
class="ant-drawer-header-title"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="ant-drawer-close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="close"
|
||||
class="anticon anticon-close"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="ant-drawer-title"
|
||||
id="test-id"
|
||||
>
|
||||
Basic Drawer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-drawer-body acss-pgpe64"
|
||||
style="font-size: 16px;"
|
||||
>
|
||||
<p>
|
||||
Some contents...
|
||||
</p>
|
||||
<p>
|
||||
Some contents...
|
||||
</p>
|
||||
<p>
|
||||
Some contents...
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="ant-drawer-footer acss-r4s437"
|
||||
style="border-top: 1px solid rgb(217, 217, 217);"
|
||||
>
|
||||
Footer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-drawer ant-drawer-right css-var-test-id ant-drawer-open ant-drawer-inline"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="ant-drawer-mask acss-c0hvaj"
|
||||
/>
|
||||
<div
|
||||
class="ant-drawer-content-wrapper"
|
||||
style="width: 378px;"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="test-id"
|
||||
aria-modal="true"
|
||||
class="ant-drawer-section acss-10412ne"
|
||||
role="dialog"
|
||||
style="box-shadow: -10px 0 10px #666;"
|
||||
>
|
||||
<div
|
||||
class="ant-drawer-header acss-1l0wu1y"
|
||||
style="border-bottom: 1px solid rgb(22, 119, 255);"
|
||||
>
|
||||
<div
|
||||
class="ant-drawer-header-title"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="ant-drawer-close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="close"
|
||||
class="anticon anticon-close"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="ant-drawer-title"
|
||||
id="test-id"
|
||||
>
|
||||
Basic Drawer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-drawer-body acss-pgpe64"
|
||||
style="font-size: 16px;"
|
||||
>
|
||||
<p>
|
||||
Some contents...
|
||||
</p>
|
||||
<p>
|
||||
Some contents...
|
||||
</p>
|
||||
<p>
|
||||
Some contents...
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="ant-drawer-footer acss-r4s437"
|
||||
style="border-top: 1px solid rgb(217, 217, 217);"
|
||||
>
|
||||
Footer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/drawer/demo/classNames.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/drawer/demo/closable-placement.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<button
|
||||
|
||||
@@ -11,37 +11,6 @@ exports[`renders components/drawer/demo/basic-right.tsx correctly 1`] = `
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`renders components/drawer/demo/classNames.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Open
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
ConfigProvider
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/drawer/demo/closable-placement.tsx correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
## zh-CN
|
||||
|
||||
通过 `classNames` 属性设置抽屉内部区域(header、body、footer、mask、wrapper)的 `className`。
|
||||
|
||||
## en-US
|
||||
|
||||
Set the `className` of the build-in module (header, body, footer, mask, wrapper) of the drawer through the `classNames`.
|
||||
@@ -1,102 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, ConfigProvider, Drawer, Space } from 'antd';
|
||||
import type { DrawerProps } from 'antd';
|
||||
import { createStyles, useTheme } from 'antd-style';
|
||||
|
||||
const useStyle = createStyles(({ token }) => ({
|
||||
'my-drawer-body': {
|
||||
background: token.blue1,
|
||||
},
|
||||
'my-drawer-mask': {
|
||||
boxShadow: `inset 0 0 15px #fff`,
|
||||
},
|
||||
'my-drawer-header': {
|
||||
background: token.green1,
|
||||
},
|
||||
'my-drawer-footer': {
|
||||
color: token.colorPrimary,
|
||||
},
|
||||
'my-drawer-section': {
|
||||
borderInlineStart: '2px dotted #333',
|
||||
},
|
||||
}));
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [open, setOpen] = useState([false, false]);
|
||||
const { styles } = useStyle();
|
||||
const token = useTheme();
|
||||
|
||||
const toggleDrawer = (idx: number, target: boolean) => {
|
||||
setOpen((p) => {
|
||||
p[idx] = target;
|
||||
return [...p];
|
||||
});
|
||||
};
|
||||
|
||||
const classNames: DrawerProps['classNames'] = {
|
||||
body: styles['my-drawer-body'],
|
||||
mask: styles['my-drawer-mask'],
|
||||
header: styles['my-drawer-header'],
|
||||
footer: styles['my-drawer-footer'],
|
||||
section: styles['my-drawer-section'],
|
||||
};
|
||||
|
||||
const drawerStyles: DrawerProps['styles'] = {
|
||||
mask: {
|
||||
backdropFilter: 'blur(10px)',
|
||||
},
|
||||
section: {
|
||||
boxShadow: '-10px 0 10px #666',
|
||||
},
|
||||
header: {
|
||||
borderBottom: `1px solid ${token.colorPrimary}`,
|
||||
},
|
||||
body: {
|
||||
fontSize: token.fontSizeLG,
|
||||
},
|
||||
footer: {
|
||||
borderTop: `1px solid ${token.colorBorder}`,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Space>
|
||||
<Button type="primary" onClick={() => toggleDrawer(0, true)}>
|
||||
Open
|
||||
</Button>
|
||||
<Button type="primary" onClick={() => toggleDrawer(1, true)}>
|
||||
ConfigProvider
|
||||
</Button>
|
||||
</Space>
|
||||
<Drawer
|
||||
title="Basic Drawer"
|
||||
placement="right"
|
||||
footer="Footer"
|
||||
onClose={() => toggleDrawer(0, false)}
|
||||
open={open[0]}
|
||||
classNames={classNames}
|
||||
styles={drawerStyles}
|
||||
>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
</Drawer>
|
||||
<ConfigProvider drawer={{ classNames, styles: drawerStyles }}>
|
||||
<Drawer
|
||||
title="Basic Drawer"
|
||||
placement="right"
|
||||
footer="Footer"
|
||||
onClose={() => toggleDrawer(1, false)}
|
||||
open={open[1]}
|
||||
>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
</Drawer>
|
||||
</ConfigProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -54,8 +54,8 @@ const stylesFn: DrawerProps['styles'] = (info) => {
|
||||
};
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [drawerOpen, setOpen] = useState(false);
|
||||
const [drawerFnOpen, setFnOpen] = useState(false);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [drawerFnOpen, setDrawerFnOpen] = useState(false);
|
||||
|
||||
const sharedProps: DrawerProps = {
|
||||
classNames,
|
||||
@@ -65,7 +65,7 @@ const App: React.FC = () => {
|
||||
const footer: React.ReactNode = (
|
||||
<Flex gap="middle" justify="flex-end">
|
||||
<Button
|
||||
onClick={() => setFnOpen(false)}
|
||||
onClick={() => setDrawerFnOpen(false)}
|
||||
styles={{ root: { borderColor: '#ccc', color: '#171717', backgroundColor: '#fff' } }}
|
||||
>
|
||||
Cancel
|
||||
@@ -73,7 +73,7 @@ const App: React.FC = () => {
|
||||
<Button
|
||||
type="primary"
|
||||
styles={{ root: { backgroundColor: '#171717' } }}
|
||||
onClick={() => setOpen(true)}
|
||||
onClick={() => setDrawerOpen(true)}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
@@ -82,8 +82,8 @@ const App: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Flex gap="middle">
|
||||
<Button onClick={() => setOpen(true)}>Open Style Drawer</Button>
|
||||
<Button type="primary" onClick={() => setFnOpen(true)}>
|
||||
<Button onClick={() => setDrawerOpen(true)}>Open Style Drawer</Button>
|
||||
<Button type="primary" onClick={() => setDrawerFnOpen(true)}>
|
||||
Open Function Drawer
|
||||
</Button>
|
||||
<Drawer
|
||||
@@ -92,7 +92,7 @@ const App: React.FC = () => {
|
||||
title="Custom Style Drawer"
|
||||
styles={styles}
|
||||
open={drawerOpen}
|
||||
onClose={() => setOpen(false)}
|
||||
onClose={() => setDrawerOpen(false)}
|
||||
>
|
||||
{sharedContent}
|
||||
</Drawer>
|
||||
@@ -103,7 +103,7 @@ const App: React.FC = () => {
|
||||
styles={stylesFn}
|
||||
mask={{ enabled: true, blur: true }}
|
||||
open={drawerFnOpen}
|
||||
onClose={() => setFnOpen(false)}
|
||||
onClose={() => setDrawerFnOpen(false)}
|
||||
>
|
||||
{sharedContent}
|
||||
</Drawer>
|
||||
|
||||
@@ -34,7 +34,6 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
|
||||
<code src="./demo/user-profile.tsx">Preview drawer</code>
|
||||
<code src="./demo/multi-level-drawer.tsx">Multi-level drawer</code>
|
||||
<code src="./demo/size.tsx">Preset size</code>
|
||||
<code src="./demo/classNames.tsx">Customize className for build-in module</code>
|
||||
<code src="./demo/mask.tsx">mask</code>
|
||||
<code src="./demo/closable-placement.tsx" version="5.28.0">Closable placement</code>
|
||||
<code src="./demo/style-class.tsx" version="6.0.0">Custom semantic dom styling</code>
|
||||
|
||||
@@ -35,7 +35,6 @@ demo:
|
||||
<code src="./demo/multi-level-drawer.tsx">多层抽屉</code>
|
||||
<code src="./demo/size.tsx">预设宽度</code>
|
||||
<code src="./demo/mask.tsx">遮罩</code>
|
||||
<code src="./demo/classNames.tsx">自定义内部样式</code>
|
||||
<code src="./demo/closable-placement.tsx" version="5.28.0">关闭按钮位置</code>
|
||||
<code src="./demo/style-class.tsx" version="6.0.0">自定义语义结构的样式和类</code>
|
||||
<code src="./demo/config-provider.tsx" debug>ConfigProvider</code>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -829,13 +829,13 @@ describe('Form', () => {
|
||||
// https://github.com/ant-design/ant-design/issues/20813
|
||||
it('should update help directly when provided', async () => {
|
||||
const App: React.FC = () => {
|
||||
const [message, updateMessage] = React.useState('');
|
||||
const [message, setMessage] = React.useState('');
|
||||
return (
|
||||
<Form>
|
||||
<Form.Item label="hello" help={message}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Button onClick={() => updateMessage('bamboo')} />
|
||||
<Button onClick={() => setMessage('bamboo')} />
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,10 +14,10 @@ const customizeRequiredMark = (label: React.ReactNode, { required }: { required:
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [requiredMark, setRequiredMarkType] = useState<RequiredMark>('optional');
|
||||
const [requiredMark, setRequiredMark] = useState<RequiredMark>('optional');
|
||||
|
||||
const onRequiredTypeChange: FormProps<any>['onValuesChange'] = ({ requiredMarkValue }) => {
|
||||
setRequiredMarkType(requiredMarkValue);
|
||||
setRequiredMark(requiredMarkValue);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ const OTPInput = React.forwardRef<InputRef, OTPInputProps>((props, ref) => {
|
||||
React.useImperativeHandle(ref, () => inputRef.current!);
|
||||
|
||||
// ========================= Input ==========================
|
||||
const onInternalChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
onChange(index, e.target.value);
|
||||
const onInternalChange: React.InputEventHandler<HTMLInputElement> = (e) => {
|
||||
onChange(index, (e.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
// ========================= Focus ==========================
|
||||
|
||||
@@ -680,52 +680,56 @@ Array [
|
||||
class="ant-typography css-var-test-id"
|
||||
>
|
||||
Ant Design
|
||||
<button
|
||||
aria-describedby="test-id"
|
||||
aria-label="Copy"
|
||||
class="ant-typography-copy"
|
||||
type="button"
|
||||
<span
|
||||
class="ant-typography-actions"
|
||||
>
|
||||
<span
|
||||
aria-label="copy"
|
||||
class="anticon anticon-copy"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="copy"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-tooltip-css-var css-var-test-id ant-tooltip-placement-top"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; right: auto; bottom: auto; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; bottom: 0px; left: 0px;"
|
||||
<button
|
||||
aria-describedby="test-id"
|
||||
aria-label="Copy"
|
||||
class="ant-typography-copy"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-tooltip-arrow-content"
|
||||
/>
|
||||
</div>
|
||||
aria-label="copy"
|
||||
class="anticon anticon-copy"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="copy"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="ant-tooltip-container"
|
||||
id="test-id"
|
||||
role="tooltip"
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-tooltip-css-var css-var-test-id ant-tooltip-placement-top"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; right: auto; bottom: auto; box-sizing: border-box;"
|
||||
>
|
||||
Copy
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; bottom: 0px; left: 0px;"
|
||||
>
|
||||
<span
|
||||
class="ant-tooltip-arrow-content"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tooltip-container"
|
||||
id="test-id"
|
||||
role="tooltip"
|
||||
>
|
||||
Copy
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>,
|
||||
<span
|
||||
class="ant-input-affix-wrapper ant-input-outlined css-var-test-id ant-input-css-var"
|
||||
@@ -3142,7 +3146,7 @@ Array [
|
||||
style="width: 100px;"
|
||||
>
|
||||
<div
|
||||
class="ant-select-content ant-select-content-has-value"
|
||||
class="ant-select-content"
|
||||
title=""
|
||||
>
|
||||
<input
|
||||
|
||||
@@ -408,31 +408,35 @@ Array [
|
||||
class="ant-typography css-var-test-id"
|
||||
>
|
||||
Ant Design
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="ant-typography-copy"
|
||||
type="button"
|
||||
<span
|
||||
class="ant-typography-actions"
|
||||
>
|
||||
<span
|
||||
aria-label="copy"
|
||||
class="anticon anticon-copy"
|
||||
role="img"
|
||||
<button
|
||||
aria-label="Copy"
|
||||
class="ant-typography-copy"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="copy"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
<span
|
||||
aria-label="copy"
|
||||
class="anticon anticon-copy"
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="copy"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>,
|
||||
<span
|
||||
class="ant-input-affix-wrapper ant-input-outlined css-var-test-id ant-input-css-var"
|
||||
@@ -680,7 +684,7 @@ Array [
|
||||
style="width:100px"
|
||||
>
|
||||
<div
|
||||
class="ant-select-content ant-select-content-has-value"
|
||||
class="ant-select-content"
|
||||
title=""
|
||||
>
|
||||
<input
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -39,39 +39,6 @@ exports[`renders components/modal/demo/button-props.tsx extend context correctly
|
||||
|
||||
exports[`renders components/modal/demo/button-props.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/modal/demo/classNames.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Open Modal
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
ConfigProvider
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/modal/demo/classNames.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/modal/demo/component-token.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
style="display: flex; flex-direction: column; row-gap: 16px;"
|
||||
|
||||
@@ -33,37 +33,6 @@ exports[`renders components/modal/demo/button-props.tsx correctly 1`] = `
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`renders components/modal/demo/classNames.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Open Modal
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
ConfigProvider
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/modal/demo/component-token.tsx correctly 1`] = `
|
||||
<div
|
||||
style="display:flex;flex-direction:column;row-gap:16px"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
## zh-CN
|
||||
|
||||
通过 `classNames` 属性设置弹窗内部区域(header、body、footer、mask、wrapper)的 `className`。
|
||||
|
||||
## en-US
|
||||
|
||||
Set the className of the build-in module (header, body, footer, mask, wrapper) of the modal through the classNames property.
|
||||
@@ -1,110 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, ConfigProvider, Modal, Space } from 'antd';
|
||||
import { createStyles, useTheme } from 'antd-style';
|
||||
|
||||
const useStyle = createStyles(({ token }) => ({
|
||||
'my-modal-body': {
|
||||
background: token.blue1,
|
||||
padding: token.paddingSM,
|
||||
},
|
||||
'my-modal-mask': {
|
||||
boxShadow: `inset 0 0 15px #fff`,
|
||||
},
|
||||
'my-modal-header': {
|
||||
borderBottom: `1px dotted ${token.colorPrimary}`,
|
||||
},
|
||||
'my-modal-footer': {
|
||||
color: token.colorPrimary,
|
||||
},
|
||||
'my-modal-content': {
|
||||
border: '1px solid #333',
|
||||
},
|
||||
}));
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [isModalOpen, setIsModalOpen] = useState([false, false]);
|
||||
const { styles } = useStyle();
|
||||
const token = useTheme();
|
||||
|
||||
const toggleModal = (idx: number, target: boolean) => {
|
||||
setIsModalOpen((p) => {
|
||||
p[idx] = target;
|
||||
return [...p];
|
||||
});
|
||||
};
|
||||
|
||||
const classNames = {
|
||||
body: styles['my-modal-body'],
|
||||
mask: styles['my-modal-mask'],
|
||||
header: styles['my-modal-header'],
|
||||
footer: styles['my-modal-footer'],
|
||||
content: styles['my-modal-content'],
|
||||
};
|
||||
|
||||
const modalStyles = {
|
||||
header: {
|
||||
borderInlineStart: `5px solid ${token.colorPrimary}`,
|
||||
borderRadius: 0,
|
||||
paddingInlineStart: 5,
|
||||
},
|
||||
body: {
|
||||
boxShadow: 'inset 0 0 5px #999',
|
||||
borderRadius: 5,
|
||||
},
|
||||
mask: {
|
||||
backdropFilter: 'blur(10px)',
|
||||
},
|
||||
footer: {
|
||||
borderTop: '1px solid #333',
|
||||
},
|
||||
content: {
|
||||
boxShadow: '0 0 30px #999',
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Space>
|
||||
<Button type="primary" onClick={() => toggleModal(0, true)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Button type="primary" onClick={() => toggleModal(1, true)}>
|
||||
ConfigProvider
|
||||
</Button>
|
||||
</Space>
|
||||
<Modal
|
||||
title="Basic Modal"
|
||||
open={isModalOpen[0]}
|
||||
onOk={() => toggleModal(0, false)}
|
||||
onCancel={() => toggleModal(0, false)}
|
||||
footer="Footer"
|
||||
classNames={classNames}
|
||||
styles={modalStyles}
|
||||
>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
</Modal>
|
||||
<ConfigProvider
|
||||
modal={{
|
||||
classNames,
|
||||
styles: modalStyles,
|
||||
}}
|
||||
>
|
||||
<Modal
|
||||
title="Basic Modal"
|
||||
open={isModalOpen[1]}
|
||||
onOk={() => toggleModal(1, false)}
|
||||
onCancel={() => toggleModal(1, false)}
|
||||
footer="Footer"
|
||||
>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
</Modal>
|
||||
</ConfigProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -60,8 +60,8 @@ const stylesFn: ModalProps['styles'] = (info) => {
|
||||
};
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [modalOpen, setOpen] = useState(false);
|
||||
const [modalFnOpen, setFnOpen] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [modalFnOpen, setModalFnOpen] = useState(false);
|
||||
|
||||
const sharedProps: ModalProps = {
|
||||
centered: true,
|
||||
@@ -71,7 +71,7 @@ const App: React.FC = () => {
|
||||
const footer: React.ReactNode = (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => setFnOpen(false)}
|
||||
onClick={() => setModalFnOpen(false)}
|
||||
styles={{ root: { borderColor: '#ccc', color: '#171717', backgroundColor: '#fff' } }}
|
||||
>
|
||||
Cancel
|
||||
@@ -79,7 +79,7 @@ const App: React.FC = () => {
|
||||
<Button
|
||||
type="primary"
|
||||
styles={{ root: { backgroundColor: '#171717' } }}
|
||||
onClick={() => setOpen(true)}
|
||||
onClick={() => setModalOpen(true)}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
@@ -88,8 +88,8 @@ const App: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Flex gap="middle">
|
||||
<Button onClick={() => setOpen(true)}>Open Style Modal</Button>
|
||||
<Button type="primary" onClick={() => setFnOpen(true)}>
|
||||
<Button onClick={() => setModalOpen(true)}>Open Style Modal</Button>
|
||||
<Button type="primary" onClick={() => setModalFnOpen(true)}>
|
||||
Open Function Modal
|
||||
</Button>
|
||||
<Modal
|
||||
@@ -98,8 +98,8 @@ const App: React.FC = () => {
|
||||
title="Custom Style Modal"
|
||||
styles={styles}
|
||||
open={modalOpen}
|
||||
onOk={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
onOk={() => setModalOpen(false)}
|
||||
onCancel={() => setModalOpen(false)}
|
||||
>
|
||||
{sharedContent}
|
||||
</Modal>
|
||||
@@ -110,8 +110,8 @@ const App: React.FC = () => {
|
||||
styles={stylesFn}
|
||||
mask={{ enabled: true, blur: true }}
|
||||
open={modalFnOpen}
|
||||
onOk={() => setFnOpen(false)}
|
||||
onCancel={() => setFnOpen(false)}
|
||||
onOk={() => setModalFnOpen(false)}
|
||||
onCancel={() => setModalFnOpen(false)}
|
||||
>
|
||||
{sharedContent}
|
||||
</Modal>
|
||||
|
||||
@@ -34,7 +34,6 @@ Additionally, if you need to show a simple confirmation dialog, you can use [`Ap
|
||||
<code src="./demo/width.tsx">To customize the width of modal</code>
|
||||
<code src="./demo/static-info.tsx">Static Method</code>
|
||||
<code src="./demo/confirm.tsx">Static confirmation</code>
|
||||
<code src="./demo/classNames.tsx">Customize className for build-in module</code>
|
||||
<code src="./demo/confirm-router.tsx">destroy confirmation modal dialog</code>
|
||||
<code src="./demo/style-class.tsx" version="6.0.0">Custom semantic dom styling</code>
|
||||
<code src="./demo/nested.tsx" debug>Nested Modal</code>
|
||||
|
||||
@@ -35,7 +35,6 @@ demo:
|
||||
<code src="./demo/width.tsx">自定义模态的宽度</code>
|
||||
<code src="./demo/static-info.tsx">静态方法</code>
|
||||
<code src="./demo/confirm.tsx">静态确认对话框</code>
|
||||
<code src="./demo/classNames.tsx">自定义内部模块 className</code>
|
||||
<code src="./demo/confirm-router.tsx">销毁确认对话框</code>
|
||||
<code src="./demo/style-class.tsx" version="6.0.0">自定义语义结构的样式和类</code>
|
||||
<code src="./demo/nested.tsx" debug>嵌套弹框</code>
|
||||
|
||||
@@ -148,7 +148,7 @@ const Pagination: React.FC<PaginationProps> = (props) => {
|
||||
|
||||
// Generate options
|
||||
const mergedPageSizeOptions = React.useMemo(() => {
|
||||
return pageSizeOptions ? pageSizeOptions.map((option) => Number(option)) : undefined;
|
||||
return pageSizeOptions ? pageSizeOptions.map<number>(Number) : undefined;
|
||||
}, [pageSizeOptions]);
|
||||
|
||||
// Render size changer
|
||||
|
||||
@@ -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) | - | |
|
||||
|
||||
@@ -147,6 +147,7 @@ const genLineStyle: GenerateStyle<ProgressToken> = (token) => {
|
||||
borderRadius: token.lineBorderRadius,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
|
||||
[`&${componentCls}-status-active`]: {
|
||||
|
||||
@@ -1778,7 +1778,9 @@ Array [
|
||||
exports[`renders components/select/demo/custom-tag-render.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/select/demo/debug.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-flex css-var-test-id ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"
|
||||
style="flex-wrap: wrap; width: 500px; position: relative; z-index: 1; border: 1px solid red; background-color: rgb(255, 255, 255);"
|
||||
@@ -2210,9 +2212,184 @@ Array [
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-select ant-select-outlined css-var-test-id ant-select-css-var ant-select-single ant-select-show-arrow"
|
||||
style="width: 120px;"
|
||||
>
|
||||
<div
|
||||
class="ant-select-content"
|
||||
title=" "
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
autocomplete="off"
|
||||
class="ant-select-input"
|
||||
id="test-id"
|
||||
readonly=""
|
||||
role="combobox"
|
||||
type="search"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-suffix"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up css-var-test-id ant-select-css-var ant-select-dropdown-placement-bottomLeft"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; right: auto; bottom: auto; box-sizing: border-box;"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="test-id_list"
|
||||
role="listbox"
|
||||
style="height: 0px; width: 0px; overflow: hidden;"
|
||||
>
|
||||
<div
|
||||
aria-label="Jack"
|
||||
aria-selected="false"
|
||||
id="test-id_list_0"
|
||||
role="option"
|
||||
>
|
||||
jack
|
||||
</div>
|
||||
<div
|
||||
aria-label="Lucy"
|
||||
aria-selected="false"
|
||||
id="test-id_list_1"
|
||||
role="option"
|
||||
>
|
||||
lucy
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="rc-virtual-list"
|
||||
style="position: relative;"
|
||||
>
|
||||
<div
|
||||
class="rc-virtual-list-holder"
|
||||
style="max-height: 256px; overflow-y: auto; overflow-anchor: none;"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="rc-virtual-list-holder-inner"
|
||||
style="display: flex; flex-direction: column;"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item ant-select-item-option ant-select-item-option-active"
|
||||
title="Jack"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
Jack
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select: none;"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-item ant-select-item-option"
|
||||
title="Lucy"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
Lucy
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select: none;"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-item ant-select-item-option ant-select-item-option-disabled"
|
||||
title="Disabled"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
Disabled
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select: none;"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-item ant-select-item-option"
|
||||
title="yiminghe"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
yiminghe
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select: none;"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-item ant-select-item-option"
|
||||
title="I am super super long!"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
I am super super long!
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select: none;"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="width: 200px; margin-top: 24px;"
|
||||
style="width: 200px;"
|
||||
>
|
||||
<div
|
||||
class="ant-select ant-select-outlined css-var-test-id ant-select-css-var ant-select-multiple ant-select-show-arrow ant-select-show-search"
|
||||
@@ -2379,8 +2556,113 @@ Array [
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
</div>
|
||||
<div
|
||||
class="ant-select ant-select-outlined css-var-test-id ant-select-css-var ant-select-single ant-select-show-arrow"
|
||||
>
|
||||
<div
|
||||
class="ant-select-content"
|
||||
title=""
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
autocomplete="off"
|
||||
class="ant-select-input"
|
||||
id="test-id"
|
||||
readonly=""
|
||||
role="combobox"
|
||||
type="search"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-suffix"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up css-var-test-id ant-select-css-var ant-select-dropdown-empty ant-select-dropdown-placement-bottomLeft"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; right: auto; bottom: auto; box-sizing: border-box;"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="ant-select-item-empty"
|
||||
id="test-id_list"
|
||||
role="listbox"
|
||||
>
|
||||
<div
|
||||
class="css-var-test-id ant-empty ant-empty-normal ant-empty-small"
|
||||
>
|
||||
<div
|
||||
class="ant-empty-image"
|
||||
>
|
||||
<svg
|
||||
height="41"
|
||||
viewBox="0 0 64 41"
|
||||
width="64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>
|
||||
No data
|
||||
</title>
|
||||
<g
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
transform="translate(0 1)"
|
||||
>
|
||||
<ellipse
|
||||
cx="32"
|
||||
cy="33"
|
||||
fill="#f5f5f5"
|
||||
rx="32"
|
||||
ry="7"
|
||||
/>
|
||||
<g
|
||||
fill-rule="nonzero"
|
||||
stroke="#d9d9d9"
|
||||
>
|
||||
<path
|
||||
d="M55 12.8 44.9 1.3Q44 0 42.9 0H21.1q-1.2 0-2 1.3L9 12.8V22h46z"
|
||||
/>
|
||||
<path
|
||||
d="M41.6 16c0-1.7 1-3 2.2-3H55v18.1c0 2.2-1.3 3.9-3 3.9H12c-1.7 0-3-1.7-3-3.9V13h11.2c1.2 0 2.2 1.3 2.2 3s1 2.9 2.2 2.9h14.8c1.2 0 2.2-1.4 2.2-3"
|
||||
fill="#fafafa"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="ant-empty-description"
|
||||
>
|
||||
No data
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/select/demo/debug.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
@@ -741,7 +741,9 @@ exports[`renders components/select/demo/custom-tag-render.tsx correctly 1`] = `
|
||||
`;
|
||||
|
||||
exports[`renders components/select/demo/debug.tsx correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-flex css-var-test-id ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"
|
||||
style="flex-wrap:wrap;width:500px;position:relative;z-index:1;border:1px solid red;background-color:#fff"
|
||||
@@ -924,9 +926,58 @@ Array [
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-select ant-select-outlined css-var-test-id ant-select-css-var ant-select-single ant-select-show-arrow"
|
||||
style="width:120px"
|
||||
>
|
||||
<div
|
||||
class="ant-select-content"
|
||||
title=" "
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
autocomplete="off"
|
||||
class="ant-select-input"
|
||||
id="test-id"
|
||||
readonly=""
|
||||
role="combobox"
|
||||
type="search"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-suffix"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="width:200px;margin-top:24px"
|
||||
style="width:200px"
|
||||
>
|
||||
<div
|
||||
class="ant-select ant-select-outlined css-var-test-id ant-select-css-var ant-select-multiple ant-select-show-arrow ant-select-show-search"
|
||||
@@ -1018,8 +1069,52 @@ Array [
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
</div>
|
||||
<div
|
||||
class="ant-select ant-select-outlined css-var-test-id ant-select-css-var ant-select-single ant-select-show-arrow"
|
||||
>
|
||||
<div
|
||||
class="ant-select-content"
|
||||
title=""
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
autocomplete="off"
|
||||
class="ant-select-input"
|
||||
id="test-id"
|
||||
readonly=""
|
||||
role="combobox"
|
||||
type="search"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-suffix"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/select/demo/debug-flip-shift.tsx correctly 1`] = `
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Button, Input, Select, Space } from 'antd';
|
||||
import { Button, Flex, Input, Select, Space } from 'antd';
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
width: 500,
|
||||
@@ -14,7 +14,7 @@ const handleChange = (value: string | string[]) => {
|
||||
};
|
||||
|
||||
const App: React.FC = () => (
|
||||
<>
|
||||
<Flex vertical gap="middle">
|
||||
<Space style={style} wrap>
|
||||
<Input style={{ width: 100 }} value="222" />
|
||||
<Select
|
||||
@@ -47,8 +47,21 @@ const App: React.FC = () => (
|
||||
/>
|
||||
<span className="debug-align">AntDesign</span>
|
||||
<Button>222</Button>
|
||||
{/* https://github.com/ant-design/ant-design/issues/56960 */}
|
||||
<Select
|
||||
style={{ width: 120 }}
|
||||
defaultValue=" "
|
||||
placeholder="Please select"
|
||||
options={[
|
||||
{ value: 'jack', label: 'Jack' },
|
||||
{ value: 'lucy', label: 'Lucy' },
|
||||
{ value: 'disabled', disabled: true, label: 'Disabled' },
|
||||
{ value: 'Yiminghe', label: 'yiminghe' },
|
||||
{ value: 'long', label: 'I am super super long!' },
|
||||
]}
|
||||
/>
|
||||
</Space>
|
||||
<div style={{ width: 200, marginTop: 24 }}>
|
||||
<div style={{ width: 200 }}>
|
||||
{/* https://github.com/ant-design/ant-design/issues/54179 */}
|
||||
<Select
|
||||
mode="multiple"
|
||||
@@ -62,7 +75,9 @@ const App: React.FC = () => (
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
<Select defaultValue="" />
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -15,12 +15,12 @@ const randomOptions = (count?: number) => {
|
||||
};
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [placement, SetPlacement] = useState<SelectCommonPlacement>('topLeft');
|
||||
const [placement, setPlacement] = useState<SelectCommonPlacement>('topLeft');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [options, setOptions] = useState(() => randomOptions(3));
|
||||
|
||||
const placementChange = (e: RadioChangeEvent) => {
|
||||
SetPlacement(e.target.value);
|
||||
setPlacement(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,10 +5,10 @@ import { Radio, Select } from 'antd';
|
||||
type SelectCommonPlacement = SelectProps['placement'];
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [placement, SetPlacement] = useState<SelectCommonPlacement>('topLeft');
|
||||
const [placement, setPlacement] = useState<SelectCommonPlacement>('topLeft');
|
||||
|
||||
const placementChange = (e: RadioChangeEvent) => {
|
||||
SetPlacement(e.target.value);
|
||||
setPlacement(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -297,6 +297,10 @@ const genSelectInputStyle: GenerateStyle<SelectToken> = (token) => {
|
||||
|
||||
[`&${componentCls}-open ${componentCls}-content`]: {
|
||||
color: token.colorTextPlaceholder,
|
||||
|
||||
'&-has-search-value': {
|
||||
color: 'transparent',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { StepsProps } from 'antd';
|
||||
import { Button, Space, Steps } from 'antd';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [percent, setPercentage] = useState<number | undefined>(0);
|
||||
const [percent, setPercent] = useState<number | undefined>(0);
|
||||
const [current, setCurrent] = useState(1);
|
||||
const [status, setStatus] = useState<StepsProps['status']>('process');
|
||||
const content = 'This is a content.';
|
||||
@@ -25,10 +25,10 @@ const App: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Space.Compact block>
|
||||
<Button onClick={() => setPercentage(undefined)}>Percentage to undefined</Button>
|
||||
<Button onClick={() => setPercent(undefined)}>Percentage to undefined</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
setPercentage((prev) => {
|
||||
setPercent((prev) => {
|
||||
const next = (prev ?? 0) + 10;
|
||||
return next > 100 ? 0 : next;
|
||||
})
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
slideUpIn,
|
||||
slideUpOut,
|
||||
} from './slide';
|
||||
import { genNoMotionStyle } from './util';
|
||||
import {
|
||||
initZoomMotion,
|
||||
zoomBigIn,
|
||||
@@ -42,6 +43,7 @@ export {
|
||||
fadeIn,
|
||||
fadeOut,
|
||||
genCollapseMotion,
|
||||
genNoMotionStyle,
|
||||
initFadeMotion,
|
||||
initMoveMotion,
|
||||
initSlideMotion,
|
||||
|
||||
10
components/style/motion/util.ts
Normal file
10
components/style/motion/util.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { CSSObject } from '@ant-design/cssinjs';
|
||||
|
||||
export const genNoMotionStyle = (): CSSObject => {
|
||||
return {
|
||||
'@media (prefers-reduced-motion: reduce)': {
|
||||
transition: 'none',
|
||||
animation: 'none',
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -162,6 +162,7 @@ type ZoomMotionTypes =
|
||||
| 'zoom-right'
|
||||
| 'zoom-up'
|
||||
| 'zoom-down';
|
||||
|
||||
const zoomMotion: Record<ZoomMotionTypes, { inKeyframes: Keyframes; outKeyframes: Keyframes }> = {
|
||||
zoom: {
|
||||
inKeyframes: zoomIn,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { unit } from '@ant-design/cssinjs';
|
||||
import { FastColor } from '@ant-design/fast-color';
|
||||
|
||||
import { genFocusStyle, resetComponent } from '../../style';
|
||||
import { genNoMotionStyle } from '../../style/motion';
|
||||
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
|
||||
import { genStyleHooks, mergeToken } from '../../theme/internal';
|
||||
|
||||
@@ -208,6 +209,7 @@ const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
|
||||
width: handleSize,
|
||||
height: handleSize,
|
||||
transition: `all ${token.switchDuration} ease-in-out`,
|
||||
...genNoMotionStyle(),
|
||||
|
||||
'&::before': {
|
||||
position: 'absolute',
|
||||
@@ -220,6 +222,7 @@ const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
|
||||
boxShadow: handleShadow,
|
||||
transition: `all ${token.switchDuration} ease-in-out`,
|
||||
content: '""',
|
||||
...genNoMotionStyle(),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -270,7 +273,7 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
|
||||
transition: [`padding-inline-start`, `padding-inline-end`]
|
||||
.map((prop) => `${prop} ${switchDuration} ease-in-out`)
|
||||
.join(', '),
|
||||
|
||||
...genNoMotionStyle(),
|
||||
[`${switchInnerCls}-checked, ${switchInnerCls}-unchecked`]: {
|
||||
display: 'block',
|
||||
color: token.colorTextLightSolid,
|
||||
@@ -280,6 +283,7 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
|
||||
transition: [`margin-inline-start`, `margin-inline-end`]
|
||||
.map((prop) => `${prop} ${switchDuration} ease-in-out`)
|
||||
.join(', '),
|
||||
...genNoMotionStyle(),
|
||||
},
|
||||
|
||||
[`${switchInnerCls}-checked`]: {
|
||||
@@ -347,7 +351,7 @@ const genSwitchStyle = (token: SwitchToken): CSSObject => {
|
||||
cursor: 'pointer',
|
||||
transition: `all ${token.motionDurationMid}`,
|
||||
userSelect: 'none',
|
||||
|
||||
...genNoMotionStyle(),
|
||||
[`&:hover:not(${componentCls}-disabled)`]: {
|
||||
background: token.colorTextTertiary,
|
||||
},
|
||||
|
||||
@@ -2010,7 +2010,7 @@ describe('Table.filter', () => {
|
||||
];
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [ddd, setData] = React.useState<Array<DataType>>([
|
||||
const [data, setData] = React.useState<Array<DataType>>([
|
||||
{
|
||||
key: '1',
|
||||
name: 'John Brown',
|
||||
@@ -2089,7 +2089,7 @@ describe('Table.filter', () => {
|
||||
<span className="rest-btn" onClick={handleClick}>
|
||||
refresh
|
||||
</span>
|
||||
<Table columns={cs} dataSource={ddd} />
|
||||
<Table columns={cs} dataSource={data} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -295,7 +295,7 @@ const FilterDropdown = <RecordType extends AnyObject = AnyObject>(
|
||||
setSearchValue('');
|
||||
|
||||
if (filterResetToDefaultFilteredValue) {
|
||||
setFilteredKeysSync((defaultFilteredValue || []).map((key) => String(key)));
|
||||
setFilteredKeysSync((defaultFilteredValue || []).map<string>(String));
|
||||
} else {
|
||||
setFilteredKeysSync([]);
|
||||
}
|
||||
@@ -330,7 +330,7 @@ const FilterDropdown = <RecordType extends AnyObject = AnyObject>(
|
||||
|
||||
const onCheckAll = (e: CheckboxChangeEvent) => {
|
||||
if (e.target.checked) {
|
||||
const allFilterKeys = flattenKeys(column?.filters).map((key) => String(key));
|
||||
const allFilterKeys = flattenKeys(column?.filters).map<string>(String);
|
||||
setFilteredKeysSync(allFilterKeys);
|
||||
} else {
|
||||
setFilteredKeysSync([]);
|
||||
@@ -349,11 +349,12 @@ const FilterDropdown = <RecordType extends AnyObject = AnyObject>(
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
const getFilterData = (node: FilterTreeDataNode): TreeColumnFilterItem => ({
|
||||
...node,
|
||||
text: node.title,
|
||||
value: node.key,
|
||||
children: node.children?.map((item) => getFilterData(item)) || [],
|
||||
children: node.children?.map<TreeColumnFilterItem>(getFilterData) || [],
|
||||
});
|
||||
|
||||
let dropdownContent: React.ReactNode;
|
||||
@@ -491,13 +492,8 @@ const FilterDropdown = <RecordType extends AnyObject = AnyObject>(
|
||||
|
||||
const getResetDisabled = () => {
|
||||
if (filterResetToDefaultFilteredValue) {
|
||||
return isEqual(
|
||||
(defaultFilteredValue || []).map((key) => String(key)),
|
||||
selectedKeys,
|
||||
true,
|
||||
);
|
||||
return isEqual((defaultFilteredValue || []).map<string>(String), selectedKeys, true);
|
||||
}
|
||||
|
||||
return selectedKeys.length === 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ const useSelection = <RecordType extends AnyObject = AnyObject>(
|
||||
const triggerSingleSelection = useCallback(
|
||||
(key: Key, selected: boolean, keys: Key[], event: Event) => {
|
||||
if (onSelect) {
|
||||
const rows = keys.map((k) => getRecordByKey(k));
|
||||
const rows = keys.map<RecordType>(getRecordByKey);
|
||||
onSelect(getRecordByKey(key), selected, rows, event);
|
||||
}
|
||||
|
||||
@@ -426,8 +426,8 @@ const useSelection = <RecordType extends AnyObject = AnyObject>(
|
||||
|
||||
onSelectAll?.(
|
||||
!checkedCurrentAll,
|
||||
keys.map((k) => getRecordByKey(k)),
|
||||
changeKeys.map((k) => getRecordByKey(k)),
|
||||
keys.map<RecordType>(getRecordByKey),
|
||||
changeKeys.map<RecordType>(getRecordByKey),
|
||||
);
|
||||
|
||||
setSelectedKeys(keys, 'all');
|
||||
@@ -584,8 +584,8 @@ const useSelection = <RecordType extends AnyObject = AnyObject>(
|
||||
|
||||
onSelectMultiple?.(
|
||||
!checked,
|
||||
keys.map((recordKey) => getRecordByKey(recordKey)),
|
||||
changedKeys.map((recordKey) => getRecordByKey(recordKey)),
|
||||
keys.map<RecordType>(getRecordByKey),
|
||||
changedKeys.map<RecordType>(getRecordByKey),
|
||||
);
|
||||
|
||||
setSelectedKeys(keys, 'multiple');
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ describe('Tour', () => {
|
||||
it('button props onClick', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<HTMLButtonElement>(null);
|
||||
const [btnName, steBtnName] = React.useState<string>('defaultBtn');
|
||||
const [btnName, setBtnName] = React.useState<string>('defaultBtn');
|
||||
return (
|
||||
<>
|
||||
<span id="btnName">{btnName}</span>
|
||||
@@ -143,17 +143,17 @@ describe('Tour', () => {
|
||||
description: '',
|
||||
target: () => coverBtnRef.current!,
|
||||
nextButtonProps: {
|
||||
onClick: () => steBtnName('nextButton'),
|
||||
onClick: () => setBtnName('nextButton'),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
target: () => coverBtnRef.current!,
|
||||
prevButtonProps: {
|
||||
onClick: () => steBtnName('prevButton'),
|
||||
onClick: () => setBtnName('prevButton'),
|
||||
},
|
||||
nextButtonProps: {
|
||||
onClick: () => steBtnName('finishButton'),
|
||||
onClick: () => setBtnName('finishButton'),
|
||||
},
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -229,6 +229,7 @@ const Transfer = <RecordType extends TransferItem = TransferItem>(
|
||||
const [hashId, cssVarCls] = useStyle(prefixCls);
|
||||
|
||||
const mergedActions = actions || operations || [];
|
||||
const isRtl = dir === 'rtl';
|
||||
|
||||
// Fill record with `key`
|
||||
const [mergedDataSource, leftDataSource, rightDataSource] = useData(
|
||||
@@ -334,7 +335,7 @@ const Transfer = <RecordType extends TransferItem = TransferItem>(
|
||||
|
||||
const onItemSelectAll = (
|
||||
direction: TransferDirection,
|
||||
keys: string[],
|
||||
keys: TransferKey[],
|
||||
checkAll: boolean | 'replace',
|
||||
) => {
|
||||
setStateKeys(direction, (prevKeys) => {
|
||||
@@ -355,13 +356,15 @@ const Transfer = <RecordType extends TransferItem = TransferItem>(
|
||||
setPrevSelectedIndex(direction, null);
|
||||
};
|
||||
|
||||
const onLeftItemSelectAll = (keys: string[], checkAll: boolean) => {
|
||||
onItemSelectAll('left', keys, checkAll);
|
||||
};
|
||||
const onLeftItemSelectAll: TransferListProps<KeyWise<RecordType>>['onItemSelectAll'] = (
|
||||
keys,
|
||||
checkAll,
|
||||
) => onItemSelectAll('left', keys, checkAll);
|
||||
|
||||
const onRightItemSelectAll = (keys: string[], checkAll: boolean) => {
|
||||
onItemSelectAll('right', keys, checkAll);
|
||||
};
|
||||
const onRightItemSelectAll: TransferListProps<KeyWise<RecordType>>['onItemSelectAll'] = (
|
||||
keys,
|
||||
checkAll,
|
||||
) => onItemSelectAll('right', keys, checkAll);
|
||||
|
||||
const leftFilter = (e: ChangeEvent<HTMLInputElement>) => onSearch?.('left', e.target.value);
|
||||
|
||||
@@ -407,15 +410,15 @@ const Transfer = <RecordType extends TransferItem = TransferItem>(
|
||||
multiple?: boolean,
|
||||
) => {
|
||||
const isLeftDirection = direction === 'left';
|
||||
const holder = [...(isLeftDirection ? sourceSelectedKeys : targetSelectedKeys)];
|
||||
const holder = isLeftDirection ? sourceSelectedKeys : targetSelectedKeys;
|
||||
const holderSet = new Set(holder);
|
||||
const data = [...(isLeftDirection ? leftDataSource : rightDataSource)].filter(
|
||||
(item) => !item?.disabled,
|
||||
const data: KeyWise<RecordType>[] = (isLeftDirection ? leftDataSource : rightDataSource).filter(
|
||||
(item): item is KeyWise<RecordType> => !item.disabled,
|
||||
);
|
||||
const currentSelectedIndex = data.findIndex((item) => item.key === selectedKey);
|
||||
// multiple select by hold down the shift key
|
||||
if (multiple && holder.length > 0) {
|
||||
handleMultipleSelect(direction, data as any, holderSet, currentSelectedIndex);
|
||||
handleMultipleSelect(direction, data, holderSet, currentSelectedIndex);
|
||||
} else {
|
||||
handleSingleSelect(direction, holderSet, selectedKey, checked, currentSelectedIndex);
|
||||
}
|
||||
@@ -434,13 +437,11 @@ const Transfer = <RecordType extends TransferItem = TransferItem>(
|
||||
onItemSelect('left', selectedKey, checked, e?.shiftKey);
|
||||
};
|
||||
|
||||
const onRightItemSelect = (
|
||||
selectedKey: TransferKey,
|
||||
checked: boolean,
|
||||
e?: React.MouseEvent<Element, MouseEvent>,
|
||||
) => {
|
||||
onItemSelect('right', selectedKey, checked, e?.shiftKey);
|
||||
};
|
||||
const onRightItemSelect: TransferListProps<KeyWise<RecordType>>['onItemSelect'] = (
|
||||
selectedKey,
|
||||
checked,
|
||||
e,
|
||||
) => onItemSelect('right', selectedKey, checked, e?.shiftKey);
|
||||
|
||||
const onRightItemRemove = (keys: TransferKey[]) => {
|
||||
setStateKeys('right', []);
|
||||
@@ -493,7 +494,7 @@ const Transfer = <RecordType extends TransferItem = TransferItem>(
|
||||
{
|
||||
[`${prefixCls}-disabled`]: mergedDisabled,
|
||||
[`${prefixCls}-customize-list`]: !!children,
|
||||
[`${prefixCls}-rtl`]: dir === 'rtl',
|
||||
[`${prefixCls}-rtl`]: isRtl,
|
||||
},
|
||||
getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
|
||||
contextClassName,
|
||||
@@ -543,14 +544,14 @@ const Transfer = <RecordType extends TransferItem = TransferItem>(
|
||||
handleFilter={leftFilter}
|
||||
handleClear={handleLeftClear}
|
||||
onItemSelect={onLeftItemSelect}
|
||||
onItemSelectAll={onLeftItemSelectAll as any}
|
||||
onItemSelectAll={onLeftItemSelectAll}
|
||||
render={render}
|
||||
showSearch={showSearch}
|
||||
renderList={children as any}
|
||||
footer={footer as any}
|
||||
onScroll={handleLeftScroll}
|
||||
disabled={mergedDisabled}
|
||||
direction={dir === 'rtl' ? 'right' : 'left'}
|
||||
direction={isRtl ? 'right' : 'left'}
|
||||
showSelectAll={showSelectAll}
|
||||
selectAllLabel={selectAllLabels[0]}
|
||||
pagination={mergedPagination}
|
||||
@@ -584,7 +585,7 @@ const Transfer = <RecordType extends TransferItem = TransferItem>(
|
||||
handleFilter={rightFilter}
|
||||
handleClear={handleRightClear}
|
||||
onItemSelect={onRightItemSelect}
|
||||
onItemSelectAll={onRightItemSelectAll as any}
|
||||
onItemSelectAll={onRightItemSelectAll}
|
||||
onItemRemove={onRightItemRemove}
|
||||
render={render}
|
||||
showSearch={showSearch}
|
||||
@@ -592,7 +593,7 @@ const Transfer = <RecordType extends TransferItem = TransferItem>(
|
||||
footer={footer as any}
|
||||
onScroll={handleRightScroll}
|
||||
disabled={mergedDisabled}
|
||||
direction={dir === 'rtl' ? 'left' : 'right'}
|
||||
direction={isRtl ? 'left' : 'right'}
|
||||
showSelectAll={showSelectAll}
|
||||
selectAllLabel={selectAllLabels[1]}
|
||||
showRemove={oneWay}
|
||||
|
||||
@@ -37,10 +37,10 @@ const treeData = [
|
||||
},
|
||||
];
|
||||
const App: React.FC = () => {
|
||||
const [placement, SetPlacement] = useState<SelectCommonPlacement>('topLeft');
|
||||
const [placement, setPlacement] = useState<SelectCommonPlacement>('topLeft');
|
||||
|
||||
const placementChange = (e: RadioChangeEvent) => {
|
||||
SetPlacement(e.target.value);
|
||||
setPlacement(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -280,6 +280,12 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
|
||||
.equal(),
|
||||
},
|
||||
|
||||
// >>> Checkbox
|
||||
// https://github.com/ant-design/ant-design/issues/56957
|
||||
[`${treeCls}-checkbox`]: {
|
||||
flexShrink: 0,
|
||||
},
|
||||
|
||||
// >>> Switcher
|
||||
[`${treeCls}-switcher`]: {
|
||||
...getSwitchStyle(prefixCls, token),
|
||||
|
||||
@@ -7,12 +7,15 @@ export interface EllipsisTooltipProps {
|
||||
tooltipProps?: TooltipProps;
|
||||
enableEllipsis: boolean;
|
||||
isEllipsis?: boolean;
|
||||
/** When true, show the ellipsis tooltip; when false, hide it. Fully controlled so tooltip re-opens when moving from copy button back to text. */
|
||||
open: boolean;
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
const EllipsisTooltip: React.FC<EllipsisTooltipProps> = ({
|
||||
enableEllipsis,
|
||||
isEllipsis,
|
||||
open,
|
||||
children,
|
||||
tooltipProps,
|
||||
}) => {
|
||||
@@ -20,8 +23,9 @@ const EllipsisTooltip: React.FC<EllipsisTooltipProps> = ({
|
||||
return children;
|
||||
}
|
||||
|
||||
const mergedOpen = open && isEllipsis;
|
||||
return (
|
||||
<Tooltip open={isEllipsis ? undefined : false} {...tooltipProps}>
|
||||
<Tooltip open={mergedOpen} {...tooltipProps}>
|
||||
{children}
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -65,8 +65,9 @@ export interface EllipsisConfig {
|
||||
tooltip?: React.ReactNode | TooltipProps;
|
||||
}
|
||||
|
||||
export interface BlockProps<C extends keyof JSX.IntrinsicElements = keyof JSX.IntrinsicElements>
|
||||
extends TypographyProps<C> {
|
||||
export interface BlockProps<
|
||||
C extends keyof JSX.IntrinsicElements = keyof JSX.IntrinsicElements,
|
||||
> extends TypographyProps<C> {
|
||||
title?: string;
|
||||
editable?: boolean | EditConfig;
|
||||
copyable?: boolean | CopyConfig;
|
||||
@@ -133,6 +134,8 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
copyable,
|
||||
component,
|
||||
title,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
...restProps
|
||||
} = props;
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
@@ -262,6 +265,8 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
};
|
||||
|
||||
const [ellipsisWidth, setEllipsisWidth] = React.useState(0);
|
||||
const [isHoveringOperations, setIsHoveringOperations] = React.useState(false);
|
||||
const [isHoveringTypography, setIsHoveringTypography] = React.useState(false);
|
||||
const onResize = ({ offsetWidth }: { offsetWidth: number }) => {
|
||||
setEllipsisWidth(offsetWidth);
|
||||
};
|
||||
@@ -407,11 +412,28 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderOperations = (canEllipsis: boolean) => [
|
||||
canEllipsis && renderExpand(),
|
||||
renderEdit(),
|
||||
renderCopy(),
|
||||
];
|
||||
const renderOperations = (canEllipsis: boolean) => {
|
||||
const expandNode = canEllipsis && renderExpand();
|
||||
const editNode = renderEdit();
|
||||
const copyNode = renderCopy();
|
||||
|
||||
if (!expandNode && !editNode && !copyNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
key="operations"
|
||||
className={`${prefixCls}-actions`}
|
||||
onMouseEnter={() => setIsHoveringOperations(true)}
|
||||
onMouseLeave={() => setIsHoveringOperations(false)}
|
||||
>
|
||||
{expandNode}
|
||||
{editNode}
|
||||
{copyNode}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const renderEllipsis = (canEllipsis: boolean) => [
|
||||
canEllipsis && !expanded && (
|
||||
@@ -430,8 +452,17 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
tooltipProps={tooltipProps}
|
||||
enableEllipsis={mergedEnableEllipsis}
|
||||
isEllipsis={isMergedEllipsis}
|
||||
open={isHoveringTypography && !isHoveringOperations}
|
||||
>
|
||||
<Typography
|
||||
onMouseEnter={(e) => {
|
||||
setIsHoveringTypography(true);
|
||||
onMouseEnter?.(e);
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
setIsHoveringTypography(false);
|
||||
onMouseLeave?.(e);
|
||||
}}
|
||||
className={clsx(
|
||||
{
|
||||
[`${prefixCls}-${type}`]: type,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -644,12 +644,12 @@ describe('Typography.Ellipsis', () => {
|
||||
it('Switch locale', async () => {
|
||||
const ref = React.createRef<HTMLElement>();
|
||||
const App = () => {
|
||||
const [locale, setLocal] = React.useState<Locale>();
|
||||
const [locale, setLocale] = React.useState<Locale>();
|
||||
|
||||
return (
|
||||
<ConfigProvider locale={locale}>
|
||||
<div>
|
||||
<button type="button" onClick={() => setLocal(zhCN)}>
|
||||
<button type="button" onClick={() => setLocale(zhCN)}>
|
||||
zhcn
|
||||
</button>
|
||||
<Base
|
||||
@@ -685,4 +685,51 @@ describe('Typography.Ellipsis', () => {
|
||||
expect(expandButtonCN).toHaveTextContent('展开');
|
||||
expect(expandButtonCN).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('copyable + ellipsis: ellipsis tooltip hides when hovering copy, shows when hovering text', async () => {
|
||||
offsetWidth = 50;
|
||||
scrollWidth = 100;
|
||||
|
||||
const ref = React.createRef<HTMLElement>();
|
||||
const { container, baseElement } = render(
|
||||
<Base ref={ref} component="p" copyable ellipsis={{ rows: 1, tooltip: true }}>
|
||||
{fullStr}
|
||||
</Base>,
|
||||
);
|
||||
|
||||
triggerResize(ref.current!);
|
||||
await waitFakeTimer();
|
||||
|
||||
const copyBtn = container.querySelector('.ant-typography-copy');
|
||||
const operationsWrapper = copyBtn?.parentElement;
|
||||
expect(operationsWrapper).toBeTruthy();
|
||||
|
||||
const typographyEl = ref.current!;
|
||||
|
||||
const getTooltipContent = () =>
|
||||
baseElement.querySelector('[role="tooltip"]')?.textContent?.trim();
|
||||
|
||||
fireEvent.mouseEnter(typographyEl);
|
||||
await waitFakeTimer();
|
||||
await waitFor(() => {
|
||||
expect(getTooltipContent()).toContain(fullStr);
|
||||
});
|
||||
|
||||
fireEvent.mouseEnter(operationsWrapper!);
|
||||
await waitFakeTimer();
|
||||
await waitFor(() => {
|
||||
const ellipsisTooltip = baseElement.querySelector('[role="tooltip"]');
|
||||
expect(ellipsisTooltip?.closest('.ant-tooltip')).toHaveClass('ant-tooltip-hidden');
|
||||
});
|
||||
|
||||
fireEvent.mouseLeave(operationsWrapper!);
|
||||
fireEvent.mouseEnter(typographyEl);
|
||||
await waitFakeTimer();
|
||||
await waitFor(() => {
|
||||
expect(getTooltipContent()).toContain(fullStr);
|
||||
});
|
||||
|
||||
fireEvent.mouseLeave(typographyEl);
|
||||
fireEvent.mouseLeave(operationsWrapper!);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
多行文本省略。
|
||||
多行文本省略。页面底部包含「可复制 + 省略」时 tooltip 行为的调试区块,便于验证:悬停文字显示省略 tooltip,悬停复制按钮仅显示复制 tooltip,从复制按钮移回文字时省略 tooltip 再次出现。
|
||||
|
||||
## en-US
|
||||
|
||||
Multiple line ellipsis support.
|
||||
Multiple line ellipsis support. The bottom section is a debug block for copyable + ellipsis tooltip behavior: hover text for ellipsis tooltip, hover copy button for copy-only tooltip, then move back to text to confirm the ellipsis tooltip shows again.
|
||||
|
||||
@@ -108,6 +108,22 @@ const App: React.FC = () => {
|
||||
<Text style={{ width: 100, whiteSpace: 'nowrap' }} ellipsis copyable>
|
||||
{templateStr}
|
||||
</Text>
|
||||
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<div style={{ marginBottom: 8, fontSize: 12, color: '#666' }}>
|
||||
<strong>Debug: copyable + ellipsis tooltips</strong>
|
||||
<br />
|
||||
1. Hover the text → ellipsis tooltip (full content) should show.
|
||||
<br />
|
||||
2. Hover the copy button → only "Copy" / "Copied" tooltip should show.
|
||||
<br />
|
||||
3. Move from copy button back to the text (without leaving the block) → ellipsis tooltip
|
||||
should show again.
|
||||
</div>
|
||||
<Text style={{ width: 280, display: 'block' }} ellipsis={{ tooltip: true }} copyable>
|
||||
{templateStr}
|
||||
</Text>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -107,6 +107,10 @@ const genTypographyStyle: GenerateStyle<TypographyToken> = (token) => {
|
||||
...getLinkStyles(token),
|
||||
|
||||
// Operation
|
||||
[`${componentCls}-actions`]: {
|
||||
display: 'inline',
|
||||
},
|
||||
|
||||
[`
|
||||
${componentCls}-expand,
|
||||
${componentCls}-collapse,
|
||||
|
||||
@@ -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)': {
|
||||
'& > *': {
|
||||
|
||||
@@ -21,13 +21,13 @@ export type AppendWatermark = (
|
||||
container: HTMLElement,
|
||||
) => void;
|
||||
|
||||
export default function useWatermark(
|
||||
function useWatermark(
|
||||
markStyle: React.CSSProperties,
|
||||
onRemove?: () => void,
|
||||
): [
|
||||
appendWatermark: AppendWatermark,
|
||||
removeWatermark: (container: HTMLElement) => void,
|
||||
isWatermarkEle: (ele: Node) => boolean,
|
||||
isWatermarkEle: (ele: Node, index?: number) => boolean,
|
||||
] {
|
||||
const watermarkMap = React.useRef(new Map<HTMLElement, HTMLDivElement>());
|
||||
const onRemoveEvent = useEvent(onRemove);
|
||||
@@ -79,5 +79,7 @@ export default function useWatermark(
|
||||
|
||||
const isWatermarkEle = (ele: any) => Array.from(watermarkMap.current.values()).includes(ele);
|
||||
|
||||
return [appendWatermark, removeWatermark, isWatermarkEle];
|
||||
return [appendWatermark, removeWatermark, isWatermarkEle] as const;
|
||||
}
|
||||
|
||||
export default useWatermark;
|
||||
|
||||
@@ -15,11 +15,14 @@ export function getPixelRatio() {
|
||||
}
|
||||
|
||||
/** Whether to re-render the watermark */
|
||||
export const reRendering = (mutation: MutationRecord, isWatermarkEle: (ele: Node) => boolean) => {
|
||||
export const reRendering = (
|
||||
mutation: MutationRecord,
|
||||
isWatermarkEle: (ele: Node, index?: number) => boolean,
|
||||
) => {
|
||||
let flag = false;
|
||||
// Whether to delete the watermark node
|
||||
if (mutation.removedNodes.length) {
|
||||
flag = Array.from<Node>(mutation.removedNodes).some((node) => isWatermarkEle(node));
|
||||
flag = Array.from<Node>(mutation.removedNodes).some(isWatermarkEle);
|
||||
}
|
||||
// Whether the watermark dom property value has been modified
|
||||
if (mutation.type === 'attributes' && isWatermarkEle(mutation.target)) {
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -65,7 +65,6 @@ export default antfu(
|
||||
'react-hooks/refs': 'off',
|
||||
'react/no-implicit-key': 'off',
|
||||
'react-naming-convention/ref-name': 'off',
|
||||
'react-naming-convention/use-state': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
12
package.json
12
package.json
@@ -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",
|
||||
@@ -136,7 +136,7 @@
|
||||
"@rc-component/rate": "~1.0.1",
|
||||
"@rc-component/resize-observer": "^1.1.1",
|
||||
"@rc-component/segmented": "~1.3.0",
|
||||
"@rc-component/select": "~1.6.5",
|
||||
"@rc-component/select": "~1.6.10",
|
||||
"@rc-component/slider": "~1.0.1",
|
||||
"@rc-component/steps": "~1.2.2",
|
||||
"@rc-component/switch": "~1.0.3",
|
||||
@@ -162,7 +162,7 @@
|
||||
"@ant-design/x": "^2.2.0",
|
||||
"@ant-design/x-sdk": "^2.2.0",
|
||||
"@antfu/eslint-config": "^7.3.0",
|
||||
"@biomejs/biome": "^2.3.10",
|
||||
"@biomejs/biome": "~2.3.10",
|
||||
"@blazediff/core": "^1.7.0",
|
||||
"@codecov/webpack-plugin": "^1.9.1",
|
||||
"@codesandbox/sandpack-react": "^2.20.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.13.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
||||
"@inquirer/prompts": "^8.0.2",
|
||||
"@madccc/duplicate-package-checker-webpack-plugin": "^1.0.0",
|
||||
@@ -210,7 +210,7 @@
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/progress": "^2.0.7",
|
||||
"@types/react": "19.2.9",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-highlight-words": "^0.20.0",
|
||||
"@types/semver": "^7.7.1",
|
||||
@@ -266,7 +266,7 @@
|
||||
"jest-image-snapshot": "^6.5.1",
|
||||
"jest-puppeteer": "^11.0.0",
|
||||
"jquery": "^4.0.0",
|
||||
"jsdom": "^28.0.0",
|
||||
"jsdom": "~28.0.0",
|
||||
"jsonml-to-react-element": "^1.1.11",
|
||||
"jsonml.js": "^0.1.0",
|
||||
"lint-staged": "^16.2.7",
|
||||
|
||||
@@ -27,9 +27,15 @@ describe('site test', () => {
|
||||
const html: string = await res.text();
|
||||
const root = new DOMParser().parseFromString(html, 'text/html');
|
||||
function getTextContent(node: any): string {
|
||||
if (!node) return '';
|
||||
if (typeof node.textContent === 'string') return node.textContent.trim();
|
||||
if (typeof node.innerText === 'string') return node.innerText.trim();
|
||||
if (!node) {
|
||||
return '';
|
||||
}
|
||||
if (typeof node.textContent === 'string') {
|
||||
return node.textContent.trim();
|
||||
}
|
||||
if (typeof node.innerText === 'string') {
|
||||
return node.innerText.trim();
|
||||
}
|
||||
// Fallback: recursively get text from children
|
||||
if (node.children && node.children.length > 0) {
|
||||
return Array.from(node.children)
|
||||
@@ -44,8 +50,10 @@ describe('site test', () => {
|
||||
return {
|
||||
length: list.length,
|
||||
text: () => {
|
||||
if (list.length === 0) return '';
|
||||
return list.map((n) => getTextContent(n)).join('');
|
||||
if (list.length === 0) {
|
||||
return '';
|
||||
}
|
||||
return list.map<string>(getTextContent).join('');
|
||||
},
|
||||
first: () => wrap(list.slice(0, 1)),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user