Compare commits

..

2 Commits

Author SHA1 Message Date
Benjy Cui
b092c022aa bump 2.3.2 2016-11-09 15:24:38 +08:00
Benjy Cui
5995aef78a fix: infinite loop, ref: #3759 2016-11-09 15:16:30 +08:00
5053 changed files with 46447 additions and 1505482 deletions

View File

@@ -1,40 +0,0 @@
const fs = require('fs');
const path = require('path');
const restCssPath = path.join(process.cwd(), 'components', 'style', 'reset.css');
const antdCssPath = path.join(process.cwd(), 'components', 'style', 'antd.css');
const tokenStatisticPath = path.join(process.cwd(), 'components', 'version', 'token.json');
const tokenMetaPath = path.join(process.cwd(), 'components', 'version', 'token-meta.json');
function finalizeCompile() {
if (fs.existsSync(path.join(__dirname, './es'))) {
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'es', 'style', 'reset.css'));
fs.copyFileSync(antdCssPath, path.join(process.cwd(), 'es', 'style', 'antd.css'));
fs.copyFileSync(tokenStatisticPath, path.join(process.cwd(), 'es', 'version', 'token.json'));
fs.copyFileSync(tokenMetaPath, path.join(process.cwd(), 'es', 'version', 'token-meta.json'));
}
if (fs.existsSync(path.join(__dirname, './lib'))) {
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'lib', 'style', 'reset.css'));
fs.copyFileSync(antdCssPath, path.join(process.cwd(), 'lib', 'style', 'antd.css'));
fs.copyFileSync(tokenStatisticPath, path.join(process.cwd(), 'lib', 'version', 'token.json'));
fs.copyFileSync(tokenMetaPath, path.join(process.cwd(), 'lib', 'version', 'token-meta.json'));
}
}
function finalizeDist() {
if (fs.existsSync(path.join(__dirname, './dist'))) {
fs.copyFileSync(restCssPath, path.join(process.cwd(), 'dist', 'reset.css'));
fs.copyFileSync(antdCssPath, path.join(process.cwd(), 'dist', 'antd.css'));
}
}
module.exports = {
compile: {
finalize: finalizeCompile,
},
dist: {
finalize: finalizeDist,
},
bail: true,
};

3
.babelrc Normal file
View File

@@ -0,0 +1,3 @@
{
"presets": ["es2015", "react", "stage-0"]
}

View File

@@ -1,26 +0,0 @@
module.exports = {
ignore: [
'**/~*/**',
'**/_*/**',
'**/icon/**',
'**/__tests__/**',
'**/style/**',
'**/locale/**',
'**/*-provider/**',
'**/*.json',
],
modulePattern: [
{
pattern: /ConfigContext.*renderEmpty/s,
module: '../empty',
},
{
pattern: /ConfigConsumer.*renderEmpty/s,
module: '../empty',
},
{
pattern: /config-provider\/context.*renderEmpty/s,
module: '../empty',
},
],
};

View File

@@ -1,16 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "ant-design",
"image": "mcr.microsoft.com/devcontainers/typescript-node:4-22-bookworm",
"postCreateCommand": "pnpm install",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"shezhangzhang.antd-design-token",
"fi3ework.vscode-antd-rush"
]
}
}
}

View File

@@ -1 +0,0 @@
node_modules/

View File

@@ -1,142 +0,0 @@
// FIXME: workaround for avoid searchbar styles be extracted to async chunk
@import 'dumi/theme-default/slots/SearchBar/index.less';
.demo-logo {
width: 120px;
min-width: 120px;
height: 32px;
background: rgba(255, 255, 255, 0.2);
border-radius: 6px;
margin-inline-end: 24px;
}
.demo-logo-vertical {
height: 32px;
margin: 16px;
background: rgba(255, 255, 255, 0.2);
border-radius: 6px;
}
html {
scrollbar-width: thin;
scrollbar-color: #eaeaea transparent;
@supports (text-autospace: normal) {
text-autospace: normal;
}
}
.rc-footer {
position: relative;
clear: both;
color: rgba(255, 255, 255, 0.4);
font-size: 14px;
line-height: 1.5;
background-color: #000;
}
.rc-footer a {
color: rgba(255, 255, 255, 0.9);
text-decoration: none;
transition: all 0.3s;
}
.rc-footer a:hover {
color: #40a9ff;
}
.rc-footer-container {
width: 100%;
max-width: 1200px;
margin: auto;
padding: 80px 0 20px;
}
.rc-footer-columns {
display: flex;
justify-content: space-around;
}
.rc-footer-column {
margin-bottom: 60px;
}
.rc-footer-column h2 {
position: relative;
margin: 0 auto;
color: #fff;
font-weight: 500;
font-size: 16px;
}
.rc-footer-column-icon {
position: relative;
top: -1px;
display: inline-block;
width: 22px;
text-align: center;
vertical-align: middle;
margin-inline-end: 0.5em;
}
.rc-footer-column-icon > span,
.rc-footer-column-icon > svg,
.rc-footer-column-icon img {
display: block;
width: 100%;
}
.rc-footer-item {
margin: 12px 0;
}
.rc-footer-item-icon {
position: relative;
top: -1px;
display: inline-block;
width: 16px;
text-align: center;
vertical-align: middle;
margin-inline-end: 0.4em;
}
.rc-footer-item-icon > span,
.rc-footer-item-icon > svg,
.rc-footer-item-icon img {
display: block;
width: 100%;
}
.rc-footer-item-separator {
margin: 0 0.3em;
}
.rc-footer-bottom-container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 16px 0;
font-size: 16px;
line-height: 32px;
text-align: center;
border-top: 1px solid rgba(255, 255, 255, 0.25);
}
.rc-footer-light {
color: rgba(0, 0, 0, 0.85);
background-color: transparent;
}
.rc-footer-light h2,
.rc-footer-light a {
color: rgba(0, 0, 0, 0.85);
}
.rc-footer-light .rc-footer-bottom-container {
border-top-color: #e8e8e8;
}
.rc-footer-light .rc-footer-item-separator,
.rc-footer-light .rc-footer-item-description {
color: rgba(0, 0, 0, 0.45);
}
@media only screen and (max-width: 767.99px) {
.rc-footer {
text-align: center;
}
.rc-footer-container {
padding: 40px 0;
}
.rc-footer-columns {
display: block;
}
.rc-footer-column {
display: block;
margin-bottom: 40px;
}
.rc-footer-column:last-child {
margin-bottom: 0;
}
}

View File

@@ -1,3 +0,0 @@
import React from 'react';
export const DarkContext = React.createContext(false);

View File

@@ -1,64 +0,0 @@
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> => {
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;

View File

@@ -1,17 +0,0 @@
import { startTransition, useState } from 'react';
const useLayoutState: typeof useState = <S>(
...args: Parameters<typeof useState<S>>
): ReturnType<typeof useState<S>> => {
const [state, setState] = useState<S>(...args);
const setLayoutState: typeof setState = (...setStateArgs) => {
startTransition(() => {
setState(...setStateArgs);
});
};
return [state, setLayoutState];
};
export default useLayoutState;

View File

@@ -1,113 +0,0 @@
import React, { useCallback, useEffect } from 'react';
const ANT_SYNC_STORAGE_EVENT_KEY = 'ANT_SYNC_STORAGE_EVENT_KEY';
const isFunction = (val: any): val is (...args: any[]) => any => {
return typeof val === 'function';
};
interface Options<T> {
defaultValue?: T;
serializer?: (value: T) => string;
deserializer?: (value: string) => T;
onError?: (error: unknown) => void;
}
const useLocalStorage = <T>(key: string, options: Options<T> = {}) => {
const storage = typeof window !== 'undefined' ? localStorage : null;
const { serializer, deserializer, onError, defaultValue } = options;
const mergedSerializer = typeof serializer === 'function' ? serializer : JSON.stringify;
const mergedDeserializer = typeof deserializer === 'function' ? deserializer : JSON.parse;
const handleError = typeof onError === 'function' ? onError : console.error;
const getStoredValue = () => {
try {
const rawData = storage?.getItem(key);
if (rawData) {
return mergedDeserializer(rawData);
}
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
handleError(e);
}
return defaultValue;
}
return defaultValue;
};
const [state, setState] = React.useState<T>(getStoredValue);
useEffect(() => {
setState(getStoredValue());
}, [key]);
const updateState: React.Dispatch<React.SetStateAction<T>> = (value) => {
const currentState = isFunction(value) ? value(state) : value;
setState(currentState);
try {
let newValue: string | null;
const oldValue = storage?.getItem(key);
if (typeof currentState === 'undefined') {
newValue = null;
storage?.removeItem(key);
} else {
newValue = mergedSerializer(currentState);
storage?.setItem(key, newValue);
}
dispatchEvent(
new CustomEvent(ANT_SYNC_STORAGE_EVENT_KEY, {
detail: { key, newValue, oldValue, storageArea: storage },
}),
);
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
handleError(e);
}
}
};
const shouldSync = (ev: StorageEvent) => {
return ev && ev.key === key && ev.storageArea === storage;
};
const onNativeStorage = useCallback(
(event: StorageEvent) => {
if (shouldSync(event)) {
setState(getStoredValue());
}
},
[key],
);
const shouldSyncCustomEvent = (ev: CustomEvent<{ key: string; storageArea: Storage }>) => {
return ev?.detail?.key === key && ev?.detail?.storageArea === storage;
};
const onCustomStorage = useCallback(
(event: Event) => {
const customEvent = event as CustomEvent;
if (shouldSyncCustomEvent(customEvent)) {
setState(getStoredValue());
}
},
[key],
);
useEffect(() => {
window?.addEventListener('storage', onNativeStorage);
window?.addEventListener(ANT_SYNC_STORAGE_EVENT_KEY, onCustomStorage);
return () => {
window?.removeEventListener('storage', onNativeStorage);
window?.removeEventListener(ANT_SYNC_STORAGE_EVENT_KEY, onCustomStorage);
};
}, [key, onNativeStorage, onCustomStorage]);
return [state, updateState] as const;
};
export default useLocalStorage;

View File

@@ -1,22 +0,0 @@
import { useLocale as useDumiLocale } from 'dumi';
export interface LocaleMap<
K extends PropertyKey = PropertyKey,
V extends string | ((...params: any[]) => string) = string,
> {
cn: Record<K, V>;
en: Record<K, V>;
}
const useLocale = <
K extends PropertyKey = PropertyKey,
V extends string | ((...params: any[]) => string) = string,
>(
localeMap?: LocaleMap<K, V>,
): [Record<K, V>, 'cn' | 'en'] => {
const { id } = useDumiLocale();
const localeType = id === 'zh-CN' ? 'cn' : 'en';
return [localeMap?.[localeType] ?? ({} as Record<K, V>), localeType] as const;
};
export default useLocale;

View File

@@ -1,48 +0,0 @@
import * as React from 'react';
import { useLocation as useDumiLocation } from 'dumi';
import useLocale from './useLocale';
function clearPath(path: string) {
return path.replace('-cn', '').replace(/\/$/, '');
}
export default function useLocation() {
const location = useDumiLocation();
const { search } = location;
const [, localeType] = useLocale();
const getLink = React.useCallback(
(path: string, hash?: string | { cn: string; en: string }) => {
let pathname = clearPath(path);
if (localeType === 'cn') {
pathname = `${pathname}-cn`;
}
if (search) {
pathname = `${pathname}${search}`;
}
if (hash) {
let hashStr: string;
if (typeof hash === 'object') {
hashStr = hash[localeType];
} else {
hashStr = hash;
}
pathname = `${pathname}#${hashStr}`;
}
return pathname;
},
[localeType, search],
);
return {
...location,
pathname: clearPath(location.pathname),
getLink,
};
}

View File

@@ -1,240 +0,0 @@
import React, { useMemo } from 'react';
import type { MenuProps } from 'antd';
import { Flex, Tag, version } from 'antd';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { useFullSidebarData, useSidebarData } from 'dumi';
import Link from '../theme/common/Link';
import useLocale from './useLocale';
import useLocation from './useLocation';
const locales = {
cn: {
deprecated: '废弃',
updated: '更新',
new: '新增',
},
en: {
deprecated: 'DEPRECATED',
updated: 'UPDATED',
new: 'NEW',
},
};
const getTagColor = (val?: string) => {
switch (val?.toUpperCase()) {
case 'UPDATED':
return 'processing';
case 'DEPRECATED':
return 'red';
default:
return 'success';
}
};
const styles = createStaticStyles(({ css, cssVar }) => ({
link: css`
display: flex;
align-items: center;
justify-content: space-between;
`,
tag: css`
margin-inline-end: 0;
`,
subtitle: css`
font-weight: normal;
font-size: ${cssVar.fontSizeSM};
opacity: 0.8;
margin-inline-start: ${cssVar.marginSM};
`,
}));
interface MenuItemLabelProps {
before?: React.ReactNode;
after?: React.ReactNode;
link: string;
title: React.ReactNode;
subtitle?: React.ReactNode;
search?: string;
tag?: string;
className?: string;
}
const MenuItemLabelWithTag: React.FC<MenuItemLabelProps> = (props) => {
const { before, after, link, title, subtitle, search, tag, className } = props;
const [locale] = useLocale(locales);
const getLocale = (name: string) => {
return (locale as any)[name.toLowerCase()] ?? name;
};
if (!before && !after) {
return (
<Link to={`${link}${search}`} className={clsx(className, { [styles.link]: tag })}>
<Flex justify="flex-start" align="center">
<span>{title}</span>
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
</Flex>
{tag && (
<Tag variant="filled" className={clsx(styles.tag)} color={getTagColor(tag)}>
{getLocale(tag.replace(/VERSION/i, version))}
</Tag>
)}
</Link>
);
}
return (
<Link to={`${link}${search}`} className={className}>
{before}
{title}
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
{after}
</Link>
);
};
export interface UseMenuOptions {
before?: React.ReactNode;
after?: React.ReactNode;
}
const useMenu = (options: UseMenuOptions = {}): readonly [MenuProps['items'], string] => {
const fullData = useFullSidebarData();
const { pathname, search } = useLocation();
const sidebarData = useSidebarData();
const { before, after } = options;
const menuItems = useMemo<MenuProps['items']>(() => {
const sidebarItems = [...(sidebarData ?? [])];
// 将设计文档未分类的放在最后
if (pathname.startsWith('/docs/spec')) {
const notGrouped = sidebarItems.splice(0, 1);
sidebarItems.push(...notGrouped);
}
// 把 /changelog 拼到开发文档中
if (pathname.startsWith('/docs/react')) {
const changelogData = Object.entries(fullData).find(([key]) =>
key.startsWith('/changelog'),
)?.[1];
if (changelogData) {
sidebarItems.splice(1, 0, changelogData[0]);
}
}
if (pathname.startsWith('/changelog')) {
const reactDocData = Object.entries(fullData).find(([key]) =>
key.startsWith('/docs/react'),
)?.[1];
if (reactDocData) {
sidebarItems.unshift(reactDocData[0]);
sidebarItems.push(...reactDocData.slice(1));
}
}
return (
sidebarItems?.reduce<Exclude<MenuProps['items'], undefined>>((result, group) => {
if (group?.title) {
// 设计文档特殊处理二级分组
if (pathname.startsWith('/docs/spec')) {
const childrenGroup = group.children.reduce<
Record<string, ReturnType<typeof useSidebarData>[number]['children']>
>((childrenResult, child) => {
const type = child.frontmatter?.type ?? 'default';
if (!childrenResult[type]) {
childrenResult[type] = [];
}
childrenResult[type].push(child);
return childrenResult;
}, {});
const childItems = [];
childItems.push(
...(childrenGroup.default?.map((item) => ({
label: (
<Link to={`${item.link}${search}`}>
{before}
{item?.title}
{after}
</Link>
),
key: item.link.replace(/(-cn$)/g, ''),
})) ?? []),
);
Object.entries(childrenGroup).forEach(([type, children]) => {
if (type !== 'default') {
childItems.push({
type: 'group',
label: type,
key: type,
children: children?.map((item) => ({
label: (
<Link to={`${item.link}${search}`}>
{before}
{item?.title}
{after}
</Link>
),
key: item.link.replace(/(-cn$)/g, ''),
})),
});
}
});
result.push({
label: group?.title,
key: group?.title,
children: childItems,
});
} else {
result.push({
type: 'group',
label: group?.title,
key: group?.title,
children: group.children?.map((item) => ({
label: (
<MenuItemLabelWithTag
before={before}
after={after}
link={item.link}
title={item?.title}
subtitle={item.frontmatter?.subtitle}
search={search}
tag={item.frontmatter?.tag}
/>
),
key: item.link.replace(/(-cn$)/g, ''),
})),
});
}
} else {
const list = group.children || [];
// 如果有 date 字段,我们就对其进行排序
if (list.every((info) => info?.frontmatter?.date)) {
list.sort((a, b) => (a.frontmatter?.date > b.frontmatter?.date ? -1 : 1));
}
result.push(
...list.map((item) => ({
label: (
<MenuItemLabelWithTag
before={before}
after={after}
link={item.link}
title={item?.title}
search={search}
tag={item.frontmatter?.tag}
/>
),
key: item.link.replace(/(-cn$)/g, ''),
})),
);
}
return result;
}, []) ?? []
);
}, [sidebarData, pathname, fullData, search, before, after]);
return [menuItems, pathname] as const;
};
export default useMenu;

View File

@@ -1,124 +0,0 @@
import { useEffect, useRef } from 'react';
import { removeCSS, updateCSS } from '@rc-component/util/lib/Dom/dynamicCSS';
import theme from '../../components/theme';
const duration = 0.5;
const viewTransitionStyle = `
@keyframes keepAlive {100% { z-index: -1 }}
::view-transition-old(root),
::view-transition-new(root) {
animation: keepAlive ${duration}s linear;
animation-fill-mode: forwards;
mix-blend-mode: normal;
}
.dark::view-transition-old(root) {
z-index: 1;
}
.dark::view-transition-new(root) {
z-index: 999;
}
::view-transition-old(root) {
z-index: 999;
}
::view-transition-new(root) {
z-index: 1;
}
`;
const useThemeAnimation = () => {
const {
token: { colorBgElevated },
} = theme.useToken();
const animateRef = useRef<{ colorBgElevated: string }>({ colorBgElevated });
const startAnimationTheme = (clipPath: string[], isDark: boolean) => {
updateCSS(
`
* {
transition: none !important;
}
`,
'disable-transition',
);
document.documentElement
.animate(
{
clipPath: isDark ? [...clipPath].reverse() : clipPath,
},
{
duration: duration * 1000,
easing: 'ease-in',
pseudoElement: isDark ? '::view-transition-old(root)' : '::view-transition-new(root)',
},
)
.addEventListener('finish', () => {
removeCSS('disable-transition');
});
};
const toggleAnimationTheme = (
event: React.MouseEvent<HTMLElement, MouseEvent>,
isDark: boolean,
) => {
if (!(event && typeof document.startViewTransition === 'function')) {
return;
}
const time = Date.now();
const x = event.clientX;
const y = event.clientY;
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
updateCSS(
`
[data-prefers-color='dark'] {
color-scheme: light !important;
}
[data-prefers-color='light'] {
color-scheme: dark !important;
}
`,
'color-scheme',
);
document
.startViewTransition(async () => {
const root = document.documentElement;
root.classList.remove(isDark ? 'dark' : 'light');
root.classList.add(isDark ? 'light' : 'dark');
})
.ready.then(() => {
// eslint-disable-next-line no-console
console.log(`Theme transition finished in ${Date.now() - time}ms`);
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
];
removeCSS('color-scheme');
startAnimationTheme(clipPath, isDark);
});
};
// inject transition style
useEffect(() => {
if (typeof document.startViewTransition === 'function') {
updateCSS(viewTransitionStyle, 'view-transition-style');
}
}, []);
useEffect(() => {
if (colorBgElevated !== animateRef.current.colorBgElevated) {
animateRef.current.colorBgElevated = colorBgElevated;
}
}, [colorBgElevated]);
return toggleAnimationTheme;
};
export default useThemeAnimation;

View File

@@ -1,3 +0,0 @@
// must be .js file, can't modify to be .ts file!
export { default } from './theme/common/Loading';

View File

@@ -1,59 +0,0 @@
import React, { useEffect } from 'react';
import { HomeOutlined } from '@ant-design/icons';
import { Button, Result } from 'antd';
import { useLocation } from 'dumi';
import Link from '../../theme/common/Link';
import * as utils from '../../theme/utils';
export interface NotFoundProps {
router: {
push: (pathname: string) => void;
replace: (pathname: string) => void;
};
}
const DIRECT_MAP: Record<string, string> = {
'docs/spec/download': 'docs/resources',
'docs/spec/work-with-us': 'docs/resources',
};
const NotFoundPage: React.FC<NotFoundProps> = ({ router }) => {
const { pathname } = useLocation();
const isZhCN = utils.isZhCN(pathname);
useEffect(() => {
const directLinks = Object.keys(DIRECT_MAP);
for (let i = 0; i < directLinks.length; i += 1) {
const matchPath = directLinks[i];
if (pathname.includes(matchPath)) {
router.replace(utils.getLocalizedPathname(`/${DIRECT_MAP[matchPath]}`, isZhCN).pathname);
}
}
// Report if necessary
const { yuyanMonitor } = window as any;
yuyanMonitor?.log({
code: 11,
msg: `Page not found: ${location.href}; Source: ${document.referrer}`,
});
}, [isZhCN, pathname, router]);
return (
<Result
status="404"
title="404"
subTitle={isZhCN ? '你访问的页面貌似不存在?' : 'Sorry, the page you visited does not exist.'}
extra={
<Link to={utils.getLocalizedPathname('/', isZhCN)}>
<Button type="primary" icon={<HomeOutlined />}>
{isZhCN ? '返回 Ant Design 首页' : 'Back to home page'}
</Button>
</Link>
}
/>
);
};
export default NotFoundPage;

View File

@@ -1,3 +0,0 @@
import Homepage from '../index/index';
export default Homepage;

View File

