mirror of
https://github.com/ant-design/ant-design.git
synced 2025-01-22 08:53:29 +08:00
Merge branch 'master' into TooltipRef
This commit is contained in:
commit
822420d536
@ -1,5 +0,0 @@
|
||||
{
|
||||
"installCommand": "npm-install",
|
||||
"sandboxes": ["antd-reproduction-template-forked-jyh2k9"],
|
||||
"node": "18"
|
||||
}
|
@ -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',
|
||||
},
|
||||
],
|
||||
|
@ -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);
|
||||
|
@ -16,5 +16,5 @@
|
||||
|
||||
html {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: unset;
|
||||
scrollbar-color: #eaeaea transparent;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import fetch from 'cross-fetch';
|
||||
|
||||
import use from '../use';
|
||||
import FetchCache from './cache';
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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}
|
||||
|
@ -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)`,
|
||||
|
@ -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';
|
||||
|
@ -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 {
|
||||
|
@ -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 />;
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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()}
|
||||
/>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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>
|
||||
}
|
||||
>
|
||||
|
@ -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"
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
@ -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
|
||||
|
@ -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') {
|
||||
|
@ -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);
|
||||
|
@ -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 {}
|
||||
}
|
||||
|
||||
|
15
.dumi/theme/builtins/Antd.tsx
Normal file
15
.dumi/theme/builtins/Antd.tsx
Normal 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;
|
48
.dumi/theme/builtins/Audio/index.tsx
Normal file
48
.dumi/theme/builtins/Audio/index.tsx
Normal 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;
|
@ -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) => {
|
||||
|
@ -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) && {
|
||||
|
@ -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>
|
||||
);
|
||||
})}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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 = {
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
80
.dumi/theme/builtins/Previewer/CodeBlockButton.tsx
Normal file
80
.dumi/theme/builtins/Previewer/CodeBlockButton.tsx
Normal 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>
|
||||
);
|
@ -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,8 +95,7 @@ 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);
|
||||
const {
|
||||
@ -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"
|
||||
|
27
.dumi/theme/builtins/Previewer/DemoFallback.tsx
Normal file
27
.dumi/theme/builtins/Previewer/DemoFallback.tsx
Normal 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;
|
@ -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}>
|
||||
|
@ -7,6 +7,7 @@ import DesignPreviewer from './DesignPreviewer';
|
||||
|
||||
export interface AntdPreviewerProps extends IPreviewerProps {
|
||||
originDebug?: IPreviewerProps['debug'];
|
||||
jsx?: string;
|
||||
}
|
||||
|
||||
const Previewer: React.FC<AntdPreviewerProps> = (props) => {
|
||||
|
91
.dumi/theme/builtins/Previewer/RiddleButton.tsx
Normal file
91
.dumi/theme/builtins/Previewer/RiddleButton.tsx
Normal 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>
|
||||
);
|
@ -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;
|
||||
|
@ -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';
|
||||
|
@ -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 = {
|
||||
|
@ -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 {
|
||||
|
@ -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],
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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 (
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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'] };
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Palette from './Palette';
|
||||
|
||||
const colors = [
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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={{
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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;
|
||||
|
12
.dumi/theme/common/LinkButton.tsx
Normal file
12
.dumi/theme/common/LinkButton.tsx
Normal 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;
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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 />}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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.",
|
||||
|
@ -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": "企业级产品设计体系,创造高效愉悦的工作体验",
|
||||
|
@ -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;
|
||||
}),
|
||||
);
|
||||
|
@ -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}
|
||||
>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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} />
|
||||
|
@ -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;
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user