mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-14 05:19:20 +08:00
Compare commits
100 Commits
5.27.4
...
chore/scri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d85e0e0163 | ||
|
|
2818ba6b87 | ||
|
|
17cdff8c4c | ||
|
|
9b953b0fcc | ||
|
|
9823f348fc | ||
|
|
bd00e59fc1 | ||
|
|
dc179c8985 | ||
|
|
fee3e97a26 | ||
|
|
7d079c2560 | ||
|
|
809b8820f9 | ||
|
|
6abbee7273 | ||
|
|
2327d5bca9 | ||
|
|
2ad2ef7bfd | ||
|
|
c4b6a30e8f | ||
|
|
669e948638 | ||
|
|
54afae7767 | ||
|
|
da79662278 | ||
|
|
aa13ad28e4 | ||
|
|
e9622c2d26 | ||
|
|
9c129ac7b1 | ||
|
|
f47947d857 | ||
|
|
bb21a2dfa6 | ||
|
|
620d1bf431 | ||
|
|
2abbbda693 | ||
|
|
e714dde0dc | ||
|
|
95bca1cf70 | ||
|
|
76392a2fc0 | ||
|
|
dde07ee096 | ||
|
|
129b4d6e6f | ||
|
|
ac515bc0a6 | ||
|
|
017fea5bb4 | ||
|
|
117673d618 | ||
|
|
72f26b3898 | ||
|
|
6047298565 | ||
|
|
63e2dfd682 | ||
|
|
7a520404aa | ||
|
|
a07161fa05 | ||
|
|
be85401160 | ||
|
|
dc362e2e1c | ||
|
|
eca2da28c9 | ||
|
|
a4bb8aef9a | ||
|
|
1b6d5527c6 | ||
|
|
450c2b2cbb | ||
|
|
91a4451c5b | ||
|
|
3dad1bd09e | ||
|
|
44cfd0cdeb | ||
|
|
b8cb35ca2d | ||
|
|
a1a6f1bfff | ||
|
|
ef45e96227 | ||
|
|
6d703575ff | ||
|
|
378fcad5e1 | ||
|
|
70a9d43393 | ||
|
|
1370c11cf7 | ||
|
|
9ac07bea68 | ||
|
|
a2c571ff00 | ||
|
|
af89759d8d | ||
|
|
fce28d79af | ||
|
|
3fcd6c8573 | ||
|
|
004eab7736 | ||
|
|
7833e84c8d | ||
|
|
6b8d833cad | ||
|
|
ec57a0eb02 | ||
|
|
3635c80085 | ||
|
|
85812ed060 | ||
|
|
b5d72b5a77 | ||
|
|
c060c55fc4 | ||
|
|
7c5ae3b168 | ||
|
|
9f90f35513 | ||
|
|
3e88285d77 | ||
|
|
87601689fa | ||
|
|
5db1e6f449 | ||
|
|
90b4a4373d | ||
|
|
8a7e15460c | ||
|
|
2589e693cf | ||
|
|
94d5b800a7 | ||
|
|
dfc34f6ae3 | ||
|
|
0b5ef77e5d | ||
|
|
4d15a1715d | ||
|
|
1d433d59d5 | ||
|
|
bd1aa0114e | ||
|
|
647eba0302 | ||
|
|
6ca1cbed80 | ||
|
|
b59a1102f1 | ||
|
|
00b24f5be7 | ||
|
|
625e034a32 | ||
|
|
b138cadf40 | ||
|
|
cb0dd956ba | ||
|
|
594ee4d989 | ||
|
|
f1036be04f | ||
|
|
030783f41b | ||
|
|
9d07df9667 | ||
|
|
b189e0ec97 | ||
|
|
6562c20134 | ||
|
|
74c26d895d | ||
|
|
18e596b1f3 | ||
|
|
e3e7db9fd1 | ||
|
|
4d86c53505 | ||
|
|
fb7bbbea50 | ||
|
|
1ab5e38e70 | ||
|
|
52a701b497 |
@@ -2,7 +2,7 @@
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
|
||||
{
|
||||
"name": "ant-design",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:3-22-bookworm",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:4-22-bookworm",
|
||||
"postCreateCommand": "pnpm install",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
export default class FetchCache {
|
||||
private cache: Map<string, PromiseLike<any>> = new Map();
|
||||
|
||||
get(key: string) {
|
||||
return this.cache.get(key);
|
||||
}
|
||||
|
||||
set(key: string, value: PromiseLike<any>) {
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
|
||||
promise<T>(key: string, promiseFn: () => PromiseLike<T>): PromiseLike<T> {
|
||||
const cached = this.get(key);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const promise = promiseFn();
|
||||
this.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import fetch from 'cross-fetch';
|
||||
|
||||
import FetchCache from './cache';
|
||||
|
||||
const cache = new FetchCache();
|
||||
|
||||
const useFetch = <T>(options: string | { request: () => PromiseLike<T>; key: string }) => {
|
||||
let request;
|
||||
let key;
|
||||
if (typeof options === 'string') {
|
||||
request = () => fetch(options).then((res) => res.json());
|
||||
key = options;
|
||||
} else {
|
||||
request = options.request;
|
||||
key = options.key;
|
||||
}
|
||||
return React.use<T>(cache.promise<T>(key, request));
|
||||
};
|
||||
|
||||
export default useFetch;
|
||||
65
.dumi/hooks/useIssueCount.ts
Normal file
65
.dumi/hooks/useIssueCount.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { useMemo } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
|
||||
const isNumber = (value: any): value is number => {
|
||||
return typeof value === 'number' && !Number.isNaN(value);
|
||||
};
|
||||
|
||||
const fetcher = async (url: string): Promise<number> => {
|
||||
// eslint-disable-next-line compat/compat
|
||||
const res = await fetch(url, { headers: { Accept: 'application/vnd.github+json' } });
|
||||
const data = await res.json();
|
||||
const totalCount = isNumber(data?.total_count) ? data.total_count : 0;
|
||||
return totalCount;
|
||||
};
|
||||
|
||||
const swrConfig: SWRConfiguration<number, Error> = {
|
||||
revalidateOnReconnect: true, // 网络重新连接时重新请求
|
||||
dedupingInterval: 1000 * 60, // 1 分钟内重复 key 不会重新请求
|
||||
shouldRetryOnError: true, // 错误重试
|
||||
errorRetryCount: 3, // 最多重试 3 次
|
||||
};
|
||||
|
||||
export interface UseIssueCountOptions {
|
||||
repo: string; // e.g. ant-design/ant-design
|
||||
proxyEndpoint?: string; // backend proxy endpoint to avoid GitHub rate limit
|
||||
titleKeywords?: string[]; // keywords to match in issue title
|
||||
}
|
||||
|
||||
export const useIssueCount = (options: UseIssueCountOptions) => {
|
||||
const { repo, proxyEndpoint, titleKeywords } = options;
|
||||
|
||||
// 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 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}`;
|
||||
return `https://api.github.com/search/issues?q=${q}`;
|
||||
}, [repo, titleKeywords]);
|
||||
|
||||
const endpoint = proxyEndpoint || searchUrl;
|
||||
|
||||
const { data, error, isLoading } = useSWR<number, Error>(endpoint || null, fetcher, swrConfig);
|
||||
|
||||
const issueNewUrl = `https://github.com/${repo}/issues/new/choose`;
|
||||
|
||||
const issueSearchUrl = useMemo(() => {
|
||||
const keywords = (titleKeywords || []).filter(Boolean).map((k) => String(k));
|
||||
const groupExpr =
|
||||
keywords.length > 0 ? `(${keywords.map((k) => `is:issue in:title ${k}`).join(' OR ')})` : '';
|
||||
const qRaw = `is:open ${groupExpr}`.trim();
|
||||
return `https://github.com/${repo}/issues?q=${encodeURIComponent(qRaw)}`;
|
||||
}, [repo, titleKeywords]);
|
||||
|
||||
return {
|
||||
issueCount: data,
|
||||
issueCountError: error,
|
||||
issueCountLoading: isLoading,
|
||||
issueNewUrl,
|
||||
issueSearchUrl,
|
||||
};
|
||||
};
|
||||
|
||||
export default useIssueCount;
|
||||
@@ -221,7 +221,7 @@ const useMenu = (options: UseMenuOptions = {}): readonly [MenuProps['items'], st
|
||||
return result;
|
||||
}, []) ?? []
|
||||
);
|
||||
}, [sidebarData, fullData, pathname, search, options]);
|
||||
}, [sidebarData, pathname, fullData, search, before, after]);
|
||||
|
||||
return [menuItems, pathname] as const;
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ const NotFoundPage: React.FC<NotFoundProps> = ({ router }) => {
|
||||
code: 11,
|
||||
msg: `Page not found: ${location.href}; Source: ${document.referrer}`,
|
||||
});
|
||||
}, []);
|
||||
}, [isZhCN, pathname, router]);
|
||||
|
||||
return (
|
||||
<Result
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Badge, Carousel, Flex, Skeleton, Typography } from 'antd';
|
||||
import { Alert, Badge, Carousel, Flex, Skeleton, Typography } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import SiteContext from '../../../theme/slots/SiteContext';
|
||||
import type { Extra, Icon } from './util';
|
||||
import { getCarouselStyle, useSiteData } from './util';
|
||||
import { getCarouselStyle, useAntdSiteConfig } from './util';
|
||||
|
||||
const useStyle = createStyles(({ token, css, cx }) => {
|
||||
const { carousel } = getCarouselStyle();
|
||||
@@ -67,17 +67,20 @@ const useStyle = createStyles(({ token, css, cx }) => {
|
||||
interface RecommendItemProps {
|
||||
extra: Extra;
|
||||
index: number;
|
||||
icons: Icon[];
|
||||
icons?: Icon[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const RecommendItem: React.FC<RecommendItemProps> = ({ extra, index, icons, className }) => {
|
||||
const RecommendItem: React.FC<RecommendItemProps> = (props) => {
|
||||
const { extra, index, icons, className } = props;
|
||||
|
||||
const { styles } = useStyle();
|
||||
|
||||
if (!extra) {
|
||||
return <Skeleton key={index} />;
|
||||
}
|
||||
const icon = icons.find((i) => i.name === extra.source);
|
||||
|
||||
const icon = icons?.find((i) => i.name === extra.source);
|
||||
|
||||
const card = (
|
||||
<a
|
||||
@@ -93,7 +96,9 @@ const RecommendItem: React.FC<RecommendItemProps> = ({ extra, index, icons, clas
|
||||
</Typography.Paragraph>
|
||||
<Flex justify="space-between" align="center">
|
||||
<Typography.Text>{extra.date}</Typography.Text>
|
||||
{icon && <img src={icon.href} draggable={false} className={styles.bannerBg} alt="banner" />}
|
||||
{icon?.href && (
|
||||
<img src={icon.href} draggable={false} className={styles.bannerBg} alt="banner" />
|
||||
)}
|
||||
</Flex>
|
||||
</a>
|
||||
);
|
||||
@@ -111,6 +116,7 @@ const RecommendItem: React.FC<RecommendItemProps> = ({ extra, index, icons, clas
|
||||
|
||||
export const BannerRecommendsFallback: React.FC = () => {
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
|
||||
const { styles } = useStyle();
|
||||
|
||||
const list = Array.from({ length: 3 });
|
||||
@@ -118,7 +124,7 @@ export const BannerRecommendsFallback: React.FC = () => {
|
||||
return isMobile ? (
|
||||
<Carousel className={styles.carousel}>
|
||||
{list.map((_, index) => (
|
||||
<div key={index} className={styles.itemBase}>
|
||||
<div key={`mobile-${index}`} className={styles.itemBase}>
|
||||
<Skeleton active style={{ padding: '0 24px' }} />
|
||||
</div>
|
||||
))}
|
||||
@@ -126,7 +132,7 @@ export const BannerRecommendsFallback: React.FC = () => {
|
||||
) : (
|
||||
<div className={styles.container}>
|
||||
{list.map((_, index) => (
|
||||
<div key={index} className={styles.itemBase}>
|
||||
<div key={`desktop-${index}`} className={styles.itemBase}>
|
||||
<Skeleton active />
|
||||
</div>
|
||||
))}
|
||||
@@ -138,25 +144,37 @@ const BannerRecommends: React.FC = () => {
|
||||
const { styles } = useStyle();
|
||||
const [, lang] = useLocale();
|
||||
const { isMobile } = React.use(SiteContext);
|
||||
const data = useSiteData();
|
||||
const extras = data?.extras?.[lang];
|
||||
const icons = data?.icons || [];
|
||||
const first3 =
|
||||
!extras || extras.length === 0 ? Array.from<any>({ length: 3 }) : extras.slice(0, 3);
|
||||
const { data, error, isLoading } = useAntdSiteConfig();
|
||||
|
||||
if (!data) {
|
||||
if (isLoading) {
|
||||
return <BannerRecommendsFallback />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Alert
|
||||
showIcon
|
||||
type="error"
|
||||
message={error.message}
|
||||
description={process.env.NODE_ENV !== 'production' ? error.stack : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const extras = data?.extras?.[lang];
|
||||
|
||||
const mergedExtras =
|
||||
!extras || !extras.length ? Array.from<Extra>({ length: 3 }) : extras.slice(0, 3);
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Carousel className={styles.carousel}>
|
||||
{first3.map((extra, index) => (
|
||||
<div key={index}>
|
||||
{mergedExtras.map((extra, index) => (
|
||||
<div key={`mobile-${index}`}>
|
||||
<RecommendItem
|
||||
extra={extra}
|
||||
index={index}
|
||||
icons={icons}
|
||||
icons={data?.icons}
|
||||
className={styles.sliderItem}
|
||||
/>
|
||||
</div>
|
||||
@@ -167,13 +185,13 @@ const BannerRecommends: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{first3.map((extra, index) => (
|
||||
{mergedExtras.map((extra, index) => (
|
||||
<RecommendItem
|
||||
key={`desktop-${index}`}
|
||||
extra={extra}
|
||||
index={index}
|
||||
icons={icons}
|
||||
icons={data?.icons}
|
||||
className={styles.cardItem}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -258,7 +258,18 @@ const ComponentsList: React.FC = () => {
|
||||
),
|
||||
},
|
||||
],
|
||||
[isMobile],
|
||||
[
|
||||
isMobile,
|
||||
locale.inProgress,
|
||||
locale.lastMonth,
|
||||
locale.lastWeek,
|
||||
locale.lastYear,
|
||||
locale.sampleContent,
|
||||
locale.success,
|
||||
locale.taskFailed,
|
||||
locale.tour,
|
||||
locale.yesterday,
|
||||
],
|
||||
);
|
||||
|
||||
return isMobile ? (
|
||||
|
||||
@@ -73,13 +73,12 @@ const ThemeColorPicker: React.FC<ThemeColorPickerProps> = ({ value, onChange, id
|
||||
|
||||
const matchColors = React.useMemo(() => {
|
||||
const valueStr = generateColor(value || '').toRgbString();
|
||||
let existActive = false;
|
||||
const colors = PRESET_COLORS.map((color) => {
|
||||
const colorStr = generateColor(color).toRgbString();
|
||||
const active = colorStr === valueStr;
|
||||
existActive = existActive || active;
|
||||
return { color, active, picker: false };
|
||||
return { color, active, picker: false } as const;
|
||||
});
|
||||
const existActive = colors.some((c) => c.active);
|
||||
|
||||
return [
|
||||
...colors,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
/* eslint-disable compat/compat */
|
||||
import { css } from 'antd-style';
|
||||
import fetch from 'cross-fetch';
|
||||
import useSWR from 'swr';
|
||||
|
||||
export interface Author {
|
||||
avatar: string;
|
||||
@@ -82,19 +82,13 @@ export function preLoad(list: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export function useSiteData(): Partial<SiteData> | undefined {
|
||||
const [data, setData] = useState<SiteData | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('https://render.alipay.com/p/h5data/antd4-config_website-h5data.json').then(
|
||||
async (res) => {
|
||||
setData(await res.json());
|
||||
},
|
||||
);
|
||||
}, []);
|
||||
|
||||
return data;
|
||||
}
|
||||
export const useAntdSiteConfig = () => {
|
||||
const { data, error, isLoading } = useSWR<Partial<SiteData>, Error>(
|
||||
`https://render.alipay.com/p/h5data/antd4-config_website-h5data.json`,
|
||||
(url: string) => fetch(url).then((res) => res.json()),
|
||||
);
|
||||
return { data, error, isLoading };
|
||||
};
|
||||
|
||||
export const getCarouselStyle = () => ({
|
||||
carousel: css`
|
||||
|
||||
@@ -51,11 +51,7 @@ const Homepage: React.FC = () => {
|
||||
|
||||
<div>
|
||||
{/* 定制主题 */}
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
algorithm: theme.defaultAlgorithm,
|
||||
}}
|
||||
>
|
||||
<ConfigProvider theme={{ algorithm: theme.defaultAlgorithm }}>
|
||||
<Suspense fallback={null}>
|
||||
<Theme />
|
||||
</Suspense>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { UnifiedTransformer } from 'dumi';
|
||||
import { unistUtilVisit } from 'dumi';
|
||||
import set from 'lodash/set';
|
||||
import semver from 'semver';
|
||||
|
||||
let hastToString: typeof import('hast-util-to-string').toString;
|
||||
|
||||
@@ -9,6 +10,24 @@ let hastToString: typeof import('hast-util-to-string').toString;
|
||||
({ toString: hastToString } = await import('hast-util-to-string'));
|
||||
})();
|
||||
|
||||
function isValidStrictVer(ver: string): boolean {
|
||||
if (!semver.valid(ver)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parts = ver.split('.');
|
||||
if (parts.length !== 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parts.every((part) => /^\d+$/.test(part));
|
||||
}
|
||||
|
||||
function isValidDate(dateStr: string): boolean {
|
||||
// (YYYY-MM-DD)
|
||||
return /^\d{4}-\d{2}-\d{2}$/.test(dateStr);
|
||||
}
|
||||
|
||||
const COMPONENT_NAME = 'RefinedChangelog';
|
||||
|
||||
function rehypeChangelog(): UnifiedTransformer<any> {
|
||||
@@ -23,32 +42,73 @@ function rehypeChangelog(): UnifiedTransformer<any> {
|
||||
const nodesToWrap: { parent: any; startIdx: number }[] = [];
|
||||
const WRAPPER_FLAG = 'data-changelog-wrapped'; // 包裹容器唯一标识
|
||||
|
||||
function checkLogSegment(node: any, strict = true) {
|
||||
if (node && node.type === 'element' && node.tagName === 'h2') {
|
||||
if (strict) {
|
||||
const ver = hastToString(node);
|
||||
return isValidStrictVer(ver) && semver.major(ver) >= 5;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
unistUtilVisit.visit(tree, 'element', (node, idx, parent) => {
|
||||
if (node.properties?.[WRAPPER_FLAG]) {
|
||||
return unistUtilVisit.SKIP;
|
||||
}
|
||||
if (
|
||||
idx !== undefined &&
|
||||
parent &&
|
||||
idx! + 2 < parent.children.length &&
|
||||
node.tagName === 'h2' &&
|
||||
parent.children[idx! + 1].tagName === 'p' &&
|
||||
parent.children[idx! + 2].tagName === 'ul'
|
||||
) {
|
||||
|
||||
if (idx !== undefined && parent && checkLogSegment(node)) {
|
||||
nodesToWrap.push({ parent, startIdx: idx! });
|
||||
}
|
||||
});
|
||||
|
||||
nodesToWrap.reverse().forEach(({ parent, startIdx }) => {
|
||||
const [heading, date, list] = parent.children.splice(startIdx, 3);
|
||||
const totalNodesToWrap = nodesToWrap.length;
|
||||
for (let i = totalNodesToWrap - 1; i >= 0; i--) {
|
||||
const { parent, startIdx } = nodesToWrap[i];
|
||||
|
||||
let endIdx = -1;
|
||||
const isEndOfWrap = i === totalNodesToWrap - 1;
|
||||
for (let j = startIdx + 1; j < parent.children.length; j++) {
|
||||
const nextNode = parent.children[j];
|
||||
if (
|
||||
(isEndOfWrap && checkLogSegment(nextNode, false)) || // 日志页通常还存在历史 major 版本
|
||||
nextNode.properties?.[WRAPPER_FLAG] || // 已经被处理
|
||||
checkLogSegment(nextNode) // 下一段日志
|
||||
) {
|
||||
endIdx = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (endIdx === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Version
|
||||
const heading = parent.children[startIdx];
|
||||
|
||||
// Find Date
|
||||
let dateIdx = -1;
|
||||
for (let j = startIdx + 1; j < endIdx; j++) {
|
||||
const node = parent.children[j];
|
||||
if (node.type === 'element' && isValidDate(hastToString(node))) {
|
||||
dateIdx = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dateIdx === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect list nodes between dateIdx and endIdx
|
||||
const version = hastToString(heading);
|
||||
const date = parent.children[dateIdx];
|
||||
const dateStr = hastToString(date);
|
||||
const details = parent.children.slice(dateIdx + 1, endIdx);
|
||||
|
||||
const headingWrap = {
|
||||
type: 'element',
|
||||
tagName: `${COMPONENT_NAME}.Version`,
|
||||
// 为标签添加语义化 className (下面同理)
|
||||
children: [set(heading, 'properties.className', 'changelog-version')],
|
||||
};
|
||||
|
||||
@@ -58,10 +118,13 @@ function rehypeChangelog(): UnifiedTransformer<any> {
|
||||
children: [set(date, 'properties.className', 'changelog-date')],
|
||||
};
|
||||
|
||||
const listWrap = {
|
||||
const detailWrap = {
|
||||
type: 'element',
|
||||
tagName: `${COMPONENT_NAME}.Details`,
|
||||
children: [set(list, 'properties.className', 'changelog-details')],
|
||||
properties: {
|
||||
className: 'changelog-details',
|
||||
},
|
||||
children: details,
|
||||
};
|
||||
|
||||
const wrapper = {
|
||||
@@ -82,11 +145,11 @@ function rehypeChangelog(): UnifiedTransformer<any> {
|
||||
value: JSON.stringify(dateStr),
|
||||
},
|
||||
],
|
||||
children: [headingWrap, dateWrap, listWrap],
|
||||
children: [headingWrap, dateWrap, detailWrap],
|
||||
};
|
||||
|
||||
parent.children.splice(startIdx, 0, wrapper);
|
||||
});
|
||||
parent.children.splice(startIdx, endIdx - startIdx, wrapper);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ const remarkAnchor = (opt: Options = {}): UnifiedTransformer<any> => {
|
||||
const ids = new Set();
|
||||
|
||||
unistUtilVisit.visit(tree, 'heading', (node) => {
|
||||
if (toArr(realOpt.level).indexOf(node.depth) === -1) {
|
||||
if (!toArr(realOpt.level).includes(node.depth)) {
|
||||
return unistUtilVisit.CONTINUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@ interface AntdProps {
|
||||
component: keyof typeof all;
|
||||
}
|
||||
|
||||
function Antd(props: AntdProps) {
|
||||
const Antd: React.FC<AntdProps> = (props) => {
|
||||
const { component, ...restProps } = props;
|
||||
const Component = (all[component] ?? React.Fragment) as React.ComponentType;
|
||||
|
||||
const Component = (all[component] ?? React.Fragment) as React.ComponentType<any>;
|
||||
return <Component {...restProps} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Antd;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Tag, TagProps } from 'antd';
|
||||
import { Tag } from 'antd';
|
||||
import type { TagProps } from 'antd';
|
||||
|
||||
// https://github.com/umijs/dumi/blob/master/src/client/theme-default/builtins/Badge/index.tsx
|
||||
interface BadgeProps extends TagProps {
|
||||
|
||||
@@ -1,46 +1,66 @@
|
||||
import React from 'react';
|
||||
import { EditOutlined, GithubOutlined, HistoryOutlined, CompassOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
BugOutlined,
|
||||
CompassOutlined,
|
||||
EditOutlined,
|
||||
GithubOutlined,
|
||||
HistoryOutlined,
|
||||
IssuesCloseOutlined,
|
||||
LoadingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { GetProp } from 'antd';
|
||||
import { Descriptions, Flex, theme, Tooltip, Typography } from 'antd';
|
||||
import { createStyles, css } from 'antd-style';
|
||||
import kebabCase from 'lodash/kebabCase';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import Link from '../../common/Link';
|
||||
|
||||
import useIssueCount from '../../../hooks/useIssueCount';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import ComponentChangelog from '../../common/ComponentChangelog';
|
||||
import Link from '../../common/Link';
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
import: '使用',
|
||||
copy: '复制',
|
||||
copied: '已复制',
|
||||
source: '源码',
|
||||
source: '反馈',
|
||||
docs: '文档',
|
||||
edit: '编辑此页',
|
||||
changelog: '更新日志',
|
||||
design: '设计指南',
|
||||
version: '版本',
|
||||
issueNew: '提交问题',
|
||||
issueOpen: '待解决',
|
||||
},
|
||||
en: {
|
||||
import: 'Import',
|
||||
copy: 'Copy',
|
||||
copied: 'Copied',
|
||||
source: 'Source',
|
||||
source: 'GitHub',
|
||||
docs: 'Docs',
|
||||
edit: 'Edit this page',
|
||||
changelog: 'Changelog',
|
||||
design: 'Design',
|
||||
version: 'Version',
|
||||
issueNew: 'Issue',
|
||||
issueOpen: 'Open issues',
|
||||
},
|
||||
};
|
||||
|
||||
const branchUrl = 'https://github.com/ant-design/ant-design/edit/master/';
|
||||
const branchUrl = (repo: string) => `https://github.com/${repo}/edit/master/`;
|
||||
|
||||
function isVersionNumber(value?: string) {
|
||||
return value && /^\d+\.\d+\.\d+$/.test(value);
|
||||
}
|
||||
|
||||
const transformComponentName = (componentName: string) => {
|
||||
if (componentName === 'Notification' || componentName === 'Message') {
|
||||
return componentName.toLowerCase();
|
||||
}
|
||||
return componentName;
|
||||
};
|
||||
|
||||
const useStyle = createStyles(({ token }) => ({
|
||||
code: css`
|
||||
cursor: pointer;
|
||||
@@ -61,7 +81,7 @@ const useStyle = createStyles(({ token }) => ({
|
||||
}
|
||||
`,
|
||||
icon: css`
|
||||
margin-inline-end: 3px;
|
||||
margin-inline-end: 4px;
|
||||
`,
|
||||
}));
|
||||
|
||||
@@ -71,15 +91,23 @@ export interface ComponentMetaProps {
|
||||
filename?: string;
|
||||
version?: string;
|
||||
designUrl?: string;
|
||||
searchTitleKeywords?: string[];
|
||||
repo: string;
|
||||
}
|
||||
|
||||
const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
const { component, source, filename, version, designUrl } = props;
|
||||
const { component, source, filename, version, designUrl, searchTitleKeywords, repo } = props;
|
||||
const { token } = theme.useToken();
|
||||
const [locale, lang] = useLocale(locales);
|
||||
const isZhCN = lang === 'cn';
|
||||
const { styles } = useStyle();
|
||||
|
||||
// ======================= Issues Count =======================
|
||||
const { issueCount, issueCountLoading, issueNewUrl, issueSearchUrl } = useIssueCount({
|
||||
repo,
|
||||
titleKeywords: searchTitleKeywords,
|
||||
});
|
||||
|
||||
// ========================= Copy =========================
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
|
||||
@@ -98,7 +126,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
if (String(source) === 'true') {
|
||||
const kebabComponent = kebabCase(component);
|
||||
return [
|
||||
`https://github.com/ant-design/ant-design/blob/master/components/${kebabComponent}`,
|
||||
`https://github.com/${repo}/blob/master/components/${kebabComponent}`,
|
||||
`components/${kebabComponent}`,
|
||||
];
|
||||
}
|
||||
@@ -108,14 +136,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
}
|
||||
|
||||
return [source, source];
|
||||
}, [component, source]);
|
||||
|
||||
const transformComponentName = (componentName: string) => {
|
||||
if (componentName === 'Notification' || componentName === 'Message') {
|
||||
return componentName.toLowerCase();
|
||||
}
|
||||
return componentName;
|
||||
};
|
||||
}, [component, repo, source]);
|
||||
|
||||
// ======================== Render ========================
|
||||
const importList = `import { ${transformComponentName(component)} } from "antd";`;
|
||||
@@ -126,9 +147,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
colon={false}
|
||||
column={1}
|
||||
style={{ marginTop: token.margin }}
|
||||
styles={{
|
||||
label: { paddingInlineEnd: token.padding, width: 56 },
|
||||
}}
|
||||
styles={{ label: { paddingInlineEnd: token.padding, width: 56 } }}
|
||||
items={
|
||||
[
|
||||
{
|
||||
@@ -150,10 +169,22 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
filledSource && {
|
||||
label: locale.source,
|
||||
children: (
|
||||
<Typography.Link className={styles.code} href={filledSource} target="_blank">
|
||||
<GithubOutlined className={styles.icon} />
|
||||
<span>{abbrSource}</span>
|
||||
</Typography.Link>
|
||||
<Flex justify="flex-start" align="center" gap="small">
|
||||
<Typography.Link className={styles.code} href={filledSource} target="_blank">
|
||||
<GithubOutlined className={styles.icon} />
|
||||
<span>{abbrSource}</span>
|
||||
</Typography.Link>
|
||||
<Typography.Link className={styles.code} href={issueNewUrl} target="_blank">
|
||||
<BugOutlined className={styles.icon} />
|
||||
<span>{locale.issueNew}</span>
|
||||
</Typography.Link>
|
||||
<Typography.Link className={styles.code} href={issueSearchUrl} target="_blank">
|
||||
<IssuesCloseOutlined className={styles.icon} />
|
||||
<span>
|
||||
{locale.issueOpen} {issueCountLoading ? <LoadingOutlined /> : issueCount}
|
||||
</span>
|
||||
</Typography.Link>
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
filename && {
|
||||
@@ -162,7 +193,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
|
||||
<Flex justify="flex-start" align="center" gap="small">
|
||||
<Typography.Link
|
||||
className={styles.code}
|
||||
href={`${branchUrl}${filename}`}
|
||||
href={`${branchUrl(repo)}${filename}`}
|
||||
target="_blank"
|
||||
>
|
||||
<EditOutlined className={styles.icon} />
|
||||
|
||||
@@ -62,7 +62,7 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
|
||||
},
|
||||
});
|
||||
}, []),
|
||||
[expandAll, showDebug],
|
||||
[expandAll, items, showDebug],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -30,7 +30,7 @@ interface CategoryProps {
|
||||
title: CategoriesKeys;
|
||||
icons: string[];
|
||||
theme: ThemeType;
|
||||
newIcons: string[];
|
||||
newIcons: ReadonlyArray<string> | string[];
|
||||
}
|
||||
|
||||
const Category: React.FC<CategoryProps> = (props) => {
|
||||
@@ -40,17 +40,20 @@ const Category: React.FC<CategoryProps> = (props) => {
|
||||
const intl = useIntl();
|
||||
const [justCopied, setJustCopied] = React.useState<string | null>(null);
|
||||
const copyId = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const onCopied = React.useCallback((type: string, text: string) => {
|
||||
message.success(
|
||||
<span>
|
||||
<code className={styles.copiedCode}>{text}</code> copied 🎉
|
||||
</span>,
|
||||
);
|
||||
setJustCopied(type);
|
||||
copyId.current = setTimeout(() => {
|
||||
setJustCopied(null);
|
||||
}, 2000);
|
||||
}, []);
|
||||
const onCopied = React.useCallback(
|
||||
(type: string, text: string) => {
|
||||
message.success(
|
||||
<span>
|
||||
<code className={styles.copiedCode}>{text}</code> copied 🎉
|
||||
</span>,
|
||||
);
|
||||
setJustCopied(type);
|
||||
copyId.current = setTimeout(() => {
|
||||
setJustCopied(null);
|
||||
}, 2000);
|
||||
},
|
||||
[message, styles.copiedCode],
|
||||
);
|
||||
React.useEffect(
|
||||
() => () => {
|
||||
if (copyId.current) {
|
||||
|
||||
@@ -35,6 +35,8 @@ interface IconSearchState {
|
||||
searchKey: string;
|
||||
}
|
||||
|
||||
const NEW_ICON_NAMES: ReadonlyArray<string> = [];
|
||||
|
||||
const IconSearch: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { styles } = useStyle();
|
||||
@@ -44,8 +46,6 @@ const IconSearch: React.FC = () => {
|
||||
});
|
||||
const token = useTheme();
|
||||
|
||||
const newIconNames: string[] = [];
|
||||
|
||||
const handleSearchIcon = debounce((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDisplayState((prevState) => ({ ...prevState, searchKey: e.target.value }));
|
||||
}, 300);
|
||||
@@ -68,7 +68,7 @@ const IconSearch: React.FC = () => {
|
||||
|
||||
const tagMatchedCategoryObj = matchCategoriesFromTag(normalizedSearchKey, metaInfo);
|
||||
|
||||
const namedMatchedCategoryObj = Object.keys(categories).reduce(
|
||||
const namedMatchedCategoryObj = Object.keys(categories).reduce<Record<string, MatchedCategory>>(
|
||||
(acc, key) => {
|
||||
let iconList = categories[key as CategoriesKeys];
|
||||
if (normalizedSearchKey) {
|
||||
@@ -89,7 +89,7 @@ const IconSearch: React.FC = () => {
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, MatchedCategory>,
|
||||
{},
|
||||
);
|
||||
|
||||
// merge matched categories from tag search
|
||||
@@ -110,13 +110,14 @@ const IconSearch: React.FC = () => {
|
||||
title={category as CategoriesKeys}
|
||||
theme={theme}
|
||||
icons={icons}
|
||||
newIcons={newIconNames}
|
||||
newIcons={NEW_ICON_NAMES}
|
||||
/>
|
||||
));
|
||||
return categoriesResult.length ? categoriesResult : <Empty style={{ margin: '2em 0' }} />;
|
||||
}, [displayState.searchKey, displayState.theme]);
|
||||
}, [displayState]);
|
||||
|
||||
const [searchBarAffixed, setSearchBarAffixed] = useState<boolean | undefined>(false);
|
||||
|
||||
const { borderRadius, colorBgContainer, anchorTop } = token;
|
||||
|
||||
const affixedStyle: CSSProperties = {
|
||||
@@ -183,36 +184,27 @@ type MatchedCategory = {
|
||||
icons: string[];
|
||||
};
|
||||
|
||||
function matchCategoriesFromTag(
|
||||
searchKey: string,
|
||||
metaInfo: IconsMeta,
|
||||
): Record<string, MatchedCategory> {
|
||||
function matchCategoriesFromTag(searchKey: string, metaInfo: IconsMeta) {
|
||||
if (!searchKey) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Object.keys(metaInfo).reduce(
|
||||
(acc, key) => {
|
||||
const icon = metaInfo[key as IconName];
|
||||
const category = icon.category;
|
||||
return Object.keys(metaInfo).reduce<Record<string, MatchedCategory>>((acc, key) => {
|
||||
const icon = metaInfo[key as IconName];
|
||||
const category = icon.category;
|
||||
|
||||
if (icon.tags.some((tag) => tag.toLowerCase().includes(searchKey))) {
|
||||
if (acc[category]) {
|
||||
// if category exists, push icon to icons array
|
||||
acc[category].icons.push(key);
|
||||
} else {
|
||||
// if category does not exist, create a new entry
|
||||
acc[category] = {
|
||||
category,
|
||||
icons: [key],
|
||||
};
|
||||
}
|
||||
if (icon.tags.some((tag) => tag.toLowerCase().includes(searchKey))) {
|
||||
if (acc[category]) {
|
||||
// if category exists, push icon to icons array
|
||||
acc[category].icons.push(key);
|
||||
} else {
|
||||
// if category does not exist, create a new entry
|
||||
acc[category] = { category, icons: [key] };
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, MatchedCategory>,
|
||||
);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function mergeCategory(
|
||||
|
||||
@@ -39,7 +39,7 @@ const LocaleLink: React.FC<React.PropsWithChildren<LocaleLinkProps>> = ({
|
||||
}
|
||||
|
||||
return to;
|
||||
}, [to]);
|
||||
}, [localeType, to]);
|
||||
|
||||
const linkProps: LocaleLinkProps = {
|
||||
...props,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Suspense, useState } from 'react';
|
||||
import React, { Suspense, useMemo, useState } from 'react';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { App, Tooltip } from 'antd';
|
||||
import { FormattedMessage } from 'dumi';
|
||||
@@ -27,17 +27,19 @@ const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies =
|
||||
|
||||
const [locale] = useLocale(locales);
|
||||
|
||||
const codeBlockPrefillConfig = {
|
||||
title: `${title} - antd@${dependencies.antd}`,
|
||||
js: `${
|
||||
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
|
||||
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
|
||||
/export default/,
|
||||
'const ComponentDemo =',
|
||||
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
|
||||
css: '',
|
||||
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
|
||||
};
|
||||
const codeBlockPrefillConfig = useMemo(() => {
|
||||
return {
|
||||
title: `${title} - antd@${dependencies.antd}`,
|
||||
js: `${
|
||||
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
|
||||
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
|
||||
/export default/,
|
||||
'const ComponentDemo =',
|
||||
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
|
||||
css: '',
|
||||
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
|
||||
};
|
||||
}, [dependencies, jsx, title]);
|
||||
|
||||
const openHituCodeBlockFn = React.useCallback(() => {
|
||||
setLoading(false);
|
||||
@@ -52,8 +54,7 @@ const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies =
|
||||
|
||||
const handleClick = () => {
|
||||
const scriptId = 'hitu-code-block-js';
|
||||
const existScript = document.getElementById(scriptId) as HTMLScriptElement | null;
|
||||
// @ts-ignore
|
||||
const existScript = document.getElementById(scriptId) as HTMLScriptElement;
|
||||
if (existScript?.dataset.loaded) {
|
||||
openHituCodeBlockFn();
|
||||
return;
|
||||
@@ -86,7 +87,7 @@ const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies =
|
||||
);
|
||||
};
|
||||
|
||||
const SuspenseCodeBlockButton: React.FC<React.ComponentProps<typeof CodeBlockButton>> = (props) => (
|
||||
const SuspenseCodeBlockButton: React.FC<CodeBlockButtonProps> = (props) => (
|
||||
<Suspense fallback={null}>
|
||||
<CodeBlockButton {...props} />
|
||||
</Suspense>
|
||||
|
||||
@@ -98,7 +98,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
|
||||
if (asset.id === hash.slice(1)) {
|
||||
anchorRef.current?.click();
|
||||
}
|
||||
}, []);
|
||||
}, [asset.id, hash]);
|
||||
|
||||
useEffect(() => {
|
||||
setCodeExpand(expand);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Project, ProjectFiles } from '@stackblitz/sdk';
|
||||
import type { Project, ProjectFiles } from '@stackblitz/sdk';
|
||||
|
||||
const getStackblitzConfig = ({
|
||||
title = '',
|
||||
@@ -34,7 +34,7 @@ const getStackblitzConfig = ({
|
||||
'@types/react-dom': '^19.1.7',
|
||||
'@vitejs/plugin-react': '^4.7.0',
|
||||
eslint: '^9.32.0',
|
||||
'eslint-plugin-react-hooks': '^5.2.0',
|
||||
'eslint-plugin-react-hooks': '^7.0.0',
|
||||
'eslint-plugin-react-refresh': '^0.4.20',
|
||||
globals: '^16.3.0',
|
||||
typescript: '~5.8.3',
|
||||
|
||||
@@ -2,7 +2,8 @@ import * as React from 'react';
|
||||
import { BugOutlined } from '@ant-design/icons';
|
||||
import { Button, Flex, Popover, theme } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import dayjs from 'dayjs';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import { matchDeprecated } from '../../utils';
|
||||
@@ -118,7 +119,10 @@ const Version: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
|
||||
const DateComp: React.FC<React.PropsWithChildren> = (props) => props.children;
|
||||
|
||||
const DetailsComp: React.FC<React.PropsWithChildren> = (props) => props.children;
|
||||
const DetailsComp: React.FC<React.PropsWithChildren<HTMLDivElement>> = (props) => {
|
||||
const { children, className } = props;
|
||||
return <div className={className}>{children}</div>;
|
||||
};
|
||||
|
||||
export default Object.assign(RefinedChangelog, {
|
||||
Version,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { Avatar, Divider, Empty, Skeleton, Tabs } from 'antd';
|
||||
import { Alert, Avatar, Divider, Empty, Skeleton, Tabs } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import dayjs from 'dayjs';
|
||||
import { FormattedMessage } from 'dumi';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import type { Article, Authors, SiteData } from '../../../pages/index/components/util';
|
||||
import { useSiteData } from '../../../pages/index/components/util';
|
||||
import { useAntdSiteConfig } from '../../../pages/index/components/util';
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => {
|
||||
const { antCls } = token;
|
||||
@@ -92,7 +92,7 @@ const ArticleList: React.FC<ArticleListProps> = ({ name, data = [], authors = []
|
||||
);
|
||||
};
|
||||
|
||||
const Articles: React.FC<{ data: Partial<SiteData> }> = ({ data }) => {
|
||||
const Articles: React.FC<{ data?: Partial<SiteData> }> = ({ data = {} }) => {
|
||||
const [, lang] = useLocale();
|
||||
const isZhCN = lang === 'cn';
|
||||
|
||||
@@ -107,7 +107,7 @@ const Articles: React.FC<{ data: Partial<SiteData> }> = ({ data }) => {
|
||||
yearData[year][article.type] = [...(yearData[year][article.type] || []), article];
|
||||
});
|
||||
return yearData;
|
||||
}, [articles]);
|
||||
}, [articles, lang]);
|
||||
|
||||
const yearList = Object.keys(mergedData).sort((a, b) => Number(b) - Number(a));
|
||||
|
||||
@@ -145,15 +145,27 @@ const Articles: React.FC<{ data: Partial<SiteData> }> = ({ data }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const ResourceArticles: React.FC = () => {
|
||||
const { styles } = useStyle();
|
||||
const data = useSiteData();
|
||||
|
||||
const articles = data ? <Articles data={data} /> : <Skeleton active />;
|
||||
|
||||
const { data, error, isLoading } = useAntdSiteConfig();
|
||||
if (isLoading) {
|
||||
return <Skeleton active />;
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<Alert
|
||||
showIcon
|
||||
type="error"
|
||||
message={error.message}
|
||||
description={process.env.NODE_ENV !== 'production' ? error.stack : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div id="articles" className={styles.articles}>
|
||||
{articles}
|
||||
<Articles data={data} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResourceArticles;
|
||||
|
||||
@@ -96,7 +96,7 @@ const TokenCompare: React.FC<TokenCompareProps> = (props) => {
|
||||
dark: color2Rgba((darkTokens as any)[tokenName]),
|
||||
};
|
||||
});
|
||||
}, [tokenNames]);
|
||||
}, [lang, tokenNames]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { RightCircleOutlined } from '@ant-design/icons';
|
||||
import type { TreeGraph } from '@antv/g6';
|
||||
import { Flex } from 'antd';
|
||||
import { createStyles, css } from 'antd-style';
|
||||
import { useRouteMeta } from 'dumi';
|
||||
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import { renderReactToHTMLString } from '../../../theme/utils/renderReactToHTML';
|
||||
|
||||
const dataTransform = (data: BehaviorMapItem) => {
|
||||
const changeData = (d: any, level = 0) => {
|
||||
const clonedData: any = { ...d };
|
||||
interface BehaviorMapItem {
|
||||
id: string;
|
||||
label: string;
|
||||
targetType?: 'mvp' | 'extension';
|
||||
children?: BehaviorMapItem[];
|
||||
link?: string;
|
||||
collapsed?: boolean;
|
||||
type?: 'behavior-start-node' | 'behavior-sub-node';
|
||||
}
|
||||
|
||||
const dataTransform = (rootData: BehaviorMapItem) => {
|
||||
const changeData = (data: BehaviorMapItem, level = 0) => {
|
||||
const clonedData: BehaviorMapItem = { ...data };
|
||||
switch (level) {
|
||||
case 0:
|
||||
clonedData.type = 'behavior-start-node';
|
||||
@@ -19,21 +33,12 @@ const dataTransform = (data: BehaviorMapItem) => {
|
||||
clonedData.type = 'behavior-sub-node';
|
||||
break;
|
||||
}
|
||||
|
||||
if (d.children) {
|
||||
clonedData.children = d.children.map((child: any) => changeData(child, level + 1));
|
||||
if (Array.isArray(data.children)) {
|
||||
clonedData.children = data.children.map((child) => changeData(child, level + 1));
|
||||
}
|
||||
return clonedData;
|
||||
};
|
||||
return changeData(data);
|
||||
};
|
||||
|
||||
type BehaviorMapItem = {
|
||||
id: string;
|
||||
label: string;
|
||||
targetType?: 'mvp' | 'extension';
|
||||
children?: BehaviorMapItem[];
|
||||
link?: string;
|
||||
return changeData(rootData);
|
||||
};
|
||||
|
||||
const useStyle = createStyles(({ token }) => ({
|
||||
@@ -100,16 +105,19 @@ const locales = {
|
||||
},
|
||||
};
|
||||
|
||||
export type BehaviorMapProps = {
|
||||
export interface BehaviorMapProps {
|
||||
data: BehaviorMapItem;
|
||||
};
|
||||
}
|
||||
|
||||
const BehaviorMap: React.FC<BehaviorMapProps> = ({ data }) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { styles } = useStyle();
|
||||
const [locale] = useLocale(locales);
|
||||
|
||||
const meta = useRouteMeta();
|
||||
|
||||
const graphRef = useRef<TreeGraph>(null);
|
||||
|
||||
useEffect(() => {
|
||||
import('@antv/g6').then((G6) => {
|
||||
G6.registerNode('behavior-start-node', {
|
||||
@@ -228,25 +236,11 @@ const BehaviorMap: React.FC<BehaviorMapProps> = ({ data }) => {
|
||||
y: -8,
|
||||
cursor: 'pointer',
|
||||
// DOM's html
|
||||
html: `
|
||||
<div style="width: 16px; height: 16px;">
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="DatePicker" transform="translate(-890.000000, -441.000000)" fill-rule="nonzero">
|
||||
<g id="编组-30" transform="translate(288.000000, 354.000000)">
|
||||
<g id="编组-7备份-7" transform="translate(522.000000, 79.000000)">
|
||||
<g id="right-circle-outlinedd" transform="translate(80.000000, 8.000000)">
|
||||
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="16" height="16"></rect>
|
||||
<path d="M10.4171875,7.8984375 L6.5734375,5.1171875 C6.490625,5.0578125 6.375,5.115625 6.375,5.21875 L6.375,5.9515625 C6.375,6.1109375 6.4515625,6.2625 6.58125,6.35625 L8.853125,8 L6.58125,9.64375 C6.4515625,9.7375 6.375,9.8875 6.375,10.0484375 L6.375,10.78125 C6.375,10.8828125 6.490625,10.9421875 6.5734375,10.8828125 L10.4171875,8.1015625 C10.4859375,8.0515625 10.4859375,7.9484375 10.4171875,7.8984375 Z" id="路径" fill="#BFBFBF"></path>
|
||||
<path d="M8,1 C4.134375,1 1,4.134375 1,8 C1,11.865625 4.134375,15 8,15 C11.865625,15 15,11.865625 15,8 C15,4.134375 11.865625,1 8,1 Z M8,13.8125 C4.790625,13.8125 2.1875,11.209375 2.1875,8 C2.1875,4.790625 4.790625,2.1875 8,2.1875 C11.209375,2.1875 13.8125,4.790625 13.8125,8 C13.8125,11.209375 11.209375,13.8125 8,13.8125 Z" id="形状" fill="#BFBFBF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
`,
|
||||
html: renderReactToHTMLString(
|
||||
<Flex align="center" justify="center">
|
||||
<RightCircleOutlined style={{ color: '#BFBFBF' }} />
|
||||
</Flex>,
|
||||
),
|
||||
},
|
||||
// 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性
|
||||
name: 'sub-node-link',
|
||||
@@ -265,25 +259,11 @@ const BehaviorMap: React.FC<BehaviorMapProps> = ({ data }) => {
|
||||
hover: {
|
||||
stroke: '#1677ff',
|
||||
'sub-node-link': {
|
||||
html: `
|
||||
<div style="width: 16px; height: 16px;">
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="DatePicker" transform="translate(-890.000000, -441.000000)" fill-rule="nonzero">
|
||||
<g id="编组-30" transform="translate(288.000000, 354.000000)">
|
||||
<g id="编组-7备份-7" transform="translate(522.000000, 79.000000)">
|
||||
<g id="right-circle-outlinedd" transform="translate(80.000000, 8.000000)">
|
||||
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="16" height="16"></rect>
|
||||
<path d="M10.4171875,7.8984375 L6.5734375,5.1171875 C6.490625,5.0578125 6.375,5.115625 6.375,5.21875 L6.375,5.9515625 C6.375,6.1109375 6.4515625,6.2625 6.58125,6.35625 L8.853125,8 L6.58125,9.64375 C6.4515625,9.7375 6.375,9.8875 6.375,10.0484375 L6.375,10.78125 C6.375,10.8828125 6.490625,10.9421875 6.5734375,10.8828125 L10.4171875,8.1015625 C10.4859375,8.0515625 10.4859375,7.9484375 10.4171875,7.8984375 Z" id="路径" fill="#1677ff"></path>
|
||||
<path d="M8,1 C4.134375,1 1,4.134375 1,8 C1,11.865625 4.134375,15 8,15 C11.865625,15 15,11.865625 15,8 C15,4.134375 11.865625,1 8,1 Z M8,13.8125 C4.790625,13.8125 2.1875,11.209375 2.1875,8 C2.1875,4.790625 4.790625,2.1875 8,2.1875 C11.209375,2.1875 13.8125,4.790625 13.8125,8 C13.8125,11.209375 11.209375,13.8125 8,13.8125 Z" id="形状" fill="#1677ff"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
`,
|
||||
html: renderReactToHTMLString(
|
||||
<Flex align="center" justify="center">
|
||||
<RightCircleOutlined style={{ color: '#1677ff' }} />
|
||||
</Flex>,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -291,7 +271,7 @@ const BehaviorMap: React.FC<BehaviorMapProps> = ({ data }) => {
|
||||
},
|
||||
'rect',
|
||||
);
|
||||
const graph = new G6.TreeGraph({
|
||||
graphRef.current = new G6.TreeGraph({
|
||||
container: ref.current!,
|
||||
width: ref.current!.scrollWidth,
|
||||
height: ref.current!.scrollHeight,
|
||||
@@ -301,10 +281,7 @@ const BehaviorMap: React.FC<BehaviorMapProps> = ({ data }) => {
|
||||
},
|
||||
defaultEdge: {
|
||||
type: 'cubic-horizontal',
|
||||
style: {
|
||||
lineWidth: 1,
|
||||
stroke: '#BFBFBF',
|
||||
},
|
||||
style: { lineWidth: 1, stroke: '#BFBFBF' },
|
||||
},
|
||||
layout: {
|
||||
type: 'mindmap',
|
||||
@@ -317,24 +294,26 @@ const BehaviorMap: React.FC<BehaviorMapProps> = ({ data }) => {
|
||||
},
|
||||
});
|
||||
|
||||
graph.on('node:mouseenter', (e) => {
|
||||
graph.setItemState(e.item!, 'hover', true);
|
||||
graphRef.current?.on('node:mouseenter', (e) => {
|
||||
graphRef.current?.setItemState(e.item!, 'hover', true);
|
||||
});
|
||||
graph.on('node:mouseleave', (e) => {
|
||||
graph.setItemState(e.item!, 'hover', false);
|
||||
graphRef.current?.on('node:mouseleave', (e) => {
|
||||
graphRef.current?.setItemState(e.item!, 'hover', false);
|
||||
});
|
||||
graph.on('node:click', (e) => {
|
||||
graphRef.current?.on('node:click', (e) => {
|
||||
const { link } = e.item!.getModel();
|
||||
if (link) {
|
||||
window.location.hash = link as string;
|
||||
}
|
||||
});
|
||||
|
||||
graph.data(dataTransform(data));
|
||||
graph.render();
|
||||
graph.fitCenter();
|
||||
graphRef.current?.data(dataTransform(data));
|
||||
graphRef.current?.render();
|
||||
graphRef.current?.fitCenter();
|
||||
});
|
||||
}, []);
|
||||
return () => {
|
||||
graphRef.current?.destroy();
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={styles.container}>
|
||||
|
||||
@@ -27,7 +27,12 @@ const BezierVisualizer = (props: BezierVisualizerProps) => {
|
||||
const controls = useMemo(() => {
|
||||
const m = RE.exec(value.toLowerCase().trim());
|
||||
if (m) {
|
||||
return m[1].split(',').map((v) => parseFloat(v.trim())) as [number, number, number, number];
|
||||
return m[1].split(',').map((v) => Number.parseFloat(v.trim())) as [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}, [value]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { Button, Tabs, Typography } from 'antd';
|
||||
import { Tabs, Typography } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import toReactElement from 'jsonml-to-react-element';
|
||||
import JsonML from 'jsonml.js/lib/utils';
|
||||
@@ -109,12 +109,16 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
||||
}
|
||||
const [highlightedCodes, setHighlightedCodes] = React.useState(initialCodes);
|
||||
const { codeType, setCodeType } = React.use(DemoContext);
|
||||
const sourceCodes = {
|
||||
// omit trailing line break
|
||||
tsx: sourceCode?.trim(),
|
||||
jsx: jsxCode?.trim(),
|
||||
style: styleCode?.trim(),
|
||||
} as Record<'tsx' | 'jsx' | 'style', string>;
|
||||
|
||||
const sourceCodes = useMemo<Record<'tsx' | 'jsx' | 'style', string>>(() => {
|
||||
return {
|
||||
// omit trailing line break
|
||||
tsx: sourceCode?.trim(),
|
||||
jsx: jsxCode?.trim(),
|
||||
style: styleCode?.trim(),
|
||||
};
|
||||
}, [sourceCode, jsxCode, styleCode]);
|
||||
|
||||
useEffect(() => {
|
||||
const codes = {
|
||||
tsx: Prism.highlight(sourceCode, Prism.languages.javascript, 'jsx'),
|
||||
@@ -153,13 +157,24 @@ const CodePreview: React.FC<CodePreviewProps> = ({
|
||||
) : (
|
||||
toReactComponent(['pre', { lang, highlighted: highlightedCodes[lang] }])
|
||||
)}
|
||||
<Button type="text" className={styles.copyButton}>
|
||||
{/* button 嵌套 button 会导致水合失败,这里需要用 div 标签,不能用 button */}
|
||||
<div className={styles.copyButton}>
|
||||
<Typography.Text className={styles.copyIcon} copyable={{ text: sourceCodes[lang] }} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
})),
|
||||
[JSON.stringify(highlightedCodes), styles.code, styles.copyButton, styles.copyIcon],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
entryName,
|
||||
error,
|
||||
highlightedCodes,
|
||||
langList,
|
||||
sourceCodes,
|
||||
styles.code,
|
||||
styles.copyButton,
|
||||
styles.copyIcon,
|
||||
],
|
||||
);
|
||||
|
||||
if (!langList.length) {
|
||||
|
||||
@@ -45,7 +45,7 @@ const ColorPaletteTool: React.FC = () => {
|
||||
}
|
||||
}
|
||||
return <span className="color-palette-picker-validation">{text.trim()}</span>;
|
||||
}, [primaryColorInstance, primaryMinSaturation, primaryMinBrightness]);
|
||||
}, [primaryColorInstance, locale]);
|
||||
return (
|
||||
<div className="color-palette-horizontal">
|
||||
<div className="color-palette-pick">
|
||||
|
||||
@@ -54,7 +54,7 @@ const ColorPaletteTool: React.FC = () => {
|
||||
{text.trim()}
|
||||
</span>
|
||||
);
|
||||
}, [primaryColorInstance]);
|
||||
}, [locale, primaryColorInstance]);
|
||||
|
||||
return (
|
||||
<div className="color-palette-horizontal color-palette-horizontal-dark">
|
||||
|
||||
@@ -10,7 +10,7 @@ interface ColorPatternsProps {
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
const ColorPatterns: React.FC<ColorPatternsProps> = ({ color, dark, backgroundColor }) => {
|
||||
const ColorPatterns: React.FC<ColorPatternsProps> = ({ color = '', dark, backgroundColor }) => {
|
||||
const colors = generate(color, dark ? { theme: 'dark', backgroundColor } : {});
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -53,17 +53,16 @@ const Palette: React.FC<PaletteProps> = (props) => {
|
||||
|
||||
const className = direction === 'horizontal' ? 'color-palette-horizontal' : 'color-palette';
|
||||
|
||||
const colors: React.ReactNode[] = [];
|
||||
|
||||
const colorPaletteMap = {
|
||||
dark: ['#fff', 'unset'],
|
||||
default: ['rgba(0, 0, 0, 0.85)', '#fff'],
|
||||
};
|
||||
const [lastColor, firstColor] = dark ? colorPaletteMap.dark : colorPaletteMap.default;
|
||||
for (let i = 1; i <= count; i += 1) {
|
||||
const colorText = `${name}-${i}`;
|
||||
const defaultBgStyle = dark && name ? presetDarkPalettes[name][i - 1] : '';
|
||||
colors.push(
|
||||
|
||||
const colors: React.ReactNode[] = Array.from({ length: count }, (_, i) => {
|
||||
const colorText = `${name}-${i + 1}`;
|
||||
const defaultBgStyle = dark && name ? presetDarkPalettes[name][i] : '';
|
||||
return (
|
||||
<CopyToClipboard
|
||||
text={hexColors[colorText]}
|
||||
onCopy={() => message.success(`@${colorText} copied: ${hexColors[colorText]}`)}
|
||||
@@ -87,9 +86,10 @@ const Palette: React.FC<PaletteProps> = (props) => {
|
||||
<span className="main-color-text">{colorText}</span>
|
||||
<span className="main-color-value">{hexColors[colorText]}</span>
|
||||
</div>
|
||||
</CopyToClipboard>,
|
||||
</CopyToClipboard>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{showTitle && (
|
||||
|
||||
@@ -3,8 +3,8 @@ import { BugOutlined } from '@ant-design/icons';
|
||||
import { Button, Drawer, Flex, Grid, Popover, Tag, Timeline, Typography } from 'antd';
|
||||
import type { TimelineItemProps } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import useFetch from '../../../hooks/useFetch';
|
||||
import useLocale from '../../../hooks/useLocale';
|
||||
import useLocation from '../../../hooks/useLocation';
|
||||
import { matchDeprecated } from '../../utils';
|
||||
@@ -212,17 +212,23 @@ const RenderChangelogList: React.FC<{ changelogList: ChangelogInfo[] }> = ({ cha
|
||||
const useChangelog = (componentPath: string, lang: 'cn' | 'en'): ChangelogInfo[] => {
|
||||
const logFileName = `components-changelog-${lang}.json`;
|
||||
|
||||
const data = useFetch({
|
||||
key: `component-changelog-${lang}`,
|
||||
request: () => import(`../../../preset/${logFileName}`),
|
||||
});
|
||||
return React.useMemo(() => {
|
||||
const component = componentPath.replace(/-/g, '');
|
||||
const componentName = Object.keys(data).find(
|
||||
(name) => name.toLowerCase() === component.toLowerCase(),
|
||||
);
|
||||
return data[componentName as keyof typeof data] as ChangelogInfo[];
|
||||
}, [data, componentPath]);
|
||||
const { data, error, isLoading } = useSWR(
|
||||
`component-changelog-${lang}`,
|
||||
() => import(`../../../preset/${logFileName}`),
|
||||
);
|
||||
|
||||
if (error || isLoading) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const component = componentPath.replace(/-/g, '');
|
||||
const componentName = Object.keys(data).find(
|
||||
(name) => name.toLowerCase() === component.toLowerCase(),
|
||||
);
|
||||
if (!componentName) {
|
||||
return [];
|
||||
}
|
||||
return data?.[componentName] || [];
|
||||
};
|
||||
|
||||
const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
|
||||
@@ -295,7 +301,17 @@ const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props)
|
||||
),
|
||||
};
|
||||
});
|
||||
}, [list]);
|
||||
}, [
|
||||
lang,
|
||||
list,
|
||||
locale.bugList,
|
||||
styles.bug,
|
||||
styles.bugReasonList,
|
||||
styles.bugReasonTitle,
|
||||
styles.versionTag,
|
||||
styles.versionTitle,
|
||||
styles.versionWrap,
|
||||
]);
|
||||
|
||||
const screens = Grid.useBreakpoint();
|
||||
const width = screens.md ? '48vw' : '90vw';
|
||||
|
||||
@@ -10,9 +10,7 @@ const Editor: React.FC<JSONEditorPropsOptional> = (props) => {
|
||||
if (container.current) {
|
||||
editorRef.current = createJSONEditor({
|
||||
target: container.current,
|
||||
props: {
|
||||
mode: Mode.text,
|
||||
},
|
||||
props: { mode: Mode.text },
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
@@ -22,7 +20,7 @@ const Editor: React.FC<JSONEditorPropsOptional> = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
editorRef.current?.updateProps(props);
|
||||
}, [props.content]);
|
||||
}, [props]);
|
||||
|
||||
return <div ref={container} className="vanilla-jsoneditor-react" />;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { MouseEvent, MouseEventHandler } from 'react';
|
||||
import React, { useMemo, forwardRef } from 'react';
|
||||
import { Link as DumiLink, useLocation, useAppData, useNavigate } from 'dumi';
|
||||
import React, { forwardRef, useMemo } from 'react';
|
||||
import { Link as DumiLink, useAppData, useLocation, useNavigate } from 'dumi';
|
||||
|
||||
export interface LinkProps {
|
||||
to: string | { pathname?: string; search?: string; hash?: string };
|
||||
@@ -21,7 +21,7 @@ const Link = forwardRef<HTMLAnchorElement, React.PropsWithChildren<LinkProps>>(
|
||||
return `${to.pathname || pathname}${to.search || ''}${to.hash || ''}`;
|
||||
}
|
||||
return to;
|
||||
}, [to]);
|
||||
}, [pathname, to]);
|
||||
const onClick = (e: MouseEvent<HTMLAnchorElement>) => {
|
||||
rest.onClick?.(e);
|
||||
if (!href?.startsWith('http')) {
|
||||
|
||||
@@ -55,7 +55,7 @@ const Markers: React.FC<MarkersProps> = (props) => {
|
||||
},
|
||||
);
|
||||
});
|
||||
}, [targetClassName]);
|
||||
}, [containerRef, targetClassName]);
|
||||
|
||||
// ======================== Render =========================
|
||||
return (
|
||||
|
||||
@@ -33,9 +33,7 @@ const Block: React.FC<BlockProps> = ({ component: Component, options, defaultVal
|
||||
defaultValue={defaultValue}
|
||||
getPopupContainer={() => divRef.current}
|
||||
options={options}
|
||||
styles={{
|
||||
popup: { zIndex: 1 },
|
||||
}}
|
||||
styles={{ popup: { zIndex: 1 } }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -101,7 +101,7 @@ function HighlightExample(props: {
|
||||
}
|
||||
|
||||
return Prism.highlight(code, Prism.languages.javascript, 'jsx');
|
||||
}, [componentName, semanticName]);
|
||||
}, [componentName, itemsAPI, semanticName]);
|
||||
|
||||
return (
|
||||
// biome-ignore lint: lint/security/noDangerouslySetInnerHtml
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export { default as HeadingAnchor } from './HeadingAnchor';
|
||||
export { default as Reset } from './Reset';
|
||||
export { default as Common } from './Common';
|
||||
export { default as Markdown } from './Markdown';
|
||||
export { default as Highlight } from './Highlight';
|
||||
export { default as Demo } from './Demo';
|
||||
export { default as Responsive } from './Responsive';
|
||||
export { default as HeadingAnchor } from './HeadingAnchor';
|
||||
export { default as Highlight } from './Highlight';
|
||||
export { default as Markdown } from './Markdown';
|
||||
export { default as NProgress } from './NProgress';
|
||||
export { default as PreviewImage } from './PreviewImage';
|
||||
export { default as Reset } from './Reset';
|
||||
export { default as Responsive } from './Responsive';
|
||||
export { default as SearchBar } from './SearchBar';
|
||||
|
||||
@@ -13,7 +13,6 @@ import useLocation from '../../../hooks/useLocation';
|
||||
import GlobalStyles from '../../common/GlobalStyles';
|
||||
import Header from '../../slots/Header';
|
||||
import SiteContext from '../../slots/SiteContext';
|
||||
|
||||
import IndexLayout from '../IndexLayout';
|
||||
import ResourceLayout from '../ResourceLayout';
|
||||
import SidebarLayout from '../SidebarLayout';
|
||||
@@ -48,7 +47,7 @@ const DocLayout: React.FC = () => {
|
||||
} else {
|
||||
dayjs.locale('en');
|
||||
}
|
||||
}, []);
|
||||
}, [lang]);
|
||||
|
||||
useEffect(() => {
|
||||
const nprogressHiddenStyle = document.getElementById('nprogress-style');
|
||||
@@ -70,13 +69,10 @@ const DocLayout: React.FC = () => {
|
||||
if (typeof (window as any).ga !== 'undefined') {
|
||||
(window as any).ga('send', 'pageview', pathname + search);
|
||||
}
|
||||
}, [location]);
|
||||
}, [pathname, search]);
|
||||
|
||||
const content = React.useMemo<React.ReactNode>(() => {
|
||||
if (
|
||||
['', '/'].some((path) => path === pathname) ||
|
||||
['/index'].some((path) => pathname.startsWith(path))
|
||||
) {
|
||||
if (['', '/'].includes(pathname) || ['/index'].some((path) => pathname.startsWith(path))) {
|
||||
return (
|
||||
<IndexLayout title={locale.title} desc={locale.description}>
|
||||
{outlet}
|
||||
@@ -90,7 +86,7 @@ const DocLayout: React.FC = () => {
|
||||
return outlet;
|
||||
}
|
||||
return <SidebarLayout>{outlet}</SidebarLayout>;
|
||||
}, [pathname, outlet]);
|
||||
}, [pathname, outlet, locale.title, locale.description]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -114,11 +110,7 @@ const DocLayout: React.FC = () => {
|
||||
<ConfigProvider
|
||||
direction={direction}
|
||||
locale={lang === 'cn' ? zhCN : undefined}
|
||||
theme={{
|
||||
token: {
|
||||
fontFamily: `AlibabaSans, ${token.fontFamily}`,
|
||||
},
|
||||
}}
|
||||
theme={{ token: { fontFamily: `AlibabaSans, ${token.fontFamily}` } }}
|
||||
>
|
||||
<GlobalStyles />
|
||||
{!hideLayout && <Header />}
|
||||
|
||||
@@ -122,12 +122,13 @@ const GlobalLayout: React.FC = () => {
|
||||
setSearchParams(nextSearchParams);
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
const updateMobileMode = () => {
|
||||
const updateMobileMode = useCallback(() => {
|
||||
updateSiteConfig({ isMobile: window.innerWidth < RESPONSIVE_MOBILE });
|
||||
};
|
||||
}, [updateSiteConfig]);
|
||||
|
||||
// 监听系统主题变化
|
||||
useEffect(() => {
|
||||
@@ -176,7 +177,8 @@ const GlobalLayout: React.FC = () => {
|
||||
return () => {
|
||||
window.removeEventListener('resize', updateMobileMode);
|
||||
};
|
||||
}, [searchParams]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchParams, updateMobileMode]);
|
||||
|
||||
const siteContextValue = React.useMemo<SiteContextProps>(
|
||||
() => ({
|
||||
@@ -198,9 +200,9 @@ const GlobalLayout: React.FC = () => {
|
||||
cssVar: useCssVar,
|
||||
hashed: !useCssVar,
|
||||
};
|
||||
}, [theme]);
|
||||
}, [theme, useCssVar]);
|
||||
|
||||
const [styleCache] = React.useState(() => createCache());
|
||||
const styleCache = React.useMemo(() => createCache(), []);
|
||||
|
||||
useServerInsertedHTML(() => {
|
||||
const styleText = extractStyle(styleCache, {
|
||||
|
||||
@@ -116,7 +116,7 @@ const AffixTabs: React.FC = () => {
|
||||
return () => {
|
||||
listenerEvents.forEach((event) => window.removeEventListener(event, onSyncAffix));
|
||||
};
|
||||
}, []);
|
||||
}, [onSyncAffix]);
|
||||
|
||||
return (
|
||||
<div className={classNames(affixTabs, fixedId && affixTabsFixed)} ref={containerRef}>
|
||||
|
||||
@@ -9,7 +9,7 @@ interface ContributorAvatarProps {
|
||||
|
||||
const ContributorAvatar: React.FC<ContributorAvatarProps> = (props) => {
|
||||
const { item: { username, url } = {} } = props;
|
||||
if (username?.includes('github-actions')) {
|
||||
if (!username) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -37,6 +37,9 @@ interface ContributorsProps {
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
// 这些机器人账号不需要展示
|
||||
const blockList = ['github-actions', 'copilot', 'renovate', 'dependabot'];
|
||||
|
||||
const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { styles } = useStyle();
|
||||
@@ -55,6 +58,7 @@ const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
|
||||
owner="ant-design"
|
||||
fileName={filename}
|
||||
className={styles.list}
|
||||
filter={(item) => !blockList.includes(item?.username?.toLowerCase() ?? '')}
|
||||
renderItem={(item, loading) => (
|
||||
<ContributorAvatar item={item} loading={loading} key={item?.url} />
|
||||
)}
|
||||
@@ -63,7 +67,7 @@ const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const SuspenseContributors: React.FC<React.ComponentProps<typeof Contributors>> = (props) => (
|
||||
const SuspenseContributors: React.FC<ContributorsProps> = (props) => (
|
||||
<Suspense fallback={null}>
|
||||
<Contributors {...props} />
|
||||
</Suspense>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { CalendarOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Flex, Skeleton, Typography } from 'antd';
|
||||
import DayJS from 'dayjs';
|
||||
import dayjs from 'dayjs';
|
||||
import { useRouteMeta } from 'dumi';
|
||||
|
||||
interface AuthorAvatarPoprs {
|
||||
@@ -17,7 +17,7 @@ const AuthorAvatar: React.FC<AuthorAvatarPoprs> = ({ name, avatar }) => {
|
||||
img.src = avatar;
|
||||
img.onload = () => setLoading(false);
|
||||
img.onerror = () => setError(true);
|
||||
}, []);
|
||||
}, [avatar]);
|
||||
if (error) {
|
||||
return null;
|
||||
}
|
||||
@@ -34,8 +34,9 @@ const AuthorAvatar: React.FC<AuthorAvatarPoprs> = ({ name, avatar }) => {
|
||||
const DocMeta: React.FC = () => {
|
||||
const meta = useRouteMeta();
|
||||
|
||||
const { author } = meta.frontmatter;
|
||||
|
||||
const mergedAuthorInfos = useMemo(() => {
|
||||
const { author } = meta.frontmatter;
|
||||
if (!author) {
|
||||
return [];
|
||||
}
|
||||
@@ -49,7 +50,7 @@ const DocMeta: React.FC = () => {
|
||||
return author;
|
||||
}
|
||||
return [];
|
||||
}, [meta.frontmatter.author]);
|
||||
}, [author]);
|
||||
|
||||
if (!meta.frontmatter.date && !meta.frontmatter.author) {
|
||||
return null;
|
||||
@@ -60,7 +61,7 @@ const DocMeta: React.FC = () => {
|
||||
<Flex gap="small">
|
||||
{meta.frontmatter.date && (
|
||||
<span style={{ opacity: 0.65 }}>
|
||||
<CalendarOutlined /> {DayJS(meta.frontmatter.date).format('YYYY-MM-DD')}
|
||||
<CalendarOutlined /> {dayjs(meta.frontmatter.date).format('YYYY-MM-DD')}
|
||||
</span>
|
||||
)}
|
||||
{mergedAuthorInfos.map<React.ReactNode>((info) => (
|
||||
|
||||
@@ -30,6 +30,7 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
|
||||
const [showDebug, setShowDebug] = useLayoutState(false);
|
||||
const [codeType, setCodeType] = useState('tsx');
|
||||
|
||||
const debugDemos = useMemo(
|
||||
() => meta.toc?.filter((item) => item._debug_demo).map((item) => item.id) || [],
|
||||
[meta],
|
||||
@@ -39,11 +40,13 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setShowDebug(process.env.NODE_ENV === 'development' || isDebugDemo);
|
||||
}, []);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isDebugDemo]);
|
||||
|
||||
const contextValue = useMemo<DemoContextProps>(
|
||||
() => ({ showDebug, setShowDebug, codeType, setCodeType }),
|
||||
[showDebug, codeType, debugDemos],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[showDebug, codeType],
|
||||
);
|
||||
|
||||
const isRTL = direction === 'rtl';
|
||||
@@ -81,6 +84,10 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
filename={meta.frontmatter.filename}
|
||||
version={meta.frontmatter.tag}
|
||||
designUrl={meta.frontmatter.designUrl}
|
||||
searchTitleKeywords={[meta.frontmatter.title, meta.frontmatter.subtitle].filter(
|
||||
Boolean,
|
||||
)}
|
||||
repo="ant-design/ant-design"
|
||||
/>
|
||||
)}
|
||||
<div style={{ minHeight: 'calc(100vh - 64px)' }}>
|
||||
|
||||
@@ -430,7 +430,7 @@ const Footer: React.FC = () => {
|
||||
],
|
||||
};
|
||||
return [col1, col2, col3, col4];
|
||||
}, [lang, location.search]);
|
||||
}, [getLink, lang]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -201,7 +201,7 @@ const Header: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
handleHideMenu();
|
||||
}, [location]);
|
||||
}, [handleHideMenu, location]);
|
||||
|
||||
useEffect(() => {
|
||||
onWindowResize();
|
||||
@@ -212,7 +212,7 @@ const Header: React.FC = () => {
|
||||
clearTimeout(pingTimer.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [onWindowResize]);
|
||||
|
||||
const handleVersionChange = useCallback((url: string) => {
|
||||
const currentUrl = window.location.href;
|
||||
@@ -247,7 +247,7 @@ const Header: React.FC = () => {
|
||||
window.location.pathname,
|
||||
utils.getLocalizedPathname(pathname, !utils.isZhCN(pathname), search).pathname,
|
||||
);
|
||||
}, [location]);
|
||||
}, [pathname, search]);
|
||||
|
||||
const nextDirectionText = useMemo<string>(
|
||||
() => (direction !== 'rtl' ? 'RTL' : 'LTR'),
|
||||
@@ -255,7 +255,7 @@ const Header: React.FC = () => {
|
||||
);
|
||||
|
||||
const getDropdownStyle = useMemo<React.CSSProperties>(
|
||||
() => (direction === 'rtl' ? { direction: 'ltr', textAlign: 'right' } : {}),
|
||||
() => (direction === 'rtl' ? { direction: 'ltr', textAlign: 'end' } : {}),
|
||||
[direction],
|
||||
);
|
||||
|
||||
|
||||
18
.dumi/theme/utils/renderReactToHTML.tsx
Normal file
18
.dumi/theme/utils/renderReactToHTML.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import type React from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
// 这个方法是将 React 组件转换为 HTML 字符串,作用和 renderToString API 一样
|
||||
// 根据 React 官方文档的解释,不建议在客户端使用 renderToString API,所以这里使用 createRoot + innerHTML 的方式来实现
|
||||
// https://zh-hans.react.dev/reference/react-dom/server/renderToString
|
||||
export const renderReactToHTMLString = (node: React.ReactNode) => {
|
||||
const div = document.createElement('div');
|
||||
const root = createRoot(div);
|
||||
// eslint-disable-next-line react-dom/no-flush-sync
|
||||
flushSync(() => {
|
||||
root.render(node);
|
||||
});
|
||||
const html = div.innerHTML;
|
||||
root.unmount();
|
||||
return html;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { format } from '@prettier/sync';
|
||||
import prettierSync from '@prettier/sync';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
/**
|
||||
@@ -31,7 +31,7 @@ export default function tsToJs(tsCode: string): string {
|
||||
|
||||
try {
|
||||
// 使用 Prettier 同步格式化代码
|
||||
const formatted = format(result.outputText, {
|
||||
const formatted = prettierSync.format(result.outputText, {
|
||||
// Prettier 格式化选项
|
||||
parser: 'babel',
|
||||
printWidth: 100,
|
||||
|
||||
123
.github/copilot-instructions.md
vendored
Normal file
123
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
# Ant Design Repository Copilot Instructions
|
||||
|
||||
This is the Ant Design (antd) repository - a React component library with enterprise-class UI design language, widely used for building professional web applications.
|
||||
|
||||
## Project Context
|
||||
|
||||
- **Framework**: TypeScript + React (compatible with React 16-19)
|
||||
- **Package**: Published as npm package `antd`
|
||||
- **Purpose**: Enterprise-class UI components for React applications
|
||||
- **Design System**: Follows Ant Design specifications
|
||||
- **Internationalization**: Full i18n support
|
||||
|
||||
## Code Standards & Best Practices
|
||||
|
||||
### TypeScript Requirements
|
||||
- Always use TypeScript with strict type checking
|
||||
- Never use `any` type - define precise types instead
|
||||
- Use interfaces (not type aliases) for object structures
|
||||
- Export all public interface types
|
||||
- Component props interfaces should be named `ComponentNameProps`
|
||||
- Component ref types should use `React.ForwardRefRenderFunction`
|
||||
- Prefer union types over enums, use `as const` for constants
|
||||
|
||||
### React Component Guidelines
|
||||
- Use functional components with hooks exclusively (no class components)
|
||||
- Use early returns to improve readability
|
||||
- Apply performance optimizations with React.memo, useMemo, useCallback appropriately
|
||||
- Support server-side rendering
|
||||
- Maintain backward compatibility - avoid breaking changes
|
||||
- Components must support ref forwarding with this structure:
|
||||
```tsx
|
||||
ComponentRef {
|
||||
nativeElement: HTMLElement;
|
||||
focus: VoidFunction;
|
||||
// other methods
|
||||
}
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
- **Components**: PascalCase (e.g., `Button`, `DatePicker`)
|
||||
- **Props**: camelCase with specific patterns:
|
||||
- Default values: `default` + `PropName` (e.g., `defaultValue`)
|
||||
- Force rendering: `forceRender`
|
||||
- Panel state: use `open` instead of `visible`
|
||||
- Display toggles: `show` + `PropName`
|
||||
- Capabilities: `PropName` + `able`
|
||||
- Data source: `dataSource`
|
||||
- Disabled state: `disabled`
|
||||
- Additional content: `extra`
|
||||
- Icons: `icon`
|
||||
- Triggers: `trigger`
|
||||
- CSS classes: `className`
|
||||
- **Events**: `on` + `EventName` (e.g., `onClick`, `onChange`)
|
||||
- **Sub-component events**: `on` + `SubComponentName` + `EventName`
|
||||
- Use complete names, never abbreviations
|
||||
|
||||
### Styling Approach
|
||||
- Use `@ant-design/cssinjs` for all styling
|
||||
- Place component styles in `style/` directory
|
||||
- Generate styles with functions named `gen[ComponentName]Style`
|
||||
- Use design tokens from the Ant Design token system
|
||||
- Never hardcode colors, sizes, or spacing values
|
||||
- Support both light and dark themes
|
||||
- Use CSS logical properties for RTL support (e.g., `margin-inline-start` instead of `margin-left`)
|
||||
- Respect `prefers-reduced-motion` for animations
|
||||
|
||||
### Bundle & Performance
|
||||
- Avoid introducing new dependencies
|
||||
- Maintain strict bundle size control
|
||||
- Support tree shaking
|
||||
- Browser compatibility: Chrome 80+
|
||||
- Optimize for minimal re-renders
|
||||
|
||||
### Testing Requirements
|
||||
- Write comprehensive tests using Jest and React Testing Library
|
||||
- Target 100% test coverage
|
||||
- Place tests in `__tests__` directory as `index.test.tsx` or `componentName.test.tsx`
|
||||
- Include snapshot tests for UI components
|
||||
|
||||
### Demo & Documentation
|
||||
- Keep demo code concise and copy-pasteable
|
||||
- Focus each demo on a single feature
|
||||
- Provide both English and Chinese documentation
|
||||
- Follow import order: React → dependencies → antd components → custom components → types → styles
|
||||
- Use 2-space indentation
|
||||
- Prefer antd built-in components over external dependencies
|
||||
|
||||
### API Documentation Format
|
||||
When documenting component APIs, use this table structure:
|
||||
- String defaults in backticks: `"default"`
|
||||
- Boolean defaults as literal values: `true` or `false`
|
||||
- Number defaults as literal values: `0`, `100`
|
||||
- No default value: `-`
|
||||
- Descriptions start with capital letter, no ending period
|
||||
- Sort API properties alphabetically
|
||||
|
||||
### Internationalization
|
||||
- Locale configuration files use pattern: `locale_COUNTRY.ts` (e.g., `zh_CN.ts`)
|
||||
- Use `useLocale` hook from `components/locale/index.tsx`
|
||||
- When modifying locale strings, update ALL language files
|
||||
- Locale content should be plain strings with `${}` placeholders for variables
|
||||
|
||||
### File Organization
|
||||
- Components in `components/[component-name]/` directory
|
||||
- Demos in `components/[component-name]/demo/` as `.tsx` files
|
||||
- Use kebab-case for demo filenames: `basic.tsx`, `custom-filter.tsx`
|
||||
- Each component demo includes both `.md` documentation and `.tsx` code
|
||||
|
||||
## Development Commands
|
||||
- `npm start` - Development server
|
||||
- `npm run build` - Build project
|
||||
- `npm test` - Run tests
|
||||
- `npm run lint` - Code linting
|
||||
- `npm run format` - Code formatting
|
||||
|
||||
## Quality Standards
|
||||
- Pass all ESLint and TypeScript checks
|
||||
- Achieve 100% test coverage
|
||||
- Support accessibility (WCAG 2.1 AA)
|
||||
- Maintain cross-browser compatibility
|
||||
- No console errors or warnings
|
||||
|
||||
When contributing code, ensure it follows these patterns and integrates seamlessly with the existing Ant Design ecosystem.
|
||||
4
.github/workflows/site-deploy.yml
vendored
4
.github/workflows/site-deploy.yml
vendored
@@ -91,7 +91,7 @@ jobs:
|
||||
bunx surge --project ./_site --domain $DEPLOY_DOMAIN --token ${{ secrets.SURGE_TOKEN }}
|
||||
|
||||
- name: Create Commit Comment
|
||||
uses: peter-evans/commit-comment@v3
|
||||
uses: peter-evans/commit-comment@v4
|
||||
with:
|
||||
body: |
|
||||
- Documentation site for this release: https://ant-design-${{ needs.build-site.outputs.formatted_version }}.surge.sh
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
cd ..
|
||||
|
||||
- name: Upload to Release
|
||||
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
files: website.tar.gz
|
||||
|
||||
14
AGENTS.md
14
AGENTS.md
@@ -276,6 +276,20 @@ export function TestComp(props) {
|
||||
- 新的属性需要声明可用的版本号
|
||||
- 属性命名符合 antd 的 API 命名规则
|
||||
|
||||
### 文档锚点 ID 规范
|
||||
|
||||
- 针对 Markdown 文件中的标题(# 到 ######)自动生成锚点 ID
|
||||
- 所有中文标题(H1-H6)必须手动指定一个简洁、有意义的英文锚点。
|
||||
- 格式: ## 中文标题 {#english-anchor-id}
|
||||
- 英文标题通常不需要手动指定锚点,但如果需要,可以使用相同的格式。
|
||||
- 锚点 ID 必须符合正则表达式 `^[a-zA-Z][\w-:\.]*$`, 且长度不应超过 32 个字符。
|
||||
- 用于演示(demo)且包含 `-demo-` 的 id 不受前面的长度限制。
|
||||
- FAQ 章节下的所有标题锚点必须以 `faq-` 作为前缀。
|
||||
- 为确保在不同语言间切换时锚点依然有效,同一问题的中英文锚点应保持完全一致。
|
||||
- 例如:
|
||||
- 中文标题:`### 如何使用组件 {#how-to-use-component}`
|
||||
- 英文标题:`### How to Use the Component {#how-to-use-component}`
|
||||
|
||||
### Changelog 规范
|
||||
|
||||
- 在 CHANGELOG.en-US.md 和 CHANGELOG.zh-CN.md 书写每个版本的变更
|
||||
|
||||
@@ -15,6 +15,19 @@ tag: vVERSION
|
||||
|
||||
---
|
||||
|
||||
## 5.27.5
|
||||
|
||||
`2025-10-14`
|
||||
|
||||
- 🐞 Fix Tour positioning when the `target` is a Table with fixed headers. [#55124](https://github.com/ant-design/ant-design/pull/55124) [@afc163](https://github.com/afc163)
|
||||
- 💄 Fix Card body extra padding when adding `gap` style. [#54974](https://github.com/ant-design/ant-design/pull/54974) [@QdabuliuQ](https://github.com/QdabuliuQ)
|
||||
- 💄 Fix DatePicker design token for text color. [#55065](https://github.com/ant-design/ant-design/pull/55065) [@765477020](https://github.com/765477020)
|
||||
- 💄 Fix List overflow problem when bordered. [#55075](https://github.com/ant-design/ant-design/pull/55075) [@Jiyur](https://github.com/Jiyur)
|
||||
- ⌨️ Fix Modal.confirm `aria-labelledby` accessibility attribute missing problem. [#55266](https://github.com/ant-design/ant-design/pull/55266) [@Jiyur](https://github.com/Jiyur)
|
||||
- ⚡️ Improve Cascader rendering for loading icon. [#55285](https://github.com/ant-design/ant-design/pull/55285) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- TypeScript
|
||||
- 🤖 Fix FloatButton type definition missing for `disabled` property. [#55156](https://github.com/ant-design/ant-design/pull/55156) [@deathemperor](https://github.com/deathemperor)
|
||||
|
||||
## 5.27.4
|
||||
|
||||
`2025-09-17`
|
||||
|
||||
@@ -15,6 +15,19 @@ tag: vVERSION
|
||||
|
||||
---
|
||||
|
||||
## 5.27.5
|
||||
|
||||
`2025-10-14`
|
||||
|
||||
- 🐞 修复 Tour 指定 `target` 为 Table 固定列头时定位错误的问题。[#55124](https://github.com/ant-design/ant-design/pull/55124) [@afc163](https://github.com/afc163)
|
||||
- 💄 修复 Card body 增加 `gap` 样式时有多余 padding 的问题。[#54974](https://github.com/ant-design/ant-design/pull/54974) [@QdabuliuQ](https://github.com/QdabuliuQ)
|
||||
- 💄 修复 DatePicker 文本颜色 token 错误的问题。[#55065](https://github.com/ant-design/ant-design/pull/55065) [@765477020](https://github.com/765477020)
|
||||
- 💄 修复 List 启用边框时会内容溢出的问题。[#55075](https://github.com/ant-design/ant-design/pull/55075) [@Jiyur](https://github.com/Jiyur)
|
||||
- ⌨️ 修复 Modal.confirm 缺失 `aria-labelledby` 可访问性属性的问题。[#55266](https://github.com/ant-design/ant-design/pull/55266) [@Jiyur](https://github.com/Jiyur)
|
||||
- ⚡️ 优化 Cascader 加载中图标的渲染。[#55285](https://github.com/ant-design/ant-design/pull/55285) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- TypeScript
|
||||
- 🤖 修复 FloatButton `disabled` 属性类型缺失的问题。[#55156](https://github.com/ant-design/ant-design/pull/55156) [@deathemperor](https://github.com/deathemperor)
|
||||
|
||||
## 5.27.4
|
||||
|
||||
`2025-09-17`
|
||||
|
||||
@@ -6,11 +6,9 @@
|
||||
|
||||
一套企业级 UI 设计语言和 React 组件库。
|
||||
|
||||
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
|
||||
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url] [![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url]
|
||||
|
||||
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url] [![DeepWiki][deepwiki-image]][deepwiki-url]
|
||||
|
||||
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
|
||||
[![Follow Twitter][twitter-image]][twitter-url] [![dumi][dumi-image]][dumi-url] [![FOSSA Status][fossa-image]][fossa-url] [![Issues need help][help-wanted-image]][help-wanted-url] [![LFX Active Contributors][lfx-image]][lfx-url]
|
||||
|
||||
[更新日志](./CHANGELOG.zh-CN.md) · [报告问题][github-issues-url] · [特性需求][github-issues-url] · [English](./README.md) · 中文
|
||||
|
||||
@@ -36,15 +34,11 @@
|
||||
[jsdelivr-url]: https://www.jsdelivr.com/package/npm/antd
|
||||
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/antd?style=flat-square
|
||||
[bundlephobia-url]: https://bundlephobia.com/package/antd
|
||||
[issues-helper-image]: https://img.shields.io/badge/using-actions--cool-blue?style=flat-square
|
||||
[issues-helper-url]: https://github.com/actions-cool
|
||||
[renovate-image]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg?style=flat-square
|
||||
[renovate-dashboard-url]: https://github.com/ant-design/ant-design/issues/32498
|
||||
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
||||
[dumi-url]: https://github.com/umijs/dumi
|
||||
[github-issues-url]: https://new-issue.ant.design
|
||||
[deepwiki-url]: https://deepwiki.com/ant-design/ant-design
|
||||
[deepwiki-image]: https://img.shields.io/badge/Chat%20with-DeepWiki%20🤖-20B2AA?style=flat-square
|
||||
[lfx-image]: https://insights.linuxfoundation.org/api/badge/active-contributors?project=ant-design-ant-design&repos=https://github.com/ant-design/ant-design
|
||||
[lfx-url]: https://insights.linuxfoundation.org/project/ant-design-ant-design/repository/ant-design-ant-design
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -6,11 +6,9 @@
|
||||
|
||||
An enterprise-class UI design language and React UI library.
|
||||
|
||||
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
|
||||
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url] [![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url]
|
||||
|
||||
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url] [![DeepWiki][deepwiki-image]][deepwiki-url]
|
||||
|
||||
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
|
||||
[![Follow Twitter][twitter-image]][twitter-url] [![dumi][dumi-image]][dumi-url] [![FOSSA Status][fossa-image]][fossa-url] [![Issues need help][help-wanted-image]][help-wanted-url] [![LFX Active Contributors][lfx-image]][lfx-url]
|
||||
|
||||
[Changelog](./CHANGELOG.en-US.md) · [Report Bug][github-issues-url] · [Request Feature][github-issues-url] · English · [中文](./README-zh_CN.md)
|
||||
|
||||
@@ -36,15 +34,11 @@ An enterprise-class UI design language and React UI library.
|
||||
[jsdelivr-url]: https://www.jsdelivr.com/package/npm/antd
|
||||
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/antd?style=flat-square
|
||||
[bundlephobia-url]: https://bundlephobia.com/package/antd
|
||||
[issues-helper-image]: https://img.shields.io/badge/using-actions--cool-blue?style=flat-square
|
||||
[issues-helper-url]: https://github.com/actions-cool
|
||||
[renovate-image]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg?style=flat-square
|
||||
[renovate-dashboard-url]: https://github.com/ant-design/ant-design/issues/32498
|
||||
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
||||
[dumi-url]: https://github.com/umijs/dumi
|
||||
[github-issues-url]: https://new-issue.ant.design
|
||||
[deepwiki-url]: https://deepwiki.com/ant-design/ant-design
|
||||
[deepwiki-image]: https://img.shields.io/badge/Chat%20with-DeepWiki%20🤖-20B2AA?style=flat-square
|
||||
[lfx-image]: https://insights.linuxfoundation.org/api/badge/active-contributors?project=ant-design-ant-design&repos=https://github.com/ant-design/ant-design
|
||||
[lfx-url]: https://insights.linuxfoundation.org/project/ant-design-ant-design/repository/ant-design-ant-design
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ Use this section to tell people about which versions of your project are current
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 4.x | :white_check_mark: |
|
||||
| < 4.0 | :x: |
|
||||
| 5.x | :white_check_mark: |
|
||||
| < 5.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
27
biome.json
27
biome.json
@@ -40,31 +40,20 @@
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"useImportType": "off",
|
||||
"useNumberNamespace": "off",
|
||||
"useNodejsImportProtocol": "off",
|
||||
"noNonNullAssertion": "off",
|
||||
"noUnusedTemplateLiteral": "off"
|
||||
"noNonNullAssertion": "off"
|
||||
},
|
||||
"complexity": {
|
||||
"noUselessTypeConstraint": "off",
|
||||
"noForEach": "off",
|
||||
"useDateNow": "off",
|
||||
"noImportantStyles": "off",
|
||||
"useIndexOf": "off",
|
||||
"useOptionalChain": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"useUniqueElementIds": "off",
|
||||
"useExhaustiveDependencies": "off",
|
||||
"useHookAtTopLevel": "off",
|
||||
"noUnusedFunctionParameters": "off",
|
||||
"noUnusedVariables": "off"
|
||||
"useExhaustiveDependencies": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noTsIgnore": "off",
|
||||
"noGlobalIsNan": "off",
|
||||
"noGlobalIsFinite": "off",
|
||||
"noExplicitAny": "off",
|
||||
"noArrayIndexKey": "off",
|
||||
"noConfusingVoidType": "off",
|
||||
@@ -74,12 +63,10 @@
|
||||
"noUnknownAtRules": "off"
|
||||
},
|
||||
"performance": {
|
||||
"noDelete": "off",
|
||||
"noAccumulatingSpread": "off",
|
||||
"noDynamicNamespaceImportAccess": "off"
|
||||
},
|
||||
"a11y": {
|
||||
"noAriaHiddenOnFocusable": "off",
|
||||
"noLabelWithoutControl": "off",
|
||||
"useFocusableInteractive": "off",
|
||||
"useKeyWithClickEvents": "off",
|
||||
@@ -100,11 +87,7 @@
|
||||
"includes": ["**/*.test.ts", "**/*.test.tsx", "tests/**/*", "scripts/**/*", ".dumi/**/*"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"noParameterAssign": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noThenProperty": "off",
|
||||
"noImplicitAnyLet": "off"
|
||||
},
|
||||
"complexity": {
|
||||
@@ -112,8 +95,7 @@
|
||||
},
|
||||
"a11y": {
|
||||
"useValidAnchor": "off",
|
||||
"useAnchorContent": "off",
|
||||
"useKeyWithClickEvents": "off"
|
||||
"useAnchorContent": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,8 +109,7 @@
|
||||
},
|
||||
"a11y": {
|
||||
"useValidAnchor": "off",
|
||||
"useAnchorContent": "off",
|
||||
"useKeyWithClickEvents": "off"
|
||||
"useAnchorContent": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ const ActionButton: React.FC<ActionButtonProps> = (props) => {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [autoFocus]);
|
||||
|
||||
const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => {
|
||||
if (!isThenable(returnValueOfOnOk)) {
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('isPrimitive', () => {
|
||||
expect(isPrimitive(false)).toBe(true);
|
||||
expect(isPrimitive(null)).toBe(true);
|
||||
expect(isPrimitive(undefined)).toBe(true);
|
||||
expect(isPrimitive(NaN)).toBe(true);
|
||||
expect(isPrimitive(Number.NaN)).toBe(true);
|
||||
expect(isPrimitive(Infinity)).toBe(true);
|
||||
expect(isPrimitive(Symbol('test'))).toBe(true);
|
||||
expect(isPrimitive(BigInt(123))).toBe(true);
|
||||
|
||||
@@ -12,15 +12,15 @@ describe('Test ScrollTo function', () => {
|
||||
dateNowMock.mockReturnValueOnce(0).mockReturnValueOnce(1000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers();
|
||||
dateNowMock.mockClear();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('test scrollTo', async () => {
|
||||
const scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation((_, y) => {
|
||||
window.scrollY = y;
|
||||
|
||||
@@ -9,14 +9,14 @@ describe('Test utils function', () => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('throttle function should work', async () => {
|
||||
const callback = jest.fn();
|
||||
const throttled = throttleByAnimationFrame(callback);
|
||||
|
||||
@@ -10,10 +10,6 @@ describe('Test warning', () => {
|
||||
spy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
@@ -22,6 +18,10 @@ describe('Test warning', () => {
|
||||
spy.mockReset();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('Test noop', async () => {
|
||||
const { noop } = await import('../warning');
|
||||
const value = noop();
|
||||
|
||||
@@ -46,12 +46,6 @@ describe('Wave component', () => {
|
||||
(window as any).ResizeObserver = FakeResizeObserver;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
expect(obCnt).not.toBe(0);
|
||||
expect(disCnt).not.toBe(0);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
(global as any).isVisible = true;
|
||||
@@ -68,6 +62,12 @@ describe('Wave component', () => {
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
expect(obCnt).not.toBe(0);
|
||||
expect(disCnt).not.toBe(0);
|
||||
});
|
||||
|
||||
function getWaveStyle() {
|
||||
const styleObj: Record<string, string> = {};
|
||||
const { style } = document.querySelector<HTMLElement>('.ant-wave')!;
|
||||
|
||||
@@ -166,5 +166,10 @@ export default function useClosable(
|
||||
}
|
||||
|
||||
return [true, mergedCloseIcon, closeBtnIsDisabled, ariaOrDataProps];
|
||||
}, [mergedClosableConfig, mergedFallbackCloseCollection]);
|
||||
}, [
|
||||
closeBtnIsDisabled,
|
||||
contextLocale.close,
|
||||
mergedClosableConfig,
|
||||
mergedFallbackCloseCollection,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { ValidChar } from './interface';
|
||||
import type { ValidChar } from './interface';
|
||||
|
||||
type TemplateSemanticClassNames<T extends string> = Partial<Record<T, string>>;
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ const WaveEffect = (props: WaveEffectProps) => {
|
||||
|
||||
// Rect
|
||||
const { borderLeftWidth, borderTopWidth } = nodeStyle;
|
||||
setLeft(isStatic ? target.offsetLeft : validateNum(-parseFloat(borderLeftWidth)));
|
||||
setTop(isStatic ? target.offsetTop : validateNum(-parseFloat(borderTopWidth)));
|
||||
setLeft(isStatic ? target.offsetLeft : validateNum(-Number.parseFloat(borderLeftWidth)));
|
||||
setTop(isStatic ? target.offsetTop : validateNum(-Number.parseFloat(borderTopWidth)));
|
||||
setWidth(target.offsetWidth);
|
||||
setHeight(target.offsetHeight);
|
||||
|
||||
@@ -82,7 +82,7 @@ const WaveEffect = (props: WaveEffectProps) => {
|
||||
borderTopRightRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomLeftRadius,
|
||||
].map((radius) => validateNum(parseFloat(radius))),
|
||||
].map((radius) => validateNum(Number.parseFloat(radius))),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ const WaveEffect = (props: WaveEffectProps) => {
|
||||
resizeObserver?.disconnect();
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
}, [target]);
|
||||
|
||||
if (!enabled) {
|
||||
return null;
|
||||
|
||||
@@ -44,16 +44,16 @@ describe('Affix Render', () => {
|
||||
|
||||
const classRect: Record<string, DOMRect> = { container: { top: 0, bottom: 100 } as DOMRect };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
domMock.mockImplementation(function fn(this: HTMLElement) {
|
||||
return classRect[this.className] || { top: 0, bottom: 0 };
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
jest.clearAllTimers();
|
||||
|
||||
@@ -211,10 +211,6 @@ const Affix = React.forwardRef<AffixRef, InternalAffixProps>((props, ref) => {
|
||||
};
|
||||
|
||||
const removeListeners = () => {
|
||||
if (timer.current) {
|
||||
clearTimeout(timer.current);
|
||||
timer.current = null;
|
||||
}
|
||||
const newTarget = targetFunc?.();
|
||||
TRIGGER_EVENTS.forEach((eventName) => {
|
||||
newTarget?.removeEventListener(eventName, lazyUpdatePosition);
|
||||
@@ -233,7 +229,14 @@ const Affix = React.forwardRef<AffixRef, InternalAffixProps>((props, ref) => {
|
||||
// [Legacy] Wait for parent component ref has its value.
|
||||
// We should use target as directly element instead of function which makes element check hard.
|
||||
timer.current = setTimeout(addListeners);
|
||||
return () => removeListeners();
|
||||
|
||||
return () => {
|
||||
if (timer.current) {
|
||||
clearTimeout(timer.current);
|
||||
timer.current = null;
|
||||
}
|
||||
removeListeners();
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -3130,6 +3130,53 @@ exports[`renders components/auto-complete/demo/variant.tsx extend context correc
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select ant-select-underlined ant-select-auto-complete ant-select-single ant-select-show-search"
|
||||
style="width: 200px;"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-wrap"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="rc_select_TEST_OR_SSR_list"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="rc_select_TEST_OR_SSR_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
id="rc_select_TEST_OR_SSR"
|
||||
role="combobox"
|
||||
type="search"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
>
|
||||
Underlined
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up 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="rc_select_TEST_OR_SSR_list"
|
||||
role="listbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
@@ -1850,5 +1850,39 @@ exports[`renders components/auto-complete/demo/variant.tsx correctly 1`] = `
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select ant-select-underlined ant-select-auto-complete ant-select-single ant-select-show-search"
|
||||
style="width:200px"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-wrap"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="undefined_list"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="undefined_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
role="combobox"
|
||||
type="search"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
>
|
||||
Underlined
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -14,14 +14,14 @@ describe('AutoComplete children could be focus', () => {
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('focus() and onFocus', () => {
|
||||
const handleFocus = jest.fn();
|
||||
const { container: wrapper } = render(<AutoComplete onFocus={handleFocus} />, { container });
|
||||
|
||||
@@ -13,7 +13,7 @@ const App: React.FC = () => (
|
||||
options={options}
|
||||
placeholder="try to type `b`"
|
||||
filterOption={(inputValue, option) =>
|
||||
option!.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||
option!.value.toUpperCase().includes(inputValue.toUpperCase())
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
可选 `outlined` `filled` `borderless` 三种形态。
|
||||
可选 `outlined` `filled` `borderless` `underlined` 四种形态。
|
||||
|
||||
## en-US
|
||||
|
||||
There are `outlined` `filled` and `borderless`, totally three variants to choose from.
|
||||
There are `outlined`, `filled`, `borderless`, and `underlined` variants to choose from.
|
||||
|
||||
@@ -37,6 +37,14 @@ const App: React.FC = () => {
|
||||
onSelect={globalThis.console.log}
|
||||
variant="borderless"
|
||||
/>
|
||||
<AutoComplete
|
||||
options={options}
|
||||
style={{ width: 200 }}
|
||||
placeholder="Underlined"
|
||||
onSearch={(text) => setOptions(getPanelValue(text))}
|
||||
onSelect={globalThis.console.log}
|
||||
variant="underlined"
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -68,7 +68,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| size | The size of the input box | `large` \| `middle` \| `small` | - | |
|
||||
| value | Selected option | string | - | |
|
||||
| styles | Semantic DOM style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.25.0 |
|
||||
| variant | Variants of input | `outlined` \| `borderless` \| `filled` | `outlined` | 5.13.0 |
|
||||
| variant | Variants of input | `outlined` \| `borderless` \| `filled` \| `underlined` | `outlined` | 5.13.0 |
|
||||
| virtual | Disable virtual scroll when set to false | boolean | true | 4.1.0 |
|
||||
| onBlur | Called when leaving the component | function() | - | |
|
||||
| onChange | Called when selecting an option or changing an input value | function(value) | - | |
|
||||
|
||||
@@ -69,7 +69,7 @@ demo:
|
||||
| size | 控件大小 | `large` \| `middle` \| `small` | - | |
|
||||
| styles | 语义化结构 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.25.0 |
|
||||
| value | 指定当前选中的条目 | string | - | |
|
||||
| variant | 形态变体 | `outlined` \| `borderless` \| `filled` | `outlined` | 5.13.0 |
|
||||
| variant | 形态变体 | `outlined` \| `borderless` \| `filled` \| `underlined` | `outlined` | 5.13.0 |
|
||||
| virtual | 设置 false 时关闭虚拟滚动 | boolean | true | 4.1.0 |
|
||||
| onBlur | 失去焦点时的回调 | function() | - | |
|
||||
| onChange | 选中 option,或 input 的 value 变化时,调用此函数 | function(value) | - | |
|
||||
|
||||
@@ -123,9 +123,9 @@ const InternalBadge = React.forwardRef<HTMLSpanElement, BadgeProps>((props, ref)
|
||||
const offsetStyle: React.CSSProperties = { marginTop: offset[1] };
|
||||
|
||||
if (direction === 'rtl') {
|
||||
offsetStyle.left = parseInt(offset[0] as string, 10);
|
||||
offsetStyle.left = Number.parseInt(offset[0] as string, 10);
|
||||
} else {
|
||||
offsetStyle.right = -parseInt(offset[0] as string, 10);
|
||||
offsetStyle.right = -Number.parseInt(offset[0] as string, 10);
|
||||
}
|
||||
|
||||
return { ...offsetStyle, ...badge?.style, ...style };
|
||||
|
||||
@@ -202,12 +202,20 @@ const generateCalendar = <DateType extends AnyObject>(generateConfig: GenerateCo
|
||||
{String(generateConfig.getDate(date)).padStart(2, '0')}
|
||||
</div>
|
||||
<div className={`${calendarPrefixCls}-date-content`}>
|
||||
{cellRender ? cellRender(date, info) : dateCellRender?.(date)}
|
||||
{typeof cellRender === 'function' ? cellRender(date, info) : dateCellRender?.(date)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[dateFullCellRender, dateCellRender, cellRender, fullCellRender],
|
||||
[
|
||||
today,
|
||||
prefixCls,
|
||||
calendarPrefixCls,
|
||||
fullCellRender,
|
||||
dateFullCellRender,
|
||||
cellRender,
|
||||
dateCellRender,
|
||||
],
|
||||
);
|
||||
|
||||
const monthRender = React.useCallback(
|
||||
@@ -232,12 +240,20 @@ const generateCalendar = <DateType extends AnyObject>(generateConfig: GenerateCo
|
||||
{months[generateConfig.getMonth(date)]}
|
||||
</div>
|
||||
<div className={`${calendarPrefixCls}-date-content`}>
|
||||
{cellRender ? cellRender(date, info) : monthCellRender?.(date)}
|
||||
{typeof cellRender === 'function' ? cellRender(date, info) : monthCellRender?.(date)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[monthFullCellRender, monthCellRender, cellRender, fullCellRender],
|
||||
[
|
||||
today,
|
||||
prefixCls,
|
||||
calendarPrefixCls,
|
||||
fullCellRender,
|
||||
monthFullCellRender,
|
||||
cellRender,
|
||||
monthCellRender,
|
||||
],
|
||||
);
|
||||
|
||||
const [contextLocale] = useLocale('Calendar', enUS);
|
||||
|
||||
@@ -2,7 +2,7 @@ import '@testing-library/jest-dom';
|
||||
|
||||
import React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TabBarExtraContent } from 'rc-tabs/lib/interface';
|
||||
import type { TabBarExtraContent } from 'rc-tabs/lib/interface';
|
||||
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
|
||||
@@ -342,7 +342,6 @@ const genCardStyle: GenerateStyle<CardToken> = (token): CSSObject => {
|
||||
[`${componentCls}-body`]: {
|
||||
padding: bodyPadding,
|
||||
borderRadius: `0 0 ${unit(token.borderRadiusLG)} ${unit(token.borderRadiusLG)}`,
|
||||
...clearFix(),
|
||||
},
|
||||
|
||||
[`${componentCls}-grid`]: genCardGridStyle(token),
|
||||
|
||||
@@ -56,9 +56,7 @@ const onChange: CascaderProps<Option>['onChange'] = (value, selectedOptions) =>
|
||||
};
|
||||
|
||||
const filter = (inputValue: string, path: DefaultOptionType[]) =>
|
||||
path.some(
|
||||
(option) => (option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) > -1,
|
||||
);
|
||||
path.some((option) => (option.label as string).toLowerCase().includes(inputValue.toLowerCase()));
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Cascader
|
||||
|
||||
@@ -3,6 +3,6 @@ import * as React from 'react';
|
||||
export default function useCheckable(cascaderPrefixCls: string, multiple?: boolean) {
|
||||
return React.useMemo(
|
||||
() => (multiple ? <span className={`${cascaderPrefixCls}-checkbox-inner`} /> : false),
|
||||
[multiple],
|
||||
[cascaderPrefixCls, multiple],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,15 +9,18 @@ const useColumnIcons = (prefixCls: string, rtl: boolean, expandIcon?: React.Reac
|
||||
mergedExpandIcon = rtl ? <LeftOutlined /> : <RightOutlined />;
|
||||
}
|
||||
|
||||
const loadingIcon = (
|
||||
<span className={`${prefixCls}-menu-item-loading-icon`}>
|
||||
<LoadingOutlined spin />
|
||||
</span>
|
||||
const loadingIcon = React.useMemo(
|
||||
() => (
|
||||
<span className={`${prefixCls}-menu-item-loading-icon`}>
|
||||
<LoadingOutlined spin />
|
||||
</span>
|
||||
),
|
||||
[prefixCls],
|
||||
);
|
||||
|
||||
return React.useMemo<Readonly<[React.ReactNode, React.ReactNode]>>(
|
||||
() => [mergedExpandIcon, loadingIcon] as const,
|
||||
[mergedExpandIcon],
|
||||
[mergedExpandIcon, loadingIcon],
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -16,18 +16,18 @@ const locales = {
|
||||
},
|
||||
};
|
||||
|
||||
const items: CollapseProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
label: 'This is panel header',
|
||||
children: <p>This is panel body</p>,
|
||||
},
|
||||
];
|
||||
|
||||
const BlockCollapse: React.FC<CollapseProps> = (props) => {
|
||||
const BlockCollapse: React.FC<NonNullable<CollapseProps['items']>[number]> = (props) => {
|
||||
const items: CollapseProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
label: 'This is panel header',
|
||||
children: <p>This is panel body</p>,
|
||||
...props,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div style={{ position: 'absolute', inset: 0 }}>
|
||||
<Collapse {...props} items={items} defaultActiveKey={['1']} />
|
||||
<Collapse items={items} defaultActiveKey={['1']} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ColorPicker from './ColorPicker';
|
||||
|
||||
export type { ColorPickerProps } from './interface';
|
||||
export type { AggregationColor as Color } from './color';
|
||||
export type { ColorPickerProps } from './interface';
|
||||
|
||||
export default ColorPicker;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CSSProperties, FC, ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
import type {
|
||||
ColorGenInput,
|
||||
ColorPickerProps as RcColorPickerProps,
|
||||
@@ -24,7 +23,7 @@ export const FORMAT_HSB = 'hsb';
|
||||
export type ColorFormatType = typeof FORMAT_HEX | typeof FORMAT_RGB | typeof FORMAT_HSB;
|
||||
|
||||
export interface PresetsItem {
|
||||
label: ReactNode;
|
||||
label: React.ReactNode;
|
||||
colors: (string | AggregationColor)[];
|
||||
/**
|
||||
* Whether the initial state is collapsed
|
||||
@@ -80,11 +79,11 @@ export type ColorPickerProps = Omit<
|
||||
arrow?: boolean | { pointAtCenter: boolean };
|
||||
panelRender?: (
|
||||
panel: React.ReactNode,
|
||||
extra: { components: { Picker: FC; Presets: FC } },
|
||||
extra: { components: { Picker: React.FC; Presets: React.FC } },
|
||||
) => React.ReactNode;
|
||||
showText?: boolean | ((color: AggregationColor) => React.ReactNode);
|
||||
size?: SizeType;
|
||||
styles?: { popup?: CSSProperties; popupOverlayInner?: CSSProperties };
|
||||
styles?: { popup?: React.CSSProperties; popupOverlayInner?: React.CSSProperties };
|
||||
rootClassName?: string;
|
||||
disabledAlpha?: boolean;
|
||||
[key: `data-${string}`]: string;
|
||||
|
||||
@@ -14,7 +14,7 @@ const defaultReactRender: RenderType = (node, container) => {
|
||||
// TODO: Remove in v6
|
||||
// Warning for React 19
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const majorVersion = parseInt(React.version.split('.')[0], 10);
|
||||
const majorVersion = Number.parseInt(React.version.split('.')[0], 10);
|
||||
const fullKeys = Object.keys(ReactDOM);
|
||||
|
||||
warning(
|
||||
|
||||
@@ -16,14 +16,14 @@ jest.mock('scroll-into-view-if-needed');
|
||||
describe('ConfigProvider.Form', () => {
|
||||
(scrollIntoView as any).mockImplementation(() => {});
|
||||
|
||||
beforeEach(() => {
|
||||
(scrollIntoView as any).mockReset();
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
(scrollIntoView as any).mockReset();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
(scrollIntoView as any).mockRestore();
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('UnstableContext', () => {
|
||||
|
||||
// TODO: Remove in v6
|
||||
it('should warning', async () => {
|
||||
const majorVersion = parseInt(version.split('.')[0], 10);
|
||||
const majorVersion = Number.parseInt(version.split('.')[0], 10);
|
||||
|
||||
if (majorVersion >= 19) {
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
@@ -39,8 +39,8 @@ import type { TooltipProps } from '../tooltip';
|
||||
import type { TourProps } from '../tour/interface';
|
||||
import type { TransferProps } from '../transfer';
|
||||
import type { TreeSelectProps } from '../tree-select';
|
||||
import type { RenderEmptyHandler } from './defaultRenderEmpty';
|
||||
import type { UploadProps } from '../upload';
|
||||
import type { RenderEmptyHandler } from './defaultRenderEmpty';
|
||||
|
||||
export const defaultPrefixCls = 'ant';
|
||||
export const defaultIconPrefixCls = 'anticon';
|
||||
@@ -321,7 +321,7 @@ export interface ConfigComponentProps {
|
||||
}
|
||||
|
||||
export interface ConfigConsumerProps extends ConfigComponentProps {
|
||||
getTargetContainer?: () => HTMLElement;
|
||||
getTargetContainer?: () => HTMLElement | Window;
|
||||
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
|
||||
rootPrefixCls?: string;
|
||||
iconPrefixCls: string;
|
||||
|
||||
@@ -8,7 +8,7 @@ type WaveConfig = GetProp<ConfigProviderProps, 'wave'>;
|
||||
// Prepare effect holder
|
||||
const createHolder = (node: HTMLElement) => {
|
||||
const { borderWidth } = getComputedStyle(node);
|
||||
const borderWidthNum = parseInt(borderWidth, 10);
|
||||
const borderWidthNum = Number.parseInt(borderWidth, 10);
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.style.position = 'absolute';
|
||||
|
||||
@@ -56,8 +56,8 @@ Some components use dynamic style to support wave effect. You can config `csp` p
|
||||
| componentSize | Config antd component size | `small` \| `middle` \| `large` | - | |
|
||||
| csp | Set [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) config | { nonce: string } | - | |
|
||||
| direction | Set direction of layout. See [demo](#config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
|
||||
| getPopupContainer | To set the container of the popup element. The default is to create a `div` element in `body` | function(triggerNode) | () => document.body | |
|
||||
| getTargetContainer | Config Affix, Anchor scroll target container | () => HTMLElement | () => window | 4.2.0 |
|
||||
| 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 | - | |
|
||||
| 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 |
|
||||
|
||||
@@ -142,8 +142,8 @@ const PASSED_PROPS: Exclude<
|
||||
];
|
||||
|
||||
export interface ConfigProviderProps {
|
||||
getTargetContainer?: () => HTMLElement | Window;
|
||||
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
|
||||
getTargetContainer?: () => HTMLElement | Window | ShadowRoot;
|
||||
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement | ShadowRoot;
|
||||
prefixCls?: string;
|
||||
iconPrefixCls?: string;
|
||||
children?: React.ReactNode;
|
||||
|
||||
@@ -57,8 +57,8 @@ export default Demo;
|
||||
| componentSize | 设置 antd 组件大小 | `small` \| `middle` \| `large` | - | |
|
||||
| csp | 设置 [Content Security Policy](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP) 配置 | { nonce: string } | - | |
|
||||
| direction | 设置文本展示方向。 [示例](#config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
|
||||
| getPopupContainer | 弹出框(Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | function(triggerNode) | () => document.body | |
|
||||
| getTargetContainer | 配置 Affix、Anchor 滚动监听容器。 | () => HTMLElement | () => window | 4.2.0 |
|
||||
| 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 | - | |
|
||||
| popupMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。`false` 时会关闭虚拟滚动 | boolean \| number | - | 5.5.0 |
|
||||
|
||||
@@ -55,7 +55,7 @@ const App: React.FC = () => (
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
disabledDate={disabledDate}
|
||||
disabledTime={disabledDateTime}
|
||||
showTime={{ defaultValue: dayjs('00:00:00', 'HH:mm:ss') }}
|
||||
showTime={{ defaultOpenValue: dayjs('00:00:00', 'HH:mm:ss') }}
|
||||
/>
|
||||
<DatePicker picker="month" disabledDate={disabledDateForMonth} />
|
||||
<RangePicker disabledDate={disabledDate} />
|
||||
@@ -64,7 +64,7 @@ const App: React.FC = () => (
|
||||
disabledTime={disabledRangeTime}
|
||||
showTime={{
|
||||
hideDisabledOptions: true,
|
||||
defaultValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('11:59:59', 'HH:mm:ss')],
|
||||
defaultOpenValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('11:59:59', 'HH:mm:ss')],
|
||||
}}
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import { DatePicker, Dropdown, Space } from 'antd';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import dayjs from 'dayjs';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
const DatePickerDemo: React.FC = () => {
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
|
||||
@@ -152,7 +152,8 @@ The following APIs are shared by DatePicker, RangePicker.
|
||||
| renderExtraFooter | Render extra footer in panel | (mode) => React.ReactNode | - | |
|
||||
| showNow | Show the fast access of current datetime | boolean | - | 4.4.0 |
|
||||
| showTime | To provide an additional time selection | object \| boolean | [TimePicker Options](/components/time-picker/#api) | |
|
||||
| showTime.defaultValue | To set default time of selected date, [demo](#date-picker-demo-disabled-date) | [dayjs](https://day.js.org/) | dayjs() | |
|
||||
| ~~showTime.defaultValue~~ | Use `showTime.defaultOpenValue` instead | [dayjs](https://day.js.org/) | dayjs() | 5.27.3 |
|
||||
| showTime.defaultOpenValue | To set default time of selected date, [demo](#date-picker-demo-disabled-date) | [dayjs](https://day.js.org/) | dayjs() | |
|
||||
| showWeek | Show week info when in DatePicker | boolean | false | 5.14.0 |
|
||||
| value | To set date | [dayjs](https://day.js.org/) | - | |
|
||||
| onChange | Callback function, can be executed when the selected time is changing | function(date: dayjs, dateString: string) | - | |
|
||||
@@ -224,7 +225,8 @@ Added in `4.1.0`.
|
||||
| renderExtraFooter | Render extra footer in panel | () => React.ReactNode | - | |
|
||||
| separator | Set separator between inputs | React.ReactNode | `<SwapRightOutlined />` | |
|
||||
| showTime | To provide an additional time selection | object \| boolean | [TimePicker Options](/components/time-picker/#api) | |
|
||||
| showTime.defaultValue | To set default time of selected date, [demo](#date-picker-demo-disabled-date) | [dayjs](https://day.js.org/)\[] | \[dayjs(), dayjs()] | |
|
||||
| ~~showTime.defaultValue~~ | Use `showTime.defaultOpenValue` instead | [dayjs](https://day.js.org/)\[] | \[dayjs(), dayjs()] | 5.27.3 |
|
||||
| showTime.defaultOpenValue | To set default time of selected date, [demo](#date-picker-demo-disabled-date) | [dayjs](https://day.js.org/)\[] | \[dayjs(), dayjs()] | |
|
||||
| value | To set date | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | - | |
|
||||
| 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 | Callback function, can be executed when the selected time is changing | function(dates: \[dayjs, dayjs], dateStrings: \[string, string]) | - | |
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user