Merge branch 'master' into TooltipRef

This commit is contained in:
afc163 2025-01-21 11:44:30 +08:00 committed by GitHub
commit 822420d536
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1730 changed files with 116773 additions and 59704 deletions

View File

@ -1,5 +0,0 @@
{
"installCommand": "npm-install",
"sandboxes": ["antd-reproduction-template-forked-jyh2k9"],
"node": "18"
}

View File

@ -11,15 +11,15 @@ module.exports = {
],
modulePattern: [
{
pattern: /ConfigContext.*renderEmpty/ms,
pattern: /ConfigContext.*renderEmpty/s,
module: '../empty',
},
{
pattern: /ConfigConsumer.*renderEmpty/ms,
pattern: /ConfigConsumer.*renderEmpty/s,
module: '../empty',
},
{
pattern: /config-provider\/context.*renderEmpty/ms,
pattern: /config-provider\/context.*renderEmpty/s,
module: '../empty',
},
],

View File

@ -43,7 +43,7 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
z-index: 999999;
box-shadow: 0 0 0 1px #fff;
pointer-events: none;
left: ${markPos[0] - MARK_BORDER_SIZE}px;
inset-inline-start: ${markPos[0] - MARK_BORDER_SIZE}px;
top: ${markPos[1] - MARK_BORDER_SIZE}px;
width: ${markPos[2] + MARK_BORDER_SIZE * 2}px;
height: ${markPos[3] + MARK_BORDER_SIZE * 2}px;
@ -66,7 +66,7 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb
export interface SemanticPreviewProps {
semantics: { name: string; desc: string; version?: string }[];
children: React.ReactElement;
children: React.ReactElement<any>;
height?: number;
}
@ -97,7 +97,7 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
// ======================== Hover =========================
const containerRef = React.useRef<HTMLDivElement>(null);
const timerRef = React.useRef<ReturnType<typeof setTimeout>>();
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(null);
const [positionMotion, setPositionMotion] = React.useState<boolean>(false);
const [hoverSemantic, setHoverSemantic] = React.useState<string | null>(null);
@ -111,12 +111,14 @@ const SemanticPreview: React.FC<SemanticPreviewProps> = (props) => {
const targetElement = containerRef.current?.querySelector<HTMLElement>(`.${targetClassName}`);
const containerRect = containerRef.current?.getBoundingClientRect();
const targetRect = targetElement?.getBoundingClientRect();
setMarkPos([
(targetRect?.left || 0) - (containerRect?.left || 0),
(targetRect?.top || 0) - (containerRect?.top || 0),
targetRect?.width || 0,
targetRect?.height || 0,
]);
timerRef.current = setTimeout(() => {
setPositionMotion(true);
}, 10);

View File

@ -16,5 +16,5 @@
html {
scrollbar-width: thin;
scrollbar-color: unset;
scrollbar-color: #eaeaea transparent;
}

View File

@ -9,22 +9,22 @@ function use<T>(promise: PromiseLike<T>): T {
}
if (internal.status === 'rejected') {
throw internal.reason;
} else if (internal.status === 'pending') {
throw internal;
} else {
internal.status = 'pending';
internal.then(
(result) => {
internal.status = 'fulfilled';
internal.value = result;
},
(reason) => {
internal.status = 'rejected';
internal.reason = reason;
},
);
}
if (internal.status === 'pending') {
throw internal;
}
internal.status = 'pending';
internal.then(
(result) => {
internal.status = 'fulfilled';
internal.value = result;
},
(reason) => {
internal.status = 'rejected';
internal.reason = reason;
},
);
throw internal;
}
export default use;

View File

@ -1,4 +1,5 @@
import fetch from 'cross-fetch';
import use from '../use';
import FetchCache from './cache';

View File