@@ -1,201 +0,0 @@
import React from 'react';
import { Alert, Badge, Carousel, Flex, Skeleton, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { clsx } from 'clsx';
import useLocale from '../../../hooks/useLocale';
import SiteContext from '../../../theme/slots/SiteContext';
import type { Extra, Icon } from './util';
import { getCarouselStyle, useAntdSiteConfig } from './util';
const useStyle = createStyles(({ cssVar, css, cx }) => {
const { carousel } = getCarouselStyle();
const itemBase = css`
display: flex;
flex: 1 1 0;
flex-direction: column;
align-items: stretch;
text-decoration: none;
background: ${cssVar.colorBgContainer};
border: ${cssVar.lineWidth} solid ${cssVar.colorBorderSecondary};
border-radius: ${cssVar.borderRadiusLG};
transition: all ${cssVar.motionDurationSlow};
padding-block: ${cssVar.paddingMD};
padding-inline: ${cssVar.paddingLG};
box-sizing: border-box;
`;
return {
itemBase,
ribbon: css`
& > .${cx(itemBase)} {
height: 100%;
}
`,
cardItem: css`
&:hover {
box-shadow: ${cssVar.boxShadowCard};
border-color: transparent;
}
`,
sliderItem: css`
margin: 0 ${cssVar.margin};
text-align: start;
`,
container: css`
display: flex;
width: 100%;
max-width: 100%;
margin-inline: auto;
box-sizing: border-box;
column-gap: calc(${cssVar.paddingMD} * 2);
align-items: stretch;
text-align: start;
min-height: 178px;
> * {
width: calc((100% - calc(${cssVar.marginXXL} * 2)) / 3);
}
`,
carousel,
bannerBg: css`
height: ${cssVar.fontSize};
`,
};
});
interface RecommendItemProps {
extra: Extra;
index: number;
icons?: Icon[];
className?: string;
}
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 card = (
<a
key={extra?.title}
href={extra.href}
target="_blank"
className={clsx(styles.itemBase, className)}
rel="noreferrer"
>
<Typography.Title level={5}>{extra?.title}</Typography.Title>
<Typography.Paragraph type="secondary" style={{ flex: 'auto' }}>
{extra.description}
</Typography.Paragraph>
<Flex justify="space-between" align="center">
<Typography.Text>{extra.date}</Typography.Text>
{icon?.href && (
<img src={icon.href} draggable={false} className={styles.bannerBg} alt="banner" />
)}
</Flex>
</a>
);
if (index === 0) {
return (
<Badge.Ribbon text="HOT" color="red" rootClassName={styles.ribbon}>
{card}
</Badge.Ribbon>
);
}
return card;
};
export const BannerRecommendsFallback: React.FC = () => {
const { isMobile } = React.use(SiteContext);
const { styles } = useStyle();
const list = Array.from({ length: 3 });
return isMobile ? (
<Carousel className={styles.carousel}>
{list.map((_, index) => (
<div key={`mobile-${index}`} className={styles.itemBase}>
<Skeleton active style={{ padding: '0 24px' }} />
</div>
))}
</Carousel>
) : (
<div className={styles.container}>
{list.map((_, index) => (
<div key={`desktop-${index}`} className={styles.itemBase}>
<Skeleton active />
</div>
))}
</div>
);
};
const BannerRecommends: React.FC = () => {
const { styles } = useStyle();
const [, lang] = useLocale();
const { isMobile } = React.use(SiteContext);
const { data, error, isLoading } = useAntdSiteConfig();
if (isLoading) {
return <BannerRecommendsFallback />;
}
if (error) {
return (
<Alert
showIcon
type="error"
title={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}>
{mergedExtras.map((extra, index) => (
<div key={`mobile-${index}`}>
<RecommendItem
extra={extra}
index={index}
icons={data?.icons}
className={styles.sliderItem}
/>
</div>
))}
</Carousel>
);
}
return (
<div className={styles.container}>
{mergedExtras.map((extra, index) => (
<RecommendItem
key={`desktop-${index}`}
extra={extra}
index={index}
icons={data?.icons}
className={styles.cardItem}
/>
))}
</div>
);
};
export default BannerRecommends;

View File

@@ -1,372 +0,0 @@
import React from 'react';
import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
import {
Card,
Carousel,
DatePicker,
Flex,
FloatButton,
Masonry,
Splitter,
Tag,
Tour,
Typography,
} from 'antd';
import { createStyles, css } from 'antd-style';
import { clsx } from 'clsx';
import dayjs from 'dayjs';
import useLocale from '../../../hooks/useLocale';
import SiteContext from '../../../theme/slots/SiteContext';
import { DarkContext } from './../../../hooks/useDark';
import { getCarouselStyle } from './util';
const { _InternalPanelDoNotUseOrYouWillBeFired: DatePickerDoNotUseOrYouWillBeFired } = DatePicker;
const { _InternalPanelDoNotUseOrYouWillBeFired: TourDoNotUseOrYouWillBeFired } = Tour;
const { _InternalPanelDoNotUseOrYouWillBeFired: FloatButtonDoNotUseOrYouWillBeFired } = FloatButton;
const SAMPLE_CONTENT_EN =
'Ant Design use CSS-in-JS technology to provide dynamic & mix theme ability. And which use component level CSS-in-JS solution get your application a better performance.';
const SAMPLE_CONTENT_CN =
'Ant Design 使用 CSS-in-JS 技术以提供动态与混合主题的能力。与此同时,我们使用组件级别的 CSS-in-JS 解决方案,让你的应用获得更好的性能。';
const locales = {
cn: {
yesterday: '昨天',
lastWeek: '上周',
lastMonth: '上月',
lastYear: '去年',
new: '新增',
update: '更新',
sampleContent: SAMPLE_CONTENT_CN,
inProgress: '进行中',
success: '成功',
taskFailed: '任务失败',
tour: '漫游导览帮助用户对新加的功能进行快速了解',
},
en: {
yesterday: 'Yesterday',
lastWeek: 'Last Week',
lastMonth: 'Last Month',
lastYear: 'Last Year',
new: 'New',
update: 'Update',
sampleContent: SAMPLE_CONTENT_EN,
inProgress: 'In Progress',
success: 'Success',
taskFailed: 'Task Failed',
tour: 'A quick guide for new come user about how to use app.',
},
};
const useStyle = createStyles(({ cssVar }, isDark: boolean) => {
const { carousel } = getCarouselStyle();
return {
card: css`
border-radius: ${cssVar.borderRadius};
border: 1px solid ${isDark ? cssVar.colorBorder : 'transparent'};
background-color: ${isDark ? cssVar.colorBgContainer : '#f5f8ff'};
padding: ${cssVar.paddingXL};
flex: none;
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
align-items: stretch;
> * {
flex: none;
}
`,
cardCircle: css`
position: absolute;
width: 120px;
height: 120px;
background: #1677ff;
border-radius: 50%;
filter: blur(40px);
opacity: 0.1;
`,
mobileCard: css`
height: 395px;
`,
nodeWrap: css`
margin-top: ${cssVar.paddingLG};
flex: auto;
display: flex;
align-items: center;
justify-content: center;
`,
carousel,
componentsList: css`
width: 100%;
overflow: hidden;
`,
mobileComponentsList: css`
margin: 0 ${cssVar.margin};
`,
};
});
const ComponentItem: React.FC<ComponentItemProps> = ({ title, node, type, index }) => {
const tagColor = type === 'new' ? 'processing' : 'warning';
const [locale] = useLocale(locales);
const tagText = type === 'new' ? locale.new : locale.update;
const isDark = React.use(DarkContext);
const { isMobile } = React.use(SiteContext);
const { styles } = useStyle(isDark);
return (
<div className={clsx(styles.card, isMobile && styles.mobileCard)}>
{/* Decorator */}
<div
className={styles.cardCircle}
style={{ insetInlineEnd: (index % 2) * -20 - 20, bottom: (index % 3) * -40 - 20 }}
/>
{/* Title */}
<Flex align="center" gap="small">
<Typography.Title level={4} style={{ fontWeight: 'normal', margin: 0 }}>
{title}
</Typography.Title>
<Tag color={tagColor}>{tagText}</Tag>
</Flex>
<div className={styles.nodeWrap}>{node}</div>
</div>
);
};
interface ComponentItemProps {
title: React.ReactNode;
node: React.ReactNode;
type: 'new' | 'update';
index: number;
}
const ComponentsList: React.FC = () => {
const { styles } = useStyle();
const [locale] = useLocale(locales);
const { isMobile } = React.use(SiteContext);
const isDark = React.use(DarkContext);
const COMPONENTS = React.useMemo<Omit<ComponentItemProps, 'index'>[]>(
() => [
// {
// title: 'Modal',
// type: 'update',
// node: (
// <ModalDoNotUseOrYouWillBeFired title="Ant Design" width={300}>
// {locale.sampleContent}
// </ModalDoNotUseOrYouWillBeFired>
// ),
// },
{
title: 'DatePicker',
type: 'update',
node: (
<DatePickerDoNotUseOrYouWillBeFired
value={dayjs('2025-11-22 00:00:00')}
// defaultValue={dayjs('2025-11-22 00:00:00')}
showToday={false}
presets={
isMobile
? []
: [
{ label: locale.yesterday, value: dayjs().add(-1, 'd') },
{ label: locale.lastWeek, value: dayjs().add(-7, 'd') },
{ label: locale.lastMonth, value: dayjs().add(-1, 'month') },
{ label: locale.lastYear, value: dayjs().add(-1, 'year') },
]
}
/>
),
},
// {
// title: 'Progress',
// type: 'update',
// node: (
// <Flex gap="small" vertical>
// <Flex gap="small" align="center">
// <Progress type="circle" railColor="#e6f4ff" percent={60} size={14} />
// {locale.inProgress}
// </Flex>
// <Flex gap="small" align="center">
// <Progress type="circle" percent={100} size={14} />
// {locale.success}
// </Flex>
// <Flex gap="small" align="center">
// <Progress type="circle" status="exception" percent={88} size={14} />
// {locale.taskFailed}
// </Flex>
// </Flex>
// ),
// },
{
title: 'Tour',
type: 'update',
node: (
<TourDoNotUseOrYouWillBeFired
title="Ant Design"
description={locale.tour}
style={{ width: isMobile ? 'auto' : 350 }}
current={3}
total={9}
/>
),
},
{
title: 'FloatButton',
type: 'update',
node: (
<Flex align="center" gap="large">
<FloatButtonDoNotUseOrYouWillBeFired
shape="square"
items={[
{ icon: <QuestionCircleOutlined /> },
{ icon: <CustomerServiceOutlined /> },
{ icon: <SyncOutlined /> },
]}
/>
<FloatButtonDoNotUseOrYouWillBeFired backTop />
<FloatButtonDoNotUseOrYouWillBeFired
items={[
{ icon: <QuestionCircleOutlined /> },
{ icon: <CustomerServiceOutlined /> },
{ icon: <SyncOutlined /> },
]}
/>
</Flex>
),
},
// {
// title: 'Steps',
// type: 'update',
// node: <Button style={{ width: PLACEHOLDER_WIDTH }}>Placeholder</Button>,
// },
{
title: 'Splitter',
type: 'new',
node: (
<Splitter
orientation="vertical"
style={{
height: 320,
width: 200,
background: isDark ? '#1f1f1f' : '#ffffff',
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
}}
>
<Splitter.Panel defaultSize="40%" min="20%" max="70%">
<Flex justify="center" align="center" style={{ height: '100%' }}>
<Typography.Title type="secondary" level={5} style={{ whiteSpace: 'nowrap' }}>
First
</Typography.Title>
</Flex>
</Splitter.Panel>
<Splitter.Panel>
<Flex justify="center" align="center" style={{ height: '100%' }}>
<Typography.Title type="secondary" level={5} style={{ whiteSpace: 'nowrap' }}>
Second
</Typography.Title>
</Flex>
</Splitter.Panel>
</Splitter>
),
},
{
title: 'Masonry',
type: 'new',
node: (
<Masonry
columns={2}
gutter={8}
style={{
width: 300,
height: 320,
}}
items={[
{ key: '1', data: 80 },
{ key: '2', data: 60 },
{ key: '3', data: 40 },
{ key: '4', data: 120 },
{ key: '5', data: 90 },
{ key: '6', data: 40 },
{ key: '7', data: 60 },
{ key: '8', data: 70 },
{ key: '9', data: 120 },
]}
itemRender={({ data, index }) => (
<Card size="small" style={{ height: data }}>
{index + 1}
</Card>
)}
/>
),
},
// {
// title: 'Alert',
// type: 'update',
// node: (
// <Alert
// style={{ width: 400 }}
// title="Ant Design"
// description={locale.sampleContent}
// closable={{ closeIcon: true, disabled: true }}
// />
// ),
// },
],
[
isDark,
isMobile,
locale.inProgress,
locale.lastMonth,
locale.lastWeek,
locale.lastYear,
locale.sampleContent,
locale.success,
locale.taskFailed,
locale.tour,
locale.yesterday,
],
);
return isMobile ? (
<div className={styles.mobileComponentsList}>
<Carousel className={styles.carousel}>
{COMPONENTS.map<React.ReactNode>(({ title, node, type }, index) => (
<ComponentItem
title={title}
node={node}
type={type}
index={index}
key={`mobile-item-${index}`}
/>
))}
</Carousel>
</div>
) : (
<Flex justify="center" className={styles.componentsList}>
<Flex align="stretch" gap="large">
{COMPONENTS.map<React.ReactNode>(({ title, node, type }, index) => (
<ComponentItem
title={title}
node={node}
type={type}
index={index}
key={`desktop-item-${index}`}
/>
))}
</Flex>
</Flex>
);
};
export default ComponentsList;

View File

@@ -1,186 +0,0 @@
import React from 'react';
import { Col, Row, Typography } from 'antd';
import { createStyles, useTheme } from 'antd-style';
import { useLocation } from 'dumi';
import useLocale from '../../../hooks/useLocale';
import Link from '../../../theme/common/Link';
import SiteContext from '../../../theme/slots/SiteContext';
import * as utils from '../../../theme/utils';
import { DarkContext } from './../../../hooks/useDark';
const SECONDARY_LIST = [
{
img: 'https://gw.alipayobjects.com/zos/bmw-prod/b874caa9-4458-412a-9ac6-a61486180a62.svg',
key: 'mobile',
url: 'https://mobile.ant.design/',
imgScale: 1.5,
scaleOrigin: '15px',
},
{
img: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
key: 'antv',
url: 'https://antv.vision/',
},
{
img: 'https://gw.alipayobjects.com/zos/bmw-prod/af1ea898-bf02-45d1-9f30-8ca851c70a5b.svg',
key: 'kitchen',
url: 'https://kitchen.alipay.com/',
},
];
const locales = {
cn: {
values: '设计价值观',
valuesDesc: '确定性、意义感、生长性、自然',
guide: '设计指引',
guideDesc: '全局样式、设计模式',
lib: '组件库',
libDesc: 'Ant Design of React / Angular / Vue',
// Secondary
mobile: 'Ant Design Mobile',
mobileDesc: 'Ant Design 移动端 UI 组件库',
antv: 'AntV',
antvDesc: '全新一代数据可视化解决方案',
kitchen: 'Kitchen',
kitchenDesc: '一款为设计者提升工作效率的 Sketch 工具集',
},
en: {
values: 'Design values',
valuesDesc: 'Certainty, Meaningfulness, Growth, Naturalness',
guide: 'Design guide',
guideDesc: 'Global style and design pattern',
lib: 'Components Libraries',
libDesc: 'Ant Design of React / Angular / Vue',
// Secondary
mobile: 'Ant Design Mobile',
mobileDesc: 'Mobile UI component library',
antv: 'AntV',
antvDesc: 'New generation of data visualization solutions',
kitchen: 'Kitchen',
kitchenDesc: 'Sketch Tool set for designers',
},
};
const useStyle = createStyles(({ cssVar, css }, isDark: boolean) => {
return {
card: css`
padding: ${cssVar.paddingSM};
border-radius: calc(${cssVar.borderRadius} * 2);
background: ${isDark ? 'rgba(0, 0, 0, 0.45)' : cssVar.colorBgElevated};
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.03),
0 1px 6px -1px rgba(0, 0, 0, 0.02),
0 2px 4px rgba(0, 0, 0, 0.02);
img {
width: 100%;
vertical-align: top;
border-radius: ${cssVar.borderRadius};
}
`,
cardMini: css`
display: block;
border-radius: calc(${cssVar.borderRadius} * 2);
padding: ${cssVar.paddingMD} ${cssVar.paddingLG};
background: ${isDark ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.02)'};
border: 1px solid ${isDark ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 0.06)'};
img {
height: 48px;
}
`,
};
});
const DesignFramework: React.FC = () => {
const [locale] = useLocale(locales);
const token = useTheme();
const { isMobile } = React.use(SiteContext);
const isDark = React.use(DarkContext);
const { styles } = useStyle(isDark);
const { pathname, search } = useLocation();
const isZhCN = utils.isZhCN(pathname);
const colSpan = isMobile ? 24 : 8;
const MAINLY_LIST = [
{
img: 'https://gw.alipayobjects.com/zos/bmw-prod/36a89a46-4224-46e2-b838-00817f5eb364.svg',
key: 'values',
path: utils.getLocalizedPathname('/docs/spec/values/', isZhCN, search),
},
{
img: 'https://gw.alipayobjects.com/zos/bmw-prod/8379430b-e328-428e-8a67-666d1dd47f7d.svg',
key: 'guide',
path: utils.getLocalizedPathname('/docs/spec/colors/', isZhCN, search),
},
{
img: 'https://gw.alipayobjects.com/zos/bmw-prod/1c363c0b-17c6-4b00-881a-bc774df1ebeb.svg',
key: 'lib',
path: utils.getLocalizedPathname('/docs/react/introduce/', isZhCN, search),
},
];
return (
<Row gutter={[token.marginXL, token.marginXL]}>
{MAINLY_LIST.map(({ img, key, path }, index) => {
const title = locale[key as keyof typeof locale];
const desc = locale[`${key}Desc` as keyof typeof locale];
return (
<Col key={index} span={colSpan}>
<Link to={path}>
<div className={styles.card}>
<img draggable={false} alt={title} src={img} />
<Typography.Title
level={4}
style={{ marginTop: token.margin, marginBottom: token.marginXS }}
>
{title}
</Typography.Title>
<Typography.Paragraph type="secondary" style={{ margin: 0 }}>
{desc}
</Typography.Paragraph>
</div>
</Link>
</Col>
);
})}
{SECONDARY_LIST.map(({ img, key, url, imgScale = 1, scaleOrigin }, index) => {
const title = locale[key as keyof typeof locale];
const desc = locale[`${key}Desc` as keyof typeof locale];
return (
<Col key={index} span={colSpan}>
<a className={styles.cardMini} target="_blank" href={url} rel="noreferrer">
<img
draggable={false}
alt={title}
src={img}
style={{ transform: `scale(${imgScale})`, transformOrigin: scaleOrigin }}
/>
<Typography.Title
level={4}
style={{ marginTop: token.margin, marginBottom: token.marginXS }}
>
{title}
</Typography.Title>
<Typography.Paragraph type="secondary" style={{ margin: 0 }}>
{desc}
</Typography.Paragraph>
</a>
</Col>
);
})}
</Row>
);
};
export default DesignFramework;

View File

@@ -1,84 +0,0 @@
import * as React from 'react';
import { Typography } from 'antd';
import { createStaticStyles, useTheme } from 'antd-style';
import { clsx } from 'clsx';
import SiteContext from '../../../theme/slots/SiteContext';
import GroupMaskLayer from './GroupMaskLayer';
const styles = createStaticStyles(({ css, cssVar }) => ({
box: css`
position: relative;
transition: all ${cssVar.motionDurationSlow};
`,
container: css`
position: absolute;
inset: 0;
overflow: hidden;
`,
typographyWrapper: css`
text-align: center;
`,
marginStyle: css`
max-width: 1208px;
margin-inline: auto;
box-sizing: border-box;
padding-inline: ${cssVar.marginXXL};
`,
withoutChildren: css`
min-height: 300px;
border-radius: ${cssVar.borderRadiusLG};
background-color: '#e9e9e9';
`,
}));
export interface GroupProps {
id?: string;
title?: React.ReactNode;
titleColor?: string;
description?: React.ReactNode;
background?: string;
/** 是否不使用两侧 margin */
collapse?: boolean;
decoration?: React.ReactNode;
}
const Group: React.FC<React.PropsWithChildren<GroupProps>> = (props) => {
const { id, title, titleColor, description, children, decoration, background, collapse } = props;
const token = useTheme();
const { isMobile } = React.use(SiteContext);
return (
<div style={{ backgroundColor: background }} className={styles.box}>
<div className={styles.container}>{decoration}</div>
<GroupMaskLayer style={{ paddingBlock: token.marginFarSM }}>
<div className={styles.typographyWrapper}>
<Typography.Title
id={id}
level={1}
style={{
fontWeight: 900,
color: titleColor,
// Special for the title
fontSize: isMobile ? token.fontSizeHeading2 : token.fontSizeHeading1,
}}
>
{title}
</Typography.Title>
<Typography.Paragraph
style={{
color: titleColor,
marginBottom: isMobile ? token.marginXXL : token.marginFarXS,
}}
>
{description}
</Typography.Paragraph>
</div>
<div className={clsx({ [styles.marginStyle]: !collapse })}>
{children ? <div>{children}</div> : <div className={styles.withoutChildren} />}
</div>
</GroupMaskLayer>
</div>
);
};
export default Group;

View File

@@ -1,35 +0,0 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
const classNames = createStaticStyles(({ css }) => ({
siteMask: css`
z-index: 1;
position: relative;
`,
}));
export interface GroupMaskLayerProps {
className?: string;
style?: React.CSSProperties;
onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
}
const GroupMaskLayer: React.FC<React.PropsWithChildren<GroupMaskLayerProps>> = (props) => {
const { children, className, style, onMouseMove, onMouseEnter, onMouseLeave } = props;
return (
<div
style={style}
className={clsx(className, classNames.siteMask)}
onMouseMove={onMouseMove}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{children}
</div>
);
};
export default GroupMaskLayer;

View File

@@ -1,220 +0,0 @@
import React from 'react';
import { AntDesignOutlined, CheckOutlined, CloseOutlined, DownOutlined } from '@ant-design/icons';
import {
Alert,
Button,
Checkbox,
ColorPicker,
Dropdown,
Input,
message,
Modal,
Progress,
Select,
Slider,
Space,
Steps,
Switch,
Tooltip,
} from 'antd';
import { createStaticStyles } from 'antd-style';
import useLocale from '../../../../hooks/useLocale';
import Tilt from './Tilt';
const { _InternalPanelDoNotUseOrYouWillBeFired: ModalPanel } = Modal;
const { _InternalPanelDoNotUseOrYouWillBeFired: InternalTooltip } = Tooltip;
const { _InternalPanelDoNotUseOrYouWillBeFired: InternalMessage } = message;
const locales = {
cn: {
range: '设置范围',
text: 'Ant Design 使用 CSS-in-JS 技术以提供动态与混合主题的能力。与此同时,我们使用组件级别的 CSS-in-JS 解决方案,让你的应用获得更好的性能。',
infoText: '信息内容展示',
dropdown: '下拉菜单',
finished: '已完成',
inProgress: '进行中',
waiting: '等待中',
option: '选项',
apple: '苹果',
banana: '香蕉',
orange: '橘子',
watermelon: '西瓜',
primary: '主要按钮',
danger: '危险按钮',
default: '默认按钮',
dashed: '虚线按钮',
icon: '图标按钮',
hello: '你好Ant Design!',
release: 'Ant Design 6.0 正式发布!',
},
en: {
range: 'Set Range',
text: 'Ant Design use CSS-in-JS technology to provide dynamic & mix theme ability. And which use component level CSS-in-JS solution get your application a better performance.',
infoText: 'Info Text',
dropdown: 'Dropdown',
finished: 'Finished',
inProgress: 'In Progress',
waiting: 'Waiting',
option: 'Option',
apple: 'Apple',
banana: 'Banana',
orange: 'Orange',
watermelon: 'Watermelon',
primary: 'Primary',
danger: 'Danger',
default: 'Default',
dashed: 'Dashed',
icon: 'Icon',
hello: 'Hello, Ant Design!',
release: 'Ant Design 6.0 is released!',
},
};
const styles = createStaticStyles(({ cssVar, css }) => {
const gap = cssVar.padding;
return {
holder: css`
width: 500px;
display: flex;
flex-direction: column;
row-gap: ${gap};
opacity: 0.8;
`,
flex: css`
display: flex;
flex-wrap: nowrap;
column-gap: ${gap};
`,
ptg_20: css`
flex: 0 1 20%;
`,
ptg_none: css`
flex: none;
`,
block: css`
background-color: ${cssVar.colorBgContainer};
padding: ${cssVar.paddingXS} ${cssVar.paddingSM};
border-radius: ${cssVar.borderRadius};
border: 1px solid ${cssVar.colorBorder};
`,
noMargin: css`
margin: 0;
`,
};
});
const ComponentsBlock: React.FC = () => {
const [locale] = useLocale(locales);
return (
<Tilt options={{ max: 4, glare: false, scale: 0.98 }} className={styles.holder}>
<ModalPanel title="Ant Design" width="100%">
{locale.text}
</ModalPanel>
<Alert title={locale.infoText} type="info" />
{/* Line */}
<div className={styles.flex}>
<ColorPicker style={{ flex: 'none' }} />
<div style={{ flex: 'none' }}>
<Space.Compact>
<Button>{locale.dropdown}</Button>
<Dropdown
menu={{
items: Array.from({ length: 5 }).map((_, index) => ({
key: `opt${index}`,
label: `${locale.option} ${index}`,
})),
}}
>
<Button icon={<DownOutlined />} />
</Dropdown>
</Space.Compact>
</div>
<Select
style={{ flex: 'auto' }}
mode="multiple"
maxTagCount="responsive"
defaultValue={[{ value: 'apple' }, { value: 'banana' }]}
options={[
{ value: 'apple', label: locale.apple },
{ value: 'banana', label: locale.banana },
{ value: 'orange', label: locale.orange },
{ value: 'watermelon', label: locale.watermelon },
]}
/>
<Input style={{ flex: 'none', width: 120 }} />
</div>
<Progress
style={{ margin: 0 }}
percent={100}
strokeColor={{ '0%': '#108ee9', '100%': '#87d068' }}
/>
<Progress style={{ margin: 0 }} percent={33} status="exception" />
<Steps
current={1}
items={[
{ title: locale.finished },
{ title: locale.inProgress },
{ title: locale.waiting },
]}
/>
{/* Line */}
<div className={styles.block}>
<Slider
style={{ marginInline: 20 }}
range
marks={{
0: '0°C',
26: '26°C',
37: '37°C',
100: {
style: { color: '#f50' },
label: <strong>100°C</strong>,
},
}}
defaultValue={[26, 37]}
/>
</div>
{/* Line */}
<div className={styles.flex}>
<Button className={styles.ptg_20} type="primary">
{locale.primary}
</Button>
<Button className={styles.ptg_20} type="primary" danger>
{locale.danger}
</Button>
<Button className={styles.ptg_20}>{locale.default}</Button>
<Button className={styles.ptg_20} type="dashed">
{locale.dashed}
</Button>
<Button className={styles.ptg_20} icon={<AntDesignOutlined />}>
{locale.icon}
</Button>
</div>
{/* Line */}
<div className={styles.block}>
<div className={styles.flex}>
<Switch
className={styles.ptg_none}
defaultChecked
checkedChildren={<CheckOutlined />}
unCheckedChildren={<CloseOutlined />}
/>
<Checkbox.Group
className={styles.ptg_none}
options={[locale.apple, locale.banana, locale.orange]}
defaultValue={[locale.apple]}
/>
</div>
</div>
<div>
<InternalMessage content={locale.release} type="success" />
</div>
<InternalTooltip title={locale.hello} placement="topLeft" className={styles.noMargin} />
<Alert title="Ant Design love you!" type="success" />
</Tilt>
);
};
export default ComponentsBlock;

View File

@@ -1,34 +0,0 @@
import React, { useEffect, useRef } from 'react';
import VanillaTilt from 'vanilla-tilt';
import type { TiltOptions } from 'vanilla-tilt';
interface TiltProps extends React.HTMLAttributes<HTMLDivElement> {
options?: TiltOptions;
}
// https://micku7zu.github.io/vanilla-tilt.js/index.html
const defaultTiltOptions: TiltOptions = {
scale: 1.02,
max: 8,
speed: 1500,
glare: true,
'max-glare': 0.8,
};
const Tilt: React.FC<TiltProps> = ({ options, ...props }) => {
const node = useRef<HTMLDivElement>(null);
useEffect(() => {
if (node.current) {
VanillaTilt.init(node.current, {
...defaultTiltOptions,
...options,
});
}
return () => {
(node.current as any)?.vanillaTilt.destroy();
};
}, []);
return <div ref={node} {...props} />;
};
export default Tilt;

View File

@@ -1,185 +0,0 @@
import React, { Suspense, use } from 'react';
import { Flex, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { clsx } from 'clsx';
import { useLocation } from 'dumi';
import useLocale from '../../../../hooks/useLocale';
import LinkButton from '../../../../theme/common/LinkButton';
import SiteContext from '../../../../theme/slots/SiteContext';
import type { SiteContextProps } from '../../../../theme/slots/SiteContext';
import * as utils from '../../../../theme/utils';
import GroupMaskLayer from '../GroupMaskLayer';
import '../SiteContext';
const ComponentsBlock = React.lazy(() => import('./ComponentsBlock'));
const locales = {
cn: {
slogan: '助力设计开发者「更灵活」地搭建出「更美」的产品,让用户「快乐工作」~',
start: '开始使用',
designLanguage: '设计语言',
},
en: {
slogan:
'Help designers/developers building beautiful products more flexible and working with happiness',
start: 'Getting Started',
designLanguage: 'Design Language',
},
};
const useStyle = createStyles(({ cssVar, css, cx }, siteConfig: SiteContextProps) => {
const textShadow = `0 0 4px ${cssVar.colorBgContainer}`;
const mask = cx(css`
position: absolute;
inset: 0;
backdrop-filter: blur(2px);
opacity: 1;
background-color: rgba(255, 255, 255, 0.2);
transition: all 1s ease;
pointer-events: none;
[data-prefers-color='dark'] & {
background-color: rgba(0, 0, 0, 0.2);
}
`);
const block = cx(css`
position: absolute;
inset-inline-end: -60px;
top: -24px;
transition: all 1s cubic-bezier(0.03, 0.98, 0.52, 0.99);
`);
return {
holder: css`
height: 640px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
perspective: 800px;
/* fix safari bug by removing blur style */
transform: translateZ(1000px);
row-gap: ${cssVar.marginXL};
&:hover {
.${mask} {
opacity: 0;
}
.${block} {
transform: scale(0.96);
}
}
`,
mask,
typography: css`
text-align: center;
position: relative;
z-index: 1;
padding-inline: ${cssVar.paddingXL};
text-shadow: ${Array.from({ length: 5 }, () => textShadow).join(', ')};
h1 {
font-weight: 900 !important;
font-size: calc(${cssVar.fontSizeHeading2} * 2) !important;
line-height: ${cssVar.lineHeightHeading2} !important;
}
p {
font-size: ${cssVar.fontSizeLG} !important;
font-weight: normal !important;
margin-bottom: 0;
}
`,
block,
child: css`
position: relative;
width: 100%;
max-width: 1200px;
margin: 0 auto;
z-index: 1;
`,
btnWrap: css`
margin-bottom: ${cssVar.marginXL};
`,
bgImg: css`
position: absolute;
width: 240px;
`,
bgImgTop: css`
top: 0;
inset-inline-start: ${siteConfig.isMobile ? '-120px' : 0};
`,
bgImgBottom: css`
bottom: 120px;
inset-inline-end: ${siteConfig.isMobile ? 0 : '40%'};
`,
};
});
const PreviewBanner: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
const { children } = props;
const [locale] = useLocale(locales);
const siteConfig = use(SiteContext);
const { styles } = useStyle(siteConfig);
const { pathname, search } = useLocation();
const isZhCN = utils.isZhCN(pathname);
return (
<GroupMaskLayer>
{/* Image Left Top */}
<img
alt="bg"
src="https://gw.alipayobjects.com/zos/bmw-prod/49f963db-b2a8-4f15-857a-270d771a1204.svg"
draggable={false}
className={clsx(styles.bgImg, styles.bgImgTop)}
/>
{/* Image Right Top */}
<img
alt="bg"
src="https://gw.alipayobjects.com/zos/bmw-prod/e152223c-bcae-4913-8938-54fda9efe330.svg"
draggable={false}
className={clsx(styles.bgImg, styles.bgImgBottom)}
/>
<div className={styles.holder}>
{/* Mobile not show the component preview */}
<Suspense fallback={null}>
{siteConfig.isMobile ? null : (
<div className={styles.block}>
<ComponentsBlock />
</div>
)}
</Suspense>
<div className={styles.mask} />
<Typography className={styles.typography}>
<h1>Ant Design</h1>
<p>{locale.slogan}</p>
</Typography>
<Flex gap="middle" className={styles.btnWrap}>
<LinkButton
size="large"
type="primary"
to={utils.getLocalizedPathname('/components/overview/', isZhCN, search)}
>
{locale.start}
</LinkButton>
<LinkButton
size="large"
to={utils.getLocalizedPathname('/docs/spec/introduce/', isZhCN, search)}
>
{locale.designLanguage}
</LinkButton>
</Flex>
<div className={styles.child}>{children}</div>
</div>
</GroupMaskLayer>
);
};
export default PreviewBanner;

View File

@@ -1,9 +0,0 @@
import * as React from 'react';
export interface SiteContextProps {
isMobile: boolean;
}
const SiteContext = React.createContext<SiteContextProps>({ isMobile: false });
export default SiteContext;

View File

@@ -1,79 +0,0 @@
import React, { useMemo, useState } from 'react';
import { CSSMotionList } from '@rc-component/motion';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import { COLOR_IMAGES, getClosetColor } from './colorUtil';
export interface BackgroundImageProps {
colorPrimary?: string;
isLight?: boolean;
}
const styles = createStaticStyles(({ css, cssVar }) => ({
image: css`
transition: all ${cssVar.motionDurationSlow};
position: absolute;
inset-inline-start: 0;
top: 0;
height: 100%;
width: 100%;
object-fit: cover;
object-position: right top;
`,
}));
const onShow = () => ({ opacity: 1 });
const onHide = () => ({ opacity: 0 });
const BackgroundImage: React.FC<BackgroundImageProps> = ({ colorPrimary, isLight }) => {
const activeColor = useMemo(() => getClosetColor(colorPrimary), [colorPrimary]);
const [keyList, setKeyList] = useState<string[]>([]);
React.useLayoutEffect(() => {
setKeyList([activeColor as string]);
}, [activeColor]);
return (
<CSSMotionList
keys={keyList}
motionName="transition"
onEnterStart={onHide}
onAppearStart={onHide}
onEnterActive={onShow}
onAppearActive={onShow}
onLeaveStart={onShow}
onLeaveActive={onHide}
motionDeadline={500}
>
{({ key: color, className, style }) => {
const cls = clsx(styles.image, className);
const entity = COLOR_IMAGES.find((ent) => ent.color === color);
if (!entity || !entity.url) {
return null as unknown as React.ReactElement;
}
const { opacity } = style || {};
return (
<picture>
<source srcSet={entity.webp} type="image/webp" />
<source srcSet={entity.url} type="image/jpeg" />
<img
draggable={false}
className={cls}
style={{ ...style, opacity: isLight ? opacity : 0 }}
src={entity.url}
alt="bg"
/>
</picture>
);
}}
</CSSMotionList>
);
};
export default BackgroundImage;

View File

@@ -1,138 +0,0 @@
import React, { useEffect, useState } from 'react';
import { ColorPicker, Flex, Input } from 'antd';
import type { ColorPickerProps, GetProp } from 'antd';
import { createStaticStyles } from 'antd-style';
import { generateColor } from 'antd/es/color-picker/util';
import { clsx } from 'clsx';
import { PRESET_COLORS } from './colorUtil';
type Color = Extract<GetProp<ColorPickerProps, 'value'>, string | { cleared: any }>;
const styles = createStaticStyles(({ cssVar, css }) => ({
color: css`
width: calc(${cssVar.controlHeightLG} / 2);
height: calc(${cssVar.controlHeightLG} / 2);
border-radius: 100%;
cursor: pointer;
transition: all ${cssVar.motionDurationFast};
display: inline-block;
& > input[type='radio'] {
width: 0;
height: 0;
opacity: 0;
}
&:focus-within {
// need
}
`,
colorActive: css`
box-shadow:
0 0 0 1px ${cssVar.colorBgContainer},
0 0 0 calc(${cssVar.controlOutlineWidth} * 2 + 1) ${cssVar.colorPrimary};
`,
}));
export interface ThemeColorPickerProps {
id?: string;
value?: string | Color;
onChange?: (value?: Color | string) => void;
}
const DebouncedColorPicker: React.FC<React.PropsWithChildren<ThemeColorPickerProps>> = (props) => {
const { value: color, children, onChange } = props;
const [value, setValue] = useState(color);
useEffect(() => {
const timeout = setTimeout(() => {
onChange?.(value);
}, 200);
return () => clearTimeout(timeout);
}, [value]);
useEffect(() => {
setValue(color);
}, [color]);
return (
<ColorPicker
value={value}
onChange={setValue}
presets={[{ label: 'PresetColors', key: 'PresetColors', colors: PRESET_COLORS }]}
>
{children}
</ColorPicker>
);
};
const ThemeColorPicker: React.FC<ThemeColorPickerProps> = ({ value, onChange, id }) => {
const matchColors = React.useMemo(() => {
const valueStr = generateColor(value || '').toRgbString();
const colors = PRESET_COLORS.map((color) => {
const colorStr = generateColor(color).toRgbString();
const active = colorStr === valueStr;
return { color, active, picker: false } as const;
});
const existActive = colors.some((c) => c.active);
return [
...colors,
{
color: 'conic-gradient(red, yellow, lime, aqua, blue, magenta, red)',
picker: true,
active: !existActive,
},
];
}, [value]);
return (
<Flex gap="large" align="center" wrap>
<Input
value={typeof value === 'string' ? value : value?.toHexString()}
onChange={(event) => onChange?.(event.target.value)}
style={{ width: 120 }}
id={id}
/>
<Flex gap="middle">
{matchColors.map<React.ReactNode>(({ color, active, picker }) => {
const colorNode = (
<label
key={color}
className={clsx(styles.color, { [styles.colorActive]: active })}
style={{ background: color }}
onClick={() => {
if (!picker) {
onChange?.(color);
}
}}
>
<input
type="radio"
name={picker ? 'picker' : 'color'}
aria-label={color}
tabIndex={picker ? -1 : 0}
onClick={(e) => e.stopPropagation()}
/>
</label>
);
return picker ? (
<DebouncedColorPicker
key={`colorpicker-${value}`}
value={value || ''}
onChange={onChange}
>
{colorNode}
</DebouncedColorPicker>
) : (
colorNode
);
})}
</Flex>
</Flex>
);
};
export default ThemeColorPicker;

View File

@@ -1,122 +0,0 @@
import * as React from 'react';
import { useState } from 'react';
import { Carousel, Typography } from 'antd';
import { createStyles, css, useTheme } from 'antd-style';
import { getCarouselStyle } from '../util';
const useStyle = createStyles(() => {
const { carousel } = getCarouselStyle();
return {
carousel,
container: css`
position: relative;
`,
title: css`
position: absolute;
top: 15%;
z-index: 1;
width: 100%;
text-align: center;
`,
img: css`
width: 100%;
`,
};
});
const mobileImageConfigList = [
{
imageSrc:
'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*KsMrRZaciFcAAAAAAAAAAAAADrJ8AQ/original',
titleColor: 'rgba(0,0,0,.88)',
},
{
imageSrc:
'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3FkqR6XRNgoAAAAAAAAAAAAADrJ8AQ/original',
titleColor: '#fff',
},
{
imageSrc:
'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*cSX_RbD3k9wAAAAAAAAAAAAADrJ8AQ/original',
titleColor: '#fff',
},
{
imageSrc:
'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*MldsRZeax6EAAAAAAAAAAAAADrJ8AQ/original',
titleColor: '#fff',
},
{
imageSrc:
'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*xCAmSL0xlZ8AAAAAAAAAAAAADrJ8AQ/original',
titleColor: '#fff',
},
{
imageSrc:
'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*vCfCSbiI_VIAAAAAAAAAAAAADrJ8AQ/original',
titleColor: '#fff',
},
{
imageSrc:
'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*xCAmSL0xlZ8AAAAAAAAAAAAADrJ8AQ/original',
titleColor: '#fff',
},
{
imageSrc:
'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*BeDBTY9UnXIAAAAAAAAAAAAADrJ8AQ/original',
titleColor: '#fff',
},
{
imageSrc:
'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*Q63XTbk8YaMAAAAAAAAAAAAADrJ8AQ/original',
titleColor: '#fff',
},
];
export interface MobileCarouselProps {
id?: string;
title?: React.ReactNode;
description?: React.ReactNode;
}
const MobileCarousel: React.FC<MobileCarouselProps> = (props) => {
const { styles } = useStyle();
const { id, title, description } = props;
const token = useTheme();
const [currentSlider, setCurrentSlider] = useState<number>(0);
return (
<div className={styles.container}>
<div className={styles.title}>
<Typography.Title
id={id}
level={1}
style={{
fontWeight: 900,
color: mobileImageConfigList[currentSlider].titleColor,
fontSize: token.fontSizeHeading2,
}}
>
{title}
</Typography.Title>
<Typography.Paragraph
style={{
marginBottom: token.marginXXL,
color: mobileImageConfigList[currentSlider].titleColor,
}}
>
{description}
</Typography.Paragraph>
</div>
<Carousel className={styles.carousel} afterChange={setCurrentSlider}>
{mobileImageConfigList.map((item, index) => (
<div key={index}>
<img draggable={false} src={item.imageSrc} className={styles.img} alt="carousel" />
</div>
))}
</Carousel>
</div>
);
};
export default MobileCarousel;

View File

@@ -1,32 +0,0 @@
import React from 'react';
import { Flex, InputNumber, Slider } from 'antd';
export interface RadiusPickerProps {
id?: string;
value?: number;
onChange?: (value: number | null) => void;
}
const RadiusPicker: React.FC<RadiusPickerProps> = ({ id, value, onChange }) => (
<Flex gap="large">
<InputNumber
value={value}
onChange={onChange}
style={{ width: 120 }}
min={0}
formatter={(val) => `${val}px`}
parser={(str) => str?.replace('px', '') as unknown as number}
id={id}
/>
<Slider
tooltip={{ open: false }}
style={{ width: 128 }}
min={0}
value={value}
max={20}
onChange={onChange}
/>
</Flex>
);
export default RadiusPicker;

View File

@@ -1,102 +0,0 @@
import * as React from 'react';
import { Flex } from 'antd';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
import useLocale from '../../../../hooks/useLocale';
export const THEMES = {
default: 'https://gw.alipayobjects.com/zos/bmw-prod/ae669a89-0c65-46db-b14b-72d1c7dd46d6.svg',
dark: 'https://gw.alipayobjects.com/zos/bmw-prod/0f93c777-5320-446b-9bb7-4d4b499f346d.svg',
lark: 'https://gw.alipayobjects.com/zos/bmw-prod/3e899b2b-4eb4-4771-a7fc-14c7ff078aed.svg',
comic: 'https://gw.alipayobjects.com/zos/bmw-prod/ed9b04e8-9b8d-4945-8f8a-c8fc025e846f.svg',
v4: 'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*bOiWT4-34jkAAAAAAAAAAAAADrJ8AQ/original',
} as const;
export type THEME = keyof typeof THEMES;
const locales = {
cn: {
default: '默认',
dark: '暗黑',
lark: '知识协作',
comic: '桃花缘',
v4: 'V4 主题',
},
en: {
default: 'Default',
dark: 'Dark',
lark: 'Document',
comic: 'Blossom',
v4: 'V4 Theme',
},
};
const styles = createStaticStyles(({ cssVar, css }) => ({
themeCard: css`
border-radius: ${cssVar.borderRadius};
cursor: pointer;
transition: all ${cssVar.motionDurationSlow};
overflow: hidden;
display: inline-block;
& > input[type='radio'] {
width: 0;
height: 0;
opacity: 0;
position: absolute;
}
img {
vertical-align: top;
box-shadow:
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
}
&:focus-within,
&:hover {
transform: scale(1.04);
}
`,
themeCardActive: css`
box-shadow:
0 0 0 1px ${cssVar.colorBgContainer},
0 0 0 calc(${cssVar.controlOutlineWidth} * 2 + 1px) ${cssVar.colorPrimary};
&,
&:hover:not(:focus-within) {
transform: scale(1);
}
`,
}));
export interface ThemePickerProps {
id?: string;
value?: string;
onChange?: (value: string) => void;
}
const ThemePicker: React.FC<ThemePickerProps> = (props) => {
const { value, id, onChange } = props;
const [locale] = useLocale(locales);
return (
<Flex gap="large" wrap>
{(Object.keys(THEMES) as (keyof typeof THEMES)[]).map<React.ReactNode>((theme, index) => (
<Flex vertical gap="small" justify="center" align="center" key={theme}>
<label
onClick={() => onChange?.(theme)}
className={clsx(styles.themeCard, { [styles.themeCardActive]: value === theme })}
>
<input type="radio" name="theme" id={index === 0 ? id : undefined} />
<img draggable={false} src={THEMES[theme]} alt={theme} />
</label>
<span>{locale[theme]}</span>
</Flex>
))}
</Flex>
);
};
export default ThemePicker;