@ -1,5 +1,6 @@
import { useLocation as useDumiLocation } from 'dumi';
import * as React from 'react';
import { useLocation as useDumiLocation } from 'dumi';
import useLocale from './useLocale';
function clearPath(path: string) {

View File

@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import type { MenuProps } from 'antd';
import { Tag, version } from 'antd';
import { Space, Tag, version } from 'antd';
import { createStyles } from 'antd-style';
import classnames from 'classnames';
import { useFullSidebarData, useSidebarData } from 'dumi';
@ -22,7 +22,6 @@ const useStyle = createStyles(({ css, token }) => ({
margin-inline-end: 0;
`,
subtitle: css`
margin-inline-start: ${token.marginXS}px;
font-weight: normal;
font-size: ${token.fontSizeSM}px;
opacity: 0.8;
@ -46,10 +45,10 @@ const MenuItemLabelWithTag: React.FC<MenuItemLabelProps> = (props) => {
if (!before && !after) {
return (
<Link to={`${link}${search}`} className={classnames(className, { [styles.link]: tag })}>
<span>
{title}
<Space>
<span>{title}</span>
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
</span>
</Space>
{tag && (
<Tag
bordered={false}

View File

@ -89,7 +89,6 @@ const useThemeAnimation = () => {
.startViewTransition(async () => {
// wait for theme change end
while (colorBgElevated === animateRef.current.colorBgElevated) {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 1000 / 60);
});
@ -99,6 +98,7 @@ const useThemeAnimation = () => {
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)`,

View File

@ -1,3 +1,3 @@
// must be .js file, can't modify to be .ts file!
// eslint-disable-next-line no-restricted-exports
export { default } from './theme/common/Loading';

View File

@ -1,7 +1,9 @@
import { HomeOutlined } from '@ant-design/icons';
import { Link, useLocation } from 'dumi';
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 {

View File

@ -1,6 +1,6 @@
import React, { useContext } from 'react';
import { Badge, Carousel, Skeleton, Typography } from 'antd';
import { createStyles, useTheme } from 'antd-style';
import { Badge, Carousel, Flex, Skeleton, Typography } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import useLocale from '../../../hooks/useLocale';
@ -36,6 +36,7 @@ const useStyle = createStyles(({ token, css, cx }) => {
cardItem: css`
&:hover {
box-shadow: ${token.boxShadowCard};
border-color: transparent;
}
`,
sliderItem: css`
@ -57,6 +58,9 @@ const useStyle = createStyles(({ token, css, cx }) => {
}
`,
carousel,
bannerBg: css`
height: ${token.fontSize}px;
`,
};
});
@ -66,8 +70,8 @@ interface RecommendItemProps {
icons: Icon[];
className?: string;
}
const RecommendItem: React.FC<RecommendItemProps> = ({ extra, index, icons, className }) => {
const token = useTheme();
const { styles } = useStyle();
if (!extra) {
@ -87,10 +91,10 @@ const RecommendItem: React.FC<RecommendItemProps> = ({ extra, index, icons, clas
<Typography.Paragraph type="secondary" style={{ flex: 'auto' }}>
{extra.description}
</Typography.Paragraph>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Flex justify="space-between" align="center">
<Typography.Text>{extra.date}</Typography.Text>
{icon && <img src={icon.href} style={{ height: token.fontSize }} alt="banner" />}
</div>
{icon && <img src={icon.href} draggable={false} className={styles.bannerBg} alt="banner" />}
</Flex>
</a>
);
@ -109,7 +113,7 @@ export const BannerRecommendsFallback: React.FC = () => {
const { isMobile } = useContext(SiteContext);
const { styles } = useStyle();
const list = Array(3).fill(1);
const list = new Array(3).fill(1);
return isMobile ? (
<Carousel className={styles.carousel}>
@ -137,7 +141,7 @@ const BannerRecommends: React.FC = () => {
const data = useSiteData();
const extras = data?.extras?.[lang];
const icons = data?.icons || [];
const first3 = !extras || extras.length === 0 ? Array(3).fill(null) : extras.slice(0, 3);
const first3 = !extras || extras.length === 0 ? new Array(3).fill(null) : extras.slice(0, 3);
if (!data) {
return <BannerRecommendsFallback />;

View File

@ -12,7 +12,7 @@ import {
Tour,
Typography,
} from 'antd';
import { createStyles, css, useTheme } from 'antd-style';
import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
import dayjs from 'dayjs';
@ -104,6 +104,13 @@ const useStyle = () => {
justify-content: center;
`,
carousel,
componentsList: css`
width: 100%;
overflow: hidden;
`,
mobileComponentsList: css`
margin: 0 ${token.margin}px;
`,
};
})();
};
@ -119,7 +126,7 @@ const ComponentItem: React.FC<ComponentItemProps> = ({ title, node, type, index
{/* Decorator */}
<div
className={styles.cardCircle}
style={{ right: (index % 2) * -20 - 20, bottom: (index % 3) * -40 - 20 }}
style={{ insetInlineEnd: (index % 2) * -20 - 20, bottom: (index % 3) * -40 - 20 }}
/>
{/* Title */}
@ -142,7 +149,6 @@ interface ComponentItemProps {
}
const ComponentsList: React.FC = () => {
const token = useTheme();
const { styles } = useStyle();
const [locale] = useLocale(locales);
const { isMobile } = useContext(SiteContext);
@ -251,7 +257,7 @@ const ComponentsList: React.FC = () => {
style={{ width: 400 }}
message="Ant Design 5.0"
description={locale.sampleContent}
closable
closable={{ closeIcon: true, disabled: true }}
/>
),
},
@ -260,21 +266,33 @@ const ComponentsList: React.FC = () => {
);
return isMobile ? (
<div style={{ margin: '0 16px' }}>
<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={index} />
<ComponentItem
title={title}
node={node}
type={type}
index={index}
key={`mobile-item-${index}`}
/>
))}
</Carousel>
</div>
) : (
<div style={{ width: '100%', overflow: 'hidden', display: 'flex', justifyContent: 'center' }}>
<div style={{ display: 'flex', alignItems: 'stretch', columnGap: token.paddingLG }}>
<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={index} />
<ComponentItem
title={title}
node={node}
type={type}
index={index}
key={`desktop-item-${index}`}
/>
))}
</div>
</div>
</Flex>
</Flex>
);
};

View File

@ -1,10 +1,11 @@
import React, { useContext } from 'react';
import { Col, Row, Typography } from 'antd';
import { createStyles, useTheme } from 'antd-style';
import { Link, useLocation } from 'dumi';
import { useLocation } from 'dumi';
import useDark from '../../../hooks/useDark';
import useLocale from '../../../hooks/useLocale';
import Link from '../../../theme/common/Link';
import SiteContext from '../../../theme/slots/SiteContext';
import * as utils from '../../../theme/utils';
@ -133,7 +134,7 @@ const DesignFramework: React.FC = () => {
<Col key={index} span={colSpan}>
<Link to={path}>
<div className={styles.card}>
<img alt={title} src={img} />
<img draggable={false} alt={title} src={img} />
<Typography.Title
level={4}
@ -157,7 +158,12 @@ const DesignFramework: React.FC = () => {
return (
<Col key={index} span={colSpan}>
<a className={styles.cardMini} target="_blank" href={url} rel="noreferrer">
<img alt={title} src={img} style={{ transform: `scale(${imgScale})` }} />
<img
draggable={false}
alt={title}
src={img}
style={{ transform: `scale(${imgScale})` }}
/>
<Typography.Title
level={4}

View File

@ -1,9 +1,11 @@
import React, { Suspense } from 'react';
import { Button, ConfigProvider, Flex, Typography } from 'antd';
import { ConfigProvider, Flex, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { Link, useLocation } from 'dumi';
import classNames from 'classnames';
import { useLocation } from 'dumi';
import useLocale from '../../../../hooks/useLocale';
import LinkButton from '../../../../theme/common/LinkButton';
import SiteContext from '../../../../theme/slots/SiteContext';
import * as utils from '../../../../theme/utils';
import GroupMaskLayer from '../GroupMaskLayer';
@ -26,6 +28,7 @@ const locales = {
const useStyle = () => {
const { direction } = React.useContext(ConfigProvider.ConfigContext);
const { isMobile } = React.useContext(SiteContext);
const isRTL = direction === 'rtl';
return createStyles(({ token, css, cx }) => {
const textShadow = `0 0 4px ${token.colorBgContainer}`;
@ -101,11 +104,23 @@ const useStyle = () => {
btnWrap: css`
margin-bottom: ${token.marginXL}px;
`,
bgImg: css`
position: absolute;
width: 240px;
`,
bgImgTop: css`
top: 0;
inset-inline-start: ${isMobile ? '-120px' : 0};
`,
bgImgBottom: css`
bottom: 120px;
inset-inline-end: ${isMobile ? 0 : '40%'};
`,
};
})();
};
const PreviewBanner: React.FC<React.PropsWithChildren> = (props) => {
const PreviewBanner: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
const { children } = props;
const [locale] = useLocale(locales);
const { styles } = useStyle();
@ -117,15 +132,17 @@ const PreviewBanner: React.FC<React.PropsWithChildren> = (props) => {
<GroupMaskLayer>
{/* Image Left Top */}
<img
style={{ position: 'absolute', left: isMobile ? -120 : 0, top: 0, width: 240 }}
src="https://gw.alipayobjects.com/zos/bmw-prod/49f963db-b2a8-4f15-857a-270d771a1204.svg"
alt="bg"
src="https://gw.alipayobjects.com/zos/bmw-prod/49f963db-b2a8-4f15-857a-270d771a1204.svg"
draggable={false}
className={classNames(styles.bgImg, styles.bgImgTop)}
/>
{/* Image Right Top */}
<img
style={{ position: 'absolute', right: isMobile ? 0 : '40%', bottom: 120, width: 240 }}
src="https://gw.alipayobjects.com/zos/bmw-prod/e152223c-bcae-4913-8938-54fda9efe330.svg"
alt="bg"
src="https://gw.alipayobjects.com/zos/bmw-prod/e152223c-bcae-4913-8938-54fda9efe330.svg"
draggable={false}
className={classNames(styles.bgImg, styles.bgImgBottom)}
/>
<div className={styles.holder}>
@ -143,14 +160,19 @@ const PreviewBanner: React.FC<React.PropsWithChildren> = (props) => {
<p>{locale.slogan}</p>
</Typography>
<Flex gap="middle" className={styles.btnWrap}>
<Link to={utils.getLocalizedPathname('/components/overview/', isZhCN, search)}>
<Button size="large" type="primary">
{locale.start}
</Button>
</Link>
<Link to={utils.getLocalizedPathname('/docs/spec/introduce/', isZhCN, search)}>
<Button size="large">{locale.designLanguage}</Button>
</Link>
<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>

View File

@ -14,7 +14,7 @@ const useStyle = createStyles(({ token }) => ({
image: css`
transition: all ${token.motionDurationSlow};
position: absolute;
left: 0;
inset-inline-start: 0;
top: 0;
height: 100%;
width: 100%;
@ -64,10 +64,11 @@ const BackgroundImage: React.FC<BackgroundImageProps> = ({ colorPrimary, isLight
<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=""
alt="bg"
/>
</picture>
);

View File

@ -1,12 +1,14 @@
import React, { useEffect, useState } from 'react';
import { ColorPicker, Flex, Input } from 'antd';
import type { ColorPickerProps, GetProp } from 'antd';
import { createStyles } from 'antd-style';
import type { Color } from 'antd/es/color-picker';
import { generateColor } from 'antd/es/color-picker/util';
import classNames from 'classnames';
import { PRESET_COLORS } from './colorUtil';
type Color = Extract<GetProp<ColorPickerProps, 'value'>, string | { cleared: any }>;
const useStyle = createStyles(({ token, css }) => ({
color: css`
width: ${token.controlHeightLG / 2}px;
@ -34,13 +36,13 @@ const useStyle = createStyles(({ token, css }) => ({
`,
}));
export interface ColorPickerProps {
export interface ThemeColorPickerProps {
id?: string;
value?: string | Color;
onChange?: (value?: Color | string) => void;
}
const DebouncedColorPicker: React.FC<React.PropsWithChildren<ColorPickerProps>> = (props) => {
const DebouncedColorPicker: React.FC<React.PropsWithChildren<ThemeColorPickerProps>> = (props) => {
const { value: color, children, onChange } = props;
const [value, setValue] = useState(color);
@ -59,14 +61,14 @@ const DebouncedColorPicker: React.FC<React.PropsWithChildren<ColorPickerProps>>
<ColorPicker
value={value}
onChange={setValue}
presets={[{ label: 'PresetColors', colors: PRESET_COLORS }]}
presets={[{ label: 'PresetColors', key: 'PresetColors', colors: PRESET_COLORS }]}
>
{children}
</ColorPicker>
);
};
const ThemeColorPicker: React.FC<ColorPickerProps> = ({ value, onChange, id }) => {
const ThemeColorPicker: React.FC<ThemeColorPickerProps> = ({ value, onChange, id }) => {
const { styles } = useStyle();
const matchColors = React.useMemo(() => {
@ -100,7 +102,6 @@ const ThemeColorPicker: React.FC<ColorPickerProps> = ({ value, onChange, id }) =
<Flex gap="middle">
{matchColors.map<React.ReactNode>(({ color, active, picker }) => {
const colorNode = (
// eslint-disable-next-line jsx-a11y/label-has-associated-control
<label
key={color}
className={classNames(styles.color, { [styles.colorActive]: active })}
@ -114,6 +115,7 @@ const ThemeColorPicker: React.FC<ColorPickerProps> = ({ value, onChange, id }) =
<input
type="radio"
name={picker ? 'picker' : 'color'}
aria-label={color}
tabIndex={picker ? -1 : 0}
onClick={(e) => e.stopPropagation()}
/>

View File

@ -113,7 +113,7 @@ const MobileCarousel: React.FC<MobileCarouselProps> = (props) => {
<Carousel className={styles.carousel} afterChange={setCurrentSlider}>
{mobileImageConfigList.map((item, index) => (
<div key={index}>
<img src={item.imageSrc} className={styles.img} alt="" />
<img draggable={false} src={item.imageSrc} className={styles.img} alt="carousel" />
</div>
))}
</Carousel>

View File

@ -15,7 +15,7 @@ const RadiusPicker: React.FC<RadiusPickerProps> = ({ id, value, onChange }) => (
style={{ width: 120 }}
min={0}
formatter={(val) => `${val}px`}
parser={(str) => (str ? parseFloat(str) : (str as any))}
parser={(str) => str?.replace('px', '') as unknown as number}
id={id}
/>
<Slider

View File

@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
import * as React from 'react';
import { Flex } from 'antd';
import { createStyles } from 'antd-style';

View File

@ -1,6 +1,8 @@
import type { Color } from 'antd/es/color-picker';
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';

View File

@ -6,11 +6,10 @@ import {
HomeOutlined,
QuestionCircleOutlined,
} from '@ant-design/icons';
import { TinyColor } from '@ctrl/tinycolor';
import type { MenuProps, ThemeConfig } from 'antd';
import { FastColor } from '@ant-design/fast-color';
import type { ColorPickerProps, GetProp, MenuProps, ThemeConfig } from 'antd';
import {
Breadcrumb,
Button,
Card,
ConfigProvider,
Flex,
@ -22,14 +21,13 @@ import {
Typography,
} from 'antd';
import { createStyles } from 'antd-style';
import type { Color } from 'antd/es/color-picker';
import { generateColor } from 'antd/es/color-picker/util';
import classNames from 'classnames';
import { useLocation } from 'dumi';
import useDark from '../../../../hooks/useDark';
import useLocale from '../../../../hooks/useLocale';
import Link from '../../../../theme/common/Link';
import LinkButton from '../../../../theme/common/LinkButton';
import SiteContext from '../../../../theme/slots/SiteContext';
import { getLocalizedPathname } from '../../../../theme/utils';
import Group from '../Group';
@ -42,10 +40,13 @@ 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 = () => {
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console
console.log('Demo Token:', theme.useToken());
}
return null;
@ -195,23 +196,23 @@ const useStyle = createStyles(({ token, css, cx }) => {
position: absolute;
`,
leftTopImagePos: css`
left: 0;
inset-inline-start: 0;
top: -100px;
height: 500px;
`,
rightBottomPos: css`
right: 0;
inset-inline-end: 0;
bottom: -100px;
height: 287px;
`,
leftTopImage: css`
left: 50%;
inset-inline-start: 50%;
transform: translate3d(-900px, 0, 0);
top: -100px;
height: 500px;
`,
rightBottomImage: css`
right: 50%;
inset-inline-end: 50%;
transform: translate3d(750px, 0, 0);
bottom: -100px;
height: 287px;
@ -265,7 +266,7 @@ const sideMenuItems: MenuProps['items'] = [
// ============================= Theme =============================
function getTitleColor(colorPrimary: string | Color, isLight?: boolean) {
function getTitleColor(colorPrimary: Color, isLight?: boolean) {
if (!isLight) {
return '#FFF';
}
@ -290,7 +291,7 @@ function getTitleColor(colorPrimary: string | Color, isLight?: boolean) {
interface ThemeData {
themeType: THEME;
colorPrimary: string | Color;
colorPrimary: Color;
borderRadius: number;
compact: 'default' | 'compact';
}
@ -323,7 +324,7 @@ const ThemesInfo: Record<THEME, Partial<ThemeData>> = {
const normalize = (value: number) => value / 255;
function rgbToColorMatrix(color: string) {
const rgb = new TinyColor(color).toRgb();
const rgb = new FastColor(color).toRgb();
const { r, g, b } = rgb;
const invertValue = normalize(r) * 100;
@ -474,7 +475,7 @@ const Theme: React.FC = () => {
filter:
closestColor === DEFAULT_COLOR ? undefined : rgbToColorMatrix(logoColor),
}}
alt=""
alt="antd logo"
/>
</div>
<h1>Ant Design 5.0</h1>
@ -518,14 +519,15 @@ const Theme: React.FC = () => {
title={locale.myTheme}
extra={
<Flex gap="small">
<Link to={getLocalizedPathname('/theme-editor', isZhCN, search)}>
<Button type="default">{locale.toDef}</Button>
</Link>
<Link
<LinkButton to={getLocalizedPathname('/theme-editor', isZhCN, search)}>
{locale.toDef}
</LinkButton>
<LinkButton
type="primary"
to={getLocalizedPathname('/docs/react/customize-theme', isZhCN, search)}
>
<Button type="primary">{locale.toUse}</Button>
</Link>
{locale.toUse}
</LinkButton>
</Flex>
}
>

View File

@ -5,8 +5,8 @@ import { createStyles, css } from 'antd-style';
import useDark from '../../hooks/useDark';
import useLocale from '../../hooks/useLocale';
import BannerRecommends from './components/BannerRecommends';
import PreviewBanner from './components/PreviewBanner';
import Group from './components/Group';
import PreviewBanner from './components/PreviewBanner';
const ComponentsList = React.lazy(() => import('./components/ComponentsList'));
const DesignFramework = React.lazy(() => import('./components/DesignFramework'));
@ -15,7 +15,7 @@ const Theme = React.lazy(() => import('./components/Theme'));
const useStyle = createStyles(() => ({
image: css`
position: absolute;
left: 0;
inset-inline-start: 0;
top: -50px;
height: 160px;
`,
@ -78,12 +78,13 @@ const Homepage: React.FC = () => {
<Group
title={locale.designTitle}
description={locale.designDesc}
background={isRootDark ? 'rgb(57, 63, 74)' : '#F5F8FF'}
background={isRootDark ? '#393F4A' : '#F5F8FF'}
decoration={
<img
draggable={false}
className={styles.image}
src="https://gw.alipayobjects.com/zos/bmw-prod/ba37a413-28e6-4be4-b1c5-01be1a0ebb1c.svg"
alt=""
alt="bg"
/>
}
>

View File

@ -1,5 +1,5 @@
import React, { Suspense, useEffect } from 'react';
import { Button, message, Skeleton } from 'antd';
import { Button, App, Skeleton } from 'antd';
import { enUS, zhCN } from 'antd-token-previewer';
import type { ThemeConfig } from 'antd/es/config-provider/context';
import { Helmet } from 'dumi';
@ -36,7 +36,7 @@ const locales = {
const ANT_DESIGN_V5_THEME_EDITOR_THEME = 'ant-design-v5-theme-editor-theme';
const CustomTheme: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage();
const { message } = App.useApp();
const [locale, lang] = useLocale(locales);
const [theme, setTheme] = React.useState<ThemeConfig>({});
@ -51,7 +51,7 @@ const CustomTheme: React.FC = () => {
const handleSave = () => {
localStorage.setItem(ANT_DESIGN_V5_THEME_EDITOR_THEME, JSON.stringify(theme));
messageApi.success(locale.saveSuccessfully);
message.success(locale.saveSuccessfully);
};
return (
@ -60,7 +60,6 @@ const CustomTheme: React.FC = () => {
<title>{`${locale.title} - Ant Design`}</title>
<meta property="og:title" content={`${locale.title} - Ant Design`} />
</Helmet>
{contextHolder}
<Suspense fallback={<Skeleton style={{ margin: 24 }} />}>
<ThemeEditor
advanced

View File

@ -72,9 +72,9 @@ function rehypeAntd(): UnifiedTransformer<HastRoot> {
if (typeof lang === 'string' && lang.startsWith('sandpack')) {
const code = (node.children[0] as any).value as string;
const configRegx = /^const sandpackConfig = ([\S\s]*?});/;
const configRegx = /^const sandpackConfig = ([\s\S]*?});/;
const [configString] = code.match(configRegx) || [];
// eslint-disable-next-line no-eval
/* 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') {

View File

@ -50,8 +50,8 @@
.mirror-modal-dialog {
position: fixed;
top: 120px;
left: 0;
right: 0;
inset-inline-start: 0;
inset-inline-end: 0;
margin: 0 auto;
width: 420px;
display: flex;
@ -141,12 +141,12 @@
const title = document.createElement('div');
title.className = 'mirror-modal-title';
title.innerText = '提示';
title.textContent = '提示';
dialog.append(title);
const content = document.createElement('div');
content.className = 'mirror-modal-content';
content.innerText = '🚀 国内用户推荐访问国内镜像以获得极速体验~';
content.textContent = '🚀 国内用户推荐访问国内镜像以获得极速体验~';
dialog.append(content);
const btnWrapper = document.createElement('div');
@ -155,7 +155,7 @@
const cancelBtn = document.createElement('a');
cancelBtn.className = 'mirror-modal-cancel-btn mirror-modal-btn';
cancelBtn.innerText = '7 天内不再显示';
cancelBtn.textContent = '7 天内不再显示';
btnWrapper.append(cancelBtn);
cancelBtn.addEventListener('click', () => {
window.localStorage.setItem(ANTD_DOT_NOT_SHOW_MIRROR_MODAL, new Date().toISOString());
@ -167,7 +167,7 @@
const confirmBtn = document.createElement('a');
confirmBtn.className = 'mirror-modal-confirm-btn mirror-modal-btn';
confirmBtn.href = window.location.href.replace(window.location.host, 'ant-design.antgroup.com');
confirmBtn.innerText = '🚀 立刻前往';
confirmBtn.textContent = '🚀 立刻前往';
btnWrapper.append(confirmBtn);
document.body.append(modal);

View File

@ -24,7 +24,6 @@ interface NewToken {
// 通过给 antd-style 扩展 CustomToken 对象类型定义,可以为 useTheme 中增加相应的 token 对象
declare module 'antd-style' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CustomToken extends NewToken {}
}

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import * as React from 'react';
import type { ColorInput } from '@ctrl/tinycolor';
import { TinyColor } from '@ctrl/tinycolor';
// @ts-ignore
import { TinyColor } from 'dumi-plugin-color-chunk/component';
import { createStyles } from 'antd-style';
const useStyle = createStyles(({ token, css }) => ({
@ -22,7 +22,7 @@ const useStyle = createStyles(({ token, css }) => ({
}));
interface ColorChunkProps {
value?: ColorInput;
value: any;
}
const ColorChunk: React.FC<React.PropsWithChildren<ColorChunkProps>> = (props) => {

View File

@ -1,12 +1,13 @@
import React from 'react';
import { EditOutlined, GithubOutlined } from '@ant-design/icons';
import { EditOutlined, GithubOutlined, HistoryOutlined } from '@ant-design/icons';
import type { GetProp } from 'antd';
import { Descriptions, theme, Tooltip, Typography } from 'antd';
import { Descriptions, Flex, theme, Tooltip, Typography } from 'antd';
import { createStyles, css } from 'antd-style';
import kebabCase from 'lodash/kebabCase';
import CopyToClipboard from 'react-copy-to-clipboard';
import useLocale from '../../../hooks/useLocale';
import ComponentChangelog from '../../common/ComponentChangelog';
const locales = {
cn: {
@ -16,6 +17,7 @@ const locales = {
source: '源码',
docs: '文档',
edit: '编辑此页',
changelog: '更新日志',
version: '版本',
},
en: {
@ -25,6 +27,7 @@ const locales = {
source: 'Source',
docs: 'Docs',
edit: 'Edit this page',
changelog: 'Changelog',
version: 'Version',
},
};
@ -43,7 +46,7 @@ const useStyle = createStyles(({ token }) => ({
align-items: center;
column-gap: ${token.paddingXXS}px;
border-radius: ${token.borderRadiusSM}px;
padding-inline: ${token.paddingXXS}px;
padding-inline: ${token.paddingXXS}px !important;
transition: all ${token.motionDurationSlow} !important;
font-family: ${token.codeFamily};
color: ${token.colorTextSecondary} !important;
@ -70,6 +73,9 @@ const useStyle = createStyles(({ token }) => ({
semicolon: css`
color: ${token.colorText};
`,
icon: css`
margin-inline-end: ${token.marginXXS}px;
`,
}));
export interface ComponentMetaProps {
@ -116,12 +122,21 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
return [source, source];
}, [component, source]);
const transformComponentName = (componentName: string) => {
if (componentName === 'Notification' || componentName === 'Message') {
return componentName.toLowerCase();
}
return componentName;
};
// ======================== Render ========================
const importList = [
<span key="import" className={styles.import}>
import
</span>,
<span key="component" className={styles.component}>{`{ ${component} }`}</span>,
<span key="component" className={styles.component}>{`{ ${transformComponentName(
component,
)} }`}</span>,
<span key="from" className={styles.from}>
from
</span>,
@ -139,7 +154,9 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
colon={false}
column={1}
style={{ marginTop: token.margin }}
labelStyle={{ paddingInlineEnd: token.padding, width: 56 }}
styles={{
label: { paddingInlineEnd: token.padding, width: 56 },
}}
items={
[
{
@ -162,7 +179,7 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
label: locale.source,
children: (
<Typography.Link className={styles.code} href={filledSource} target="_blank">
<GithubOutlined style={{ marginInlineEnd: 4 }} />
<GithubOutlined className={styles.icon} />
<span>{abbrSource}</span>
</Typography.Link>
),
@ -170,14 +187,22 @@ const ComponentMeta: React.FC<ComponentMetaProps> = (props) => {
filename && {
label: locale.docs,
children: (
<Typography.Link
className={styles.code}
href={`${branchUrl}${filename}`}
target="_blank"
>
<EditOutlined style={{ marginInlineEnd: 4 }} />
<span>{locale.edit}</span>
</Typography.Link>
<Flex justify="flex-start" align="center" gap="middle">
<Typography.Link
className={styles.code}
href={`${branchUrl}${filename}`}
target="_blank"
>
<EditOutlined className={styles.icon} />
<span>{locale.edit}</span>
</Typography.Link>
<ComponentChangelog>
<Typography.Link className={styles.code}>
<HistoryOutlined className={styles.icon} />
<span>{locale.changelog}</span>
</Typography.Link>
</ComponentChangelog>
</Flex>
),
},
isVersionNumber(version) && {

View File

@ -198,46 +198,59 @@ const Overview: React.FC = () => {
</Title>
<Row gutter={[24, 24]}>
{components.map((component) => {
let url = component.link;
/** 是否是外链 */
const isExternalLink = component.link.startsWith('http');
let url = `${component.link}`;
const isExternalLink = url.startsWith('http');
if (!isExternalLink) {
url += urlSearch;
}
return (
<Col xs={24} sm={12} lg={8} xl={6} key={component?.title}>
<Link to={url}>
<Card
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>
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
src={
theme.includes('dark') && component.coverDark
? component.coverDark
: component.cover
}
>
<div className={styles.componentsOverviewImg}>
<img
src={
theme.includes('dark') && component.coverDark
? component.coverDark
: component.cover
}
alt={component?.title}
/>
</div>
</Card>
</Link>
alt={component.title}
/>
</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>
);
})}

View File

@ -8,6 +8,7 @@ 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');
@ -108,13 +109,13 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
const data = tokens
.sort(component ? undefined : compare)
.map((name) => {
.map<TokenData>((name) => {
const meta = component
? tokenMeta.components[component].find((item) => item.token === name)
: tokenMeta.global[name];
if (!meta) {
return null;
return null as unknown as TokenData;
}
return {
@ -156,7 +157,7 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
{title}
<Popover
title={null}
popupStyle={{ width: 400 }}
styles={{ root: { width: 400 } }}
content={
<Typography>
{/* <SourceCode lang="jsx">{code}</SourceCode> */}
@ -177,7 +178,7 @@ const SubTokenTable: React.FC<SubTokenTableProps> = (props) => {
</div>
{open && (
<ConfigProvider theme={{ token: { borderRadius: 0 } }}>
<Table
<Table<TokenData>
size="middle"
columns={columns}
bordered

View File

@ -1,19 +1,13 @@
import React, { useContext } from 'react';
import {
BugFilled,
BugOutlined,
CodeFilled,
CodeOutlined,
ExperimentFilled,
ExperimentOutlined,
} from '@ant-design/icons';
import { ConfigProvider, Tooltip } from 'antd';
import classNames from 'classnames';
import { DumiDemoGrid, FormattedMessage } from 'dumi';
import React, { Suspense, useContext } from 'react';
import { BugOutlined, CodeOutlined, ExperimentOutlined } from '@ant-design/icons';
import { ConfigProvider, Tooltip, Button } from 'antd';
import { DumiDemoGrid, FormattedMessage, DumiDemo } from 'dumi';
import { css, Global } from '@emotion/react';
import useLayoutState from '../../../hooks/useLayoutState';
import useLocale from '../../../hooks/useLocale';
import DemoContext from '../../slots/DemoContext';
import DemoFallback from '../Previewer/DemoFallback';
const locales = {
cn: {
@ -33,10 +27,6 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
const [expandAll, setExpandAll] = useLayoutState(false);
const [enableCssVar, setEnableCssVar] = useLayoutState(true);
const expandTriggerClass = classNames('code-box-expand-trigger', {
'code-box-expand-trigger-active': expandAll,
});
const handleVisibleToggle = () => {
setShowDebug?.(!showDebug);
};
@ -51,70 +41,85 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
const demos = React.useMemo(
() =>
items.reduce(
(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,
},
});
},
[] as typeof items,
),
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, 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'}`} />
}
>
{expandAll ? (
<CodeFilled className={expandTriggerClass} onClick={handleExpandToggle} />
) : (
<CodeOutlined className={expandTriggerClass} onClick={handleExpandToggle} />
)}
<Button
type="text"
size="small"
icon={<CodeOutlined />}
onClick={handleExpandToggle}
className={expandAll ? 'icon-enabled' : ''}
/>
</Tooltip>
<Tooltip
title={
<FormattedMessage id={`app.component.examples.${showDebug ? 'hide' : 'visible'}`} />
}
>
{showDebug ? (
<BugFilled className={expandTriggerClass} onClick={handleVisibleToggle} />
) : (
<BugOutlined className={expandTriggerClass} onClick={handleVisibleToggle} />
)}
<Button
type="text"
size="small"
icon={<BugOutlined />}
onClick={handleVisibleToggle}
className={showDebug ? 'icon-enabled' : ''}
/>
</Tooltip>
<Tooltip title={enableCssVar ? locale.disableCssVar : locale.enableCssVar}>
{enableCssVar ? (
<ExperimentFilled className={expandTriggerClass} onClick={handleCssVarToggle} />
) : (
<ExperimentOutlined className={expandTriggerClass} onClick={handleCssVarToggle} />
)}
<Button
type="text"
size="small"
icon={<ExperimentOutlined />}
onClick={handleCssVarToggle}
className={enableCssVar ? 'icon-enabled' : ''}
/>
</Tooltip>
</span>
<ConfigProvider theme={{ cssVar: enableCssVar, hashed: !enableCssVar }}>
<DumiDemoGrid items={demos} />
<DumiDemoGrid
items={demos}
demoRender={(item) => (
<Suspense key={item.demo.id} fallback={<DemoFallback />}>
<DumiDemo {...item} />
</Suspense>
)}
/>
</ConfigProvider>
</div>
);

View File

@ -9,39 +9,17 @@ import type { ThemeType } from './IconSearch';
const useStyle = createStyles(({ token, css }) => ({
anticonsList: css`
margin: ${token.marginSM}px 0;
margin: ${token.margin}px 0;
overflow: hidden;
direction: ltr;
list-style: none;
li {
position: relative;
float: left;
width: 16.66%;
height: 100px;
margin: ${token.marginXXS}px 0;
padding: ${token.paddingSM}px 0 0;
overflow: hidden;
color: #555;
text-align: center;
list-style: none;
background-color: inherit;
border-radius: ${token.borderRadiusSM}px;
cursor: pointer;
transition: all ${token.motionDurationSlow} ease-in-out;
.rtl & {
margin: ${token.marginXXS}px 0;
padding: ${token.paddingSM}px 0 0;
}
${token.iconCls} {
margin: ${token.marginSM}px 0 ${token.marginXS}px;
font-size: 36px;
transition: transform ${token.motionDurationSlow} ease-in-out;
will-change: transform;
}
}
display: grid;
grid-gap: ${token.margin}px;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
padding: 0;
`,
copiedCode: css`
padding: 2px 4px;
padding: 0 ${token.paddingXXS}px;
font-size: ${token.fontSizeSM}px;
background-color: ${token.colorBgLayout};
border-radius: ${token.borderRadiusXS}px;

View File

@ -1,6 +1,6 @@
import React from 'react';
import * as AntdIcons from '@ant-design/icons';
import { Badge, message } from 'antd';
import { App, Badge } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import CopyToClipboard from 'react-copy-to-clipboard';
@ -12,15 +12,40 @@ const allIcons: { [key: PropertyKey]: any } = AntdIcons;
const useStyle = createStyles(({ token, css }) => {
const { antCls, iconCls } = token;
return {
item: css`
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: ${token.borderRadiusSM}px;
cursor: pointer;
transition: all ${token.motionDurationSlow} ease-in-out;
${token.iconCls} {
margin: ${token.marginXS}px 0;
font-size: 36px;
transition: transform ${token.motionDurationSlow} ease-in-out;
will-change: transform;
}
&:hover {
color: #fff;
color: ${token.colorWhite};
background-color: ${token.colorPrimary};
${iconCls} {
transform: scale(1.3);
}
${antCls}-badge {
color: #fff;
color: ${token.colorWhite};
}
}
&.TwoTone:hover {
@ -33,13 +58,13 @@ const useStyle = createStyles(({ token, css }) => {
content: 'Copied!';
position: absolute;
top: 0;
left: 0;
inset-inline-start: 0;
width: 100%;
height: 100%;
color: #fff;
line-height: 110px;
line-height: 100px;
color: ${token.colorTextLightSolid};
text-align: center;
background-color: #1677ff;
background-color: ${token.colorPrimary};
opacity: 0;
transition: all ${token.motionDurationSlow} cubic-bezier(0.18, 0.89, 0.32, 1.28);
}
@ -69,6 +94,7 @@ export interface CopyableIconProps {
}
const CopyableIcon: React.FC<CopyableIconProps> = (props) => {
const { message } = App.useApp();
const { name, isNew, justCopied, theme, onCopied } = props;
const { styles } = useStyle();
const onCopy = (text: string, result: boolean) => {
@ -80,7 +106,7 @@ const CopyableIcon: React.FC<CopyableIconProps> = (props) => {
};
return (
<CopyToClipboard text={`<${name} />`} onCopy={onCopy}>
<li className={classNames(theme, styles.item, { copied: justCopied === name })}>
<li className={classNames(theme, styles.iconItem, { copied: justCopied === name })}>
{React.createElement(allIcons[name])}
<span className={styles.anticonCls}>
<Badge dot={isNew}>{name}</Badge>

View File

@ -1,9 +1,9 @@
import type { CSSProperties } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import Icon, * as AntdIcons from '@ant-design/icons';
import type { SegmentedProps } from 'antd';
import { Affix, Empty, Input, Segmented } from 'antd';
import { createStyles, useTheme } from 'antd-style';
import type { SegmentedOptions } from 'antd/es/segmented';
import { useIntl } from 'dumi';
import debounce from 'lodash/debounce';
@ -28,26 +28,6 @@ const useStyle = createStyles(({ token, css }) => ({
`,
}));
const options = (
formatMessage: (values: Record<string, string>) => React.ReactNode,
): SegmentedProps['options'] => [
{
value: ThemeType.Outlined,
icon: <Icon component={OutlinedIcon} />,
label: formatMessage({ id: 'app.docs.components.icon.outlined' }),
},
{
value: ThemeType.Filled,
icon: <Icon component={FilledIcon} />,
label: formatMessage({ id: 'app.docs.components.icon.filled' }),
},
{
value: ThemeType.TwoTone,
icon: <Icon component={TwoToneIcon} />,
label: formatMessage({ id: 'app.docs.components.icon.two-tone' }),
},
];
interface IconSearchState {
theme: ThemeType;
searchKey: string;
@ -80,8 +60,8 @@ const IconSearch: React.FC = () => {
let iconList = categories[key as CategoriesKeys];
if (searchKey) {
const matchKey = searchKey
// eslint-disable-next-line prefer-regex-literals
.replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name)
.replace(/^<([a-z]*)\s\/>$/gi, (_, name) => name)
.replace(/(Filled|Outlined|TwoTone)$/, '')
.toLowerCase();
iconList = iconList.filter((iconName) => iconName.toLowerCase().includes(matchKey));
@ -124,14 +104,35 @@ const IconSearch: React.FC = () => {
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
<Segmented<ThemeType>
size="large"
value={displayState.theme}
options={options(intl.formatMessage)}
options={memoizedOptions}
onChange={handleChangeTheme}
/>
<Input.Search

View File

@ -44,15 +44,13 @@ const IconSearchFallback: React.FC = () => {
</div>
<Skeleton.Button active style={{ margin: '28px 0 10px', width: 100 }} />
<div className={styles.fallbackWrapper}>
{Array(24)
.fill(1)
.map((_, index) => (
<div key={index} className={styles.skeletonWrapper}>
<Skeleton.Node active style={{ height: 110, width: '100%' }}>
{' '}
</Skeleton.Node>
</div>
))}
{new Array(24).fill(1).map((_, index) => (
<div key={index} className={styles.skeletonWrapper}>
<Skeleton.Node active style={{ height: 110, width: '100%' }}>
{' '}
</Skeleton.Node>
</div>
))}
</div>
</>
);

View File

@ -12,6 +12,7 @@ export const FilledIcon: CustomIconComponent = (props) => {
'0c0-53-43-96-96-96z';
return (
<svg {...props} viewBox="0 0 1024 1024">
<title>Filled Icon</title>
<path d={path} />
</svg>
);
@ -26,6 +27,7 @@ export const OutlinedIcon: CustomIconComponent = (props) => {
' 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>
);
@ -39,6 +41,7 @@ export const TwoToneIcon: CustomIconComponent = (props) => {
'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

@ -117,6 +117,7 @@ const ImagePreview: React.FC<React.PropsWithChildren<ImagePreviewProps>> = (prop
<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>

View File

@ -1,6 +1,7 @@
import { PictureOutlined } from '@ant-design/icons';
import React from 'react';
import { PictureOutlined } from '@ant-design/icons';
import { Image, Tooltip, Typography } from 'antd';
import useLocale from '../../../hooks/useLocale';
const locales = {

View File

@ -3,10 +3,10 @@ import { ConfigProvider, Tabs } from 'antd';
import SourceCode from 'dumi/theme-default/builtins/SourceCode';
import type { Tab } from 'rc-tabs/lib/interface';
import BunLogo from './bun';
import NpmLogo from './npm';
import PnpmLogo from './pnpm';
import YarnLogo from './yarn';
import BunLogo from './bun';
interface InstallProps {
npm?: string;

View File

@ -31,6 +31,7 @@ const NpmIcon: React.FC<IconProps> = (props) => {
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>

View File

@ -33,6 +33,7 @@ const PnpmIcon: React.FC<IconProps> = (props) => {
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>

View File

@ -32,6 +32,7 @@ const YarnIcon: React.FC<IconProps> = (props) => {
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>

View File

@ -0,0 +1,80 @@
import React, { Suspense, useEffect, useState } from 'react';
import { Tooltip } from 'antd';
import { FormattedMessage } from 'dumi';
import { ping } from '../../utils';
let pingDeferrer: PromiseLike<boolean>;
const codeBlockJs =
'https://renderoffice.a' +
'lipay' +
'objects.com/p' +
'/yuyan/180020010001206410/parseFileData-v1.0.1.js';
function useShowCodeBlockButton() {
const [showCodeBlockButton, setShowCodeBlockButton] = useState(false);
useEffect(() => {
pingDeferrer ??= new Promise<boolean>((resolve) => {
ping((status) => {
if (status !== 'timeout' && status !== 'error') {
// Async insert `codeBlockJs` into body end
const script = document.createElement('script');
script.src = codeBlockJs;
script.async = true;
document.body.appendChild(script);
return resolve(true);
}
return resolve(false);
});
});
pingDeferrer.then(setShowCodeBlockButton);
}, []);
return showCodeBlockButton;
}
interface CodeBlockButtonProps {
title?: string;
dependencies: Record<PropertyKey, string>;
jsx: string;
}
const CodeBlockButton: React.FC<CodeBlockButtonProps> = ({ title, dependencies = {}, jsx }) => {
const showCodeBlockButton = useShowCodeBlockButton();
const codeBlockPrefillConfig = {
title: `${title} - antd@${dependencies.antd}`,
js: `${
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
/export default/,
'const ComponentDemo =',
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
css: '',
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
};
return showCodeBlockButton ? (
<Tooltip title={<FormattedMessage id="app.demo.codeblock" />}>
<div className="code-box-code-action">
<img
alt="codeblock"
src="https://mdn.alipayobjects.com/huamei_wtld8u/afts/img/A*K8rjSJpTNQ8AAAAAAAAAAAAADhOIAQ/original"
className="code-box-codeblock"
onClick={() => {
openHituCodeBlock(JSON.stringify(codeBlockPrefillConfig));
}}
/>
</div>
</Tooltip>
) : null;
};
export default (props: CodeBlockButtonProps) => (
<Suspense>
<CodeBlockButton {...props} />
</Suspense>
);

View File

@ -11,16 +11,15 @@ import LZString from 'lz-string';
import useLocation from '../../../hooks/useLocation';
import BrowserFrame from '../../common/BrowserFrame';
import ClientOnly from '../../common/ClientOnly';
import CodePenIcon from '../../common/CodePenIcon';
import CodePreview from '../../common/CodePreview';
import CodeSandboxIcon from '../../common/CodeSandboxIcon';
import EditButton from '../../common/EditButton';
import ExternalLinkIcon from '../../common/ExternalLinkIcon';
import RiddleIcon from '../../common/RiddleIcon';
import CodePenIcon from '../../icons/CodePenIcon';
import CodeSandboxIcon from '../../icons/CodeSandboxIcon';
import ExternalLinkIcon from '../../icons/ExternalLinkIcon';
import DemoContext from '../../slots/DemoContext';
import type { SiteContextProps } from '../../slots/SiteContext';
import SiteContext from '../../slots/SiteContext';
import { ping } from '../../utils';
import CodeBlockButton from './CodeBlockButton';
import type { AntdPreviewerProps } from './Previewer';
const { ErrorBoundary } = Alert;
@ -39,27 +38,6 @@ const track = ({ type, demo }: { type: string; demo: string }) => {
window.gtag('event', 'demo', { event_category: type, event_label: demo });
};
let pingDeferrer: PromiseLike<boolean>;
function useShowRiddleButton() {
const [showRiddleButton, setShowRiddleButton] = useState(false);
useEffect(() => {
pingDeferrer ??= new Promise<boolean>((resolve) => {
ping((status) => {
if (status !== 'timeout' && status !== 'error') {
return resolve(true);
}
return resolve(false);
});
});
pingDeferrer.then(setShowRiddleButton);
}, []);
return showRiddleButton;
}
const useStyle = createStyles(({ token }) => {
const { borderRadius } = token;
return {
@ -98,7 +76,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
title,
description,
originDebug,
jsx,
jsx = '',
style,
compact,
background,
@ -108,7 +86,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
clientOnly,
pkgDependencyList,
} = props;
const { showDebug, codeType } = useContext(DemoContext);
const { codeType } = useContext(DemoContext);
const { pkg } = useSiteData();
const location = useLocation();
@ -117,7 +95,6 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
const entryName = 'index.tsx';
const entryCode = asset.dependencies[entryName].value;
const showRiddleButton = useShowRiddleButton();
const previewDemo = useRef<React.ReactNode>(null);
const demoContainer = useRef<HTMLElement>(null);
@ -127,11 +104,10 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
setSource: setLiveDemoSource,
} = useLiveDemo(asset.id, {
iframe: Boolean(iframe),
containerRef: demoContainer,
containerRef: demoContainer as React.RefObject<HTMLElement>,
});
const anchorRef = useRef<HTMLAnchorElement>(null);
const codeSandboxIconRef = useRef<HTMLFormElement>(null);
const riddleIconRef = useRef<HTMLFormElement>(null);
const codepenIconRef = useRef<HTMLFormElement>(null);
const [codeExpand, setCodeExpand] = useState<boolean>(false);
const { theme } = useContext<SiteContextProps>(SiteContext);
@ -278,18 +254,6 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
js_pre_processor: 'typescript',
};
const riddlePrefillConfig = {
title: `${localizedTitle} - antd@${dependencies.antd}`,
js: `${
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
/export default/,
'const ComponentDemo =',
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
css: '',
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
};
// Reorder source code
let parsedSourceCode = suffix === 'tsx' ? entryCode : jsx;
let importReactContent = "import React from 'react';";
@ -308,7 +272,9 @@ ${parsedSourceCode}
.trim()
.replace(new RegExp(`#${asset.id}\\s*`, 'g'), '')
.replace('</style>', '')
.replace('<style>', '');
.replace('<style>', '')
.replace('```css', '')
.replace('```', '');
const indexJsContent = `import React from 'react';
import { createRoot } from 'react-dom/client';
@ -401,6 +367,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
{description && (
<div
className="code-box-description"
// biome-ignore lint/security/noDangerouslySetInnerHtml: it's for markdown
dangerouslySetInnerHTML={{ __html: description }}
/>
)}
@ -417,24 +384,27 @@ createRoot(document.getElementById('container')).render(<Demo />);
</a>
</Tooltip>
)}
{showRiddleButton ? (
<form
className="code-box-code-action"
action="//riddle.alibaba-inc.com/riddles/define"
method="POST"
target="_blank"
ref={riddleIconRef}
onClick={() => {
track({ type: 'riddle', demo: asset.id });
riddleIconRef.current?.submit();
}}
>
<input type="hidden" name="data" value={JSON.stringify(riddlePrefillConfig)} />
<Tooltip title={<FormattedMessage id="app.demo.riddle" />}>
<RiddleIcon className="code-box-riddle" />
</Tooltip>
</form>
) : null}
<form
className="code-box-code-action"
action="https://codesandbox.io/api/v1/sandboxes/define"
method="POST"
target="_blank"
ref={codeSandboxIconRef}
onClick={() => {
track({ type: 'codesandbox', demo: asset.id });
codeSandboxIconRef.current?.submit();
}}
>
<input
type="hidden"
name="parameters"
value={compress(JSON.stringify(codesanboxPrefillConfig))}
/>
<Tooltip title={<FormattedMessage id="app.demo.codesandbox" />}>
<CodeSandboxIcon className="code-box-codesandbox" />
</Tooltip>
</form>
<CodeBlockButton title={localizedTitle} dependencies={dependencies} jsx={jsx} />
<Tooltip title={<FormattedMessage id="app.demo.stackblitz" />}>
<span
className="code-box-code-action"
@ -469,28 +439,6 @@ createRoot(document.getElementById('container')).render(<Demo />);
<CodePenIcon className="code-box-codepen" />
</Tooltip>
</form>
{showDebug && (
<form
className="code-box-code-action"
action="https://codesandbox.io/api/v1/sandboxes/define"
method="POST"
target="_blank"
ref={codeSandboxIconRef}
onClick={() => {
track({ type: 'codesandbox', demo: asset.id });
codeSandboxIconRef.current?.submit();
}}
>
<input
type="hidden"
name="parameters"
value={compress(JSON.stringify(codesanboxPrefillConfig))}
/>
<Tooltip title={<FormattedMessage id="app.demo.codesandbox" />}>
<CodeSandboxIcon className="code-box-codesandbox" />
</Tooltip>
</form>
)}
<Tooltip title={<FormattedMessage id="app.demo.separate" />}>
<a
className="code-box-code-action"

View File

@ -0,0 +1,27 @@
import React from 'react';
import { Skeleton } from 'antd';
import { createStyles } from 'antd-style';
const useStyle = createStyles(({ token, css }) => ({
skeletonWrapper: css`
width: 100% !important;
height: 250px;
margin-bottom: ${token.margin}px;
border-radius: ${token.borderRadiusLG}px;
`,
}));
const DemoFallback = () => {
const { styles } = useStyle();
return (
<Skeleton.Node
active
className={styles.skeletonWrapper}
style={{ width: '100%', height: '100%' }}
>
{' '}
</Skeleton.Node>
);
};
export default DemoFallback;

View File

@ -41,6 +41,10 @@ const useStyle = createStyles(({ token, css }) => ({
`,
copyTip: css`
color: ${token.colorTextTertiary};
border: none;
background: transparent;
padding: 0;
cursor: pointer;
`,
copiedTip: css`
.anticon {
@ -80,6 +84,7 @@ const DesignPreviewer: FC<AntdPreviewerProps> = ({ children, title, description,
{title}
</a>
{description && (
// biome-ignore lint/security/noDangerouslySetInnerHtml: description is from markdown
<div className={styles.description} dangerouslySetInnerHTML={{ __html: description }} />
)}
<div className={styles.copy}>
@ -89,10 +94,10 @@ const DesignPreviewer: FC<AntdPreviewerProps> = ({ children, title, description,
<span style={{ marginInlineStart: 8 }}>使 Kitchen </span>
</div>
) : (
<div onClick={handleCopy} className={styles.copyTip}>
<button type="button" onClick={handleCopy} className={styles.copyTip}>
<SketchOutlined />
<span style={{ marginInlineStart: 8 }}> Sketch JSON</span>
</div>
</button>
)}
</div>
<div className={styles.demo} ref={demoRef}>

View File

@ -7,6 +7,7 @@ import DesignPreviewer from './DesignPreviewer';
export interface AntdPreviewerProps extends IPreviewerProps {
originDebug?: IPreviewerProps['debug'];
jsx?: string;
}
const Previewer: React.FC<AntdPreviewerProps> = (props) => {

View File

@ -0,0 +1,91 @@
import React, { Suspense, useEffect, useRef, useState } from 'react';
import { Tooltip } from 'antd';
import { FormattedMessage } from 'dumi';
import type { IPreviewerProps } from 'dumi';
import RiddleIcon from '../../icons/RiddleIcon';
import { ping } from '../../utils';
let pingDeferrer: PromiseLike<boolean>;
function useShowRiddleButton() {
const [showRiddleButton, setShowRiddleButton] = useState(false);
useEffect(() => {
pingDeferrer ??= new Promise<boolean>((resolve) => {
ping((status) => {
if (status !== 'timeout' && status !== 'error') {
return resolve(true);
}
return resolve(false);
});
});
pingDeferrer.then(setShowRiddleButton);
}, []);
return showRiddleButton;
}
interface RiddleButtonProps {
title?: string;
dependencies: Record<PropertyKey, string>;
jsx: string;
track: ({
type,
demo,
}: {
type: string;
demo: string;
}) => void;
asset: IPreviewerProps['asset'];
}
const RiddleButton: React.FC<RiddleButtonProps> = ({
title,
dependencies = {},
jsx,
track,
asset,
}) => {
const riddleIconRef = useRef<HTMLFormElement>(null);
const showRiddleButton = useShowRiddleButton();
const riddlePrefillConfig = {
title: `${title} - antd@${dependencies.antd}`,
js: `${
/import React(\D*)from 'react';/.test(jsx) ? '' : `import React from 'react';\n`
}import { createRoot } from 'react-dom/client';\n${jsx.replace(
/export default/,
'const ComponentDemo =',
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
css: '',
json: JSON.stringify({ name: 'antd-demo', dependencies }, null, 2),
};
return showRiddleButton ? (
<form
className="code-box-code-action"
action="//riddle.alibaba-inc.com/riddles/define"
method="POST"
target="_blank"
ref={riddleIconRef}
onClick={() => {
track({ type: 'riddle', demo: asset.id });
riddleIconRef.current?.submit();
}}
>
<input type="hidden" name="data" value={JSON.stringify(riddlePrefillConfig)} />
<Tooltip title={<FormattedMessage id="app.demo.riddle" />}>
<RiddleIcon className="code-box-riddle" />
</Tooltip>
</form>
) : null;
};
export default (props: RiddleButtonProps) => (
<Suspense>
<RiddleButton {...props} />
</Suspense>
);

View File

@ -1,40 +1,17 @@
import React, { Suspense } from 'react';
import { Alert, Skeleton } from 'antd';
import { createStyles } from 'antd-style';
import { Alert } from 'antd';
import Previewer from './Previewer';
import DemoFallback from './DemoFallback';
import type { IPreviewerProps } from 'dumi';
const { ErrorBoundary } = Alert;
const Previewer = React.lazy(() => import('./Previewer'));
const useStyle = createStyles(({ token, css }) => ({
skeletonWrapper: css`
width: 100% !important;
height: 250px;
margin-bottom: ${token.margin}px;
border-radius: ${token.borderRadiusLG}px;
`,
}));
const PreviewerSuspense: React.FC<IPreviewerProps> = (props) => {
const { styles } = useStyle();
return (
<ErrorBoundary>
<Suspense
fallback={
<Skeleton.Node
active
className={styles.skeletonWrapper}
style={{ width: '100%', height: '100%' }}
>
{' '}
</Skeleton.Node>
}
>
<Previewer {...props} />
</Suspense>
</ErrorBoundary>
);
};
const PreviewerSuspense: React.FC<IPreviewerProps> = (props) => (
<ErrorBoundary>
<Suspense fallback={<DemoFallback />}>
<Previewer {...props} />
</Suspense>
</ErrorBoundary>
);
export default PreviewerSuspense;

View File

@ -1,4 +1,3 @@
/* eslint-disable react/no-array-index-key */
import * as React from 'react';
import { Avatar, Divider, Empty, Skeleton, Tabs } from 'antd';
import { createStyles } from 'antd-style';

View File

@ -1,40 +1,41 @@
import React from 'react';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Col, Row, Tooltip, Card, Typography } from 'antd';
import { Card, Col, Row, Tooltip, Typography } from 'antd';
import { createStyles } from 'antd-style';
import useLocale from '../../../hooks/useLocale';
const { Paragraph } = Typography;
const useStyle = createStyles(({ token, css }) => ({
card: css`
position: relative;
position: relative;
overflow: hidden;
${token.antCls}-card-cover {
overflow: hidden;
.ant-card-cover {
overflow: hidden;
}
img {
transition: all ${token.motionDurationSlow} ease-out;
}
&:hover img {
transform: scale(1.3);
`,
}
img {
display: block;
transition: all ${token.motionDurationSlow} ease-out;
}
&:hover img {
transform: scale(1.3);
}
`,
badge: css`
position: absolute;
top: 8px;
right: 8px;
padding: ${token.paddingXXS}px ${token.paddingXS}px;
color: #fff;
font-size: ${token.fontSizeSM}px;
line-height: 1;
background: rgba(0, 0, 0, 0.65);
border-radius: ${token.borderRadiusLG}px;
box-shadow: 0 0 2px rgba(255, 255, 255, 0.2);
display: inline-flex;
column-gap: ${token.paddingXXS}px;
`,
position: absolute;
top: 8px;
inset-inline-end: 8px;
padding: ${token.paddingXXS}px ${token.paddingXS}px;
color: #fff;
font-size: ${token.fontSizeSM}px;
line-height: 1;
background: rgba(0, 0, 0, 0.65);
border-radius: ${token.borderRadiusLG}px;
box-shadow: 0 0 2px rgba(255, 255, 255, 0.2);
display: inline-flex;
column-gap: ${token.paddingXXS}px;
`,
}));
export type Resource = {

View File

@ -1,6 +1,6 @@
// 用于 color.md 中的颜色对比
import React from 'react';
import { TinyColor } from '@ctrl/tinycolor';
import { FastColor } from '@ant-design/fast-color';
import { Flex, theme } from 'antd';
import { createStyles } from 'antd-style';
import tokenMeta from 'antd/es/version/token-meta.json';
@ -55,7 +55,7 @@ const useStyle = createStyles(({ token, css }) => {
});
function color2Rgba(color: string) {
return `#${new TinyColor(color).toHex8().toUpperCase()}`;
return `#${new FastColor(color).toHexString().toUpperCase()}`;
}
interface ColorCircleProps {

View File

@ -14,7 +14,7 @@ type TokenTableProps = {
lang: 'zh' | 'en';
};
type TokenData = {
export type TokenData = {
name: string;
desc: string;
type: string;
@ -99,7 +99,7 @@ const TokenTable: FC<TokenTableProps> = ({ type }) => {
name: token,
desc: lang === 'cn' ? meta.desc : meta.descEn,
type: meta.type,
value: defaultToken[token],
value: defaultToken[token as keyof typeof defaultToken],
})),
[type, lang],
);

View File

@ -6,7 +6,7 @@ import classNames from 'classnames';
const useStyles = createStyles(({ cx, token }) => {
const play = css`
position: absolute;
right: ${token.paddingLG}px;
inset-inline-end: ${token.paddingLG}px;
bottom: ${token.paddingLG}px;
font-size: 64px;
display: flex;

View File

@ -1,185 +1,7 @@
import React, { useEffect, useRef } from 'react';
import G6 from '@antv/g6';
import { createStyles, css } from 'antd-style';
import { useRouteMeta } from 'dumi';
G6.registerNode('behavior-start-node', {
draw: (cfg, group) => {
const textWidth = G6.Util.getTextSize(cfg!.label, 16)[0];
const size = [textWidth + 20 * 2, 48];
const keyShape = group!.addShape('rect', {
name: 'start-node',
attrs: {
width: size[0],
height: size[1],
y: -size[1] / 2,
radius: 8,
fill: '#fff',
},
});
group!.addShape('text', {
attrs: {
text: `${cfg!.label}`,
fill: 'rgba(0, 0, 0, 0.88)',
fontSize: 16,
fontWeight: 500,
x: 20,
textBaseline: 'middle',
},
name: 'start-node-text',
});
return keyShape;
},
getAnchorPoints() {
return [
[0, 0.5],
[1, 0.5],
];
},
});
G6.registerNode(
'behavior-sub-node',
{
draw: (cfg, group) => {
const textWidth = G6.Util.getTextSize(cfg!.label, 14)[0];
const padding = 16;
const size = [textWidth + 16 * 2 + (cfg!.targetType ? 12 : 0) + (cfg!.link ? 20 : 0), 40];
const keyShape = group!.addShape('rect', {
name: 'sub-node',
attrs: {
width: size[0],
height: size[1],
y: -size[1] / 2,
radius: 8,
fill: '#fff',
cursor: 'pointer',
},
});
group!.addShape('text', {
attrs: {
text: `${cfg!.label}`,
x: cfg!.targetType ? 12 + 16 : padding,
fill: 'rgba(0, 0, 0, 0.88)',
fontSize: 14,
textBaseline: 'middle',
cursor: 'pointer',
},
name: 'sub-node-text',
});
if (cfg!.targetType) {
group!.addShape('rect', {
name: 'sub-node-type',
attrs: {
width: 8,
height: 8,
radius: 4,
y: -4,
x: 12,
fill: cfg!.targetType === 'mvp' ? '#1677ff' : '#A0A0A0',
cursor: 'pointer',
},
});
}
if (cfg!.children) {
const { length } = cfg!.children as any;
group!.addShape('rect', {
name: 'sub-node-children-length',
attrs: {
width: 20,
height: 20,
radius: 10,
y: -10,
x: size[0] - 4,
fill: '#404040',
cursor: 'pointer',
},
});
group!.addShape('text', {
name: 'sub-node-children-length-text',
attrs: {
text: `${length}`,
x: size[0] + 6 - G6.Util.getTextSize(`${length}`, 12)[0] / 2,
textBaseline: 'middle',
fill: '#fff',
fontSize: 12,
cursor: 'pointer',
},
});
}
if (cfg!.link) {
group!.addShape('dom', {
attrs: {
width: 16,
height: 16,
x: size[0] - 12 - 16,
y: -8,
cursor: 'pointer',
// DOM's html
html: `
<div style="width: 16px; height: 16px;">
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="DatePicker" transform="translate(-890.000000, -441.000000)" fill-rule="nonzero">
<g id="编组-30" transform="translate(288.000000, 354.000000)">
<g id="编组-7备份-7" transform="translate(522.000000, 79.000000)">
<g id="right-circle-outlinedd" transform="translate(80.000000, 8.000000)">
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="16" height="16"></rect>
<path d="M10.4171875,7.8984375 L6.5734375,5.1171875 C6.490625,5.0578125 6.375,5.115625 6.375,5.21875 L6.375,5.9515625 C6.375,6.1109375 6.4515625,6.2625 6.58125,6.35625 L8.853125,8 L6.58125,9.64375 C6.4515625,9.7375 6.375,9.8875 6.375,10.0484375 L6.375,10.78125 C6.375,10.8828125 6.490625,10.9421875 6.5734375,10.8828125 L10.4171875,8.1015625 C10.4859375,8.0515625 10.4859375,7.9484375 10.4171875,7.8984375 Z" id="路径" fill="#BFBFBF"></path>
<path d="M8,1 C4.134375,1 1,4.134375 1,8 C1,11.865625 4.134375,15 8,15 C11.865625,15 15,11.865625 15,8 C15,4.134375 11.865625,1 8,1 Z M8,13.8125 C4.790625,13.8125 2.1875,11.209375 2.1875,8 C2.1875,4.790625 4.790625,2.1875 8,2.1875 C11.209375,2.1875 13.8125,4.790625 13.8125,8 C13.8125,11.209375 11.209375,13.8125 8,13.8125 Z" id="形状" fill="#BFBFBF"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
`,
},
// 在 G6 3.3 及之后的版本中,必须指定 name可以是任意字符串但需要在同一个自定义元素类型中保持唯一性
name: 'sub-node-link',
});
}
return keyShape;
},
getAnchorPoints() {
return [
[0, 0.5],
[1, 0.5],
];
},
options: {
stateStyles: {
hover: {
stroke: '#1677ff',
'sub-node-link': {
html: `
<div style="width: 16px; height: 16px;">
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="DatePicker" transform="translate(-890.000000, -441.000000)" fill-rule="nonzero">
<g id="编组-30" transform="translate(288.000000, 354.000000)">
<g id="编组-7备份-7" transform="translate(522.000000, 79.000000)">
<g id="right-circle-outlinedd" transform="translate(80.000000, 8.000000)">
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="16" height="16"></rect>
<path d="M10.4171875,7.8984375 L6.5734375,5.1171875 C6.490625,5.0578125 6.375,5.115625 6.375,5.21875 L6.375,5.9515625 C6.375,6.1109375 6.4515625,6.2625 6.58125,6.35625 L8.853125,8 L6.58125,9.64375 C6.4515625,9.7375 6.375,9.8875 6.375,10.0484375 L6.375,10.78125 C6.375,10.8828125 6.490625,10.9421875 6.5734375,10.8828125 L10.4171875,8.1015625 C10.4859375,8.0515625 10.4859375,7.9484375 10.4171875,7.8984375 Z" id="路径" fill="#1677ff"></path>
<path d="M8,1 C4.134375,1 1,4.134375 1,8 C1,11.865625 4.134375,15 8,15 C11.865625,15 15,11.865625 15,8 C15,4.134375 11.865625,1 8,1 Z M8,13.8125 C4.790625,13.8125 2.1875,11.209375 2.1875,8 C2.1875,4.790625 4.790625,2.1875 8,2.1875 C11.209375,2.1875 13.8125,4.790625 13.8125,8 C13.8125,11.209375 11.209375,13.8125 8,13.8125 Z" id="形状" fill="#1677ff"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
`,
},
},
},
},
},
'rect',
);
const dataTransform = (data: BehaviorMapItem) => {
const changeData = (d: any, level = 0) => {
const clonedData: any = {
@ -227,14 +49,14 @@ const useStyle = createStyles(({ token }) => ({
title: css`
position: absolute;
top: 20px;
left: 20px;
inset-inline-start: 20px;
font-size: ${token.fontSizeLG}px;
`,
tips: css`
display: flex;
position: absolute;
bottom: 20px;
right: 20px;
inset-inline-end: 20px;
`,
mvp: css`
margin-inline-end: ${token.marginMD}px;
@ -275,48 +97,229 @@ const BehaviorMap: React.FC<BehaviorMapProps> = ({ data }) => {
const meta = useRouteMeta();
useEffect(() => {
const graph = new G6.TreeGraph({
container: ref.current!,
width: ref.current!.scrollWidth,
height: ref.current!.scrollHeight,
renderer: 'svg',
modes: {
default: ['collapse-expand', 'drag-canvas'],
},
defaultEdge: {
type: 'cubic-horizontal',
style: {
lineWidth: 1,
stroke: '#BFBFBF',
import('@antv/g6').then((G6) => {
G6.registerNode('behavior-start-node', {
draw: (cfg, group) => {
const textWidth = G6.Util.getTextSize(cfg!.label, 16)[0];
const size = [textWidth + 20 * 2, 48];
const keyShape = group!.addShape('rect', {
name: 'start-node',
attrs: {
width: size[0],
height: size[1],
y: -size[1] / 2,
radius: 8,
fill: '#fff',
},
});
group!.addShape('text', {
attrs: {
text: `${cfg!.label}`,
fill: 'rgba(0, 0, 0, 0.88)',
fontSize: 16,
fontWeight: 500,
x: 20,
textBaseline: 'middle',
},
name: 'start-node-text',
});
return keyShape;
},
},
layout: {
type: 'mindmap',
direction: 'LR',
getHeight: () => 48,
getWidth: (node: any) => G6.Util.getTextSize(node.label, 16)[0] + 20 * 2,
getVGap: () => 10,
getHGap: () => 60,
getSide: (node: any) => node.data.direction,
},
});
getAnchorPoints() {
return [
[0, 0.5],
[1, 0.5],
];
},
});
graph.on('node:mouseenter', (e) => {
graph.setItemState(e.item!, 'hover', true);
});
graph.on('node:mouseleave', (e) => {
graph.setItemState(e.item!, 'hover', false);
});
graph.on('node:click', (e) => {
const { link } = e.item!.getModel();
if (link) {
window.location.hash = link as string;
}
});
G6.registerNode(
'behavior-sub-node',
{
draw: (cfg, group) => {
const textWidth = G6.Util.getTextSize(cfg!.label, 14)[0];
const padding = 16;
const size = [
textWidth + 16 * 2 + (cfg!.targetType ? 12 : 0) + (cfg!.link ? 20 : 0),
40,
];
const keyShape = group!.addShape('rect', {
name: 'sub-node',
attrs: {
width: size[0],
height: size[1],
y: -size[1] / 2,
radius: 8,
fill: '#fff',
cursor: 'pointer',
},
});
group!.addShape('text', {
attrs: {
text: `${cfg!.label}`,
x: cfg!.targetType ? 12 + 16 : padding,
fill: 'rgba(0, 0, 0, 0.88)',
fontSize: 14,
textBaseline: 'middle',
cursor: 'pointer',
},
name: 'sub-node-text',
});
if (cfg!.targetType) {
group!.addShape('rect', {
name: 'sub-node-type',
attrs: {
width: 8,
height: 8,
radius: 4,
y: -4,
x: 12,
fill: cfg!.targetType === 'mvp' ? '#1677ff' : '#A0A0A0',
cursor: 'pointer',
},
});
}
if (cfg!.children) {
const { length } = cfg!.children as any;
group!.addShape('rect', {
name: 'sub-node-children-length',
attrs: {
width: 20,
height: 20,
radius: 10,
y: -10,
x: size[0] - 4,
fill: '#404040',
cursor: 'pointer',
},
});
group!.addShape('text', {
name: 'sub-node-children-length-text',
attrs: {
text: `${length}`,
x: size[0] + 6 - G6.Util.getTextSize(`${length}`, 12)[0] / 2,
textBaseline: 'middle',
fill: '#fff',
fontSize: 12,
cursor: 'pointer',
},
});
}
if (cfg!.link) {
group!.addShape('dom', {
attrs: {
width: 16,
height: 16,
x: size[0] - 12 - 16,
y: -8,
cursor: 'pointer',
// DOM's html
html: `
<div style="width: 16px; height: 16px;">
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="DatePicker" transform="translate(-890.000000, -441.000000)" fill-rule="nonzero">
<g id="编组-30" transform="translate(288.000000, 354.000000)">
<g id="编组-7备份-7" transform="translate(522.000000, 79.000000)">
<g id="right-circle-outlinedd" transform="translate(80.000000, 8.000000)">
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="16" height="16"></rect>
<path d="M10.4171875,7.8984375 L6.5734375,5.1171875 C6.490625,5.0578125 6.375,5.115625 6.375,5.21875 L6.375,5.9515625 C6.375,6.1109375 6.4515625,6.2625 6.58125,6.35625 L8.853125,8 L6.58125,9.64375 C6.4515625,9.7375 6.375,9.8875 6.375,10.0484375 L6.375,10.78125 C6.375,10.8828125 6.490625,10.9421875 6.5734375,10.8828125 L10.4171875,8.1015625 C10.4859375,8.0515625 10.4859375,7.9484375 10.4171875,7.8984375 Z" id="路径" fill="#BFBFBF"></path>
<path d="M8,1 C4.134375,1 1,4.134375 1,8 C1,11.865625 4.134375,15 8,15 C11.865625,15 15,11.865625 15,8 C15,4.134375 11.865625,1 8,1 Z M8,13.8125 C4.790625,13.8125 2.1875,11.209375 2.1875,8 C2.1875,4.790625 4.790625,2.1875 8,2.1875 C11.209375,2.1875 13.8125,4.790625 13.8125,8 C13.8125,11.209375 11.209375,13.8125 8,13.8125 Z" id="形状" fill="#BFBFBF"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
`,
},
// 在 G6 3.3 及之后的版本中,必须指定 name可以是任意字符串但需要在同一个自定义元素类型中保持唯一性
name: 'sub-node-link',
});
}
return keyShape;
},
getAnchorPoints() {
return [
[0, 0.5],
[1, 0.5],
];
},
options: {
stateStyles: {
hover: {
stroke: '#1677ff',
'sub-node-link': {
html: `
<div style="width: 16px; height: 16px;">
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="DatePicker" transform="translate(-890.000000, -441.000000)" fill-rule="nonzero">
<g id="编组-30" transform="translate(288.000000, 354.000000)">
<g id="编组-7备份-7" transform="translate(522.000000, 79.000000)">
<g id="right-circle-outlinedd" transform="translate(80.000000, 8.000000)">
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="16" height="16"></rect>
<path d="M10.4171875,7.8984375 L6.5734375,5.1171875 C6.490625,5.0578125 6.375,5.115625 6.375,5.21875 L6.375,5.9515625 C6.375,6.1109375 6.4515625,6.2625 6.58125,6.35625 L8.853125,8 L6.58125,9.64375 C6.4515625,9.7375 6.375,9.8875 6.375,10.0484375 L6.375,10.78125 C6.375,10.8828125 6.490625,10.9421875 6.5734375,10.8828125 L10.4171875,8.1015625 C10.4859375,8.0515625 10.4859375,7.9484375 10.4171875,7.8984375 Z" id="路径" fill="#1677ff"></path>
<path d="M8,1 C4.134375,1 1,4.134375 1,8 C1,11.865625 4.134375,15 8,15 C11.865625,15 15,11.865625 15,8 C15,4.134375 11.865625,1 8,1 Z M8,13.8125 C4.790625,13.8125 2.1875,11.209375 2.1875,8 C2.1875,4.790625 4.790625,2.1875 8,2.1875 C11.209375,2.1875 13.8125,4.790625 13.8125,8 C13.8125,11.209375 11.209375,13.8125 8,13.8125 Z" id="形状" fill="#1677ff"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
`,
},
},
},
},
},
'rect',
);
const graph = new G6.TreeGraph({
container: ref.current!,
width: ref.current!.scrollWidth,
height: ref.current!.scrollHeight,
renderer: 'svg',
modes: {
default: ['collapse-expand', 'drag-canvas'],
},
defaultEdge: {
type: 'cubic-horizontal',
style: {
lineWidth: 1,
stroke: '#BFBFBF',
},
},
layout: {
type: 'mindmap',
direction: 'LR',
getHeight: () => 48,
getWidth: (node: any) => G6.Util.getTextSize(node.label, 16)[0] + 20 * 2,
getVGap: () => 10,
getHGap: () => 60,
getSide: (node: any) => node.data.direction,
},
});
graph.data(dataTransform(data));
graph.render();
graph.fitCenter();
graph.on('node:mouseenter', (e) => {
graph.setItemState(e.item!, 'hover', true);
});
graph.on('node:mouseleave', (e) => {
graph.setItemState(e.item!, 'hover', false);
});
graph.on('node:click', (e) => {
const { link } = e.item!.getModel();
if (link) {
window.location.hash = link as string;
}
});
graph.data(dataTransform(data));
graph.render();
graph.fitCenter();
});
}, []);
return (

View File

@ -10,7 +10,7 @@ const useStyle = createStyles(({ token, css }) => ({
&::before {
position: absolute;
top: -1.25em;
left: 1em;
inset-inline-start: 1em;
display: block;
width: 0.5em;
height: 0.5em;
@ -27,7 +27,7 @@ const useStyle = createStyles(({ token, css }) => ({
display: block;
position: absolute;
top: -1.6em;
left: 5.5em;
inset-inline-start: 5.5em;
width: calc(100% - 6em);
height: 1.2em;
background-color: #fff;

View File

@ -40,9 +40,9 @@ const useStyle = createStyles(({ token, css }) => {
display: block;
position: absolute;
top: -5px;
left: -9px;
inset-inline-start: -9px;
bottom: -5px;
right: -9px;
inset-inline-end: -9px;
}
}
${antCls}-typography-copy:not(${antCls}-typography-copy-success) {
@ -79,6 +79,7 @@ function toReactComponent(jsonML: any[]) {
const attr = JsonML.getAttributes(node);
return (
<pre key={index} className={`language-${attr.lang}`}>
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: it's for markdown */}
<code dangerouslySetInnerHTML={{ __html: attr.highlighted }} />
</pre>
);

View File

@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { App } from 'antd';
import CopyToClipboard from 'react-copy-to-clipboard';
import { message } from 'antd';
interface ColorBlockProps {
color: string;
@ -9,6 +9,7 @@ interface ColorBlockProps {
}
const ColorBlock: React.FC<ColorBlockProps> = (props) => {
const { message } = App.useApp();
const { color, index, dark } = props;
const textStyle = useMemo<React.CSSProperties>(() => {
const colorMap = { default: ['#fff', 'unset'], dark: ['#314659', '#fff'] };

View File

@ -1,8 +1,10 @@
import { FormattedMessage } from 'dumi';
import React, { useMemo, useState } from 'react';
import { Col, ColorPicker, Row } from 'antd';
import ColorPatterns from './ColorPatterns';
import type { Color } from 'antd/es/color-picker';
import { FormattedMessage } from 'dumi';
import useLocale from '../../../hooks/useLocale';
import ColorPatterns from './ColorPatterns';
const primaryMinSaturation = 70; // 主色推荐最小饱和度
const primaryMinBrightness = 70; // 主色推荐最小亮度
@ -23,7 +25,7 @@ const locales = {
const ColorPaletteTool: React.FC = () => {
const [primaryColor, setPrimaryColor] = useState<string>('#1890ff');
const [backgroundColor, setBackgroundColor] = useState<string>('#141414');
const [primaryColorInstance, setPrimaryColorInstance] = useState<Color>(null);
const [primaryColorInstance, setPrimaryColorInstance] = useState<Color | null>(null);
const [locale] = useLocale(locales);
@ -32,7 +34,7 @@ const ColorPaletteTool: React.FC = () => {
setPrimaryColorInstance(color);
};
const handleChangeBackgroundColor = (_, hex: string) => {
const handleChangeBackgroundColor = (_: Color, hex: string) => {
setBackgroundColor(hex);
};

View File

@ -1,5 +1,6 @@
import classNames from 'classnames';
import React from 'react';
import classNames from 'classnames';
import Palette from './Palette';
const colors = [

View File

@ -1,6 +1,7 @@
import React from 'react';
import { generate } from '@ant-design/colors';
import uniq from 'lodash/uniq';
import ColorBlock from './ColorBlock';
interface ColorPatternsProps {

View File

@ -21,7 +21,7 @@ const gray: { [key: number]: string } = {
const ColorStyle: React.FC = () => {
const token = useTheme();
const makePalette = (color: string, index: number = 1): string => {
const makePalette = (color: string, index = 1): string => {
if (index <= 10) {
return `
.palette-${color}-${index} {
@ -33,7 +33,7 @@ ${makePalette(color, index + 1)}
return '';
};
const makeGrayPalette = (index: number = 1): string => {
const makeGrayPalette = (index = 1): string => {
if (index <= 13) {
return `
.palette-gray-${index} {
@ -176,7 +176,7 @@ ${makeGrayPalette(index + 1)}
&-item &-value {
position: relative;
left: 3px;
inset-inline-start: ${token.marginXXS}px;
float: right;
transform: scale(0.85);
transform-origin: 100% 50%;
@ -203,7 +203,7 @@ ${makeGrayPalette(index + 1)}
.main-color:hover {
.main-color-value {
left: 0;
inset-inline-start: 0;
opacity: 0.7;
}
}
@ -264,7 +264,7 @@ ${makeGrayPalette(index + 1)}
&-value {
position: absolute;
bottom: 0;
left: 0;
inset-inline-start: 0;
width: 100%;
text-align: center;
transform-origin: unset;

View File

@ -1,10 +1,13 @@
import React, { useEffect } from 'react';
import { presetDarkPalettes } from '@ant-design/colors';
import { message } from 'antd';
import { App } from 'antd';
import CopyToClipboard from 'react-copy-to-clipboard';
const rgbToHex = (rgbString: string): string => {
const rgb = rgbString.match(/\d+/g);
if (!rgb) {
return '';
}
let r = parseInt(rgb[0], 10).toString(16);
let g = parseInt(rgb[1], 10).toString(16);
let b = parseInt(rgb[2], 10).toString(16);
@ -36,9 +39,10 @@ const Palette: React.FC<PaletteProps> = (props) => {
} = props;
const [hexColors, setHexColors] = React.useState<Record<PropertyKey, string>>({});
const colorNodesRef = React.useRef<Record<PropertyKey, HTMLDivElement>>({});
const { message } = App.useApp();
useEffect(() => {
const colors = {};
const colors: Record<string, string> = {};
Object.keys(colorNodesRef.current || {}).forEach((key) => {
const computedColor = getComputedStyle(colorNodesRef.current[key])['background-color'];
if (computedColor.includes('rgba')) {
@ -70,7 +74,9 @@ const Palette: React.FC<PaletteProps> = (props) => {
<div
key={i}
ref={(node) => {
colorNodesRef.current[`${name}-${i}`] = node;
if (node) {
colorNodesRef.current[`${name}-${i}`] = node;
}
}}
className={`main-color-item palette-${name}-${i}`}
style={{

View File

@ -1,7 +1,6 @@
/* eslint-disable global-require */
import React from 'react';
import { BugOutlined, HistoryOutlined } from '@ant-design/icons';
import { Button, Drawer, Grid, Popover, Timeline, Typography } from 'antd';
import React, { cloneElement, isValidElement } from 'react';
import { BugOutlined } from '@ant-design/icons';
import { Button, Drawer, Flex, Grid, Popover, Tag, Timeline, Typography } from 'antd';
import type { TimelineItemProps } from 'antd';
import { createStyles } from 'antd-style';
import semver from 'semver';
@ -9,6 +8,7 @@ import semver from 'semver';
import deprecatedVersions from '../../../../BUG_VERSIONS.json';
import useFetch from '../../../hooks/useFetch';
import useLocale from '../../../hooks/useLocale';
import useLocation from '../../../hooks/useLocation';
import Link from '../Link';
interface MatchDeprecatedResult {
@ -16,6 +16,14 @@ interface MatchDeprecatedResult {
reason: string[];
}
interface ChangelogInfo {
version: string;
changelog: string;
refs: string[];
contributors: string[];
releaseDate: string;
}
function matchDeprecated(v: string): MatchDeprecatedResult {
const match = Object.keys(deprecatedVersions).find((depreciated) =>
semver.satisfies(v, depreciated),
@ -28,17 +36,12 @@ function matchDeprecated(v: string): MatchDeprecatedResult {
}
const useStyle = createStyles(({ token, css }) => ({
history: css`
position: absolute;
top: 0;
inset-inline-end: ${token.marginXS}px;
listWrap: css`
> li {
line-height: 2;
}
`,
li: css`
// white-space: pre;
`,
ref: css`
linkRef: css`
margin-inline-start: ${token.marginXS}px;
`,
bug: css`
@ -67,15 +70,40 @@ const useStyle = createStyles(({ token, css }) => ({
}
}
`,
extraLink: css`
font-size: ${token.fontSize}px;
`,
drawerContent: {
position: 'relative',
[`> ${token.antCls}-drawer-body`]: {
scrollbarWidth: 'thin',
scrollbarGutter: 'stable',
},
},
versionWrap: css`
margin-bottom: 1em;
`,
versionTitle: css`
height: 28px;
line-height: 28px;
font-weight: 600;
font-size: 20px;
margin: 0 !important;
`,
versionTag: css`
user-select: none;
display: inline-flex;
align-items: center;
justify-content: center;
&:last-child {
margin-inline-end: 0;
}
`,
}));
export interface ComponentChangelogProps {
pathname: string;
}
const locales = {
cn: {
full: '完整更新日志',
full: '查看完整日志',
changelog: '更新日志',
loading: '加载中...',
empty: '暂无更新',
@ -90,29 +118,38 @@ const locales = {
},
};
const ParseChangelog: React.FC<{ changelog: string; refs: string[]; styles: any }> = (props) => {
const { changelog = '', refs = [], styles } = props;
const ParseChangelog: React.FC<{ changelog: string }> = (props) => {
const { changelog = '' } = props;
const parsedChangelog = React.useMemo(() => {
const nodes: React.ReactNode[] = [];
let isQuota = false;
let isBold = false;
let lastStr = '';
for (let i = 0; i < changelog.length; i += 1) {
const char = changelog[i];
const isDoubleAsterisk = char === '*' && changelog[i + 1] === '*';
if (char !== '`') {
if (char !== '`' && !isDoubleAsterisk) {
lastStr += char;
} else {
let node: React.ReactNode = lastStr;
if (isQuota) {
node = <code>{node}</code>;
} else if (isBold) {
node = <strong>{node}</strong>;
}
nodes.push(node);
lastStr = '';
isQuota = !isQuota;
if (char === '`') {
isQuota = !isQuota;
} else if (isDoubleAsterisk) {
isBold = !isBold;
i += 1; // Skip the next '*'
}
}
}
@ -121,30 +158,79 @@ const ParseChangelog: React.FC<{ changelog: string; refs: string[]; styles: any
return nodes;
}, [changelog]);
return <span>{parsedChangelog}</span>;
};
const RefLinks: React.FC<{ refs: string[]; contributors: string[] }> = ({ refs, contributors }) => {
const { styles } = useStyle();
return (
<>
{/* Changelog */}
<span>{parsedChangelog}</span>
{/* Refs */}
{refs?.map((ref) => (
<a className={styles.ref} key={ref} href={ref} target="_blank" rel="noreferrer">
#{ref.match(/^.*\/(\d+)$/)?.[1]}
</a>
<React.Fragment key={ref}>
<a className={styles.linkRef} key={ref} href={ref} target="_blank" rel="noreferrer">
#{ref.match(/[^/]+$/)?.[0]}
</a>
</React.Fragment>
))}
{contributors?.map((contributor) => (
<React.Fragment key={contributor}>
<a
className={styles.linkRef}
key={contributor}
href={`https://github.com/${contributor}`}
target="_blank"
rel="noreferrer"
>
@{contributor}
</a>
</React.Fragment>
))}
</>
);
};
interface ChangelogInfo {
version: string;
changelog: string;
refs: string[];
}
const RenderChangelogList: React.FC<{ changelogList: ChangelogInfo[] }> = ({ changelogList }) => {
const elements: React.ReactNode[] = [];
const { styles } = useStyle();
const len = changelogList.length;
for (let i = 0; i < len; i += 1) {
const { refs, changelog, contributors } = changelogList[i];
// Check if the next line is an image link and append it to the current line
if (i + 1 < len && changelogList[i + 1].changelog.trim().startsWith('<img')) {
const imgDom = new DOMParser().parseFromString(changelogList[i + 1].changelog, 'text/html');
const imgElement = imgDom.querySelector<HTMLImageElement>('img');
elements.push(
<li key={i}>
<ParseChangelog changelog={changelog} />
<RefLinks refs={refs} contributors={contributors} />
<br />
<img
src={imgElement?.getAttribute('src') || ''}
alt={imgElement?.getAttribute('alt') || ''}
width={imgElement?.getAttribute('width') || ''}
/>
</li>,
);
i += 1; // Skip the next line
} else {
elements.push(
<li key={i}>
<ParseChangelog changelog={changelog} />
<RefLinks refs={refs} contributors={contributors} />
</li>,
);
}
}
return <ul className={styles.listWrap}>{elements}</ul>;
};
const useChangelog = (componentPath: string, lang: 'cn' | 'en'): ChangelogInfo[] => {
const logFileName = `components-changelog-${lang}.json`;
const data = useFetch({
key: `component-changelog-${lang}`,
request: () => import(`../../../preset/components-changelog-${lang}.json`),
request: () => import(`../../../preset/${logFileName}`),
});
return React.useMemo(() => {
const component = componentPath.replace(/-/g, '');
@ -155,10 +241,11 @@ const useChangelog = (componentPath: string, lang: 'cn' | 'en'): ChangelogInfo[]
}, [data, componentPath]);
};
const ComponentChangelog: React.FC<ComponentChangelogProps> = (props) => {
const { pathname = '' } = props;
const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
const { children } = props;
const [locale, lang] = useLocale(locales);
const [show, setShow] = React.useState(false);
const { pathname } = useLocation();
const { styles } = useStyle();
@ -180,42 +267,46 @@ const ComponentChangelog: React.FC<ComponentChangelogProps> = (props) => {
return {
children: (
<Typography>
<Typography.Title level={4}>
{version}
{bugVersionInfo.match && (
<Popover
destroyTooltipOnHide
placement="right"
title={<span className={styles.bugReasonTitle}>{locale.bugList}</span>}
content={
<ul className={styles.bugReasonList}>
{bugVersionInfo.reason.map<React.ReactNode>((reason, index) => (
<li key={`reason-${index}`}>
<a type="link" target="_blank" rel="noreferrer" href={reason}>
<BugOutlined />
{reason
?.replace(/#.*$/, '')
?.replace(
/^https:\/\/github\.com\/ant-design\/ant-design\/(issues|pull)\//,
'#',
)}
</a>
</li>
))}
</ul>
}
>
<BugOutlined className={styles.bug} />
</Popover>
)}
</Typography.Title>
<ul>
{changelogList.map<React.ReactNode>((info, index) => (
<li key={index} className={styles.li}>
<ParseChangelog {...info} styles={styles} />
</li>
))}
</ul>
<Flex className={styles.versionWrap} justify="flex-start" align="center" gap="middle">
<Button
color="default"
className={styles.versionTitle}
variant="link"
href={`/changelog${lang === 'cn' ? '-cn' : ''}/#${version.replace(/\./g, '').replace(/\s.*/g, '-')}`}
>
{version}
{bugVersionInfo.match && (
<Popover
destroyTooltipOnHide
placement="right"
title={<span className={styles.bugReasonTitle}>{locale.bugList}</span>}
content={
<ul className={styles.bugReasonList}>
{bugVersionInfo.reason.map<React.ReactNode>((reason, index) => (
<li key={`reason-${index}`}>
<a type="link" target="_blank" rel="noreferrer" href={reason}>
<BugOutlined />
{reason
?.replace(/#.*$/, '')
?.replace(
/^https:\/\/github\.com\/ant-design\/ant-design\/(issues|pull)\//,
'#',
)}
</a>
</li>
))}
</ul>
}
>
<BugOutlined className={styles.bug} />
</Popover>
)}
</Button>
<Tag className={styles.versionTag} bordered={false} color="blue">
{changelogList[0]?.releaseDate}
</Tag>
</Flex>
<RenderChangelogList changelogList={changelogList} />
</Typography>
),
};
@ -225,34 +316,28 @@ const ComponentChangelog: React.FC<ComponentChangelogProps> = (props) => {
const screens = Grid.useBreakpoint();
const width = screens.md ? '48vw' : '90vw';
if (!list || !list.length) {
if (!pathname.startsWith('/components/') || !list || !list.length) {
return null;
}
return (
<>
<Button
className={styles.history}
icon={<HistoryOutlined />}
onClick={() => {
setShow(true);
}}
>
{locale.changelog}
</Button>
{isValidElement(children) &&
cloneElement(children as React.ReactElement<any>, {
onClick: () => setShow(true),
})}
<Drawer
destroyOnClose
className={styles.drawerContent}
title={locale.changelog}
extra={
<Link style={{ fontSize: 14 }} to={`/changelog${lang === 'cn' ? '-cn' : ''}`}>
<Link className={styles.extraLink} to={`/changelog${lang === 'cn' ? '-cn' : ''}`}>
{locale.full}
</Link>
}
open={show}
width={width}
onClose={() => {
setShow(false);
}}
destroyOnClose
onClose={() => setShow(false)}
>
<Timeline items={timelineItems} />
</Drawer>

View File

@ -1,9 +1,11 @@
import React from 'react';
import type { ComponentChangelogProps } from './ComponentChangelog';
import ComponentChangelog from './ComponentChangelog';
export default (props: ComponentChangelogProps) => (
const ChangeLog: React.FC<Readonly<React.PropsWithChildren>> = ({ children }) => (
<React.Suspense fallback={null}>
<ComponentChangelog {...props} />
<ComponentChangelog>{children}</ComponentChangelog>
</React.Suspense>
);
export default ChangeLog;

View File

@ -1,16 +1,20 @@
import React, { useEffect, useRef } from 'react';
import type { JSONEditorPropsOptional } from 'vanilla-jsoneditor';
import { JSONEditor, Mode } from 'vanilla-jsoneditor';
import type { JsonEditor, JSONEditorPropsOptional } from 'vanilla-jsoneditor';
import { createJSONEditor, Mode } from 'vanilla-jsoneditor';
const Editor: React.FC<JSONEditorPropsOptional> = (props) => {
const editorRef = useRef<JSONEditor>(null);
const editorRef = useRef<JsonEditor>();
const container = useRef<HTMLDivElement>(null);
useEffect(() => {
editorRef.current = new JSONEditor({
target: container.current,
props: { mode: Mode.text },
});
if (container.current) {
editorRef.current = createJSONEditor({
target: container.current,
props: {
mode: Mode.text,
},
});
}
return () => {
editorRef.current?.destroy();
};

View File

@ -1,58 +1,56 @@
import type { MouseEvent, MouseEventHandler } from 'react';
import React, { forwardRef, useLayoutEffect, useTransition } from 'react';
import { useLocation, useNavigate } from 'dumi';
import nprogress from 'nprogress';
import React, { useMemo, forwardRef } from 'react';
import { Link as DumiLink, useLocation, useAppData, useNavigate } from 'dumi';
export interface LinkProps {
to: string | { pathname?: string; search?: string; hash?: string };
style?: React.CSSProperties;
className?: string;
onClick?: MouseEventHandler;
component?: React.ComponentType<any>;
children?: React.ReactNode;
}
nprogress.configure({ showSpinner: false });
const Link = forwardRef<HTMLAnchorElement, React.PropsWithChildren<LinkProps>>((props, ref) => {
const { to, children, ...rest } = props;
const [isPending, startTransition] = useTransition();
const navigate = useNavigate();
const { pathname } = useLocation();
const href = React.useMemo<string>(() => {
if (typeof to === 'object') {
return `${to.pathname || pathname}${to.search || ''}${to.hash || ''}`;
}
return to;
}, [to]);
const handleClick = (e: MouseEvent<HTMLAnchorElement>) => {
props.onClick?.(e);
if (!href?.startsWith('http')) {
// Should support open in new tab
if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
e.preventDefault();
startTransition(() => {
if (href) {
navigate(href);
}
});
const Link = forwardRef<HTMLAnchorElement, React.PropsWithChildren<LinkProps>>(
({ component, children, to, ...rest }, ref) => {
const { pathname } = useLocation();
const { preloadRoute } = useAppData();
const navigate = useNavigate();
const href = useMemo<string>(() => {
if (typeof to === 'object') {
return `${to.pathname || pathname}${to.search || ''}${to.hash || ''}`;
}
return to;
}, [to]);
const onClick = (e: MouseEvent<HTMLAnchorElement>) => {
rest.onClick?.(e);
if (!href?.startsWith('http')) {
// Should support open in new tab
if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
e.preventDefault();
navigate(href);
}
}
};
if (component) {
return React.createElement(
component,
{
...rest,
ref,
href,
onClick,
onMouseEnter: () => preloadRoute?.(href),
},
children,
);
}
};
useLayoutEffect(() => {
if (isPending) {
nprogress.start();
} else {
nprogress.done();
}
}, [isPending]);
return (
<a ref={ref} onClick={handleClick} {...rest} href={href}>
{children}
</a>
);
});
return (
<DumiLink ref={ref} {...rest} to={href} prefetch>
{children}
</DumiLink>
);
},
);
export default Link;

View File

@ -0,0 +1,12 @@
import React from 'react';
import { Button } from 'antd';
import type { ButtonProps } from 'antd';
import Link from './Link';
import type { LinkProps } from './Link';
type LinkButtonProps = LinkProps &
Readonly<React.PropsWithChildren<Pick<ButtonProps, 'type' | 'size'>>>;
const LinkButton: React.FC<LinkButtonProps> = (props) => <Link component={Button} {...props} />;
export default LinkButton;

View File

@ -1,24 +1,23 @@
import type { ComponentProps, FC } from 'react';
import React, { useEffect, useState } from 'react';
import { EditFilled } from '@ant-design/icons';
import { Tooltip } from 'antd';
import React from 'react';
import { createStyles } from 'antd-style';
import SourceCodeEditor from 'dumi/theme-default/slots/SourceCodeEditor';
import useLocale from '../../hooks/useLocale';
import LiveError from '../slots/LiveError';
const useStyle = createStyles(({ token, css }) => {
const { colorBgContainer, colorIcon } = token;
const { colorBgContainer } = token;
return {
editor: css`
// override dumi editor styles
.dumi-default-source-code-editor {
.dumi-default-source-code {
background: ${colorBgContainer};
&-scroll-container {
scrollbar-width: thin;
scrollbar-gutter: stable;
}
}
.dumi-default-source-code > pre,
.dumi-default-source-code-scroll-content > pre,
.dumi-default-source-code-editor-textarea {
@ -48,70 +47,24 @@ const useStyle = createStyles(({ token, css }) => {
}
}
`,
editableIcon: css`
position: absolute;
z-index: 2;
height: 32px;
width: 32px;
display: flex;
align-items: center;
justify-content: center;
top: 16px;
inset-inline-end: 56px;
color: ${colorIcon};
`,
};
});
const locales = {
cn: {
demoEditable: '编辑 Demo 可实时预览',
},
en: {
demoEditable: 'Edit demo with real-time preview',
},
};
const HIDE_LIVE_DEMO_TIP = 'hide-live-demo-tip';
const LiveCode: FC<
{
error: Error | null;
} & Pick<ComponentProps<typeof SourceCodeEditor>, 'lang' | 'initialValue' | 'onChange'>
> = (props) => {
const [open, setOpen] = useState(false);
const { styles } = useStyle();
const [locale] = useLocale(locales);
useEffect(() => {
const shouldOpen = !localStorage.getItem(HIDE_LIVE_DEMO_TIP);
if (shouldOpen) {
setOpen(true);
}
}, []);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
if (!newOpen) {
localStorage.setItem(HIDE_LIVE_DEMO_TIP, 'true');
}
};
return (
<>
<div className={styles.editor}>
<SourceCodeEditor
lang={props.lang}
initialValue={props.initialValue}
onChange={props.onChange}
/>
<LiveError error={props.error} />
</div>
<Tooltip title={locale.demoEditable} open={open} onOpenChange={handleOpenChange}>
<EditFilled className={styles.editableIcon} />
</Tooltip>
</>
<div className={styles.editor}>
<SourceCodeEditor
lang={props.lang}
initialValue={props.initialValue}
onChange={props.onChange}
/>
<LiveError error={props.error} />
</div>
);
};

View File

@ -141,13 +141,23 @@ const PrevAndNext: React.FC<{ rtl?: boolean }> = ({ rtl }) => {
return (
<section className={styles.prevNextNav}>
{prev &&
React.cloneElement(prev.label as ReactElement, {
className: classNames(styles.pageNav, styles.prevNav, prev.className),
})}
React.cloneElement(
prev.label as ReactElement<{
className: string;
}>,
{
className: classNames(styles.pageNav, styles.prevNav, prev.className),
},
)}
{next &&
React.cloneElement(next.label as ReactElement, {
className: classNames(styles.pageNav, styles.nextNav, next.className),
})}
React.cloneElement(
next.label as ReactElement<{
className: string;
}>,
{
className: classNames(styles.pageNav, styles.nextNav, next.className),
},
)}
</section>
);
};

View File

@ -1,20 +1,19 @@
import React from 'react';
import Icon from '@ant-design/icons';
const ThemeIcon: React.FC<{ className?: string }> = (props) => {
const SVGIcon = React.useCallback(
() => (
<svg width={20} height={20} viewBox="0 0 24 24" fill="currentColor" {...props}>
<g fillRule="evenodd">
<g fillRule="nonzero">
<path d="M7.02 3.635l12.518 12.518a1.863 1.863 0 010 2.635l-1.317 1.318a1.863 1.863 0 01-2.635 0L3.068 7.588A2.795 2.795 0 117.02 3.635zm2.09 14.428a.932.932 0 110 1.864.932.932 0 010-1.864zm-.043-9.747L7.75 9.635l9.154 9.153 1.318-1.317-9.154-9.155zM3.52 12.473c.514 0 .931.417.931.931v.932h.932a.932.932 0 110 1.864h-.932v.931a.932.932 0 01-1.863 0l-.001-.931h-.93a.932.932 0 010-1.864h.93v-.932c0-.514.418-.931.933-.931zm15.374-3.727a1.398 1.398 0 110 2.795 1.398 1.398 0 010-2.795zM4.385 4.953a.932.932 0 000 1.317l2.046 2.047L7.75 7 5.703 4.953a.932.932 0 00-1.318 0zM14.701.36a.932.932 0 01.931.932v.931h.932a.932.932 0 010 1.864h-.933l.001.932a.932.932 0 11-1.863 0l-.001-.932h-.93a.932.932 0 110-1.864h.93v-.931a.932.932 0 01.933-.932z" />
</g>
</g>
</svg>
),
[props],
);
return <Icon component={SVGIcon} {...props} />;
};
const SVGIcon: React.FC = (props) => (
<svg width={20} height={20} viewBox="0 0 24 24" fill="currentColor" {...props}>
<title>Theme icon</title>
<g fillRule="evenodd">
<g fillRule="nonzero">
<path d="M7.02 3.635l12.518 12.518a1.863 1.863 0 010 2.635l-1.317 1.318a1.863 1.863 0 01-2.635 0L3.068 7.588A2.795 2.795 0 117.02 3.635zm2.09 14.428a.932.932 0 110 1.864.932.932 0 010-1.864zm-.043-9.747L7.75 9.635l9.154 9.153 1.318-1.317-9.154-9.155zM3.52 12.473c.514 0 .931.417.931.931v.932h.932a.932.932 0 110 1.864h-.932v.931a.932.932 0 01-1.863 0l-.001-.931h-.93a.932.932 0 010-1.864h.93v-.932c0-.514.418-.931.933-.931zm15.374-3.727a1.398 1.398 0 110 2.795 1.398 1.398 0 010-2.795zM4.385 4.953a.932.932 0 000 1.317l2.046 2.047L7.75 7 5.703 4.953a.932.932 0 00-1.318 0zM14.701.36a.932.932 0 01.931.932v.931h.932a.932.932 0 010 1.864h-.933l.001.932a.932.932 0 11-1.863 0l-.001-.932h-.93a.932.932 0 110-1.864h.93v-.931a.932.932 0 01.933-.932z" />
</g>
</g>
</svg>
);
const ThemeIcon: React.FC<{ className?: string }> = (props) => (
<Icon component={SVGIcon} {...props} />
);
export default ThemeIcon;

View File

@ -1,13 +1,13 @@
import React from 'react';
import { BgColorsOutlined, SmileOutlined } from '@ant-design/icons';
import { FloatButton } from 'antd';
import { useTheme } from 'antd-style';
import { CompactTheme, DarkTheme } from 'antd-token-previewer/es/icons';
// import { Motion } from 'antd-token-previewer/es/icons';
import { FormattedMessage, Link, useLocation } from 'dumi';
import { FormattedMessage, useLocation } from 'dumi';
import useThemeAnimation from '../../../hooks/useThemeAnimation';
import { getLocalizedPathname, isZhCN } from '../../utils';
import Link from '../Link';
import ThemeIcon from './ThemeIcon';
export type ThemeName = 'light' | 'dark' | 'compact' | 'motion-off' | 'happy-work';
@ -19,7 +19,6 @@ export interface ThemeSwitchProps {
const ThemeSwitch: React.FC<ThemeSwitchProps> = (props) => {
const { value = ['light'], onChange } = props;
const token = useTheme();
const { pathname, search } = useLocation();
// const isMotionOff = value.includes('motion-off');
@ -34,11 +33,10 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = (props) => {
icon={<ThemeIcon />}
aria-label="Theme Switcher"
badge={{ dot: true }}
style={{ zIndex: 1010 }}
>
<Link
to={getLocalizedPathname('/theme-editor', isZhCN(pathname), search)}
style={{ display: 'block', marginBottom: token.margin }}
style={{ display: 'block' }}
>
<FloatButton
icon={<BgColorsOutlined />}

View File

@ -71,18 +71,6 @@ const GlobalDemoStyles: React.FC = () => {
border: 1px solid ${token.colorPrimary};
}
&-expand-trigger {
position: relative;
color: #3b4357;
font-size: ${token.fontSizeXL}px;
cursor: pointer;
opacity: 0.75;
transition: all ${token.motionDurationSlow};
&:hover {
opacity: 1;
}
}
&-title {
position: absolute;
top: -14px;
@ -108,7 +96,7 @@ const GlobalDemoStyles: React.FC = () => {
a.edit-button {
position: absolute;
top: 7px;
right: -16px;
inset-inline-end: -16px;
font-size: ${token.fontSizeSM}px;
text-decoration: none;
background: inherit;
@ -125,8 +113,8 @@ const GlobalDemoStyles: React.FC = () => {
}
${antCls}-row${antCls}-row-rtl & {
right: auto;
left: -22px;
inset-inline-end: auto;
inset-inline-start: -22px;
}
}
@ -184,7 +172,7 @@ const GlobalDemoStyles: React.FC = () => {
.code-expand-icon-hide {
position: absolute;
top: 0;
left: 0;
inset-inline-start: 0;
width: 100%;
max-width: 100%;
margin: 0;
@ -193,8 +181,8 @@ const GlobalDemoStyles: React.FC = () => {
user-select: none;
${antCls}-row-rtl & {
right: 0;
left: auto;
inset-inline-end: 0;
inset-inline-start: auto;
}
}
@ -294,12 +282,13 @@ const GlobalDemoStyles: React.FC = () => {
cursor: pointer;
}
&-riddle {
width: 14px;
height: 14px;
&-codeblock {
width: 16px;
height: 16px;
overflow: hidden;
border: 0;
cursor: pointer;
max-width: 100% !important;
}
&-codesandbox {
@ -353,13 +342,24 @@ const GlobalDemoStyles: React.FC = () => {
inset-inline-end: 0;
display: flex;
align-items: center;
column-gap: ${token.marginSM}px;
column-gap: ${token.marginXS}px;
}
${antCls}-btn {
&.icon-enabled {
background-color: ${token.colorFillSecondary};
opacity: 1;
${iconCls} {
color: ${token.colorTextBase};
font-weight: bold;
}
}
}
${antCls}-row-rtl {
#components-tooltip-demo-placement,
#components-popover-demo-placement,
#components-popconfirm-demo-placement {
#tooltip-demo-placement,
#popover-demo-placement,
#popconfirm-demo-placement {
.code-box-demo {
direction: ltr;
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { TinyColor } from '@ctrl/tinycolor';
import { FastColor } from '@ant-design/fast-color';
import { css, Global } from '@emotion/react';
import { useTheme } from 'antd-style';
@ -34,10 +34,10 @@ const GlobalStyle: React.FC = () => {
margin: 0 auto;
}
.markdown p > img {
.markdown p > img,
.markdown li > img {
margin: 34px auto;
box-shadow: 0 8px 20px rgba(143, 168, 191, 0.35);
max-width: 1024px;
display: block;
}
@ -111,39 +111,24 @@ const GlobalStyle: React.FC = () => {
}
}
.markdown ul > li {
margin-inline-start: ${token.marginMD}px;
.markdown ul > li,
.markdown ol > li {
padding-inline-start: ${token.paddingXXS}px;
list-style-type: circle;
.rtl & {
margin-inline-end: ${token.marginMD}px;
margin-inline-start: 0;
padding-inline-end: ${token.paddingXXS}px;
padding-inline-start: 0;
margin-inline-start: ${token.marginMD}px;
> p {
margin: 0.2em 0;
}
&:empty {
display: none;
}
}
.markdown ol > li {
margin-inline-start: ${token.marginMD}px;
padding-inline-start: ${token.paddingXXS}px;
list-style-type: decimal;
${antCls}-row-rtl & {
margin-inline-end: ${token.marginMD}px;
margin-inline-start: 0;
padding-inline-end: ${token.paddingXXS}px;
padding-inline-start: 0;
}
.markdown ul > li {
list-style-type: circle;
}
.markdown ul > li > p,
.markdown ol > li > p {
margin: 0.2em 0;
.markdown ol > li {
list-style-type: decimal;
}
.markdown code {
@ -183,6 +168,8 @@ const GlobalStyle: React.FC = () => {
background-color: ${token.siteMarkdownCodeBg};
border-radius: ${token.borderRadius}px;
> pre.prism-code {
scrollbar-width: thin;
scrollbar-gutter: stable;
padding: ${token.paddingSM}px ${token.paddingMD}px;
font-size: ${token.fontSize}px;
line-height: 2;
@ -197,6 +184,7 @@ const GlobalStyle: React.FC = () => {
margin: 0 ${token.marginMD}px;
color: #aaa;
font-size: 30px;
user-select: none;
}
}
@ -282,6 +270,10 @@ const GlobalStyle: React.FC = () => {
}
.markdown .dumi-default-table {
&-content {
scrollbar-width: thin;
scrollbar-gutter: stable;
}
table {
margin: 0;
overflow-x: auto;
@ -381,10 +373,28 @@ const GlobalStyle: React.FC = () => {
}
}
}
/*
Api del ) css
移步讨论区: https://github.com/ant-design/ant-design/discussions/51298
*/
tr:has(td:first-child > del) {
color: ${token.colorWarning} !important;
background-color: ${token.colorWarningBg} !important;
display: var(--antd-site-api-deprecated-display, none);
del {
color: ${token.colorWarning};
}
&:hover del {
text-decoration: none;
}
}
}
.grid-demo,
[id^='components-grid-demo-'] {
[id^='grid-demo-'] {
${antCls}-row > div,
.code-box-demo ${antCls}-row > div {
min-height: 30px;
@ -400,7 +410,7 @@ const GlobalStyle: React.FC = () => {
background: ${demoGridColor};
&:nth-child(2n + 1) {
background: ${new TinyColor(demoGridColor).setAlpha(0.75).toHex8String()};
background: ${new FastColor(demoGridColor).setA(0.75).toHexString()};
}
}
@ -416,12 +426,12 @@ const GlobalStyle: React.FC = () => {
}
${antCls}-row .demo-col-1 {
background: ${new TinyColor(demoGridColor).setAlpha(0.75).toHexString()};
background: ${new FastColor(demoGridColor).setA(0.75).toHexString()};
}
${antCls}-row .demo-col-2,
.code-box-demo ${antCls}-row .demo-col-2 {
background: ${new TinyColor(demoGridColor).setAlpha(0.75).toHexString()};
background: ${new FastColor(demoGridColor).setA(0.75).toHexString()};
}
${antCls}-row .demo-col-3,
@ -432,7 +442,7 @@ const GlobalStyle: React.FC = () => {
${antCls}-row .demo-col-4,
.code-box-demo ${antCls}-row .demo-col-4 {
background: ${new TinyColor(demoGridColor).setAlpha(0.6).toHexString()};
background: ${new FastColor(demoGridColor).setA(0.6).toHexString()};
}
${antCls}-row .demo-col-5,
@ -462,8 +472,8 @@ const GlobalStyle: React.FC = () => {
}
}
[id='components-grid-demo-playground'],
[id='components-grid-demo-gutter'] {
[id='grid-demo-playground'],
[id='grid-demo-gutter'] {
> .code-box-demo ${antCls}-row > div {
margin-top: 0;
margin-bottom: 0;

View File

@ -70,14 +70,14 @@ export default () => {
+ svg {
position: absolute;
top: 0;
left: 0;
inset-inline-start: 0;
}
}
.preview-image-wrapper.good::after {
position: absolute;
bottom: 0;
left: 0;
inset-inline-start: 0;
display: block;
width: 100%;
height: 3px;
@ -88,7 +88,7 @@ export default () => {
.preview-image-wrapper.bad::after {
position: absolute;
bottom: 0;
left: 0;
inset-inline-start: 0;
display: block;
width: 100%;
height: 3px;

View File

@ -1,5 +1,5 @@
import { css, Global } from '@emotion/react';
import React from 'react';
import { css, Global } from '@emotion/react';
import { useTheme } from 'antd-style';
export default () => {
@ -11,7 +11,8 @@ export default () => {
@font-face {
font-weight: normal;
font-family: AlibabaPuHuiTi;
src: url('//at.alicdn.com/t/webfont_6e11e43nfj.woff2') format('woff2'),
src:
url('//at.alicdn.com/t/webfont_6e11e43nfj.woff2') format('woff2'),
url('//at.alicdn.com/t/webfont_6e11e43nfj.woff') format('woff'),
/* chrome、firefox */ url('//at.alicdn.com/t/webfont_6e11e43nfj.ttf') format('truetype'); /* chrome、firefox、opera、Safari, Android, iOS 4.2+ */
font-display: swap;
@ -20,7 +21,8 @@ export default () => {
@font-face {
font-weight: bold;
font-family: AlibabaPuHuiTi;
src: url('//at.alicdn.com/t/webfont_exesdog9toj.woff2') format('woff2'),
src:
url('//at.alicdn.com/t/webfont_exesdog9toj.woff2') format('woff2'),
url('//at.alicdn.com/t/webfont_exesdog9toj.woff') format('woff'),
/* chrome、firefox */ url('//at.alicdn.com/t/webfont_exesdog9toj.ttf')
format('truetype'); /* chrome、firefox、opera、Safari, Android, iOS 4.2+ */
@ -32,7 +34,8 @@ export default () => {
@font-face {
font-weight: 900;
font-family: 'AliPuHui';
src: url('//at.alicdn.com/wf/webfont/exMpJIukiCms/Gsw2PSKrftc1yNWMNlXgw.woff2')
src:
url('//at.alicdn.com/wf/webfont/exMpJIukiCms/Gsw2PSKrftc1yNWMNlXgw.woff2')
format('woff2'),
url('//at.alicdn.com/wf/webfont/exMpJIukiCms/vtu73by4O2gEBcvBuLgeu.woff') format('woff');
font-display: swap;

View File

@ -11,7 +11,7 @@ export default () => {
.nav-phone-icon {
position: absolute;
bottom: 17px;
right: 30px;
inset-inline-end: 30px;
z-index: 1;
display: none;
width: 16px;
@ -98,8 +98,8 @@ export default () => {
.drawer {
.ant-menu-inline .ant-menu-item::after,
.ant-menu-vertical .ant-menu-item::after {
right: auto;
left: 0;
inset-inline-end: auto;
inset-inline-start: 0;
}
}

View File

@ -3,12 +3,13 @@ import Icon from '@ant-design/icons';
const SVGIcon: React.FC = () => (
<svg viewBox="0 0 15 15" fill="currentColor">
<title>codepen icon</title>
<path d="M14.777304,4.75062256 L7.77734505,0.0839936563 C7.60939924,-0.0279665065 7.39060662,-0.0279665065 7.22266081,0.0839936563 L0.222701813,4.75062256 C0.0836082937,4.84334851 5.66973453e-05,4.99945222 4.6875e-05,5.16662013 L4.6875e-05,9.83324903 C4.6875e-05,10.0004355 0.0836088906,10.1565596 0.222701812,10.2492466 L7.22266081,14.9158755 C7.30662908,14.9718752 7.403316,14.999875 7.50000292,14.999875 C7.59668984,14.999875 7.69337678,14.9718752 7.77734505,14.9158755 L14.777304,10.2492466 C14.9163976,10.1565206 14.9999492,10.0004169 14.999959,9.83324903 L14.999959,5.16662013 C14.9999492,4.99945222 14.9163976,4.84334851 14.777304,4.75062256 Z M7.50000292,9.23237755 L4.90139316,7.4999502 L7.50000292,5.76755409 L10.0986127,7.4999502 L7.50000292,9.23237755 Z M8,4.89905919 L8,1.43423573 L13.598561,5.16665138 L10.9999824,6.89904747 L8,4.89905919 Z M7.00000586,4.89905919 L4.00002344,6.89904747 L1.40141366,5.16665138 L7.00000586,1.43423573 L7.00000586,4.89905919 Z M3.09865372,7.4999502 L1.00004102,8.89903575 L1.00004102,6.10089589 L3.09865372,7.4999502 Z M4.00002344,8.10085292 L7.00000586,10.1008412 L7.00000586,13.5656334 L1.40141366,9.83328028 L4.00002344,8.10085292 Z M8,10.1008412 L10.9999824,8.10085292 L13.5985922,9.83328028 L8,13.5656647 L8,10.1008412 L8,10.1008412 Z M11.9013521,7.4999502 L13.9999648,6.10089589 L13.9999648,8.899067 L11.9013521,7.4999502 Z" />
</svg>
);
const CodePenIcon: React.FC<{ className?: string }> = (props) => (
<Icon component={SVGIcon} {...props} />
);
const CodePenIcon = React.forwardRef<HTMLSpanElement, { className?: string }>((props, ref) => (
<Icon component={SVGIcon} ref={ref} {...props} />
));
export default CodePenIcon;

View File

@ -3,12 +3,13 @@ import Icon from '@ant-design/icons';
const SVGIcon: React.FC = () => (
<svg viewBox="0 0 1024 1024" fill="currentColor">
<title>CodeSandbox Icon</title>
<path d="M755 140.3l0.5-0.3h0.3L512 0 268.3 140h-0.3l0.8 0.4L68.6 256v512L512 1024l443.4-256V256L755 140.3z m-30 506.4v171.2L548 920.1V534.7L883.4 341v215.7l-158.4 90z m-584.4-90.6V340.8L476 534.4v385.7L300 818.5V646.7l-159.4-90.6zM511.7 280l171.1-98.3 166.3 96-336.9 194.5-337-194.6 165.7-95.7L511.7 280z" />
</svg>
);
const CodeSandboxIcon: React.FC<{ className?: string }> = (props) => (
<Icon component={SVGIcon} {...props} />
);
const CodeSandboxIcon = React.forwardRef<HTMLSpanElement, { className?: string }>((props, ref) => (
<Icon component={SVGIcon} ref={ref} {...props} />
));
export default CodeSandboxIcon;

View File

@ -15,12 +15,13 @@ const DirectionSvg: React.FC<DirectionIconProps> = ({ direction }) => (
fill="currentColor"
style={{ transform: `scaleX(${direction === 'ltr' ? '1' : '-1'})` }}
>
<title>Direction Icon</title>
<path d="m14.6961816 11.6470802.0841184.0726198 2 2c.2662727.2662727.2904793.682876.0726198.9764816l-.0726198.0841184-2 2c-.2929.2929-.7677.2929-1.0606 0-.2662727-.2662727-.2904793-.682876-.0726198-.9764816l.0726198-.0841184.7196-.7197h-10.6893c-.41421 0-.75-.3358-.75-.75 0-.3796833.28215688-.6934889.64823019-.7431531l.10176981-.0068469h10.6893l-.7196-.7197c-.2929-.2929-.2929-.7677 0-1.0606.2662727-.2662727.682876-.2904793.9764816-.0726198zm-8.1961616-8.6470802c.30667 0 .58246.18671.69635.47146l3.00003 7.50004c.1538.3845-.0333.821-.41784.9749-.38459.1538-.82107-.0333-.9749-.4179l-.81142-2.0285h-2.98445l-.81142 2.0285c-.15383.3846-.59031.5717-.9749.4179-.38458-.1539-.57165-.5904-.41781-.9749l3-7.50004c.1139-.28475.38968-.47146.69636-.47146zm8.1961616 1.14705264.0841184.07261736 2 2c.2662727.26626364.2904793.68293223.0726198.97654222l-.0726198.08411778-2 2c-.2929.29289-.7677.29289-1.0606 0-.2662727-.26626364-.2904793-.68293223-.0726198-.97654222l.0726198-.08411778.7196-.7196675h-3.6893c-.4142 0-.75-.3357925-.75-.7500025 0-.3796925.2821653-.69348832.6482323-.74315087l.1017677-.00684663h3.6893l-.7196-.7196725c-.2929-.29289-.2929-.76777 0-1.06066.2662727-.26626364.682876-.29046942.9764816-.07261736zm-8.1961616 1.62238736-.89223 2.23056h1.78445z" />
</svg>
);
const DirectionIcon: React.FC<DirectionIconProps> = (props) => (
<Icon {...props} component={() => <DirectionSvg direction={props.direction} />} />
);
const DirectionIcon = React.forwardRef<HTMLSpanElement, DirectionIconProps>((props, ref) => (
<Icon ref={ref} component={() => <DirectionSvg direction={props.direction} />} {...props} />
));
export default DirectionIcon;

View File

@ -1,15 +1,21 @@
import React from 'react';
import Icon from '@ant-design/icons';
interface ExternalIconProps {
className?: string;
color?: string;
}
const SVGIcon: React.FC<{ color?: string }> = ({ color = 'currentColor' }) => (
<svg viewBox="0 0 1024 1024" width="1em" height="1em" fill={color}>
<title>External Link Icon</title>
<path d="M853.333 469.333A42.667 42.667 0 0 0 810.667 512v256A42.667 42.667 0 0 1 768 810.667H256A42.667 42.667 0 0 1 213.333 768V256A42.667 42.667 0 0 1 256 213.333h256A42.667 42.667 0 0 0 512 128H256a128 128 0 0 0-128 128v512a128 128 0 0 0 128 128h512a128 128 0 0 0 128-128V512a42.667 42.667 0 0 0-42.667-42.667z" />
<path d="M682.667 213.333h67.413L481.707 481.28a42.667 42.667 0 0 0 0 60.587 42.667 42.667 0 0 0 60.586 0L810.667 273.92v67.413A42.667 42.667 0 0 0 853.333 384 42.667 42.667 0 0 0 896 341.333V170.667A42.667 42.667 0 0 0 853.333 128H682.667a42.667 42.667 0 0 0 0 85.333z" />
</svg>
);
const ExternalLinkIcon: React.FC<{ className?: string }> = (props) => (
<Icon component={SVGIcon} {...props} />
);
const ExternalLinkIcon = React.forwardRef<HTMLSpanElement, ExternalIconProps>((props, ref) => (
<Icon component={() => <SVGIcon color={props.color} />} ref={ref} {...props} />
));
export default ExternalLinkIcon;

View File

@ -3,12 +3,13 @@ import Icon from '@ant-design/icons';
const SVGIcon: React.FC = () => (
<svg viewBox="0 0 14 14" fill="currentColor">
<title>Riddle logo</title>
<path d="M13.8875145,13.1234844 C13.8687399,13.0691875 13.8499977,13.0329687 13.8312555,12.9786562 L11.3687445,8.83296875 C12.9187468,8.05754687 13.9640694,6.49009375 13.9640694,4.68728125 C13.9624994,2.09095312 11.7968694,0 9.10938728,0 L3.86404855,0 C3.04217572,0 2.37028902,0.648703125 2.37028902,1.44223437 L2.37028902,1.82090625 L0.746871676,1.82090625 C0.33593526,1.82090625 0,2.14526562 0,2.54203125 L0,13.4478437 C0,13.7540937 0.242191908,13.9879375 0.559368786,13.9879375 C0.615627746,13.9879375 0.67187052,13.9698281 0.72812948,13.9517187 L13.440615,13.9517187 C13.7578081,13.9517187 14,13.7178906 14,13.4116406 C14,13.321125 13.9624994,13.2125 13.8875145,13.1234844 Z M3.49061272,8.0394375 L3.49061272,2.9206875 L8.71719306,2.9206875 C9.74375723,2.9206875 10.5843723,3.73232812 10.5843723,4.7235 C10.5843723,5.71465625 9.76249942,6.5081875 8.71719306,6.5081875 L6.53280462,6.5081875 L6.53280462,6.52629688 C6.45781965,6.52629688 6.3828185,6.5625 6.3093711,6.59870313 C6.04843699,6.74354688 5.95469364,7.08598438 6.10467977,7.33792188 L8.3078104,11.0325469 L3.4906289,11.0325469 L3.4906289,8.0394375 L3.49061272,8.0394375 Z M1.1203237,12.8881406 L1.1203237,2.9206875 L2.3703052,2.9206875 L2.3703052,11.5545313 C2.3703052,11.8607813 2.61249711,12.0946094 2.92969017,12.0946094 L2.94843237,12.0946094 C2.98593295,12.1127188 3.04219191,12.1127188 3.09843468,12.1127188 L9.16563006,12.1127188 C9.48280694,12.1127188 9.72499884,11.878875 9.72499884,11.572625 L9.72499884,11.5364219 C9.76249942,11.3915938 9.74375723,11.2482813 9.66875607,11.1215469 L7.5593526,7.58835938 L8.6984185,7.58835938 C10.3406104,7.58835938 11.6843514,6.29095313 11.6843514,4.703875 C11.6843514,3.1168125 10.3406104,1.81939063 8.6984185,1.81939063 L3.4906289,1.81939063 L3.4906289,1.44073437 C3.4906289,1.24310937 3.65937341,1.08017187 3.86406474,1.08017187 L9.09061272,1.08017187 C11.143741,1.08017187 12.8234173,2.7019375 12.8234173,4.68578125 C12.8234173,6.21853125 11.8343538,7.5340625 10.4343538,8.05603125 C10.378111,8.07414063 10.3406104,8.09223438 10.2843514,8.11034375 C10.0234173,8.25517188 9.92967399,8.597625 10.0796763,8.8495625 L12.5062405,12.8881563 L1.12030751,12.8881563 L1.1203237,12.8881406 Z" />
</svg>
);
const RiddleIcon: React.FC<{ className?: string }> = (props) => (
<Icon component={SVGIcon} {...props} />
);
const RiddleIcon = React.forwardRef<HTMLSpanElement, { className?: string }>((props, ref) => (
<Icon component={SVGIcon} ref={ref} {...props} />
));
export default RiddleIcon;

View File

@ -37,7 +37,7 @@ const DocLayout: React.FC = () => {
const location = useLocation();
const { pathname, search, hash } = location;
const [locale, lang] = useLocale(locales);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const timerRef = useRef<ReturnType<typeof setTimeout>>(null!);
const { direction } = useContext(SiteContext);
const { loading } = useSiteData();
@ -51,11 +51,10 @@ const DocLayout: React.FC = () => {
useEffect(() => {
const nprogressHiddenStyle = document.getElementById('nprogress-style');
if (nprogressHiddenStyle) {
timerRef.current = setTimeout(() => {
nprogressHiddenStyle.parentNode?.removeChild(nprogressHiddenStyle);
}, 0);
}
timerRef.current = setTimeout(() => {
nprogressHiddenStyle?.remove();
}, 0);
return () => clearTimeout(timerRef.current);
}, []);
// handle hash change or visit page hash from Link component, and jump after async chunk loaded

View File

@ -1,4 +1,5 @@
import React, { Suspense, useCallback, useEffect } from 'react';
import { Monitoring } from 'react-scan/monitoring';
import {
createCache,
extractStyle,
@ -12,7 +13,13 @@ import { getSandpackCssText } from '@codesandbox/sandpack-react';
import { theme as antdTheme, App } from 'antd';
import type { MappingAlgorithm } from 'antd';
import type { DirectionType, ThemeConfig } from 'antd/es/config-provider';
import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi';
import {
createSearchParams,
useOutlet,
useParams,
useSearchParams,
useServerInsertedHTML,
} from 'dumi';
import { DarkContext } from '../../hooks/useDark';
import useLayoutState from '../../hooks/useLayoutState';
@ -22,6 +29,8 @@ import SiteThemeProvider from '../SiteThemeProvider';
import type { SiteContextProps } from '../slots/SiteContext';
import SiteContext from '../slots/SiteContext';
import '@ant-design/v5-patch-for-react-19';
const ThemeSwitch = React.lazy(() => import('../common/ThemeSwitch'));
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][];
@ -35,6 +44,16 @@ export const ANT_DESIGN_NOT_SHOW_BANNER = 'ANT_DESIGN_NOT_SHOW_BANNER';
// (global as any).styleCache = styleCache;
// }
// Compatible with old anchors
if (typeof window !== 'undefined') {
const hashId = location.hash.slice(1);
if (hashId.startsWith('components-')) {
if (!document.querySelector(`#${hashId}`)) {
location.hash = `#${hashId.replace(/^components-/, '')}`;
}
}
}
const getAlgorithm = (themes: ThemeName[] = []) =>
themes
.map((theme) => {
@ -51,6 +70,7 @@ const getAlgorithm = (themes: ThemeName[] = []) =>
const GlobalLayout: React.FC = () => {
const outlet = useOutlet();
const { pathname } = useLocation();
const params = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const [{ theme = [], direction, isMobile, bannerVisible = false }, setSiteState] =
useLayoutState<SiteState>({
@ -60,6 +80,9 @@ const GlobalLayout: React.FC = () => {
bannerVisible: false,
});
// TODO: This can be remove in v6
const useCssVar = searchParams.get('cssVar') !== 'false';
const updateSiteConfig = useCallback(
(props: SiteState) => {
setSiteState((prev) => ({ ...prev, ...props }));
@ -140,8 +163,8 @@ const GlobalLayout: React.FC = () => {
() => ({
algorithm: getAlgorithm(theme),
token: { motion: !theme.includes('motion-off') },
cssVar: true,
hashed: false,
cssVar: useCssVar,
hashed: !useCssVar,
}),
[theme],
);
@ -153,6 +176,7 @@ const GlobalLayout: React.FC = () => {
plain: true,
types: 'style',
});
// biome-ignore lint/security/noDangerouslySetInnerHtml: only used in .dumi
return <style data-type="antd-cssinjs" dangerouslySetInnerHTML={{ __html: styleText }} />;
});
@ -166,6 +190,7 @@ const GlobalLayout: React.FC = () => {
data-type="antd-css-var"
data-rc-order="prepend"
data-rc-priority="-9999"
// biome-ignore lint/security/noDangerouslySetInnerHtml: only used in .dumi
dangerouslySetInnerHTML={{ __html: styleText }}
/>
);
@ -175,6 +200,7 @@ const GlobalLayout: React.FC = () => {
<style
data-sandpack="true"
id="sandpack"
// biome-ignore lint/security/noDangerouslySetInnerHtml: only used in .dumi
dangerouslySetInnerHTML={{ __html: getSandpackCssText() }}
/>
));
@ -207,7 +233,17 @@ const GlobalLayout: React.FC = () => {
>
<SiteContext.Provider value={siteContextValue}>
<SiteThemeProvider theme={themeConfig}>
<HappyProvider disabled={!theme.includes('happy-work')}>{content}</HappyProvider>
<HappyProvider disabled={!theme.includes('happy-work')}>
{content}
<Monitoring
apiKey="GhrCCNrHZHXlf4P6E03ntrFwhRLxJL30" // Safe to expose publically
url="https://monitoring.react-scan.com/api/v1/ingest"
commit={process.env.COMMIT_HASH}
branch={process.env.BRANCH}
params={params as Record<string, string>}
path={pathname}
/>
</HappyProvider>
</SiteThemeProvider>
</SiteContext.Provider>
</StyleProvider>

View File

@ -6,7 +6,7 @@ import throttle from 'lodash/throttle';
import scrollTo from '../../../../components/_util/scrollTo';
const listenerEvents = ['scroll', 'resize'] as const;
const listenerEvents: (keyof WindowEventMap)[] = ['scroll', 'resize'];
const useStyle = createStyles(({ token, css }) => {
const { boxShadowSecondary, antCls } = token;
@ -15,8 +15,8 @@ const useStyle = createStyles(({ token, css }) => {
affixTabs: css`
position: fixed;
top: 0;
right: 0;
left: 0;
inset-inline-end: 0;
inset-inline-start: 0;
z-index: 1001;
padding: 0 40px;
background: #fff;

View File

@ -10,7 +10,7 @@ import EditButton from '../../common/EditButton';
import Footer from '../../slots/Footer';
import AffixTabs from './AffixTabs';
export type ResourceLayoutProps = PropsWithChildren<{}>;
export type ResourceLayoutProps = PropsWithChildren<NonNullable<any>>;
const resourcePadding = 40;
const articleMaxWidth = 1208;

View File

@ -35,7 +35,7 @@
"app.demo.codepen": "Open in CodePen",
"app.demo.codesandbox": "Open in CodeSandbox",
"app.demo.stackblitz": "Open in Stackblitz",
"app.demo.riddle": "Open in Riddle",
"app.demo.codeblock": "Open in Hitu",
"app.demo.separate": "Open in a new window",
"app.demo.online": "Online Address",
"app.home.introduce": "A design system for enterprise-level products. Create an efficient and enjoyable work experience.",

View File

@ -35,7 +35,7 @@
"app.demo.codepen": "在 CodePen 中打开",
"app.demo.codesandbox": "在 CodeSandbox 中打开",
"app.demo.stackblitz": "在 Stackblitz 中打开",
"app.demo.riddle": "在 Riddle 中打开",
"app.demo.codeblock": "在海兔中打开",
"app.demo.separate": "在新窗口打开",
"app.demo.online": "线上地址",
"app.home.introduce": "企业级产品设计体系,创造高效愉悦的工作体验",

View File

@ -2,7 +2,6 @@ import { createHash } from 'crypto';
import fs from 'fs';
import path from 'path';
import createEmotionServer from '@emotion/server/create-instance';
import chalk from 'chalk';
import type { IApi, IRoute } from 'dumi';
import ReactTechStack from 'dumi/dist/techStacks/react';
import sylvanas from 'sylvanas';
@ -37,8 +36,14 @@ export const getHash = (str: string, length = 8) =>
* extends dumi internal tech stack, for customize previewer props
*/
class AntdReactTechStack extends ReactTechStack {
// eslint-disable-next-line class-methods-use-this
generatePreviewerProps(...[props, opts]: any) {
props.pkgDependencyList = { ...devDependencies, ...dependencies };
props.jsx ??= '';
if (opts.type === 'code-block') {
props.jsx = opts?.entryPointCode ? sylvanas.parseText(opts.entryPointCode) : '';
}
if (opts.type === 'external') {
// try to find md file with the same name as the demo tsx file
const locale = opts.mdAbsPath.match(/index\.([a-z-]+)\.md$/i)?.[1];
@ -48,7 +53,6 @@ class AntdReactTechStack extends ReactTechStack {
const codePath = opts.fileAbsPath!.replace(/\.\w+$/, '.tsx');
const code = fs.existsSync(codePath) ? fs.readFileSync(codePath, 'utf-8') : '';
props.pkgDependencyList = { ...devDependencies, ...dependencies };
props.jsx = sylvanas.parseText(code);
if (md) {
@ -121,7 +125,8 @@ class AntdReactTechStack extends ReactTechStack {
const resolve = (p: string): string => require.resolve(p);
const RoutesPlugin = (api: IApi) => {
const RoutesPlugin = async (api: IApi) => {
const chalk = await import('chalk').then((m) => m.default);
// const ssrCssFileName = `ssr-${Date.now()}.css`;
const writeCSSFile = (key: string, hashKey: string, cssString: string) => {
@ -181,26 +186,6 @@ const RoutesPlugin = (api: IApi) => {
// exclude dynamic route path, to avoid deploy failed by `:id` directory
.filter((f) => !f.path.includes(':'))
.map((file) => {
let globalStyles = '';
// Debug for file content: uncomment this if need check raw out
// const tmpFileName = `_${file.path.replace(/\//g, '-')}`;
// const tmpFilePath = path.join(api.paths.absOutputPath, tmpFileName);
// fs.writeFileSync(tmpFilePath, file.content, 'utf8');
// extract all emotion style tags from body
file.content = file.content.replace(
/<style (data-emotion|data-sandpack)[\S\s]+?<\/style>/g,
(s) => {
globalStyles += s;
return '';
},
);
// insert emotion style tags to head
file.content = file.content.replace('</head>', `${globalStyles}</head>`);
// 1. 提取 antd-style 样式
const styles = extractEmotionStyle(file.content);
@ -217,30 +202,6 @@ const RoutesPlugin = (api: IApi) => {
file.content = addLinkStyle(file.content, cssFile);
});
// Insert antd style to head
const matchRegex = /<style data-type="antd-cssinjs">([\S\s]+?)<\/style>/;
const matchList = file.content.match(matchRegex) || [];
// Init to order the `@layer`
let antdStyle = '@layer global, antd;';
matchList.forEach((text) => {
file.content = file.content.replace(text, '');
antdStyle += text.replace(matchRegex, '$1');
});
const cssFile = writeCSSFile('antd', antdStyle, antdStyle);
file.content = addLinkStyle(file.content, cssFile, true);
// Insert antd cssVar to head
const cssVarMatchRegex = /<style data-type="antd-css-var"[\S\s]+?<\/style>/;
const cssVarMatchList = file.content.match(cssVarMatchRegex) || [];
cssVarMatchList.forEach((text) => {
file.content = file.content.replace(text, '');
file.content = file.content.replace('<head>', `<head>${text}`);
});
return file;
}),
);

View File

@ -2,6 +2,7 @@ import React from 'react';
import { RightOutlined, YuqueOutlined, ZhihuOutlined } from '@ant-design/icons';
import { Button, Card, Divider } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import useLocale from '../../../hooks/useLocale';
import JuejinLogo from './JuejinLogo';
@ -27,7 +28,7 @@ const useStyle = createStyles(({ token, css }) => ({
justify-content: space-between;
align-items: center;
`,
left: css`
leftCard: css`
display: flex;
justify-content: flex-start;
align-items: center;
@ -42,6 +43,7 @@ const useStyle = createStyles(({ token, css }) => ({
color: #444;
font-size: ${token.fontSizeLG}px;
font-weight: ${token.fontWeightStrong};
user-select: none;
`,
subTitle: css`
display: flex;
@ -54,37 +56,34 @@ const useStyle = createStyles(({ token, css }) => ({
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.logo {
width: 24px;
height: 24px;
font-size: 24px;
&.zhihu-logo {
color: #056de8;
}
&.yuque-logo {
color: #00b96b;
}
&.juejin-logo {
color: #1e80ff;
}
`,
logo: css`
width: 24px;
height: 24px;
font-size: 24px;
&.zhihu-logo {
color: #056de8;
}
.arrowIcon {
color: #8a8f8d;
margin: 0 ${token.marginXS}px;
font-size: ${token.fontSizeSM}px;
&.yuque-logo {
color: #00b96b;
}
.zl-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 0;
color: #646464;
&.juejin-logo {
color: #1e80ff;
}
`,
btn: css`
display: flex;
justify-content: center;
align-items: center;
arrowIcon: css`
color: #8a8f8d;
margin: 0 ${token.marginXS}px;
font-size: ${token.fontSizeSM}px;
`,
zlBtn: css`
padding: 0;
color: #646464;
`,
discussLogo: css`
width: 16px;
height: 16px;
font-size: 16px;
`,
}));
@ -114,7 +113,18 @@ interface Props {
const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
const [locale] = useLocale(locales);
const {
styles: { card, bigTitle, cardBody, left, title, subTitle, btn },
styles: {
card,
bigTitle,
cardBody,
leftCard,
title,
subTitle,
logo,
arrowIcon,
zlBtn,
discussLogo,
},
} = useStyle();
if (!zhihuLink && !yuqueLink && !juejinLink) {
return null;
@ -123,52 +133,54 @@ const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
<Card className={card} bordered={false}>
<h3 className={bigTitle}>{locale.bigTitle}</h3>
{zhihuLink && (
<div className={cardBody}>
<div className={left}>
<img src={ANTD_IMG_URL} alt="antd" />
<div>
<p className={title}>Ant Design</p>
<div className={subTitle}>
<ZhihuOutlined className="logo zhihu-logo" />
<RightOutlined className="arrowIcon" />
<Button
target="_blank"
href="https://www.zhihu.com/column/c_1564262000561106944"
className="zl-btn"
type="link"
>
{locale.zhiHu}
</Button>
<>
<Divider />
<div className={cardBody}>
<div className={leftCard}>
<img draggable={false} src={ANTD_IMG_URL} alt="antd" />
<div>
<p className={title}>Ant Design</p>
<div className={subTitle}>
<ZhihuOutlined className={classNames(logo, 'zhihu-logo')} />
<RightOutlined className={arrowIcon} />
<Button
target="_blank"
href="https://www.zhihu.com/column/c_1564262000561106944"
className={zlBtn}
type="link"
>
{locale.zhiHu}
</Button>
</div>
</div>
</div>
<Button
ghost
type="primary"
icon={<ZhihuOutlined className={discussLogo} />}
target="_blank"
href={zhihuLink}
>
{locale.buttonText}
</Button>
</div>
<Button
type="primary"
className={btn}
icon={<ZhihuOutlined style={{ fontSize: 16 }} />}
ghost
target="_blank"
href={zhihuLink}
>
{locale.buttonText}
</Button>
</div>
</>
)}
{yuqueLink && (
<>
<Divider />
<div className={cardBody}>
<div className={left}>
<img src={ANTD_IMG_URL} alt="antd" />
<div className={leftCard}>
<img draggable={false} src={ANTD_IMG_URL} alt="antd" />
<div>
<p className={title}>Ant Design</p>
<div className={subTitle}>
<YuqueOutlined className="logo yuque-logo" />
<RightOutlined className="arrowIcon" />
<YuqueOutlined className={classNames(logo, 'yuque-logo')} />
<RightOutlined className={arrowIcon} />
<Button
target="_blank"
href="https://www.yuque.com/ant-design/ant-design"
className="zl-btn"
className={zlBtn}
type="link"
>
{locale.yuQue}
@ -177,10 +189,9 @@ const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
</div>
</div>
<Button
type="primary"
className={btn}
icon={<YuqueOutlined style={{ fontSize: 16 }} />}
ghost
type="primary"
icon={<YuqueOutlined className={discussLogo} />}
target="_blank"
href={yuqueLink}
>
@ -193,17 +204,17 @@ const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
<>
<Divider />
<div className={cardBody}>
<div className={left}>
<img src={ANTD_IMG_URL} alt="antd" />
<div className={leftCard}>
<img draggable={false} src={ANTD_IMG_URL} alt="antd" />
<div>
<p className={title}>Ant Design</p>
<div className={subTitle}>
<JuejinLogo className="logo juejin-logo" />
<RightOutlined className="arrowIcon" />
<JuejinLogo className={classNames(logo, 'juejin-logo')} />
<RightOutlined className={arrowIcon} />
<Button
target="_blank"
href="https://juejin.cn/column/7247354308258054200"
className="zl-btn"
className={zlBtn}
type="link"
>
{locale.junjin}
@ -212,10 +223,9 @@ const ColumnCard: React.FC<Props> = ({ zhihuLink, yuqueLink, juejinLink }) => {
</div>
</div>
<Button
type="primary"
className={btn}
icon={<JuejinLogo style={{ fontSize: 16, width: 16, height: 16 }} />}
ghost
type="primary"
icon={<JuejinLogo className={discussLogo} />}
target="_blank"
href={juejinLink}
>

View File

@ -1,19 +1,6 @@
import React from 'react';
import type { AvatarListItem } from '@qixian.cs/github-contributors-list/dist/AvatarList';
import { Avatar, Skeleton, Tooltip } from 'antd';
const AvatarPlaceholder: React.FC<{ num?: number }> = ({ num = 3 }) => (
<li>
{Array.from({ length: num }).map<React.ReactNode>((_, i) => (
<Skeleton.Avatar
size="small"
active
key={i}
style={{ marginInlineStart: i === 0 ? 0 : -8 }}
/>
))}
</li>
);
import { Avatar, Tooltip } from 'antd';
interface ContributorAvatarProps {
loading?: boolean;
@ -23,11 +10,7 @@ interface ContributorAvatarProps {
const ContributorAvatar: React.FC<ContributorAvatarProps> = (props) => {
const {
item: { username, url } = {},
loading,
} = props;
if (loading) {
return <AvatarPlaceholder />;
}
if (username?.includes('github-actions')) {
return null;
}

View File

@ -8,9 +8,6 @@ import SiteContext from '../SiteContext';
import ContributorAvatar from './ContributorAvatar';
const useStyle = createStyles(({ token, css }) => ({
contributorsList: css`
margin-top: 120px !important;
`,
listMobile: css`
margin: 1em 0 !important;
`,
@ -50,7 +47,7 @@ const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
}
return (
<div className={classNames(styles.contributorsList, { [styles.listMobile]: isMobile })}>
<div className={classNames({ [styles.listMobile]: isMobile })}>
<div className={styles.title}>{formatMessage({ id: 'app.content.contributors' })}</div>
<ContributorsList
cache

View File

@ -5,12 +5,12 @@ import type { AnchorLinkItemProps } from 'antd/es/anchor/Anchor';
import classNames from 'classnames';
import { useRouteMeta, useTabMeta } from 'dumi';
const useStyle = createStyles(({ token, css }) => {
export const useStyle = createStyles(({ token, css }) => {
const { antCls } = token;
return {
anchorToc: css`
scrollbar-width: thin;
scrollbar-color: unset;
scrollbar-gutter: stable;
${antCls}-anchor {
${antCls}-anchor-link-title {
font-size: ${token.fontSizeSM}px;
@ -19,13 +19,13 @@ const useStyle = createStyles(({ token, css }) => {
`,
tocWrapper: css`
position: fixed;
top: ${token.headerHeight + token.contentMarginTop - 8}px;
top: ${token.headerHeight + token.contentMarginTop - 4}px;
inset-inline-end: 0;
width: 160px;
padding: ${token.paddingXS}px;
width: 148px;
padding: 0;
border-radius: ${token.borderRadius}px;
box-sizing: border-box;
margin-inline-end: calc(16px - 100vw + 100%);
margin-inline-end: calc(8px - 100vw + 100%);
z-index: 10;
.toc-debug {
color: ${token.purple6};
@ -48,15 +48,11 @@ const useStyle = createStyles(({ token, css }) => {
}
`,
articleWrapper: css`
padding: 0 170px 32px 64px;
&.rtl {
padding: 0 64px 144px 170px;
}
padding-inline: 48px 164px;
padding-block: 0 32px;
@media only screen and (max-width: ${token.screenLG}px) {
&,
&.rtl {
& {
padding: 0 ${token.paddingLG * 2}px;
}
}

View File

@ -17,6 +17,7 @@ const JuejinLogo: React.FC<Props> = (props) => {
viewBox="0 0 36 28"
fill="none"
>
<title>Juejin logo</title>
<path
fillRule="evenodd"
clipRule="evenodd"

View File

@ -1,6 +1,5 @@
import React, { useContext, useLayoutEffect, useMemo, useState } from 'react';
import { Col, Flex, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { Col, Flex, Space, Typography, Skeleton } from 'antd';
import classNames from 'classnames';
import { FormattedMessage, useRouteMeta } from 'dumi';
@ -11,6 +10,7 @@ import type { DemoContextProps } from '../DemoContext';
import DemoContext from '../DemoContext';
import SiteContext from '../SiteContext';
import InViewSuspense from './InViewSuspense';
import { useStyle } from './DocAnchor';
const Contributors = React.lazy(() => import('./Contributors'));
const ColumnCard = React.lazy(() => import('./ColumnCard'));
@ -18,23 +18,12 @@ const DocAnchor = React.lazy(() => import('./DocAnchor'));
const DocMeta = React.lazy(() => import('./DocMeta'));
const Footer = React.lazy(() => import('../Footer'));
const PrevAndNext = React.lazy(() => import('../../common/PrevAndNext'));
const ComponentChangelog = React.lazy(() => import('../../common/ComponentChangelog'));
const EditButton = React.lazy(() => import('../../common/EditButton'));
const useStyle = createStyles(({ token, css }) => ({
articleWrapper: css`
padding: 0 170px 32px 64px;
&.rtl {
padding: 0 64px 144px 170px;
}
@media only screen and (max-width: ${token.screenLG}px) {
&,
&.rtl {
padding: 0 ${token.paddingLG * 2}px;
}
}
`,
}));
const AvatarPlaceholder: React.FC<{ num?: number }> = ({ num = 6 }) =>
Array.from({ length: num }).map<React.ReactNode>((_, i) => (
<Skeleton.Avatar size="small" active key={i} style={{ marginInlineStart: i === 0 ? 0 : -8 }} />
));
const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
const meta = useRouteMeta();
@ -70,25 +59,22 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
</InViewSuspense>
<article className={classNames(styles.articleWrapper, { rtl: isRTL })}>
{meta.frontmatter?.title ? (
<Typography.Title style={{ fontSize: 30, position: 'relative' }}>
<Flex gap="small">
<div>{meta.frontmatter?.title}</div>
<div>{meta.frontmatter?.subtitle}</div>
{!pathname.startsWith('/components/overview') && (
<InViewSuspense fallback={null}>
<EditButton
title={<FormattedMessage id="app.content.edit-page" />}
filename={meta.frontmatter.filename}
/>
</InViewSuspense>
)}
</Flex>
{pathname.startsWith('/components/') && (
<InViewSuspense fallback={null}>
<ComponentChangelog pathname={pathname} />
</InViewSuspense>
)}
</Typography.Title>
<Flex justify="space-between">
<Typography.Title style={{ fontSize: 32, position: 'relative' }}>
<Space>
<span>{meta.frontmatter?.title}</span>
<span>{meta.frontmatter?.subtitle}</span>
{!pathname.startsWith('/components/overview') && (
<InViewSuspense fallback={null}>
<EditButton
title={<FormattedMessage id="app.content.edit-page" />}
filename={meta.frontmatter.filename}
/>
</InViewSuspense>
)}
</Space>
</Typography.Title>
</Flex>
) : null}
<InViewSuspense fallback={null}>
<DocMeta />
@ -105,19 +91,19 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
version={meta.frontmatter.tag}
/>
)}
<div style={{ minHeight: 'calc(100vh - 64px)', width: 'calc(100% - 10px)' }}>
{children}
</div>
<InViewSuspense>
<div style={{ minHeight: 'calc(100vh - 64px)' }}>{children}</div>
<InViewSuspense fallback={null}>
<ColumnCard
zhihuLink={meta.frontmatter.zhihu_url}
yuqueLink={meta.frontmatter.yuque_url}
juejinLink={meta.frontmatter.juejin_url}
/>
</InViewSuspense>
<InViewSuspense fallback={<div style={{ height: 50, marginTop: 120 }} />}>
<Contributors filename={meta.frontmatter.filename} />
</InViewSuspense>
<div style={{ marginTop: 120 }}>
<InViewSuspense fallback={<AvatarPlaceholder />}>
<Contributors filename={meta.frontmatter.filename} />
</InViewSuspense>
</div>
</article>
<InViewSuspense fallback={null}>
<PrevAndNext rtl={isRTL} />

View File

@ -1,4 +1,5 @@
import * as React from 'react';
import { createStyles } from 'antd-style';
import { removeCSS, updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
import useLocale from '../../../hooks/useLocale';
@ -19,9 +20,38 @@ const locales = {
},
};
const useStyle = createStyles(({ css, token }) => ({
container: css`
position: fixed;
inset-inline-start: 0;
inset-inline-end: 0;
top: 0;
bottom: 0;
z-index: 99999999;
background-color: ${token.colorTextSecondary};
display: flex;
justify-content: center;
align-items: center;
`,
alertBox: css`
border: 1px solid ${token.colorWarningBorder};
background-color: ${token.colorWarningBg};
color: ${token.colorTextHeading};
padding: ${token.paddingXS}px ${token.paddingSM}px;
border-radius: ${token.borderRadiusLG}px;
z-index: 9999999999;
line-height: 22px;
width: 520px;
a {
color: ${token.colorPrimary};
text-decoration-line: none;
}
`,
}));
// Check for browser support `:where` or not
// Warning user if not support to modern browser
function InfoNewVersion() {
const InfoNewVersion: React.FC = () => {
const [location] = useLocale(locales);
const [supportWhere, setSupportWhere] = React.useState(true);
@ -50,40 +80,19 @@ function InfoNewVersion() {
removeCSS(whereCls);
}, []);
return supportWhere ? null : (
<div
style={{
position: 'fixed',
left: 0,
right: 0,
top: 0,
bottom: 0,
zIndex: 99999999,
background: 'rgba(0,0,0,0.65)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div
style={{
border: `1px solid #ffe58f`,
background: '#fffbe6',
color: 'rgba(0,0,0,0.88)',
padding: '8px 12px',
borderRadius: '8px',
zIndex: 9999999999,
lineHeight: '22px',
width: 520,
}}
>
{location.whereNotSupport}{' '}
<a style={{ color: '#1677ff', textDecoration: 'none' }} href={location.whereDocUrl}>
{location.whereDocTitle}
</a>
const { styles } = useStyle();
if (supportWhere) {
return null;
}
return (
<div className={styles.container}>
<div className={styles.alertBox}>
{location.whereNotSupport} <a href={location.whereDocUrl}>{location.whereDocTitle}</a>
</div>
</div>
);
}
};
export default InfoNewVersion;

View File

@ -13,7 +13,7 @@ import {
UsergroupAddOutlined,
ZhihuOutlined,
} from '@ant-design/icons';
import { TinyColor } from '@ctrl/tinycolor';
import { FastColor } from '@ant-design/fast-color';
import { createStyles } from 'antd-style';
import getAlphaColor from 'antd/es/theme/util/getAlphaColor';
import { FormattedMessage, Link } from 'dumi';
@ -37,7 +37,7 @@ const locales = {
const useStyle = () => {
const { isMobile } = useContext(SiteContext);
return createStyles(({ token, css }) => {
const background = new TinyColor(getAlphaColor('#f0f3fa', '#fff'))
const background = new FastColor(getAlphaColor('#f0f3fa', '#fff'))
.onBackground(token.colorBgContainer)
.toHexString();
@ -101,6 +101,11 @@ const Footer: React.FC = () => {
const col1 = {
title: <FormattedMessage id="app.footer.resources" />,
items: [
{
title: 'Ant Design X',
url: isZhCN ? 'https://ant-design-x.antgroup.com' : 'https://x.ant.design',
openExternal: true,
},
{
title: 'Ant Design Charts',
url: isZhCN ? 'https://ant-design-charts.antgroup.com' : 'https://charts.ant.design',
@ -112,7 +117,7 @@ const Footer: React.FC = () => {
openExternal: true,
},
{
title: 'Ant Design Pro Components',
title: 'Pro Components',
url: 'https://procomponents.ant.design',
openExternal: true,
},
@ -126,6 +131,11 @@ const Footer: React.FC = () => {
url: isZhCN ? 'https://ant-design-mini.antgroup.com/' : 'https://mini.ant.design',
openExternal: true,
},
{
title: 'Ant Design Web3',
url: isZhCN ? 'https://web3.antdigital.dev' : 'https://web3.ant.design',
openExternal: true,
},
{
title: 'Ant Design Landing',
description: <FormattedMessage id="app.footer.landing" />,
@ -156,12 +166,6 @@ const Footer: React.FC = () => {
url: 'https://qiankun.umijs.org',
openExternal: true,
},
{
title: 'ahooks',
description: <FormattedMessage id="app.footer.hooks" />,
url: 'https://github.com/alibaba/hooks',
openExternal: true,
},
{
title: 'Ant Motion',
description: <FormattedMessage id="app.footer.motion" />,
@ -202,7 +206,7 @@ const Footer: React.FC = () => {
src="https://gw.alipayobjects.com/zos/rmsportal/XuVpGqBFxXplzvLjJBZB.svg"
width={16}
height={16}
alt="yuque"
alt="yuque logo"
/>
),
title: <FormattedMessage id="app.footer.yuque.repo" />,
@ -227,7 +231,7 @@ const Footer: React.FC = () => {
src="https://gw.alipayobjects.com/zos/rmsportal/mZBWtboYbnMkTBaRIuWQ.png"
width={16}
height={16}
alt="seeconf"
alt="seeconf logo"
/>
),
title: 'SEE Conf',
@ -310,7 +314,7 @@ const Footer: React.FC = () => {
src="https://gw.alipayobjects.com/zos/rmsportal/nBVXkrFdWHxbZlmMbsaH.svg"
width={22}
height={22}
alt="Ant XTech"
alt="Ant XTech logo"
/>
),
title: <FormattedMessage id="app.footer.more-product" />,
@ -321,7 +325,7 @@ const Footer: React.FC = () => {
src="https://gw.alipayobjects.com/zos/rmsportal/XuVpGqBFxXplzvLjJBZB.svg"
width={16}
height={16}
alt="yuque"
alt="yuque logo"
/>
),
title: <FormattedMessage id="app.footer.yuque" />,
@ -335,7 +339,7 @@ const Footer: React.FC = () => {
src="https://gw.alipayobjects.com/zos/antfincdn/nc7Fc0XBg5/8a6844f5-a6ed-4630-9177-4fa5d0b7dd47.png"
width={16}
height={16}
alt="AntV"
alt="AntV logo"
/>
),
title: 'AntV',
@ -344,7 +348,7 @@ const Footer: React.FC = () => {
openExternal: true,
},
{
icon: <img src="https://www.eggjs.org/logo.svg" alt="Egg" width={16} height={16} />,
icon: <img src="https://www.eggjs.org/logo.svg" alt="Egg logo" width={16} height={16} />,
title: 'Egg',
url: 'https://eggjs.org',
description: <FormattedMessage id="app.footer.egg.slogan" />,
@ -356,7 +360,7 @@ const Footer: React.FC = () => {
src="https://gw.alipayobjects.com/zos/rmsportal/DMDOlAUhmktLyEODCMBR.ico"
width={16}
height={16}
alt="kitchen"
alt="Kitchen logo"
/>
),
title: 'Kitchen',
@ -370,7 +374,7 @@ const Footer: React.FC = () => {
src="https://mdn.alipayobjects.com/huamei_j9rjmc/afts/img/A*3ittT5OEo2gAAAAAAAAAAAAADvGmAQ/original"
width={16}
height={16}
alt="Galacean"
alt="Galacean logo"
/>
),
title: <FormattedMessage id="app.footer.galacean" />,
@ -384,7 +388,7 @@ const Footer: React.FC = () => {
src="https://gw.alipayobjects.com/zos/rmsportal/nBVXkrFdWHxbZlmMbsaH.svg"
width={16}
height={16}
alt="xtech"
alt="xtech logo"
/>
),
title: <FormattedMessage id="app.footer.xtech" />,

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