View File

@@ -1,91 +0,0 @@
import type { ColorPickerProps, GetProp } from 'antd';
import { generateColor } from 'antd/es/color-picker/util';
type Color = GetProp<ColorPickerProps, 'value'>;
export const DEFAULT_COLOR = '#1677FF';
export const PINK_COLOR = '#ED4192';
export const COLOR_IMAGES = [
{
color: DEFAULT_COLOR,
// url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*QEAoSL8uVi4AAAAAAAAAAAAAARQnAQ',
url: null,
webp: null,
},
{
color: '#5A54F9',
url: 'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*H8nRT7_q0EwAAAAAAAAAAAAADrJ8AQ/original',
webp: 'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*H8nRT7_q0EwAAAAAAAAAAAAADrJ8AQ/fmt.webp',
},
{
color: '#9E339F',
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*FMluR4vJhaQAAAAAAAAAAAAAARQnAQ',
webp: 'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*CMCMTKV51tIAAAAAAAAAAAAADrJ8AQ/fmt.webp',
},
{
color: PINK_COLOR,
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*DGZXS4YOGp0AAAAAAAAAAAAAARQnAQ',
webp: 'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*s5OdR6wZZIkAAAAAAAAAAAAADrJ8AQ/fmt.webp',
},
{
color: '#E0282E',
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*w6xcR7MriwEAAAAAAAAAAAAAARQnAQ',
webp: 'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*HE_4Qp_XfQQAAAAAAAAAAAAADrJ8AQ/fmt.webp',
},
{
color: '#F4801A',
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*VWFOTbEyU9wAAAAAAAAAAAAAARQnAQ',
webp: 'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*xTG2QbottAQAAAAAAAAAAAAADrJ8AQ/fmt.webp',
},
{
color: '#F2BD27',
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*1yydQLzw5nYAAAAAAAAAAAAAARQnAQ',
webp: 'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*hbPfSbF-xPIAAAAAAAAAAAAADrJ8AQ/fmt.webp',
},
{
color: '#00B96B',
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*XpGeRoZKGycAAAAAAAAAAAAAARQnAQ',
webp: 'https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*iM6CQ496P3oAAAAAAAAAAAAADrJ8AQ/fmt.webp',
},
] as const;
export const PRESET_COLORS = COLOR_IMAGES.map(({ color }) => color);
const DISTANCE = 33;
export function getClosetColor(colorPrimary?: Color | string | null) {
if (!colorPrimary) {
return null;
}
const colorPrimaryRGB = generateColor(colorPrimary).toRgb();
const distance = COLOR_IMAGES.map(({ color }) => {
const colorObj = generateColor(color).toRgb();
const dist = Math.sqrt(
(colorObj.r - colorPrimaryRGB.r) ** 2 +
(colorObj.g - colorPrimaryRGB.g) ** 2 +
(colorObj.b - colorPrimaryRGB.b) ** 2,
);
return { color, dist };
});
const firstMatch = distance.sort((a, b) => a.dist - b.dist)[0];
return firstMatch.dist <= DISTANCE ? firstMatch.color : null;
}
export function getAvatarURL(color?: string | null) {
const closestColor = getClosetColor(color);
if (!closestColor) {
return null;
}
return (
COLOR_IMAGES.find((obj) => obj.color === closestColor)?.url ||
'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*CLp0Qqc11AkAAAAAAAAAAAAAARQnAQ'
);
}

View File

@@ -1,619 +0,0 @@
import * as React from 'react';
import { defaultAlgorithm, defaultTheme } from '@ant-design/compatible';
import { FastColor } from '@ant-design/fast-color';
import {
BellOutlined,
FolderOutlined,
HomeOutlined,
QuestionCircleOutlined,
} from '@ant-design/icons';
import type { ColorPickerProps, GetProp, MenuProps, ThemeConfig } from 'antd';
import {
Breadcrumb,
Card,
ConfigProvider,
Flex,
Form,
Layout,
Menu,
Radio,
theme,
Typography,
} from 'antd';
import { createStaticStyles } from 'antd-style';
import { generateColor } from 'antd/es/color-picker/util';
import { clsx } from 'clsx';
import { useLocation } from 'dumi';
import useLocale from '../../../../hooks/useLocale';
import LinkButton from '../../../../theme/common/LinkButton';
import SiteContext from '../../../../theme/slots/SiteContext';
import { getLocalizedPathname } from '../../../../theme/utils';
import Group from '../Group';
import { getCarouselStyle } from '../util';
import { DarkContext } from './../../../../hooks/useDark';
import BackgroundImage from './BackgroundImage';
import ColorPicker from './ColorPicker';
import { DEFAULT_COLOR, getAvatarURL, getClosetColor, PINK_COLOR } from './colorUtil';
import MobileCarousel from './MobileCarousel';
import RadiusPicker from './RadiusPicker';
import type { THEME } from './ThemePicker';
import ThemePicker from './ThemePicker';
type Color = Extract<GetProp<ColorPickerProps, 'value'>, string | { cleared: any }>;
const { Header, Content, Sider } = Layout;
const TokenChecker: React.FC = () => {
const token = theme.useToken();
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console
console.log('Demo Token:', token);
}
return null;
};
// ============================= Theme =============================
const locales = {
cn: {
themeTitle: '定制主题,随心所欲',
themeDesc: 'Ant Design 开放更多样式算法,让你定制主题更简单',
customizeTheme: '定制主题',
myTheme: '我的主题',
titlePrimaryColor: '主色',
titleBorderRadius: '圆角',
titleCompact: '宽松度',
default: '默认',
compact: '紧凑',
titleTheme: '主题',
light: '亮色',
dark: '暗黑',
toDef: '深度定制',
toUse: '去使用',
},
en: {
themeTitle: 'Flexible theme customization',
themeDesc: 'Ant Design enable extendable algorithm, make custom theme easier',
customizeTheme: 'Customize Theme',
myTheme: 'My Theme',
titlePrimaryColor: 'Primary Color',
titleBorderRadius: 'Border Radius',
titleCompact: 'Compact',
titleTheme: 'Theme',
default: 'Default',
compact: 'Compact',
light: 'Light',
dark: 'Dark',
toDef: 'More',
toUse: 'Apply',
},
};
// ============================= Style =============================
const styles = createStaticStyles(({ cssVar, css, cx }) => {
const { carousel } = getCarouselStyle();
const demo = css`
overflow: hidden;
background: rgba(240, 242, 245, 0.25);
backdrop-filter: blur(50px);
box-shadow: 0 2px 10px 2px rgba(0, 0, 0, 0.1);
transition: all ${cssVar.motionDurationSlow};
`;
return {
demo,
otherDemo: css`
&.${cx(demo)} {
backdrop-filter: blur(10px);
background: rgba(247, 247, 247, 0.5);
}
`,
darkDemo: css`
&.${cx(demo)} {
background: #000;
}
`,
larkDemo: css`
&.${cx(demo)} {
// background: #f7f7f7;
background: rgba(240, 242, 245, 0.65);
}
`,
comicDemo: css`
&.${cx(demo)} {
// background: #ffe4e6;
background: rgba(240, 242, 245, 0.65);
}
`,
menu: css`
margin-inline-start: auto;
`,
header: css`
display: flex;
align-items: center;
border-bottom: 1px solid ${cssVar.colorSplit};
padding-inline: ${cssVar.paddingLG} !important;
height: calc(${cssVar.controlHeightLG} * 1.2);
line-height: calc(${cssVar.controlHeightLG} * 1.2);
`,
headerDark: css`
border-bottom-color: rgba(255, 255, 255, 0.1);
`,
avatar: css`
width: ${cssVar.controlHeight};
height: ${cssVar.controlHeight};
border-radius: 100%;
background: rgba(240, 240, 240, 0.75);
background-size: cover;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
`,
avatarDark: css`
background: rgba(200, 200, 200, 0.3);
`,
logo: css`
display: flex;
align-items: center;
column-gap: ${cssVar.padding};
h1 {
font-weight: 400;
font-size: ${cssVar.fontSizeLG};
line-height: 1.5;
}
`,
logoImg: css`
width: 30px;
height: 30px;
overflow: hidden;
img {
width: 30px;
height: 30px;
vertical-align: top;
}
`,
transBg: css`
background: transparent !important;
`,
form: css`
width: 100%;
margin: 0 auto;
`,
pos: css`
position: absolute;
`,
leftTopImagePos: css`
inset-inline-start: 0;
top: -100px;
height: 500px;
`,
rightBottomPos: css`
inset-inline-end: 0;
bottom: -100px;
height: 287px;
`,
leftTopImage: css`
inset-inline-start: 50%;
transform: translate3d(-900px, 0, 0);
top: -100px;
height: 500px;
`,
rightBottomImage: css`
inset-inline-end: 50%;
transform: translate3d(750px, 0, 0);
bottom: -100px;
height: 287px;
`,
motion: css`
transition: all ${cssVar.motionDurationSlow};
`,
op1: css`
opacity: 1;
`,
op0: css`
opacity: 0;
`,
carousel,
};
});
// ========================== Menu Config ==========================
const subMenuItems = [
{
key: `Design Values`,
label: `Design Values`,
},
{
key: `Global Styles`,
label: `Global Styles`,
},
{
key: `Themes`,
label: `Themes`,
},
{
key: `DesignPatterns`,
label: `Design Patterns`,
},
];
const sideMenuItems: MenuProps['items'] = [
{
key: `Design`,
label: `Design`,
icon: <FolderOutlined />,
children: subMenuItems,
},
{
key: `Development`,
label: `Development`,
icon: <FolderOutlined />,
},
];
// ============================= Theme =============================
function getTitleColor(colorPrimary: Color, isLight?: boolean) {
if (!isLight) {
return '#FFF';
}
const color = generateColor(colorPrimary);
const closestColor = getClosetColor(colorPrimary);
switch (closestColor) {
case DEFAULT_COLOR:
case PINK_COLOR:
case '#F2BD27':
return undefined;
case '#5A54F9':
case '#E0282E':
return '#FFF';
default:
return color.toHsb().b < 0.7 ? '#FFF' : undefined;
}
}
interface ThemeData {
themeType: THEME;
colorPrimary: Color;
borderRadius: number;
compact: 'default' | 'compact';
}
const ThemeDefault: ThemeData = {
themeType: 'default',
colorPrimary: '#1677FF',
borderRadius: 6,
compact: 'default',
};
const ThemesInfo: Record<THEME, Partial<ThemeData>> = {
default: {},
dark: {
borderRadius: 2,
},
lark: {
colorPrimary: '#00B96B',
borderRadius: 4,
},
comic: {
colorPrimary: PINK_COLOR,
borderRadius: 16,
},
v4: {
...defaultTheme.token,
},
};
const normalize = (value: number) => value / 255;
function rgbToColorMatrix(color: string) {
const rgb = new FastColor(color).toRgb();
const { r, g, b } = rgb;
const invertValue = normalize(r) * 100;
const sepiaValue = 100;
const saturateValue = Math.max(normalize(r), normalize(g), normalize(b)) * 10000;
const hueRotateValue =
((Math.atan2(
Math.sqrt(3) * (normalize(g) - normalize(b)),
2 * normalize(r) - normalize(g) - normalize(b),
) *
180) /
Math.PI +
360) %
360;
return `invert(${invertValue}%) sepia(${sepiaValue}%) saturate(${saturateValue}%) hue-rotate(${hueRotateValue}deg)`;
}
const Theme: React.FC = () => {
const [locale, lang] = useLocale(locales);
const isZhCN = lang === 'cn';
const { search } = useLocation();
const [themeData, setThemeData] = React.useState<ThemeData>(ThemeDefault);
const onThemeChange = (_: Partial<ThemeData>, nextThemeData: ThemeData) => {
React.startTransition(() => {
setThemeData({ ...ThemesInfo[nextThemeData.themeType], ...nextThemeData });
});
};
const { compact, themeType, colorPrimary, ...themeToken } = themeData;
const isLight = themeType !== 'dark';
const [form] = Form.useForm();
const { isMobile } = React.use(SiteContext);
const colorPrimaryValue = React.useMemo(
() => (typeof colorPrimary === 'string' ? colorPrimary : colorPrimary.toHexString()),
[colorPrimary],
);
// const algorithmFn = isLight ? theme.defaultAlgorithm : theme.darkAlgorithm;
const algorithmFn = React.useMemo(() => {
const algorithms = [isLight ? theme.defaultAlgorithm : theme.darkAlgorithm];
if (compact === 'compact') {
algorithms.push(theme.compactAlgorithm);
}
if (themeType === 'v4') {
algorithms.push(defaultAlgorithm);
}
return algorithms;
}, [isLight, compact, themeType]);
// ================================ Themes ================================
React.useEffect(() => {
const mergedData = {
...ThemeDefault,
themeType,
...ThemesInfo[themeType],
};
setThemeData(mergedData);
form.setFieldsValue(mergedData);
}, [form, themeType]);
const isDark = React.use(DarkContext);
React.useEffect(() => {
onThemeChange({}, { ...themeData, themeType: isDark ? 'dark' : 'default' });
}, [isDark]);
// ================================ Tokens ================================
const closestColor = getClosetColor(colorPrimaryValue);
const [backgroundColor, avatarColor] = React.useMemo(() => {
let bgColor = 'transparent';
const mapToken = theme.defaultAlgorithm({
...theme.defaultConfig.token,
colorPrimary: colorPrimaryValue,
});
if (themeType === 'dark') {
bgColor = '#393F4A';
} else if (closestColor === DEFAULT_COLOR) {
bgColor = '#F5F8FF';
} else {
bgColor = mapToken.colorPrimaryHover;
}
return [bgColor, mapToken.colorPrimaryBgHover];
}, [themeType, closestColor, colorPrimaryValue]);
const logoColor = React.useMemo(() => {
const hsb = generateColor(colorPrimaryValue).toHsb();
hsb.b = Math.min(hsb.b, 0.7);
return generateColor(hsb).toHexString();
}, [colorPrimaryValue]);
const memoTheme = React.useMemo<ThemeConfig>(
() => ({
token: { ...themeToken, colorPrimary: colorPrimaryValue },
algorithm: algorithmFn,
components: {
Layout: isLight ? { headerBg: 'transparent', bodyBg: 'transparent' } : {},
Menu: isLight
? { itemBg: 'transparent', subMenuItemBg: 'transparent', activeBarBorderWidth: 0 }
: {},
...(themeType === 'v4' ? defaultTheme.components : {}),
},
}),
[themeToken, colorPrimaryValue, algorithmFn, isLight, themeType],
);
// ================================ Render ================================
const themeNode = (
<ConfigProvider theme={memoTheme}>
<TokenChecker />
<div
className={clsx(styles.demo, {
[styles.otherDemo]: isLight && closestColor !== DEFAULT_COLOR && styles.otherDemo,
[styles.darkDemo]: !isLight,
})}
style={{ borderRadius: themeData.borderRadius }}
>
<Layout className={styles.transBg}>
<Header className={clsx(styles.header, styles.transBg, !isLight && styles.headerDark)}>
{/* Logo */}
<div className={styles.logo}>
<div className={styles.logoImg}>
<img
draggable={false}
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
style={{
filter:
closestColor === DEFAULT_COLOR ? undefined : rgbToColorMatrix(logoColor),
}}
alt="antd logo"
/>
</div>
<h1>Ant Design</h1>
</div>
<Flex className={styles.menu} gap="middle">
<BellOutlined />
<QuestionCircleOutlined />
<div
className={clsx(styles.avatar, { [styles.avatarDark]: themeType === 'dark' })}
style={{
backgroundColor: avatarColor,
backgroundImage: `url(${getAvatarURL(closestColor)})`,
}}
/>
</Flex>
</Header>
<Layout className={styles.transBg} hasSider>
<Sider className={clsx(styles.transBg)} width={200}>
<Menu
mode="inline"
className={clsx(styles.transBg)}
selectedKeys={['Themes']}
openKeys={['Design']}
style={{ height: '100%', borderInlineEnd: 0 }}
items={sideMenuItems}
expandIcon={false}
/>
</Sider>
<Layout className={styles.transBg} style={{ padding: '0 24px 24px' }}>
<Breadcrumb
style={{ margin: '16px 0' }}
items={[
{ title: <HomeOutlined /> },
{ title: 'Design', menu: { items: subMenuItems } },
{ title: 'Themes' },
]}
/>
<Content>
<Typography.Title level={2}>{locale.customizeTheme}</Typography.Title>
<Card
title={locale.myTheme}
extra={
<Flex gap="small">
<LinkButton to={getLocalizedPathname('/theme-editor', isZhCN, search)}>
{locale.toDef}
</LinkButton>
<LinkButton
type="primary"
to={getLocalizedPathname('/docs/react/customize-theme', isZhCN, search)}
>
{locale.toUse}
</LinkButton>
</Flex>
}
>
<Form
form={form}
initialValues={themeData}
onValuesChange={onThemeChange}
labelCol={{ span: 3 }}
wrapperCol={{ span: 21 }}
className={styles.form}
>
<Form.Item label={locale.titleTheme} name="themeType">
<ThemePicker />
</Form.Item>
<Form.Item label={locale.titlePrimaryColor} name="colorPrimary">
<ColorPicker />
</Form.Item>
<Form.Item label={locale.titleBorderRadius} name="borderRadius">
<RadiusPicker />
</Form.Item>
<Form.Item label={locale.titleCompact} name="compact" htmlFor="compact_default">
<Radio.Group
options={[
{ label: locale.default, value: 'default', id: 'compact_default' },
{ label: locale.compact, value: 'compact' },
]}
/>
</Form.Item>
</Form>
</Card>
</Content>
</Layout>
</Layout>
</Layout>
</div>
</ConfigProvider>
);
return isMobile ? (
<MobileCarousel title={locale.themeTitle} description={locale.themeDesc} id="flexible" />
) : (
<Group
title={locale.themeTitle}
titleColor={getTitleColor(colorPrimaryValue, isLight)}
description={locale.themeDesc}
id="flexible"
background={backgroundColor}
decoration={
// =========================== Theme Background ===========================
<>
{/* >>>>>> Default <<<<<< */}
<div
className={clsx(
styles.motion,
isLight && closestColor === DEFAULT_COLOR ? styles.op1 : styles.op0,
)}
>
{/* Image Left Top */}
<img
draggable={false}
className={clsx(styles.pos, styles.leftTopImage)}
src="https://gw.alipayobjects.com/zos/bmw-prod/bd71b0c6-f93a-4e52-9c8a-f01a9b8fe22b.svg"
alt="image-left-top"
/>
{/* Image Right Bottom */}
<img
draggable={false}
className={clsx(styles.pos, styles.rightBottomImage)}
src="https://gw.alipayobjects.com/zos/bmw-prod/84ad805a-74cb-4916-b7ba-9cdc2bdec23a.svg"
alt="image-right-bottom"
/>
</div>
{/* >>>>>> Dark <<<<<< */}
<div className={clsx(styles.motion, !isLight || !closestColor ? styles.op1 : styles.op0)}>
{/* Image Left Top */}
<img
draggable={false}
className={clsx(styles.pos, styles.leftTopImagePos)}
src="https://gw.alipayobjects.com/zos/bmw-prod/a213184a-f212-4afb-beec-1e8b36bb4b8a.svg"
alt="image-left-top"
/>
{/* Image Right Bottom */}
<img
draggable={false}
className={clsx(styles.pos, styles.rightBottomPos)}
src="https://gw.alipayobjects.com/zos/bmw-prod/bb74a2fb-bff1-4d0d-8c2d-2ade0cd9bb0d.svg"
alt="image-right-bottom"
/>
</div>
{/* >>>>>> Background Image <<<<<< */}
<BackgroundImage isLight={isLight} colorPrimary={colorPrimaryValue} />
</>
}
>
{themeNode}
</Group>
);
};
export default Theme;

View File

@@ -1,144 +0,0 @@
import { css } from 'antd-style';
import useSWR from 'swr';
export interface Author {
avatar: string;
href: string;
type: 'design' | 'develop';
name: string;
}
export interface Article {
title: string;
href: string;
date: string;
type: 'design' | 'develop';
author: Author['name'];
}
export interface Recommendation {
title?: string;
img?: string;
href?: string;
popularize?: boolean;
description?: string;
}
type SourceType = 'zhihu' | 'yuque';
export interface Extra {
title: string;
description: string;
date: string;
img: string;
source: SourceType;
href: string;
}
export interface Icon {
name: string;
href: string;
}
export type Articles = {
cn: Article[];
en: Article[];
};
export type Authors = Author[];
export type Recommendations = {
cn: Recommendation[];
en: Recommendation[];
};
export type Extras = {
cn: Extra[];
en: Extra[];
};
export type Icons = Icon[];
export type HeadingBanner = {
[key in 'cn' | 'en']: {
title?: string;
href?: string;
};
};
export type SiteData = {
headingBanner: HeadingBanner;
articles: Articles;
authors: Authors;
recommendations: Recommendations;
extras: Extras;
icons: Icons;
};
export function preLoad(list: string[]) {
if (typeof window !== 'undefined') {
// 图处预加载;
const div = document.createElement('div');
div.style.display = 'none';
document.body.appendChild(div);
list.forEach((src) => {
const img = new Image();
img.src = src;
div.appendChild(img);
});
}
}
/**
* Banner 硬编码,以防止页面闪烁问题
* 返回 null 表示不显示
*/
export const getBannerData = (): null | {
title: string;
href: string;
} => {
// return {
// title: 'See Conf 2025 震撼来袭 - 探索 AI 时代的用户体验与工程实践',
// href: 'https://seeconf.antfin.com/',
// };
return null;
};
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()),
{
suspense: false,
// revalidateOnMount: false,
revalidateIfStale: false,
revalidateOnFocus: false,
},
);
return { data, error, isLoading };
};
export const getCarouselStyle = () => ({
carousel: css`
.slick-dots.slick-dots-bottom {
bottom: -22px;
li {
width: 6px;
height: 6px;
background: #e1eeff;
border-radius: 50%;
button {
height: 6px;
background: #e1eeff;
border-radius: 50%;
}
&.slick-active {
background: #4b9cff;
button {
background: #4b9cff;
}
}
}
}
`,
});

View File

@@ -1,95 +0,0 @@
import React, { Suspense } from 'react';
import { ConfigProvider, theme } from 'antd';
import { createStaticStyles } from 'antd-style';
import useLocale from '../../hooks/useLocale';
import { DarkContext } from './../../hooks/useDark';
import BannerRecommends from './components/BannerRecommends';
import Group from './components/Group';
import PreviewBanner from './components/PreviewBanner';
const ComponentsList = React.lazy(() => import('./components/ComponentsList'));
const DesignFramework = React.lazy(() => import('./components/DesignFramework'));
const Theme = React.lazy(() => import('./components/Theme'));
const classNames = createStaticStyles(({ css }) => ({
image: css`
position: absolute;
inset-inline-start: 0;
top: -50px;
height: 160px;
`,
}));
const locales = {
cn: {
assetsTitle: '组件丰富,选用自如',
assetsDesc: '大量实用组件满足你的需求,灵活定制与拓展',
designTitle: '设计语言与研发框架',
designDesc: '配套生态,让你快速搭建网站应用',
},
en: {
assetsTitle: 'Rich components',
assetsDesc: 'Practical components to meet your needs, flexible customization and expansion',
designTitle: 'Design and framework',
designDesc: 'Supporting ecology, allowing you to quickly build website applications',
},
};
const Homepage: React.FC = () => {
const [locale] = useLocale(locales);
const { token } = theme.useToken();
const isDark = React.use(DarkContext);
return (
<section>
<PreviewBanner>
<BannerRecommends />
</PreviewBanner>
<div>
{/* 定制主题 */}
<ConfigProvider theme={{ algorithm: theme.defaultAlgorithm }}>
<Suspense fallback={null}>
<Theme />
</Suspense>
</ConfigProvider>
{/* 组件列表 */}
<Group
background={token.colorBgElevated}
collapse
title={locale.assetsTitle}
description={locale.assetsDesc}
id="design"
>
<Suspense fallback={null}>
<ComponentsList />
</Suspense>
</Group>
{/* 设计语言 */}
<Group
title={locale.designTitle}
description={locale.designDesc}
background={isDark ? '#393F4A' : '#F5F8FF'}
decoration={
<img
draggable={false}
className={classNames.image}
src="https://gw.alipayobjects.com/zos/bmw-prod/ba37a413-28e6-4be4-b1c5-01be1a0ebb1c.svg"
alt="bg"
/>
}
>
<Suspense fallback={null}>
<DesignFramework />
</Suspense>
</Group>
</div>
</section>
);
};
export default Homepage;

View File

@@ -1,3 +0,0 @@
import ThemeEditor from '../theme-editor';
export default ThemeEditor;

View File

@@ -1,97 +0,0 @@
import React, { Suspense } from 'react';
import { App, Button, ConfigProvider, Skeleton, version } from 'antd';
import { createStaticStyles } from 'antd-style';
import { enUS, zhCN } from 'antd-token-previewer';
import type { ThemeConfig } from 'antd/es/config-provider/context';
import { Helmet } from 'dumi';
import useLocale from '../../hooks/useLocale';
const ThemeEditor = React.lazy(() => import('antd-token-previewer/lib/ThemeEditor'));
const classNames = createStaticStyles(({ css }) => ({
editor: css`
svg,
img {
display: inline;
}
`,
}));
const locales = {
cn: {
title: '主题编辑器',
save: '保存',
edit: '编辑',
export: '导出',
editModelTitle: '编辑主题配置',
editJsonContentTypeError: '主题 JSON 格式错误',
editSuccessfully: '编辑成功',
saveSuccessfully: '保存成功',
initialEditor: '正在初始化编辑器...',
},
en: {
title: 'Theme Editor',
save: 'Save',
edit: 'Edit',
export: 'Export',
editModelTitle: 'edit Theme Config',
editJsonContentTypeError: 'The theme of the JSON format is incorrect',
editSuccessfully: 'Edited successfully',
saveSuccessfully: 'Saved successfully',
initialEditor: 'Initializing Editor...',
},
};
const [antdMajor] = version.split('.');
const ANT_DESIGN_V5_THEME_EDITOR_THEME = `ant-design-v${antdMajor}-theme-editor-theme`;
const CustomTheme: React.FC = () => {
const { message } = App.useApp();
const [locale, lang] = useLocale(locales);
const [theme, setTheme] = React.useState<ThemeConfig>(() => {
try {
const storedConfig = localStorage.getItem(ANT_DESIGN_V5_THEME_EDITOR_THEME);
return storedConfig ? JSON.parse(storedConfig) : {};
} catch {
return {};
}
});
const handleSave = () => {
localStorage.setItem(ANT_DESIGN_V5_THEME_EDITOR_THEME, JSON.stringify(theme));
message.success(locale.saveSuccessfully);
};
return (
<div>
<Helmet>
<title>{`${locale.title} - Ant Design`}</title>
<meta property="og:title" content={`${locale.title} - Ant Design`} />
</Helmet>
<Suspense fallback={<Skeleton style={{ margin: 24 }} />}>
<ConfigProvider theme={{ inherit: false }}>
<ThemeEditor
advanced
hideAdvancedSwitcher
theme={{ name: 'Custom Theme', key: 'test', config: theme }}
style={{ height: 'calc(100vh - 64px)' }}
className={classNames.editor}
onThemeChange={(newTheme) => {
setTheme(newTheme.config);
}}
locale={lang === 'cn' ? zhCN : enUS}
actions={
<Button type="primary" onClick={handleSave}>
{locale.save}
</Button>
}
/>
</ConfigProvider>
</Suspense>
</div>
);
};
export default CustomTheme;

View File

@@ -1,106 +0,0 @@
import assert from 'assert';
import type { HastRoot, UnifiedTransformer } from 'dumi';
import { unistUtilVisit } from 'dumi';
/**
* plugin for modify hast tree when docs compiling
*/
function rehypeAntd(): UnifiedTransformer<HastRoot> {
return (tree, vFile) => {
const { filename } = vFile.data.frontmatter as any;
unistUtilVisit.visit(tree, 'element', (node, i, parent) => {
if (node.tagName === 'DumiDemoGrid') {
// replace DumiDemoGrid to DemoWrapper, to implement demo toolbar
node.tagName = 'DemoWrapper';
} else if (node.tagName === 'ResourceCards') {
const propNames = ['title', 'cover', 'description', 'src', 'official'];
const contentNode = node.children[0];
assert(
contentNode.type === 'text',
`ResourceCards content must be plain text!\nat ${filename}`,
);
// clear children
node.children = [];
// generate JSX props
(node as any).JSXAttributes = [
{
type: 'JSXAttribute',
name: 'resources',
value: JSON.stringify(
contentNode.value
.trim()
.split('\n')
.reduce<any>((acc, cur) => {
// match text from ` - 桌面组件 Sketch 模板包`
const [, isProp, val] = cur.match(/(\s+)?-\s(.+)/)!;
if (!isProp) {
// create items when match title
acc.push({ [propNames[0]]: val });
} else {
// add props when match others
const prev = acc[acc.length - 1];
prev[propNames[Object.keys(prev).length]] = val;
}
return acc;
}, []),
),
},
];
} else if (
node.type === 'element' &&
node.tagName === 'Table' &&
/^components/.test(filename)
) {
if (!node.properties) {
return;
}
node.properties.className ??= [];
(node.properties.className as string[]).push('component-api-table');
} else if (node.type === 'element' && (node.tagName === 'Link' || node.tagName === 'a')) {
const { tagName } = node;
node.properties!.sourceType = tagName;
node.tagName = 'LocaleLink';
} else if (node.type === 'element' && node.tagName === 'video') {
node.tagName = 'VideoPlayer';
} else if (node.tagName === 'SourceCode') {
const { lang } = node.properties!;
if (typeof lang === 'string' && lang.startsWith('sandpack')) {
const code = (node.children[0] as any).value as string;
const configRegx = /^const sandpackConfig = ([\s\S]*?});/;
const [configString] = code.match(configRegx) || [];
/* biome-ignore lint/security/noGlobalEval: used in documentation */ /* eslint-disable-next-line no-eval */
const config = configString && eval(`(${configString.replace(configRegx, '$1')})`);
Object.keys(config || {}).forEach((key) => {
if (typeof config[key] === 'object') {
config[key] = JSON.stringify(config[key]);
}
});
parent!.children.splice(i!, 1, {
type: 'element',
tagName: 'Sandpack',
properties: {
...config,
},
children: [
{
type: 'text',
value: code.replace(configRegx, '').trim(),
},
],
});
}
}
});
};
}
export default rehypeAntd;

View File

@@ -1,156 +0,0 @@
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;
// workaround to import pure esm module
(async () => {
({ 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> {
return (tree, vFile) => {
const { filename } = vFile.data.frontmatter as any;
// 只处理 changelog 文件
if (!/^changelog\.\S+\.md$/i.test(filename)) {
return;
}
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 && checkLogSegment(node)) {
nodesToWrap.push({ parent, startIdx: idx! });
}
});
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`,
children: [set(heading, 'properties.className', 'changelog-version')],
};
const dateWrap = {
type: 'element',
tagName: `${COMPONENT_NAME}.Date`,
children: [set(date, 'properties.className', 'changelog-date')],
};
const detailWrap = {
type: 'element',
tagName: `${COMPONENT_NAME}.Details`,
properties: {
className: 'changelog-details',
},
children: details,
};
const wrapper = {
type: 'element',
tagName: COMPONENT_NAME,
properties: {
[WRAPPER_FLAG]: true,
},
JSXAttributes: [
{
type: 'JSXAttribute',
name: 'version',
value: JSON.stringify(version),
},
{
type: 'JSXAttribute',
name: 'date',
value: JSON.stringify(dateStr),
},
],
children: [headingWrap, dateWrap, detailWrap],
};
parent.children.splice(startIdx, endIdx - startIdx, wrapper);
}
};
}
export default rehypeChangelog;

View File

@@ -1,89 +0,0 @@
import { unistUtilVisit } from 'dumi';
import type { UnifiedTransformer } from 'dumi';
let toSlug: typeof import('github-slugger').slug;
// workaround to import pure esm module
(async () => {
({ slug: toSlug } = await import('github-slugger'));
})();
const isNil = (value: any) => value == null;
const toArr = <T>(value: T | T[]) => {
if (isNil(value)) {
return [];
}
return Array.isArray(value) ? value : [value];
};
const patch = (context: Record<string, any>, key: string, value: any) => {
if (!context[key]) {
context[key] = value;
}
return context[key];
};
interface Options {
level?: number;
}
const remarkAnchor = (opt: Options = {}): UnifiedTransformer<any> => {
// https://regex101.com/r/WDjkK0/1
const RE = /\s*\{#([^}]+)\}$/;
const realOpt = {
level: [1, 2, 3, 4, 5, 6],
...opt,
};
return function transformer(tree) {
const ids = new Set();
unistUtilVisit.visit(tree, 'heading', (node) => {
if (!toArr(realOpt.level).includes(node.depth)) {
return unistUtilVisit.CONTINUE;
}
const lastIndex = node.children.length - 1;
const lastChild = node.children[lastIndex];
if (lastChild?.type === 'text') {
const text = lastChild.value;
const match = text.match(RE);
if (match) {
const id = match[1];
if (id !== toSlug(id)) {
throw new Error(
`Expected header ID to be a valid slug. You specified: {#${id}}. Replace it with: {#${toSlug(id)}}`,
);
}
node.data ??= {};
node.data.hProperties = { ...node.data.hProperties, id };
lastChild.value = text.replace(RE, '');
if (lastChild.value === '') {
node.children.pop();
}
if (ids.has(id)) {
throw new Error(`Cannot have a duplicate header with id "${id}" on the page.
Rename the section or give it an explicit unique ID. For example: #### Arguments {#setstate-arguments}`);
}
ids.add(id);
const data = patch(node, 'data', {});
patch(data, 'id', id);
patch(data, 'htmlAttributes', {});
patch(data, 'hProperties', {});
patch(data.htmlAttributes, 'id', id);
patch(data.hProperties, 'id', id);
}
}
});
};
};
export default remarkAnchor;

View File

@@ -1,15 +0,0 @@
import { unistUtilVisit } from 'dumi';
import type { UnifiedTransformer } from 'dumi';
function remarkMeta(): UnifiedTransformer<any> {
return (tree, vFile) => {
// read frontmatter
unistUtilVisit.visit(tree, 'yaml', (node) => {
if (!/(^|[\n\r])description:/.test(node.value)) {
(vFile.data.frontmatter as any).__autoDescription = true;
}
});
};
}
export default remarkMeta;

View File

@@ -1,14 +0,0 @@
/* eslint-disable */
// https://clarity.microsoft.com
(function (c, l, a, r, i, t, y) {
c[a] =
c[a] ||
function () {
(c[a].q = c[a].q || []).push(arguments);
};
t = l.createElement(r);
t.async = 1;
t.src = 'https://www.clarity.ms/tag/' + i;
y = l.getElementsByTagName(r)[0];
y.parentNode.insertBefore(t, y);
})(window, document, 'clarity', 'script', 'lyia7jfwui');

View File

@@ -1,273 +0,0 @@
(function createMirrorModal() {
const SIGN = Symbol.for('antd.mirror-notify');
const always = window.localStorage.getItem('DEBUG') === 'antd';
const officialChinaMirror = 'https://ant-design.antgroup.com?utm_source=mirror-notify';
const enabledCondition = [
// Check if the browser language is Chinese
navigator.languages.includes('zh') || navigator.languages.includes('zh-CN'),
// Check if the URL path ends with -cn
/-cn\/?$/.test(window.location.pathname),
// chinese mirror URL
!['ant-design.gitee.io', new URL(officialChinaMirror).hostname].includes(
window.location.hostname,
),
// PR review URL
!window.location.host.includes('surge'),
// development mode
!['127.0.0.1', 'localhost'].includes(window.location.hostname),
];
const isEnabled = always || enabledCondition.every(Boolean);
if (!isEnabled) {
return;
}
const prefixCls = 'antd-mirror-notify';
const primaryColor = '#1677ff';
function insertCss() {
const style = document.createElement('style');
style.innerHTML = `
@keyframes slideInRight {
from {
transform: translate3d(100%, 0, 0);
visibility: visible;
}
to {
transform: translate3d(0, 0, 0);
}
}
.${prefixCls} {
position: fixed;
inset-inline-end: 12px;
inset-block-start: 12px;
z-index: 9999;
width: 360px;
background-color: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
border-radius: 4px;
overflow: hidden;
animation: slideInRight 0.3s ease-in-out;
}
.${prefixCls}-content {
padding: 16px;
}
.${prefixCls}-content a {
color: ${primaryColor};
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
.${prefixCls}-title {
font-size: 16px;
font-weight: bold;
margin-block-end: 8px;
}
.${prefixCls}-message {
font-size: 14px;
color: #555;
line-height: 1.57;
}
.${prefixCls}-footer {
display: none;
margin-block-start: 16px;
justify-content: flex-end;
}
.${prefixCls}-progress {
position: relative;
inset-inline-end: 0;
width: 100%;
height: 4px;
background-color: #f0f0f0;
border-radius: 2px;
overflow: hidden;
}
.${prefixCls}-progress::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--progress, 0%);
background-color: ${primaryColor};
transition: width 0.05s linear; /* Adjusted for smoother animation matching refreshRate */
}
.${prefixCls}-close {
all: unset;
position: absolute;
inset-inline-end: 2px;
inset-block-start: 2px;
width: 32px;
height: 32px;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
color: #999;
cursor: pointer;
}
.${prefixCls}-close:hover {
color: #333;
}
.${prefixCls}-action {
all: unset;
display: inline-block;
padding: 4px 8px;
background-color: ${primaryColor};
color: #fff;
border-radius: 4px;
text-align: center;
cursor: pointer;
font-size: 14px;
}
`;
document.head.append(style);
}
function createNotification() {
insertCss();
const notify = document.createElement('div');
notify.className = `${prefixCls} slideInRight`;
notify.innerHTML = `
<div class="${prefixCls}-content">
<div class="${prefixCls}-title">🇨🇳 访问不畅?试试国内镜像</div>
<div class="${prefixCls}-message">
国内镜像站点可以帮助您更快地访问文档和资源。<br>
请尝试访问 <a class="${prefixCls}-link" href="${officialChinaMirror}">国内镜像站点</a>,以获得更好的体验。
</div>
<div class="${prefixCls}-footer">
<button class="${prefixCls}-action ${prefixCls}-link">🚀 立即前往</button>
</div>
</div>
<button class="${prefixCls}-close">X</button>
<div class="${prefixCls}-progress" style="--progress: 100%;"></div>
`;
document.body.appendChild(notify);
notify.querySelector(`.${prefixCls}-close`).addEventListener('click', () => {
removeNotify();
});
const goToChinaMirror = (event) => {
event.preventDefault();
if (window.gtag) {
window.gtag('event', '点击', {
event_category: '前往国内镜像',
event_label: officialChinaMirror,
});
}
window.location.href = officialChinaMirror;
removeNotify();
};
notify.querySelectorAll(`.${prefixCls}-link`).forEach((link) => {
link.addEventListener('click', goToChinaMirror);
});
const refreshRate = 50; // ms
const duration = 10; // s
const step = 100 / ((duration * 1000) / refreshRate);
let progressInterval = -1;
function removeNotify() {
clearInterval(progressInterval);
notify.remove();
}
const progressEl = notify.querySelector(`.${prefixCls}-progress`);
let currentProgressValue = 100;
const progress = {
get value() {
return currentProgressValue;
},
set value(val) {
currentProgressValue = Math.max(0, Math.min(100, val));
progressEl.style.setProperty('--progress', `${currentProgressValue}%`);
},
};
function startProgressTimer() {
if (progressInterval !== -1) {
clearInterval(progressInterval);
}
progressInterval = setInterval(() => {
if (progress.value <= 0) {
removeNotify();
} else {
progress.value -= step;
}
}, refreshRate);
}
startProgressTimer();
notify.addEventListener('mouseenter', () => {
clearInterval(progressInterval);
});
notify.addEventListener('mouseleave', () => {
startProgressTimer();
});
}
function checkMirrorAvailable(timeout = 1500) {
return new Promise((resolve) => {
const img = new Image();
let done = false;
img.onload = () => {
if (!done) {
done = true;
resolve(true);
}
};
img.onerror = () => {
if (!done) {
done = true;
resolve(false);
}
};
img.src = new URL('/llms.txt', officialChinaMirror).href;
setTimeout(() => {
if (!done) {
done = true;
resolve(false);
}
}, timeout);
});
}
// 断定网络不畅阈值(秒)
const delayDuration = 3;
const reactTimeoutId = setTimeout(() => {
if (typeof window[SIGN]?.YES === 'undefined') {
console.error(
`antd.mirror-notify: 页面加载超过 ${delayDuration} 秒,可能是网络不畅。\n请尝试访问国内镜像站点。%c${officialChinaMirror}`,
`color: ${primaryColor}; font-weight: bold;`,
);
checkMirrorAvailable().then((isFast) => {
if (isFast) {
createNotification();
}
});
}
}, delayDuration * 1000);
// 交给 React effect 清理
window[SIGN] = function stopMirrorNotify() {
window[SIGN].YES = Date.now();
clearTimeout(reactTimeoutId);
};
})();

View File

@@ -1,90 +0,0 @@
import React from 'react';
import { theme as antdTheme, ConfigProvider } from 'antd';
import type { ThemeConfig } from 'antd';
import type { ThemeProviderProps } from 'antd-style';
import { ThemeProvider } from 'antd-style';
import { updateCSS } from '@rc-component/util/lib/Dom/dynamicCSS';
import SiteContext from './slots/SiteContext';
interface NewToken {
bannerHeight: number;
headerHeight: number;
menuItemBorder: number;
mobileMaxWidth: number;
siteMarkdownCodeBg: string;
siteMarkdownCodeBgDark: string;
antCls: string;
iconCls: string;
marginFarXS: number;
marginFarSM: number;
marginFar: number;
codeFamily: string;
anchorTop: number;
}
// 通过给 antd-style 扩展 CustomToken 对象类型定义,可以为 useTheme 中增加相应的 token 对象
declare module 'antd-style' {
export interface CustomToken extends NewToken {}
}
const headerHeight = 64;
const bannerHeight = 38;
const SiteThemeProvider: React.FC<ThemeProviderProps<any>> = ({ children, theme, ...rest }) => {
const { getPrefixCls, iconPrefixCls } = React.use(ConfigProvider.ConfigContext);
const rootPrefixCls = getPrefixCls();
const { token } = antdTheme.useToken();
const { bannerVisible } = React.use(SiteContext);
React.useEffect(() => {
// 需要注意与 components/config-provider/demo/holderRender.tsx 配置冲突
ConfigProvider.config({ theme: theme as ThemeConfig });
}, [theme]);
React.useEffect(() => {
// iframe demo 生效
if (window.parent !== window) {
updateCSS(
`
[data-prefers-color='dark'] {
color-scheme: dark !important;
}
[data-prefers-color='light'] {
color-scheme: light !important;
}
`,
'color-scheme',
);
}
}, [theme]);
return (
<ThemeProvider<NewToken>
{...rest}
theme={theme}
customToken={{
headerHeight,
bannerHeight,
menuItemBorder: 2,
mobileMaxWidth: 767.99,
siteMarkdownCodeBg: token.colorFillTertiary,
siteMarkdownCodeBgDark: '#000',
antCls: `.${rootPrefixCls}`,
iconCls: `.${iconPrefixCls}`,
/** 56 */
marginFarXS: (token.marginXXL / 6) * 7,
/** 80 */
marginFarSM: (token.marginXXL / 3) * 5,
/** 96 */
marginFar: token.marginXXL * 2,
codeFamily: `'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace`,
anchorTop: headerHeight + token.margin + (bannerVisible ? bannerHeight : 0),
}}
>
{children}
</ThemeProvider>
);
};
export default SiteThemeProvider;

View File

@@ -1,8 +0,0 @@
import React from 'react';
const APITable: React.FC = () => (
// TODO: implement api table, depend on the new markdown data structure passed
<>API Table</>
);
export default APITable;

View File

@@ -1,14 +0,0 @@
import * as React from 'react';
import * as all from 'antd';
interface AntdProps {
component: keyof typeof all;
}
const Antd: React.FC<AntdProps> = (props) => {
const { component, ...restProps } = props;
const Component = (all[component] ?? React.Fragment) as React.ComponentType<any>;
return <Component {...restProps} />;
};
export default Antd;

View File

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

View File

@@ -1,24 +0,0 @@
import * as React from 'react';
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 {
type: 'info' | 'warning' | 'error' | 'success';
}
const colorMap = {
info: 'blue',
warning: 'orange',
error: 'red',
success: 'green',
};
export default ({ type = 'info', ...props }: BadgeProps) => (
<Tag
variant="filled"
color={colorMap[type]}
{...props}
style={{ verticalAlign: 'top', ...props.style }}
/>
);

View File

@@ -1,69 +0,0 @@
import * as React from 'react';
import { FastColor } from '@ant-design/fast-color';
import type { ColorInput } from '@ant-design/fast-color';
import { Popover } from 'antd';
import { createStyles } from 'antd-style';
const useStyle = createStyles(({ css, cssVar, token }) => ({
codeSpan: css`
padding: 0.2em 0.4em;
font-size: 0.9em;
background: ${token.siteMarkdownCodeBg};
border-radius: ${cssVar.borderRadius};
font-family: monospace;
`,
dot: css`
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-inline-end: ${cssVar.marginXXS};
border: 1px solid ${cssVar.colorSplit};
`,
}));
interface ColorChunkProps {
value: ColorInput;
enablePopover?: boolean;
}
const ColorChunk: React.FC<React.PropsWithChildren<ColorChunkProps>> = (props) => {
const { styles, theme } = useStyle();
const { value, children, enablePopover } = props;
const dotColor = React.useMemo(() => new FastColor(value).toHexString(), [value]);
let dotNode = (
<span className={styles.codeSpan}>
<span className={styles.dot} style={{ backgroundColor: dotColor }} />
{children ?? dotColor}
</span>
);
if (enablePopover) {
dotNode = (
<Popover
placement="left"
content={<div hidden />}
styles={{
container: {
backgroundColor: dotColor,
width: 120,
height: 120,
borderRadius: theme.borderRadiusLG,
},
root: {
'--antd-arrow-background-color': dotColor,
backgroundColor: 'transparent',
} as React.CSSProperties,
}}
>
{dotNode}
</Popover>
);
}
return dotNode;
};
export default ColorChunk;

View File

@@ -1,3 +0,0 @@
import ColorPaletteTool from '../../common/Color/ColorPaletteTool';
export default ColorPaletteTool;

View File

@@ -1,3 +0,0 @@
import ColorPaletteToolDark from '../../common/Color/ColorPaletteToolDark';
export default ColorPaletteToolDark;

View File

@@ -1,3 +0,0 @@
import ColorPalettes from '../../common/Color/ColorPalettes';
export default ColorPalettes;

View File

@@ -1,236 +0,0 @@
import React from 'react';
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 copy from 'antd/es/_util/copy';
import kebabCase from 'lodash/kebabCase';
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: '反馈',
docs: '文档',
edit: '编辑此页',
changelog: '更新日志',
design: '设计指南',
version: '版本',
issueNew: '提交问题',
issueOpen: '待解决',
},
en: {
import: 'Import',
copy: 'Copy',
copied: 'Copied',
source: 'GitHub',
docs: 'Docs',
edit: 'Edit this page',
changelog: 'Changelog',
design: 'Design',
version: 'Version',
issueNew: 'Issue',
issueOpen: 'Open issues',
},
};
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(({ cssVar, token }) => ({
code: css`
cursor: pointer;
position: relative;
display: inline-flex;
align-items: center;
column-gap: ${cssVar.paddingXXS};
border-radius: ${cssVar.borderRadiusSM};
padding-inline: ${cssVar.paddingXXS} !important;
transition: all ${cssVar.motionDurationSlow} !important;
font-family: ${token.codeFamily};
color: ${cssVar.colorTextSecondary} !important;
&:hover {
background: ${cssVar.controlItemBgHover};
}
a&:hover {
text-decoration: underline !important;
}
`,
icon: css`
margin-inline-end: 4px;
`,
}));
export interface ComponentMetaProps {
component: string;
source: string | true;
filename?: string;
version?: string;
designUrl?: string;
searchTitleKeywords?: string[];
repo: string;
}
const ComponentMeta: React.FC<ComponentMetaProps> = (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);
const importCode =
component === 'Icon'
? `import { AntDesignOutlined } from '@ant-design/icons';`
: `import { ${transformComponentName(component)} } from 'antd';`;
const onCopy = async () => {
await copy(importCode);
setCopied(true);
};
const onOpenChange = (open: boolean) => {
if (open) {
setCopied(false);
}
};
// ======================== Source ========================
const [filledSource, abbrSource] = React.useMemo(() => {
if (String(source) === 'true') {
const kebabComponent = kebabCase(component);
return [
`https://github.com/${repo}/blob/master/components/${kebabComponent}`,
`components/${kebabComponent}`,
];
}
if (typeof source !== 'string') {
return [null, null];
}
return [source, source];
}, [component, repo, source]);
return (
<Descriptions
size="small"
colon={false}
column={1}
style={{ marginTop: token.margin }}
styles={{ label: { paddingInlineEnd: token.padding, width: 56 } }}
items={
[
{
label: locale.import,
children: (
<Tooltip
placement="right"
title={copied ? locale.copied : locale.copy}
onOpenChange={onOpenChange}
>
<Typography.Text
className={styles.code}
style={{ cursor: 'pointer' }}
onClick={onCopy}
>
{importCode}
</Typography.Text>
</Tooltip>
),
},
filledSource && {
label: locale.source,
children: (
<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 && {
label: locale.docs,
children: (
<Flex justify="flex-start" align="center" gap="small">
<Typography.Link
className={styles.code}
href={`${branchUrl(repo)}${filename}`}
target="_blank"
>
<EditOutlined className={styles.icon} />
<span>{locale.edit}</span>
</Typography.Link>
{designUrl && (
<Link className={styles.code} to={designUrl}>
<CompassOutlined className={styles.icon} />
<span>{locale.design}</span>
</Link>
)}
<ComponentChangelog>
<Typography.Link className={styles.code}>
<HistoryOutlined className={styles.icon} />
<span>{locale.changelog}</span>
</Typography.Link>
</ComponentChangelog>
</Flex>
),
},
isVersionNumber(version) && {
label: locale.version,
children: (
<Typography.Text className={styles.code}>
{isZhCN ? `${version} 起支持` : `supported since ${version}`}
</Typography.Text>
),
},
].filter(Boolean) as GetProp<typeof Descriptions, 'items'>
}
/>
);
};
export default ComponentMeta;

View File

@@ -1,55 +0,0 @@
export type Component = {
title: string;
subtitle?: string;
cover: string;
coverDark?: string;
link: string;
tag?: string;
};
const proComponentsList: Component[] = [
{
cover: 'https://gw.alipayobjects.com/zos/antfincdn/4n5H%24UX%24j/bianzu%2525204.svg',
link: 'https://procomponents.ant.design/components/layout',
subtitle: '高级布局',
title: 'ProLayout',
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
},
{
cover: 'https://gw.alipayobjects.com/zos/antfincdn/mStei5BFC/bianzu%2525207.svg',
link: 'https://procomponents.ant.design/components/form',
subtitle: '高级表单',
title: 'ProForm',
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
},
{
cover: 'https://gw.alipayobjects.com/zos/antfincdn/AwU0Cv%26Ju/bianzu%2525208.svg',
link: 'https://procomponents.ant.design/components/table',
subtitle: '高级表格',
title: 'ProTable',
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
},
{
cover: 'https://gw.alipayobjects.com/zos/antfincdn/H0%26LSYYfh/bianzu%2525209.svg',
link: 'https://procomponents.ant.design/components/descriptions',
subtitle: '高级定义列表',
title: 'ProDescriptions',
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
},
{
cover: 'https://gw.alipayobjects.com/zos/antfincdn/uZUmLtne5/bianzu%2525209.svg',
link: 'https://procomponents.ant.design/components/list',
subtitle: '高级列表',
title: 'ProList',
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
},
{
cover: 'https://gw.alipayobjects.com/zos/antfincdn/N3eU432oA/bianzu%2525209.svg',
link: 'https://procomponents.ant.design/components/editable-table',
subtitle: '可编辑表格',
title: 'EditableProTable',
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
},
];
export default proComponentsList;

View File

@@ -1,260 +0,0 @@
import React, { memo, useMemo, useRef, useState } from 'react';
import type { CSSProperties } from 'react';
import { SearchOutlined } from '@ant-design/icons';
import { Affix, Card, Col, Divider, Flex, Input, Row, Tag, Typography } from 'antd';
import { createStaticStyles, useTheme } from 'antd-style';
import { useIntl, useLocation, useSidebarData } from 'dumi';
import debounce from 'lodash/debounce';
import scrollIntoView from 'scroll-into-view-if-needed';
import Link from '../../common/Link';
import SiteContext from '../../slots/SiteContext';
import type { Component } from './ProComponentsList';
import proComponentsList from './ProComponentsList';
const styles = createStaticStyles(({ cssVar, css }) => ({
componentsOverviewGroupTitle: css`
margin-bottom: ${cssVar.marginLG} !important;
`,
componentsOverviewTitle: css`
overflow: hidden;
color: ${cssVar.colorTextHeading};
text-overflow: ellipsis;
`,
componentsOverviewImg: css`
display: flex;
align-items: center;
justify-content: center;
height: 152px;
`,
componentsOverviewCard: css`
cursor: pointer;
transition: all 0.5s;
&:hover {
box-shadow:
0 6px 16px -8px #00000014,
0 9px 28px #0000000d,
0 12px 48px 16px #00000008;
}
`,
componentsOverviewAffix: css`
display: flex;
transition: all ${cssVar.motionDurationSlow};
justify-content: space-between;
`,
componentsOverviewSearch: css`
padding: 0;
box-shadow: none !important;
.anticon-search {
color: ${cssVar.colorTextDisabled};
}
`,
componentsOverviewContent: css`
&:empty:after {
display: block;
padding: ${cssVar.padding} 0 calc(${cssVar.paddingMD} * 2);
color: ${cssVar.colorTextDisabled};
text-align: center;
border-bottom: 1px solid ${cssVar.colorSplit};
content: 'Not Found';
}
`,
}));
const onClickCard = (pathname: string) => {
window.gtag?.('event', '点击', {
event_category: '组件总览卡片',
event_label: pathname,
});
};
const reportSearch = debounce<(value: string) => void>((value) => {
window.gtag?.('event', '搜索', {
event_category: '组件总览卡片',
event_label: value,
});
}, 2000);
const { Title } = Typography;
const Overview: React.FC = () => {
const { isDark } = React.use(SiteContext);
const data = useSidebarData();
const [searchBarAffixed, setSearchBarAffixed] = useState<boolean>(false);
const token = useTheme();
const { borderRadius, colorBgContainer, fontSizeXL, anchorTop } = token;
const affixedStyle: CSSProperties = {
boxShadow: 'rgba(50, 50, 93, 0.25) 0 6px 12px -2px, rgba(0, 0, 0, 0.3) 0 3px 7px -3px',
padding: 8,
margin: -8,
borderRadius,
backgroundColor: colorBgContainer,
};
const { search: urlSearch } = useLocation();
const { locale, formatMessage } = useIntl();
const [search, setSearch] = useState<string>(() => {
const params = new URLSearchParams(urlSearch);
if (params.has('s')) {
return params.get('s') || '';
}
return '';
});
const sectionRef = useRef<HTMLElement>(null);
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
if (event.keyCode === 13 && search.trim().length) {
sectionRef.current?.querySelector<HTMLElement>(`.${styles.componentsOverviewCard}`)?.click();
}
};
const groups = useMemo<{ title: string; children: Component[] }[]>(
() =>
data
.filter((item) => item?.title)
.map<{ title: string; children: Component[] }>((item) => ({
title: item?.title || '',
children: item.children.map((child) => ({
title: child.frontmatter?.title || '',
subtitle: child.frontmatter?.subtitle,
cover: child.frontmatter?.cover,
coverDark: child.frontmatter?.coverDark,
link: child.link,
})),
}))
.concat([
{
title: locale === 'zh-CN' ? '重型组件' : 'Others',
children:
locale === 'zh-CN'
? proComponentsList
: proComponentsList.map((component) => ({ ...component, subtitle: '' })),
},
]),
[data, locale],
);
return (
<section className="markdown" ref={sectionRef}>
<Divider />
<Affix offsetTop={anchorTop} onChange={(affixed) => setSearchBarAffixed(!!affixed)}>
<div
className={styles.componentsOverviewAffix}
style={searchBarAffixed ? affixedStyle : {}}
>
<Input
autoFocus
value={search}
placeholder={formatMessage({ id: 'app.components.overview.search' })}
className={styles.componentsOverviewSearch}
onChange={(e) => {
setSearch(e.target.value);
reportSearch(e.target.value);
if (sectionRef.current && searchBarAffixed) {
scrollIntoView(sectionRef.current, {
scrollMode: 'if-needed',
block: 'start',
behavior: (actions) =>
actions.forEach(({ el, top }) => {
el.scrollTop = top - 64;
}),
});
}
}}
onKeyDown={onKeyDown}
variant="borderless"
suffix={<SearchOutlined />}
style={{ fontSize: searchBarAffixed ? fontSizeXL - 2 : fontSizeXL }}
/>
</div>
</Affix>
<Divider />
<div className={styles.componentsOverviewContent}>
{groups
.filter((i) => i?.title)
.map((group) => {
const components = group?.children?.filter(
(component) =>
!search.trim() ||
component?.title?.toLowerCase()?.includes(search.trim().toLowerCase()) ||
(component?.subtitle || '').toLowerCase().includes(search.trim().toLowerCase()),
);
return components?.length ? (
<div key={group?.title}>
<Title level={2} className={styles.componentsOverviewGroupTitle}>
<Flex gap="small" align="center">
<span style={{ fontSize: 24 }}>{group?.title}</span>
<Tag style={{ display: 'block' }}>{components.length}</Tag>
</Flex>
</Title>
<Row gutter={[24, 24]}>
{components.map((component) => {
let url = component.link;
/** 是否是外链 */
const isExternalLink = url.startsWith('http');
if (!isExternalLink) {
url += urlSearch;
}
const cardContent = (
<Card
key={component.title}
onClick={() => onClickCard(url)}
styles={{
body: {
backgroundRepeat: 'no-repeat',
backgroundPosition: 'bottom right',
backgroundImage: `url(${component.tag || ''})`,
},
}}
size="small"
className={styles.componentsOverviewCard}
title={
<div className={styles.componentsOverviewTitle}>
{component.title} {component.subtitle}
</div>
}
>
<div className={styles.componentsOverviewImg}>
<img
draggable={false}
src={
isDark && component.coverDark ? component.coverDark : component.cover
}
alt=""
/>
</div>
</Card>
);
const linkContent = isExternalLink ? (
<a href={url} key={component.title}>
{cardContent}
</a>
) : (
<Link to={url} key={component.title}>
{cardContent}
</Link>
);
return (
<Col xs={24} sm={12} lg={8} xl={6} key={component.title}>
{linkContent}
</Col>
);
})}
</Row>
</div>
) : null;
})}
</div>
</section>
);
};
export default memo(Overview);

View File

@@ -1,251 +0,0 @@
import React, { useMemo, useState } from 'react';
import { LinkOutlined, QuestionCircleOutlined, RightOutlined } from '@ant-design/icons';
import { ConfigProvider, Flex, Popover, Table, Typography } from 'antd';
import { createStyles, css, useTheme } from 'antd-style';
import { getDesignToken } from 'antd-token-previewer';
import tokenMeta from 'antd/es/version/token-meta.json';
import tokenData from 'antd/es/version/token.json';
import useLocale from '../../../hooks/useLocale';
import { useColumns } from '../TokenTable';
import type { TokenData } from '../TokenTable';
const compare = (token1: string, token2: string) => {
const hasColor1 = token1.toLowerCase().includes('color');
const hasColor2 = token2.toLowerCase().includes('color');
if (hasColor1 && !hasColor2) {
return -1;
}
if (!hasColor1 && hasColor2) {
return 1;
}
return token1 < token2 ? -1 : 1;
};
const defaultToken = getDesignToken();
const locales = {
cn: {
token: 'Token 名称',
description: '描述',
type: '类型',
value: '默认值',
componentToken: '组件 Token',
globalToken: '全局 Token',
componentComment: '这里是你的组件 token',
globalComment: '这里是你的全局 token',
help: '如何定制?',
customizeTokenLink: '/docs/react/customize-theme-cn#修改主题变量',
customizeComponentTokenLink: '/docs/react/customize-theme-cn#修改组件变量',
},
en: {
token: 'Token Name',
description: 'Description',
type: 'Type',
value: 'Default Value',
componentToken: 'Component Token',
globalToken: 'Global Token',
componentComment: 'here is your component tokens',
globalComment: 'here is your global tokens',
help: 'How to use?',
customizeTokenLink: '/docs/react/customize-theme#customize-design-token',
customizeComponentTokenLink: 'docs/react/customize-theme#customize-component-token',
},
};
const useStyle = createStyles(({ cssVar }) => ({
tableTitle: css`
cursor: pointer;
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
line-height: 40px;
gap: ${cssVar.marginXS};
`,
arrowIcon: css`
font-size: ${cssVar.fontSizeLG};
& svg {
transition: all ${cssVar.motionDurationSlow};
}
`,
help: css`
font-size: ${cssVar.fontSizeSM};
font-weight: normal;
color: #999;
a {
color: #999;
}
`,
tokenTitle: css`
font-size: ${cssVar.fontSizeLG};
font-weight: bold;
`,
}));
interface SubTokenTableProps {
defaultOpen?: boolean;
title: string;
helpText: React.ReactNode;
helpLink: string;
tokens: string[];
component?: string;
comment?: {
componentComment?: string;
globalComment?: string;
};
}
const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
const { defaultOpen, tokens, title, helpText, helpLink, component, comment } = props;
const [, lang] = useLocale(locales);
const token = useTheme();
const columns = useColumns();
const [open, setOpen] = useState<boolean>(defaultOpen ?? process.env.NODE_ENV !== 'production');
const { styles } = useStyle();
if (!tokens.length) {
return null;
}
const data = tokens
.sort(component ? undefined : compare)
.map<TokenData>((name) => {
const meta = component
? tokenMeta.components[component].find((item) => item.token === name)
: tokenMeta.global[name];
if (!meta) {
return null as unknown as TokenData;
}
return {
name,
desc: lang === 'cn' ? meta.desc : meta.descEn,
type: meta.type,
value: component ? tokenData[component].component[name] : defaultToken[name],
};
})
.filter(Boolean);
const code = component
? `<ConfigProvider
theme={{
components: {
${component}: {
/* ${comment?.componentComment} */
},
},
}}
>
...
</ConfigProvider>`
: `<ConfigProvider
theme={{
token: {
/* ${comment?.globalComment} */
},
}}
>
...
</ConfigProvider>`;
return (
<>
<div className={styles.tableTitle} onClick={() => setOpen(!open)}>
<RightOutlined className={styles.arrowIcon} rotate={open ? 90 : 0} />
<Flex className={styles.tokenTitle} gap="small" justify="flex-start" align="center">
{title}
<Popover
title={null}
destroyOnHidden
styles={{ root: { width: 400 } }}
content={
<Typography>
<pre dir="ltr" style={{ fontSize: 12 }}>
<code dir="ltr">{code}</code>
</pre>
<a href={helpLink} target="_blank" rel="noreferrer">
<LinkOutlined style={{ marginInlineEnd: 4 }} />
{helpText}
</a>
</Typography>
}
>
<span className={styles.help}>
<QuestionCircleOutlined style={{ marginInlineEnd: 4 }} />
{helpText}
</span>
</Popover>
</Flex>
</div>
{open && (
<ConfigProvider theme={{ token: { borderRadius: 0 } }}>
<Table<TokenData>
size="middle"
columns={columns}
bordered
dataSource={data}
style={{ marginBottom: token.margin }}
pagination={false}
rowKey={(record) => record.name}
/>
</ConfigProvider>
)}
</>
);
};
export interface ComponentTokenTableProps {
component: string;
}
const ComponentTokenTable: React.FC<ComponentTokenTableProps> = ({ component }) => {
const [locale] = useLocale(locales);
const [mergedGlobalTokens] = useMemo(() => {
const globalTokenSet = new Set<string>();
component.split(',').forEach((comp) => {
const { global: globalTokens = [] } = tokenData[comp] || {};
globalTokens.forEach((token: string) => {
globalTokenSet.add(token);
});
});
return [Array.from(globalTokenSet)] as const;
}, [component]);
return (
<>
{tokenMeta.components[component] && (
<SubTokenTable
defaultOpen
title={locale.componentToken}
helpText={locale.help}
helpLink={locale.customizeTokenLink}
tokens={tokenMeta.components[component].map((item) => item.token)}
component={component}
comment={{
componentComment: locale.componentComment,
globalComment: locale.globalComment,
}}
/>
)}
<SubTokenTable
title={locale.globalToken}
helpText={locale.help}
helpLink={locale.customizeComponentTokenLink}
tokens={mergedGlobalTokens}
comment={{
componentComment: locale.componentComment,
globalComment: locale.globalComment,
}}
/>
</>
);
};
export default React.memo(ComponentTokenTable);

View File

@@ -1,42 +0,0 @@
/**
* copied: https://github.com/arvinxx/dumi-theme-antd-style/tree/master/src/builtins/Container
*/
import * as React from 'react';
import { Alert } from 'antd';
import useStyles from './style';
interface ContainerProps {
type: 'info' | 'warning' | 'success' | 'error';
title?: string;
}
const Container: React.FC<React.PropsWithChildren<ContainerProps>> = ({
type,
title,
children,
}) => {
const { styles, cx } = useStyles();
return (
<Alert
showIcon
type={type}
title={title || type.toUpperCase()}
description={
<div
className={cx(
styles.desc,
// 为了让 markdown 的样式生效,需要在这里添加一个额外的 class
'markdown',
)}
>
{children}
</div>
}
className={styles.alert}
/>
);
};
export default Container;

View File

@@ -1,19 +0,0 @@
import { createStyles } from 'antd-style';
const useStyles = createStyles(({ prefixCls, css }) => ({
alert: css`
padding: 12px 16px;
.${prefixCls}-alert-message {
font-weight: bold;
}
`,
/* 使用 `&&` 加一点点权重 */
desc: css`
&& p {
margin: 0;
}
`,
}));
export default useStyles;

View File

@@ -1,99 +0,0 @@
import React, { Suspense } from 'react';
import { BugOutlined, CodeOutlined } from '@ant-design/icons';
import { css, Global } from '@emotion/react';
import { Button, Tooltip } from 'antd';
import { DumiDemo, DumiDemoGrid, FormattedMessage } from 'dumi';
import useLayoutState from '../../../hooks/useLayoutState';
import DemoContext from '../../slots/DemoContext';
import DemoFallback from '../Previewer/DemoFallback';
const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
const { showDebug, setShowDebug } = React.use(DemoContext);
const [expandAll, setExpandAll] = useLayoutState(false);
const handleVisibleToggle = () => {
setShowDebug?.(!showDebug);
};
const handleExpandToggle = () => {
setExpandAll(!expandAll);
};
const demos = React.useMemo(
() =>
items.reduce<typeof items>((acc, item) => {
const { previewerProps } = item;
const { debug } = previewerProps;
if (debug && !showDebug) {
return acc;
}
return acc.concat({
...item,
previewerProps: {
...previewerProps,
expand: expandAll,
// always override debug property, because dumi will hide debug demo in production
debug: false,
/**
* antd extra marker for the original debug
* @see https://github.com/ant-design/ant-design/pull/40130#issuecomment-1380208762
*/
originDebug: debug,
},
});
}, []),
[expandAll, items, showDebug],
);
return (
<div className="demo-wrapper">
<Global
styles={css`
:root {
--antd-site-api-deprecated-display: ${showDebug ? 'table-row' : 'none'};
}
`}
/>
<span className="all-code-box-controls">
<Tooltip
title={
<FormattedMessage id={`app.component.examples.${expandAll ? 'collapse' : 'expand'}`} />
}
>
<Button
type="text"
size="small"
icon={<CodeOutlined />}
onClick={handleExpandToggle}
className={expandAll ? 'icon-enabled' : ''}
/>
</Tooltip>
<Tooltip
title={
<FormattedMessage id={`app.component.examples.${showDebug ? 'hide' : 'visible'}`} />
}
>
<Button
type="text"
size="small"
icon={<BugOutlined />}
onClick={handleVisibleToggle}
className={showDebug ? 'icon-enabled' : ''}
/>
</Tooltip>
</span>
<DumiDemoGrid
items={demos}
demoRender={(item) => (
<Suspense key={item.demo.id} fallback={<DemoFallback />}>
<DumiDemo {...item} />
</Suspense>
)}
/>
</div>
);
};
export default DemoWrapper;

View File

@@ -1,62 +0,0 @@
import React from 'react';
import { Flex } from 'antd';
import type { FlexProps } from 'antd';
import { createStyles } from 'antd-style';
import { clsx } from 'clsx';
import ImagePreview from '../ImagePreview';
import type { ImagePreviewProps } from '../ImagePreview';
const isNonNullable = <T,>(val: T): val is NonNullable<T> => {
return val !== undefined && val !== null;
};
const useStyle = createStyles(({ css, token }) => {
return {
wrapper: css`
color: ${token.colorText};
font-size: ${token.fontSize}px;
line-height: 2;
`,
title: css`
margin: 1em 0;
`,
description: css`
margin: 1em 0;
padding-inline-start: 0.8em;
color: ${token.colorTextSecondary};
font-size: 90%;
border-inline-start: 4px solid ${token.colorSplit};
p {
margin: 0;
}
`,
};
});
interface FlexWithImagePreviewProps {
imagePreviewProps?: ImagePreviewProps;
title?: string;
description?: string;
}
const FlexWithImagePreview: React.FC<
FlexWithImagePreviewProps & React.PropsWithChildren<FlexProps>
> = (props) => {
const { imagePreviewProps, title, description, className, style, children, ...rest } = props;
const { styles } = useStyle();
if (!title && !description) {
return <ImagePreview {...imagePreviewProps}>{children}</ImagePreview>;
}
return (
<Flex className={clsx(styles.wrapper, className)} style={style} {...rest}>
<Flex align="flex-start" justify="flex-start" vertical>
{isNonNullable(title) && <div className={styles.title}>{title}</div>}
{isNonNullable(description) && <div className={styles.description}>{description}</div>}
</Flex>
<ImagePreview {...imagePreviewProps}>{children}</ImagePreview>
</Flex>
);
};
export default FlexWithImagePreview;

View File

@@ -1,83 +0,0 @@
import * as React from 'react';
import { App } from 'antd';
import { createStaticStyles } from 'antd-style';
import { useIntl } from 'dumi';
import CopyableIcon from './CopyableIcon';
import type { CategoriesKeys } from './fields';
import type { ThemeType } from './IconSearch';
const styles = createStaticStyles(({ css, cssVar }) => ({
anticonsList: css`
margin: ${cssVar.margin} 0;
overflow: hidden;
direction: ltr;
list-style: none;
display: grid;
grid-gap: ${cssVar.margin};
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
padding: 0;
`,
copiedCode: css`
padding: 0 ${cssVar.paddingXXS};
font-size: ${cssVar.fontSizeSM};
background-color: ${cssVar.colorBgLayout};
border-radius: ${cssVar.borderRadiusXS};
`,
}));
interface CategoryProps {
title: CategoriesKeys;
icons: string[];
theme: ThemeType;
newIcons: ReadonlyArray<string> | string[];
}
const Category: React.FC<CategoryProps> = (props) => {
const { message } = App.useApp();
const { icons, title, newIcons, theme } = 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);
},
[message, styles.copiedCode],
);
React.useEffect(
() => () => {
if (copyId.current) {
clearTimeout(copyId.current);
}
},
[],
);
return (
<div>
<h3>{intl.formatMessage({ id: `app.docs.components.icon.category.${title}` })}</h3>
<ul className={styles.anticonsList}>
{icons.map((name) => (
<CopyableIcon
key={name}
name={name}
theme={theme}
isNew={newIcons.includes(name)}
justCopied={justCopied}
onCopied={onCopied}
/>
))}
</ul>
</div>
);
};
export default Category;

View File

@@ -1,134 +0,0 @@
import React from 'react';
import * as AntdIcons from '@ant-design/icons';
import { App, Badge } from 'antd';
import { createStyles } from 'antd-style';
import copy from 'antd/es/_util/copy';
import { clsx } from 'clsx';
import useLocale from '../../../hooks/useLocale';
import type { ThemeType } from './IconSearch';
const allIcons: { [key: PropertyKey]: any } = AntdIcons;
const useStyle = createStyles(({ cssVar, token, css }) => {
const { antCls, iconCls } = token;
return {
iconItem: css`
display: inline-flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-inline-start: 0 !important;
margin-inline-end: 0 !important;
padding-inline-start: 0 !important;
padding-inline-end: 0 !important;
position: relative;
width: 200px;
height: 100px;
overflow: hidden;
color: #555;
text-align: center;
list-style: none;
background-color: inherit;
border-radius: ${cssVar.borderRadiusSM};
cursor: pointer;
transition: all ${cssVar.motionDurationSlow} ease-in-out;
${token.iconCls} {
margin: ${cssVar.marginXS} 0;
font-size: 36px;
transition: transform ${cssVar.motionDurationSlow} ease-in-out;
will-change: transform;
}
&:hover {
color: ${cssVar.colorWhite};
background-color: ${cssVar.colorPrimary};
${iconCls} {
transform: scale(1.3);
}
${antCls}-badge {
color: ${cssVar.colorWhite};
}
}
&.TwoTone:hover {
background-color: #8ecafe;
}
&.copied:hover {
color: rgba(255, 255, 255, 0.2);
}
&::after {
content: 'Copied!';
position: absolute;
top: 0;
inset-inline-start: 0;
width: 100%;
height: 100%;
line-height: 100px;
color: ${cssVar.colorTextLightSolid};
text-align: center;
background-color: ${cssVar.colorPrimary};
opacity: 0;
transition: all ${cssVar.motionDurationSlow} cubic-bezier(0.18, 0.89, 0.32, 1.28);
}
&.copied::after {
opacity: 1;
}
`,
anticonCls: css`
display: block;
font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
white-space: nowrap;
text-align: center;
transform: scale(0.8);
${antCls}-badge {
transition: color ${cssVar.motionDurationSlow} ease-in-out;
}
`,
};
});
const locales = {
cn: {
errMessage: '复制名称失败,请重试',
},
en: {
errMessage: 'Copy icon name failed, please try again.',
},
};
export interface CopyableIconProps {
name: string;
isNew: boolean;
theme: ThemeType;
justCopied: string | null;
onCopied: (type: string, text: string) => void;
}
const CopyableIcon: React.FC<CopyableIconProps> = (props) => {
const { message } = App.useApp();
const { name, isNew, justCopied, theme, onCopied } = props;
const [locale] = useLocale(locales);
const { styles } = useStyle();
const onCopy = async (text: string) => {
const result = await copy(text);
if (result) {
onCopied(name, text);
} else {
message.error(locale.errMessage);
}
};
return (
<li
className={clsx(theme, styles.iconItem, { copied: justCopied === name })}
onClick={() => onCopy(`<${name} />`)}
style={{ cursor: 'pointer' }}
>
{React.createElement(allIcons[name])}
<span className={styles.anticonCls}>
<Badge dot={isNew}>{name}</Badge>
</span>
</li>
);
};
export default CopyableIcon;

View File

@@ -1,227 +0,0 @@
import type { CSSProperties } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import Icon, * as AntdIcons from '@ant-design/icons';
import { Affix, Empty, Input, Segmented } from 'antd';
import { createStaticStyles, useTheme } from 'antd-style';
import type { SegmentedOptions } from 'antd/es/segmented';
import { useIntl } from 'dumi';
import debounce from 'lodash/debounce';
import Category from './Category';
import type { CategoriesKeys } from './fields';
import { all, categories } from './fields';
import metaInfo from './meta';
import type { IconName, IconsMeta } from './meta';
import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
export enum ThemeType {
Filled = 'Filled',
Outlined = 'Outlined',
TwoTone = 'TwoTone',
}
const allIcons: { [key: string]: any } = AntdIcons;
const styles = createStaticStyles(({ css, cssVar }) => ({
iconSearchAffix: css`
display: flex;
transition: all ${cssVar.motionDurationSlow};
justify-content: space-between;
`,
}));
interface IconSearchState {
theme: ThemeType;
searchKey: string;
}
const NEW_ICON_NAMES: ReadonlyArray<string> = [];
const IconSearch: React.FC = () => {
const intl = useIntl();
const [displayState, setDisplayState] = useState<IconSearchState>({
searchKey: '',
theme: ThemeType.Outlined,
});
const token = useTheme();
const handleSearchIcon = debounce((e: React.ChangeEvent<HTMLInputElement>) => {
setDisplayState((prevState) => ({ ...prevState, searchKey: e.target.value }));
document.getElementById('list-of-icons')?.scrollIntoView({ behavior: 'smooth' });
}, 300);
const handleChangeTheme = useCallback((value: ThemeType) => {
setDisplayState((prevState) => ({ ...prevState, theme: value as ThemeType }));
}, []);
const renderCategories = useMemo<React.ReactNode | React.ReactNode[]>(() => {
const { searchKey = '', theme } = displayState;
// loop over metaInfo to find all the icons which has searchKey in their tags
let normalizedSearchKey = searchKey?.trim();
if (normalizedSearchKey) {
normalizedSearchKey = normalizedSearchKey
.replace(/^<([a-z]*)\s\/>$/gi, (_, name) => name)
.replace(/(Filled|Outlined|TwoTone)$/, '')
.toLowerCase();
}
const tagMatchedCategoryObj = matchCategoriesFromTag(normalizedSearchKey, metaInfo);
const namedMatchedCategoryObj = Object.keys(categories).reduce<Record<string, MatchedCategory>>(
(acc, key) => {
let iconList = categories[key as CategoriesKeys];
if (normalizedSearchKey) {
const matchKey = normalizedSearchKey;
iconList = iconList.filter((iconName) => iconName.toLowerCase().includes(matchKey));
}
const ignore = [
'CopyrightCircle', // same as Copyright
'DollarCircle', // same as Dollar
];
iconList = iconList.filter((icon) => !ignore.includes(icon));
acc[key] = {
category: key,
icons: iconList,
};
return acc;
},
{},
);
// merge matched categories from tag search
const merged = mergeCategory(namedMatchedCategoryObj, tagMatchedCategoryObj);
const matchedCategories = Object.values(merged)
.map((item) => {
item.icons = item.icons
.map((iconName) => iconName + theme)
.filter((iconName) => allIcons[iconName]);
return item;
})
.filter(({ icons }) => !!icons.length);
const categoriesResult = matchedCategories.map(({ category, icons }) => (
<Category
key={category}
title={category as CategoriesKeys}
theme={theme}
icons={icons}
newIcons={NEW_ICON_NAMES}
/>
));
return categoriesResult.length ? categoriesResult : <Empty style={{ margin: '2em 0' }} />;
}, [displayState]);
const [searchBarAffixed, setSearchBarAffixed] = useState<boolean | undefined>(false);
const { borderRadius, colorBgContainer, anchorTop } = token;
const affixedStyle: CSSProperties = {
boxShadow: 'rgba(50, 50, 93, 0.25) 0 6px 12px -2px, rgba(0, 0, 0, 0.3) 0 3px 7px -3px',
padding: 8,
margin: -8,
borderRadius,
backgroundColor: colorBgContainer,
};
const memoizedOptions = React.useMemo<SegmentedOptions<ThemeType>>(
() => [
{
value: ThemeType.Outlined,
icon: <Icon component={OutlinedIcon} />,
label: intl.formatMessage({ id: 'app.docs.components.icon.outlined' }),
},
{
value: ThemeType.Filled,
icon: <Icon component={FilledIcon} />,
label: intl.formatMessage({ id: 'app.docs.components.icon.filled' }),
},
{
value: ThemeType.TwoTone,
icon: <Icon component={TwoToneIcon} />,
label: intl.formatMessage({ id: 'app.docs.components.icon.two-tone' }),
},
],
[intl],
);
return (
<div className="markdown">
<Affix offsetTop={anchorTop} onChange={setSearchBarAffixed}>
<div className={styles.iconSearchAffix} style={searchBarAffixed ? affixedStyle : {}}>
<Segmented<ThemeType>
size="large"
value={displayState.theme}
options={memoizedOptions}
onChange={handleChangeTheme}
/>
<Input.Search
placeholder={intl.formatMessage(
{ id: 'app.docs.components.icon.search.placeholder' },
{ total: all.length },
)}
style={{ flex: 1, marginInlineStart: 16 }}
allowClear
autoFocus
size="large"
onChange={handleSearchIcon}
/>
</div>
</Affix>
{renderCategories}
</div>
);
};
export default IconSearch;
type MatchedCategory = {
category: string;
icons: string[];
};
function matchCategoriesFromTag(searchKey: string, metaInfo: IconsMeta) {
if (!searchKey) {
return {};
}
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] };
}
}
return acc;
}, {});
}
function mergeCategory(
categoryA: Record<string, MatchedCategory>,
categoryB: Record<string, MatchedCategory>,
) {
const merged: Record<string, MatchedCategory> = { ...categoryA };
Object.keys(categoryB).forEach((key) => {
if (merged[key]) {
// merge icons array and remove duplicates
merged[key].icons = Array.from(new Set([...merged[key].icons, ...categoryB[key].icons]));
} else {
merged[key] = categoryB[key];
}
});
return merged;
}

View File

@@ -1,242 +0,0 @@
import * as AntdIcons from '@ant-design/icons/lib/icons';
export const all = Object.keys(AntdIcons)
.map((n) => n.replace(/(Outlined|Filled|TwoTone)$/, ''))
.filter((n, i, arr) => arr.indexOf(n) === i);
const direction = [
'StepBackward',
'StepForward',
'FastBackward',
'FastForward',
'Shrink',
'ArrowsAlt',
'Down',
'Up',
'Left',
'Right',
'CaretUp',
'CaretDown',
'CaretLeft',
'CaretRight',
'UpCircle',
'DownCircle',
'LeftCircle',
'RightCircle',
'DoubleRight',
'DoubleLeft',
'VerticalLeft',
'VerticalRight',
'VerticalAlignTop',
'VerticalAlignMiddle',
'VerticalAlignBottom',
'Forward',
'Backward',
'Rollback',
'Enter',
'Retweet',
'Swap',
'SwapLeft',
'SwapRight',
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'PlayCircle',
'UpSquare',
'DownSquare',
'LeftSquare',
'RightSquare',
'Login',
'Logout',
'MenuFold',
'MenuUnfold',
'BorderBottom',
'BorderHorizontal',
'BorderInner',
'BorderOuter',
'BorderLeft',
'BorderRight',
'BorderTop',
'BorderVerticle',
'PicCenter',
'PicLeft',
'PicRight',
'RadiusBottomleft',
'RadiusBottomright',
'RadiusUpleft',
'RadiusUpright',
'Fullscreen',
'FullscreenExit',
];
const suggestion = [
'Question',
'QuestionCircle',
'Plus',
'PlusCircle',
'Pause',
'PauseCircle',
'Minus',
'MinusCircle',
'PlusSquare',
'MinusSquare',
'Info',
'InfoCircle',
'Exclamation',
'ExclamationCircle',
'Close',
'CloseCircle',
'CloseSquare',
'Check',
'CheckCircle',
'CheckSquare',
'ClockCircle',
'Warning',
'IssuesClose',
'Stop',
];
const editor = [
'Edit',
'Form',
'Copy',
'Scissor',
'Delete',
'Snippets',
'Diff',
'Highlight',
'AlignCenter',
'AlignLeft',
'AlignRight',
'BgColors',
'Bold',
'Italic',
'Underline',
'Strikethrough',
'Redo',
'Undo',
'ZoomIn',
'ZoomOut',
'FontColors',
'FontSize',
'LineHeight',
'Dash',
'SmallDash',
'SortAscending',
'SortDescending',
'Drag',
'OrderedList',
'UnorderedList',
'RadiusSetting',
'ColumnWidth',
'ColumnHeight',
];
const data = [
'AreaChart',
'PieChart',
'BarChart',
'DotChart',
'LineChart',
'RadarChart',
'HeatMap',
'Fall',
'Rise',
'Stock',
'BoxPlot',
'Fund',
'Sliders',
];
const logo = [
'Android',
'Apple',
'Windows',
'Ie',
'Chrome',
'Github',
'Aliwangwang',
'Dingding',
'WeiboSquare',
'WeiboCircle',
'TaobaoCircle',
'Html5',
'Weibo',
'Twitter',
'Wechat',
'WhatsApp',
'Youtube',
'AlipayCircle',
'Taobao',
'Dingtalk',
'Skype',
'Qq',
'MediumWorkmark',
'Gitlab',
'Medium',
'Linkedin',
'GooglePlus',
'Dropbox',
'Facebook',
'Codepen',
'CodeSandbox',
'CodeSandboxCircle',
'Amazon',
'Google',
'CodepenCircle',
'Alipay',
'AntDesign',
'AntCloud',
'Aliyun',
'Zhihu',
'Slack',
'SlackSquare',
'Behance',
'BehanceSquare',
'Dribbble',
'DribbbleSquare',
'Instagram',
'Yuque',
'Alibaba',
'Yahoo',
'Reddit',
'Sketch',
'WechatWork',
'OpenAI',
'Discord',
'X',
'Bilibili',
'Pinterest',
'TikTok',
'Spotify',
'Twitch',
'Linux',
'Java',
'JavaScript',
'Python',
'Ruby',
'DotNet',
'Kubernetes',
'Docker',
'Baidu',
'HarmonyOS',
];
const datum = [...direction, ...suggestion, ...editor, ...data, ...logo];
const other = all.filter((n) => !datum.includes(n));
export const categories = {
direction,
suggestion,
editor,
data,
logo,
other,
};
export default categories;
export type Categories = typeof categories;
export type CategoriesKeys = keyof Categories;

View File

@@ -1,61 +0,0 @@
import React, { Suspense } from 'react';
import { Skeleton } from 'antd';
import { createStaticStyles } from 'antd-style';
const IconSearch = React.lazy(() => import('./IconSearch'));
const styles = createStaticStyles(({ css, cssVar }) => ({
searchWrapper: css`
display: flex;
gap: ${cssVar.padding};
> *:first-child {
flex: 0 0 328px;
}
> *:last-child {
flex: 1;
}
`,
fallbackWrapper: css`
display: flex;
flex-wrap: wrap;
justify-content: space-between;
> * {
flex: 0 0 15%;
margin: ${cssVar.marginXXS} 0;
}
`,
skeletonWrapper: css`
text-align: center;
> * {
width: 100% !important;
}
`,
}));
const IconSearchFallback: React.FC = () => {
return (
<>
<div className={styles.searchWrapper}>
<Skeleton.Button active style={{ width: '100%', height: 40 }} />
<Skeleton.Input active style={{ width: '100%', height: 40 }} />
</div>
<Skeleton.Button active style={{ margin: '28px 0 10px', width: 100 }} />
<div className={styles.fallbackWrapper}>
{Array.from({ length: 24 }).map((_, index) => (
<div key={index} className={styles.skeletonWrapper}>
<Skeleton.Node active style={{ height: 110, width: '100%' }}>
{' '}
</Skeleton.Node>
</div>
))}
</div>
</>
);
};
export default () => (
<Suspense fallback={<IconSearchFallback />}>
<IconSearch />
</Suspense>
);

View File

@@ -1,8 +0,0 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...financial, 'ledger'],
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...financial],
category: 'logo',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { check } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: check,
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { check } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: check,
category: 'suggestion',
} as const satisfies IconMetaSchema;

View File

@@ -1,7 +0,0 @@
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: ['time', 'watch', 'alarm'],
category: 'suggestion',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { ellipsis } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...ellipsis, 'feedback', 'discussion', 'reply', 'opinion', 'note'],
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,7 +0,0 @@
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: ['复制', 'clone', 'duplicate'],
category: 'editor',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...financial],
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { ellipsis } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...ellipsis],
category: 'editor',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { ellipsis } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...ellipsis],
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { ellipsis } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...ellipsis],
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { check } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...check],
category: 'suggestion',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { ellipsis } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...ellipsis, 'vertical'],
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...financial, 'safety', 'protection', 'security', 'shield'],
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: financial,
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,7 +0,0 @@
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: ['ai'],
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { check } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...check, 'protection', 'security', 'shield', 'safety', 'privacy'],
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { check } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...check, 'time', 'clock', 'calendar'],
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,8 +0,0 @@
import { financial } from './tags';
import type { IconMetaSchema } from './tags';
export default {
contributors: ['ant-design'],
tags: [...financial],
category: 'other',
} as const satisfies IconMetaSchema;

View File

@@ -1,62 +0,0 @@
import AccountBook from './AccountBook';
import Alipay from './Alipay';
import Bank from './Bank';
import CarryOut from './CarryOut';
import Check from './Check';
import ClockSquare from './ClockSquare';
import Comment from './Comment';
import Copy from './Copy';
import CreditCard from './CreditCard';
import Dash from './Dash';
import Dollar from './Dollar';
import Ellipsis from './Ellipsis';
import Euro from './Euro';
import Holder from './Holder';
import IssuesClose from './IssuesClose';
import More from './More';
import PayCircle from './PayCircle';
import PoundCircle from './PoundCircle';
import PropertySafety from './PropertySafety';
import RedEnvelope from './RedEnvelope';
import Robot from './Robot';
import Safety from './Safety';
import Schedule from './Schedule';
import Transaction from './Transaction';
const all = {
AccountBook,
Alipay,
AlipayCircle: Alipay,
Bank,
CarryOut,
Check,
CheckCircle: Check,
CheckSquare: Check,
ClockSquare,
Comment,
Copy,
CreditCard,
Dash,
SmallDash: Dash,
Dollar,
Ellipsis,
Euro,
EuroCircle: Euro,
Holder,
IssuesClose,
More,
PayCircle,
PoundCircle,
Pound: PoundCircle,
PropertySafety,
RedEnvelope,
Robot,
Safety,
Schedule,
Transaction,
};
export type IconsMeta = typeof all;
export type IconName = keyof IconsMeta;
export default all;

View File

@@ -1,87 +0,0 @@
import type { CategoriesKeys } from '../fields';
export type IconMetaSchema = Readonly<{
contributors: string[];
tags: readonly string[];
category: CategoriesKeys;
}>;
export const check = [
'check',
'done',
'todo',
'tick',
'complete',
'finish',
'task',
'ok',
'success',
'confirm',
'approve',
'agree',
'validation',
'√',
'✔',
'✓',
'勾',
'对',
'正确',
'right',
] as const;
export const financial = [
'monetization',
'marketing',
'currency',
'money',
'payment',
'finance',
'cash',
'bank',
'transaction',
'balance',
'expense',
'income',
'budget',
'investment',
'savings',
'profit',
'cost',
'wealth',
'economy',
'wallet',
'exchange',
] as const;
export const ellipsis = [
'...',
'。。。',
'…',
'more',
'更多',
'dots',
'ellipsis',
'expand',
'collapse',
'menu',
'dropdown',
'options',
'settings',
'et cetera',
'etc',
'loader',
'loading',
'progress',
'pending',
'throbber',
'spinner',
'operator',
'code',
'spread',
'rest',
'further',
'extra',
'overflow',
] as const;

View File

@@ -1,48 +0,0 @@
import * as React from 'react';
import type { CustomIconComponentProps } from '@ant-design/icons/es/components/Icon';
type CustomIconComponent = React.ComponentType<
CustomIconComponentProps | React.SVGProps<SVGSVGElement>
>;
export const FilledIcon: CustomIconComponent = (props) => {
const path =
'M864 64H160C107 64 64 107 64 160v' +
'704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' +
'0c0-53-43-96-96-96z';
return (
<svg {...props} viewBox="0 0 1024 1024">
<title>Filled Icon</title>
<path d={path} />
</svg>
);
};
export const OutlinedIcon: CustomIconComponent = (props) => {
const path =
'M864 64H160C107 64 64 107 64 160v7' +
'04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' +
'0-53-43-96-96-96z m-12 800H172c-6.6 0-12-5.4-' +
'12-12V172c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4' +
' 12 12v680c0 6.6-5.4 12-12 12z';
return (
<svg {...props} viewBox="0 0 1024 1024">
<title>Outlined Icon</title>
<path d={path} />
</svg>
);
};
export const TwoToneIcon: CustomIconComponent = (props) => {
const path =
'M16 512c0 273.932 222.066 496 496 49' +
'6s496-222.068 496-496S785.932 16 512 16 16 238.' +
'066 16 512z m496 368V144c203.41 0 368 164.622 3' +
'68 368 0 203.41-164.622 368-368 368z';
return (
<svg {...props} viewBox="0 0 1024 1024">
<title>TwoTone Icon</title>
<path d={path} />
</svg>
);
};

View File

@@ -1,131 +0,0 @@
import React from 'react';
import { toArray } from '@rc-component/util';
import { Image } from 'antd';
import { clsx } from 'clsx';
export interface ImagePreviewProps {
className?: string;
/** Do not show padding & background */
pure?: boolean;
}
function isGood(className: string): boolean {
return /\bgood\b/i.test(className);
}
function isBad(className: string): boolean {
return /\bbad\b/i.test(className);
}
function isInline(className: string): boolean {
return /\binline\b/i.test(className);
}
function isGoodBadImg(imgMeta: any): boolean {
return imgMeta.isGood || imgMeta.isBad;
}
function isCompareImg(imgMeta: any): boolean {
return isGoodBadImg(imgMeta) || imgMeta.inline;
}
interface MateType {
className: string;
alt: string;
description: string;
src: string;
isGood: boolean;
isBad: boolean;
inline: boolean;
}
const ImagePreview: React.FC<React.PropsWithChildren<ImagePreviewProps>> = (props) => {
const { children, className: rootClassName, pure } = props;
const imgs: React.ReactElement<any>[] = toArray(children).filter((ele) => ele.type === 'img');
const imgsMeta = imgs.map<Partial<MateType>>((img) => {
const { alt, description, src, className } = img.props;
return {
className,
alt,
description,
src,
isGood: isGood(className),
isBad: isBad(className),
inline: isInline(className),
};
});
const imagesList = imgsMeta.map<React.ReactNode>((meta, index) => {
const metaCopy = { ...meta };
delete metaCopy.description;
delete metaCopy.isGood;
delete metaCopy.isBad;
return (
<div key={index}>
<div className="image-modal-container">
<img {...metaCopy} draggable={false} src={meta.src} alt={meta.alt} />
</div>
</div>
);
});
const comparable =
(imgs.length === 2 && imgsMeta.every(isCompareImg)) ||
(imgs.length >= 2 && imgsMeta.every(isGoodBadImg));
const style: React.CSSProperties = comparable
? { width: `${(100 / imgs.length).toFixed(3)}%` }
: {};
const hasCarousel = imgs.length > 1 && !comparable;
const previewClassName = clsx(rootClassName, 'clearfix', 'preview-image-boxes', {
'preview-image-boxes-compare': comparable,
'preview-image-boxes-with-carousel': hasCarousel,
});
// ===================== Render =====================
const imgWrapperCls = 'preview-image-wrapper';
return (
<div className={previewClassName}>
{!imgs.length && (
<div
className={imgWrapperCls}
style={pure ? { background: 'transparent', padding: 0 } : {}}
>
{children}
</div>
)}
{imagesList.map((_, index) => {
if (!comparable && index !== 0) {
return null;
}
const coverMeta = imgsMeta[index];
const imageWrapperClassName = clsx(imgWrapperCls, {
good: coverMeta.isGood,
bad: coverMeta.isBad,
});
return (
<div className="preview-image-box" style={style} key={index}>
<div className={imageWrapperClassName}>
<Image className={coverMeta.className} src={coverMeta.src} alt={coverMeta.alt} />
</div>
<div className="preview-image-title">{coverMeta.alt}</div>
<div
className="preview-image-description"
// biome-ignore lint/security/noDangerouslySetInnerHtml: it's for markdown
dangerouslySetInnerHTML={{ __html: coverMeta.description ?? '' }}
/>
</div>
);
})}
</div>
);
};
export default ImagePreview;

View File

@@ -1,50 +0,0 @@
import React from 'react';
import { PictureOutlined } from '@ant-design/icons';
import { Image, Tooltip, Typography } from 'antd';
import useLocale from '../../../hooks/useLocale';
const locales = {
cn: {
tip: '预览',
},
en: {
tip: 'Preview',
},
};
export interface InlinePopoverProps {
previewURL?: string;
}
// 鼠标悬浮弹出 Popover 组件,用于帮助用户更快看到一些属性对应的预览效果
const InlinePopover: React.FC<InlinePopoverProps> = (props) => {
const { previewURL } = props;
const [locale] = useLocale(locales);
const [visible, setVisible] = React.useState(false);
return (
<>
<Tooltip title={locale.tip}>
<Typography.Link onClick={() => setVisible(true)}>
<PictureOutlined />
</Typography.Link>
</Tooltip>
<Image
width={10}
style={{ display: 'none' }}
src={previewURL}
preview={{
visible,
src: previewURL,
onVisibleChange: (value) => {
setVisible(value);
},
}}
/>
</>
);
};
export default InlinePopover;

View File

@@ -1,141 +0,0 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
interface IconProps {
className?: string;
style?: React.CSSProperties;
}
const classNames = createStaticStyles(({ css }) => ({
iconWrap: css`
display: inline-flex;
align-items: center;
line-height: 0;
text-align: center;
vertical-align: -0.125em;
`,
}));
const BunIcon: React.FC<IconProps> = (props) => {
const { className, style } = props;
return (
<span className={clsx(classNames.iconWrap, className)} style={style}>
<svg id="Bun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 70" width="1em" height="1em">
<title>Bun Logo</title>
<path
id="Shadow"
d="M71.09,20.74c-.16-.17-.33-.34-.5-.5s-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5A26.46,26.46,0,0,1,75.5,35.7c0,16.57-16.82,30.05-37.5,30.05-11.58,0-21.94-4.23-28.83-10.86l.5.5.5.5.5.5.5.5.5.5.5.5.5.5C19.55,65.3,30.14,69.75,42,69.75c20.68,0,37.5-13.48,37.5-30C79.5,32.69,76.46,26,71.09,20.74Z"
/>
<g id="Body">
<path
id="Background"
d="M73,35.7c0,15.21-15.67,27.54-35,27.54S3,50.91,3,35.7C3,26.27,9,17.94,18.22,13S33.18,3,38,3s8.94,4.13,19.78,10C67,17.94,73,26.27,73,35.7Z"
style={{
fill: '#fbf0df',
}}
/>
<path
id="Bottom_Shadow"
data-name="Bottom Shadow"
d="M73,35.7a21.67,21.67,0,0,0-.8-5.78c-2.73,33.3-43.35,34.9-59.32,24.94A40,40,0,0,0,38,63.24C57.3,63.24,73,50.89,73,35.7Z"
style={{
fill: '#f6dece',
}}
/>
<path
id="Light_Shine"
data-name="Light Shine"
d="M24.53,11.17C29,8.49,34.94,3.46,40.78,3.45A9.29,9.29,0,0,0,38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7c0,.4,0,.8,0,1.19C9.06,15.48,20.07,13.85,24.53,11.17Z"
style={{
fill: '#fffefc',
}}
/>
<path
id="Top"
d="M35.12,5.53A16.41,16.41,0,0,1,29.49,18c-.28.25-.06.73.3.59,3.37-1.31,7.92-5.23,6-13.14C35.71,5,35.12,5.12,35.12,5.53Zm2.27,0A16.24,16.24,0,0,1,39,19c-.12.35.31.65.55.36C41.74,16.56,43.65,11,37.93,5,37.64,4.74,37.19,5.14,37.39,5.49Zm2.76-.17A16.42,16.42,0,0,1,47,17.12a.33.33,0,0,0,.65.11c.92-3.49.4-9.44-7.17-12.53C40.08,4.54,39.82,5.08,40.15,5.32ZM21.69,15.76a16.94,16.94,0,0,0,10.47-9c.18-.36.75-.22.66.18-1.73,8-7.52,9.67-11.12,9.45C21.32,16.4,21.33,15.87,21.69,15.76Z"
style={{
fill: '#ccbea7',
fillRule: 'evenodd',
}}
/>
<path
id="Outline"
d="M38,65.75C17.32,65.75.5,52.27.5,35.7c0-10,6.18-19.33,16.53-24.92,3-1.6,5.57-3.21,7.86-4.62,1.26-.78,2.45-1.51,3.6-2.19C32,1.89,35,.5,38,.5s5.62,1.2,8.9,3.14c1,.57,2,1.19,3.07,1.87,2.49,1.54,5.3,3.28,9,5.27C69.32,16.37,75.5,25.69,75.5,35.7,75.5,52.27,58.68,65.75,38,65.75ZM38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7,3,50.89,18.7,63.25,38,63.25S73,50.89,73,35.7C73,26.62,67.31,18.13,57.78,13,54,11,51.05,9.12,48.66,7.64c-1.09-.67-2.09-1.29-3-1.84C42.63,4,40.42,3,38,3Z"
/>
</g>
<g id="Mouth">
<g id="Background-2" data-name="Background">
<path
d="M45.05,43a8.93,8.93,0,0,1-2.92,4.71,6.81,6.81,0,0,1-4,1.88A6.84,6.84,0,0,1,34,47.71,8.93,8.93,0,0,1,31.12,43a.72.72,0,0,1,.8-.81H44.26A.72.72,0,0,1,45.05,43Z"
style={{
fill: '#b71422',
}}
/>
</g>
<g id="Tongue">
<path
id="Background-3"
data-name="Background"
d="M34,47.79a6.91,6.91,0,0,0,4.12,1.9,6.91,6.91,0,0,0,4.11-1.9,10.63,10.63,0,0,0,1-1.07,6.83,6.83,0,0,0-4.9-2.31,6.15,6.15,0,0,0-5,2.78C33.56,47.4,33.76,47.6,34,47.79Z"
style={{
fill: '#ff6164',
}}
/>
<path
id="Outline-2"
data-name="Outline"
d="M34.16,47a5.36,5.36,0,0,1,4.19-2.08,6,6,0,0,1,4,1.69c.23-.25.45-.51.66-.77a7,7,0,0,0-4.71-1.93,6.36,6.36,0,0,0-4.89,2.36A9.53,9.53,0,0,0,34.16,47Z"
/>
</g>
<path
id="Outline-3"
data-name="Outline"
d="M38.09,50.19a7.42,7.42,0,0,1-4.45-2,9.52,9.52,0,0,1-3.11-5.05,1.2,1.2,0,0,1,.26-1,1.41,1.41,0,0,1,1.13-.51H44.26a1.44,1.44,0,0,1,1.13.51,1.19,1.19,0,0,1,.25,1h0a9.52,9.52,0,0,1-3.11,5.05A7.42,7.42,0,0,1,38.09,50.19Zm-6.17-7.4c-.16,0-.2.07-.21.09a8.29,8.29,0,0,0,2.73,4.37A6.23,6.23,0,0,0,38.09,49a6.28,6.28,0,0,0,3.65-1.73,8.3,8.3,0,0,0,2.72-4.37.21.21,0,0,0-.2-.09Z"
/>
</g>
<g id="Face">
<ellipse
id="Right_Blush"
data-name="Right Blush"
cx="53.22"
cy="40.18"
rx="5.85"
ry="3.44"
style={{
fill: '#febbd0',
}}
/>
<ellipse
id="Left_Bluch"
data-name="Left Bluch"
cx="22.95"
cy="40.18"
rx="5.85"
ry="3.44"
style={{
fill: '#febbd0',
}}
/>
<path
id="Eyes"
d="M25.7,38.8a5.51,5.51,0,1,0-5.5-5.51A5.51,5.51,0,0,0,25.7,38.8Zm24.77,0A5.51,5.51,0,1,0,45,33.29,5.5,5.5,0,0,0,50.47,38.8Z"
style={{
fillRule: 'evenodd',
}}
/>
<path
id="Iris"
d="M24,33.64a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,24,33.64Zm24.77,0a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,48.75,33.64Z"
style={{
fill: '#fff',
fillRule: 'evenodd',
}}
/>
</g>
</svg>
</span>
);
};
export default BunIcon;

View File

@@ -1,54 +0,0 @@
import React from 'react';
import { ConfigProvider, Tabs } from 'antd';
import SourceCode from 'dumi/theme-default/builtins/SourceCode';
import type { Tab } from '@rc-component/tabs/lib/interface';
import BunLogo from './bun';
import NpmLogo from './npm';
import PnpmLogo from './pnpm';
import YarnLogo from './yarn';
interface InstallProps {
npm?: string;
yarn?: string;
pnpm?: string;
bun?: string;
}
const InstallDependencies: React.FC<InstallProps> = (props) => {
const { npm, yarn, pnpm, bun } = props;
const items: Tab[] = [
{
key: 'npm',
label: 'npm',
children: npm ? <SourceCode lang="bash">{npm}</SourceCode> : null,
icon: <NpmLogo />,
},
{
key: 'yarn',
label: 'yarn',
children: yarn ? <SourceCode lang="bash">{yarn}</SourceCode> : null,
icon: <YarnLogo />,
},
{
key: 'pnpm',
label: 'pnpm',
children: pnpm ? <SourceCode lang="bash">{pnpm}</SourceCode> : null,
icon: <PnpmLogo />,
},
{
key: 'bun',
label: 'Bun',
children: bun ? <SourceCode lang="bash">{bun}</SourceCode> : null,
icon: <BunLogo />,
},
].filter((item) => item.children);
return (
<ConfigProvider theme={{ components: { Tabs: { horizontalMargin: '0' } } }}>
<Tabs className="markdown" size="small" defaultActiveKey="npm" items={items} />
</ConfigProvider>
);
};
export default InstallDependencies;

View File

@@ -1,40 +0,0 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
interface IconProps {
className?: string;
style?: React.CSSProperties;
}
const classNames = createStaticStyles(({ css }) => ({
iconWrap: css`
display: inline-flex;
align-items: center;
line-height: 0;
text-align: center;
vertical-align: -0.125em;
`,
}));
const NpmIcon: React.FC<IconProps> = (props) => {
const { className, style } = props;
return (
<span className={clsx(classNames.iconWrap, className)} style={style}>
<svg
fill="#E53E3E"
focusable="false"
height="1em"
stroke="#E53E3E"
strokeWidth="0"
viewBox="0 0 16 16"
width="1em"
>
<title>npm icon</title>
<path d="M0 0v16h16v-16h-16zM13 13h-2v-8h-3v8h-5v-10h10v10z" />
</svg>
</span>
);
};
export default NpmIcon;

View File

@@ -1,42 +0,0 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
interface IconProps {
className?: string;
style?: React.CSSProperties;
}
const classNames = createStaticStyles(({ css }) => ({
iconWrap: css`
display: inline-flex;
align-items: center;
line-height: 0;
text-align: center;
vertical-align: -0.125em;
`,
}));
const PnpmIcon: React.FC<IconProps> = (props) => {
const { className, style } = props;
return (
<span className={clsx(classNames.iconWrap, className)} style={style}>
<svg
aria-hidden="true"
fill="#F69220"
focusable="false"
height="1em"
role="img"
stroke="#F69220"
strokeWidth="0"
viewBox="0 0 24 24"
width="1em"
>
<title>pnpm icon</title>
<path d="M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z" />
</svg>
</span>
);
};
export default PnpmIcon;

View File

@@ -1,41 +0,0 @@
import React from 'react';
import { createStaticStyles } from 'antd-style';
import { clsx } from 'clsx';
interface IconProps {
className?: string;
style?: React.CSSProperties;
}
const classNames = createStaticStyles(({ css }) => ({
iconWrap: css`
display: inline-flex;
align-items: center;
line-height: 0;
text-align: center;
vertical-align: -0.125em;
`,
}));
const YarnIcon: React.FC<IconProps> = (props) => {
const { className, style } = props;
return (
<span className={clsx(classNames.iconWrap, className)} style={style}>
<svg
aria-hidden="true"
fill="#2C8EBB"
focusable="false"
height="1em"
stroke="#2C8EBB"
strokeWidth="0"
viewBox="0 0 496 512"
width="1em"
>
<title>yarn icon</title>
<path d="M393.9 345.2c-39 9.3-48.4 32.1-104 47.4 0 0-2.7 4-10.4 5.8-13.4 3.3-63.9 6-68.5 6.1-12.4.1-19.9-3.2-22-8.2-6.4-15.3 9.2-22 9.2-22-8.1-5-9-9.9-9.8-8.1-2.4 5.8-3.6 20.1-10.1 26.5-8.8 8.9-25.5 5.9-35.3.8-10.8-5.7.8-19.2.8-19.2s-5.8 3.4-10.5-3.6c-6-9.3-17.1-37.3 11.5-62-1.3-10.1-4.6-53.7 40.6-85.6 0 0-20.6-22.8-12.9-43.3 5-13.4 7-13.3 8.6-13.9 5.7-2.2 11.3-4.6 15.4-9.1 20.6-22.2 46.8-18 46.8-18s12.4-37.8 23.9-30.4c3.5 2.3 16.3 30.6 16.3 30.6s13.6-7.9 15.1-5c8.2 16 9.2 46.5 5.6 65.1-6.1 30.6-21.4 47.1-27.6 57.5-1.4 2.4 16.5 10 27.8 41.3 10.4 28.6 1.1 52.7 2.8 55.3.8 1.4 13.7.8 36.4-13.2 12.8-7.9 28.1-16.9 45.4-17 16.7-.5 17.6 19.2 4.9 22.2zM496 256c0 136.9-111.1 248-248 248S0 392.9 0 256 111.1 8 248 8s248 111.1 248 248zm-79.3 75.2c-1.7-13.6-13.2-23-28-22.8-22 .3-40.5 11.7-52.8 19.2-4.8 3-8.9 5.2-12.4 6.8 3.1-44.5-22.5-73.1-28.7-79.4 7.8-11.3 18.4-27.8 23.4-53.2 4.3-21.7 3-55.5-6.9-74.5-1.6-3.1-7.4-11.2-21-7.4-9.7-20-13-22.1-15.6-23.8-1.1-.7-23.6-16.4-41.4 28-12.2.9-31.3 5.3-47.5 22.8-2 2.2-5.9 3.8-10.1 5.4h.1c-8.4 3-12.3 9.9-16.9 22.3-6.5 17.4.2 34.6 6.8 45.7-17.8 15.9-37 39.8-35.7 82.5-34 36-11.8 73-5.6 79.6-1.6 11.1 3.7 19.4 12 23.8 12.6 6.7 30.3 9.6 43.9 2.8 4.9 5.2 13.8 10.1 30 10.1 6.8 0 58-2.9 72.6-6.5 6.8-1.6 11.5-4.5 14.6-7.1 9.8-3.1 36.8-12.3 62.2-28.7 18-11.7 24.2-14.2 37.6-17.4 12.9-3.2 21-15.1 19.4-28.2z" />
</svg>
</span>
);
};
export default YarnIcon;

View File

@@ -1,55 +0,0 @@
import * as React from 'react';
import { Link } from 'dumi';
import useLocale from '../../../hooks/useLocale';
type LinkProps = Parameters<typeof Link>[0];
export interface LocaleLinkProps extends LinkProps {
sourceType: 'a' | 'Link';
}
const LocaleLink: React.FC<React.PropsWithChildren<LocaleLinkProps>> = ({
sourceType,
to,
...props
}) => {
const Component = sourceType === 'a' ? 'a' : Link;
const [, localeType] = useLocale();
const localeTo = React.useMemo(() => {
if (!to || typeof to !== 'string') {
return to;
}
// Auto locale switch
const cells = to.match(/(\/[^#]*)(#.*)?/);
if (cells) {
let path = cells[1].replace(/\/$/, '');
const hash = cells[2] || '';
if (localeType === 'cn' && !path.endsWith('-cn')) {
path = `${path}-cn`;
} else if (localeType === 'en' && path.endsWith('-cn')) {
path = path.replace(/-cn$/, '');
}
return `${path}${hash}`;
}
return to;
}, [localeType, to]);
const linkProps: LocaleLinkProps = {
...props,
} as LocaleLinkProps;
if (to) {
linkProps.to = localeTo;
}
return <Component {...linkProps} />;
};
export default LocaleLink;

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