Merge branch 'master' into feature-merge-master

This commit is contained in:
kiner-tang(文辉) 2023-09-20 14:37:33 +08:00
commit 3cd47b9a4e
63 changed files with 1515 additions and 281 deletions

View File

@ -1,11 +1,11 @@
export default function use<T>(promise: PromiseLike<T>): T {
function use<T>(promise: PromiseLike<T>): T {
const internal: PromiseLike<T> & {
status?: 'pending' | 'fulfilled' | 'rejected';
value?: T;
reason?: any;
} = promise;
if (internal.status === 'fulfilled') {
return internal.value;
return internal.value as T;
}
if (internal.status === 'rejected') {
throw internal.reason;
@ -26,3 +26,5 @@ export default function use<T>(promise: PromiseLike<T>): T {
throw internal;
}
}
export default use;

View File

@ -5,10 +5,12 @@ export interface LocaleMap<Key extends string> {
en: Record<Key, string>;
}
export default function useLocale<Key extends string>(
function useLocale<Key extends string>(
localeMap?: LocaleMap<Key>,
): [Record<Key, string>, 'cn' | 'en'] {
const { id } = useDumiLocale();
const localeType = id === 'zh-CN' ? 'cn' : 'en';
return [localeMap?.[localeType], localeType];
const localeType = id === 'zh-CN' ? ('cn' as const) : ('en' as const);
return [localeMap?.[localeType]!, localeType];
}
export default useLocale;

View File

@ -1,7 +1,8 @@
import { useFullSidebarData, useSidebarData } from 'dumi';
import React, { useMemo } from 'react';
import type { MenuProps } from 'antd';
import { Tag, version } from 'antd';
import { useFullSidebarData, useSidebarData } from 'dumi';
import Link from '../theme/common/Link';
import useLocation from './useLocation';
@ -136,7 +137,7 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] =>
const list = group.children || [];
// 如果有 date 字段,我们就对其进行排序
if (list.every((info) => info?.frontmatter?.date)) {
list.sort((a, b) => (a.frontmatter.date > b.frontmatter.date ? -1 : 1));
list.sort((a, b) => (a.frontmatter?.date > b.frontmatter?.date ? -1 : 1));
}
result.push(

View File

@ -32,11 +32,7 @@ const useThemeAnimation = () => {
token: { colorBgElevated },
} = theme.useToken();
const animateRef = useRef<{
colorBgElevated: string;
}>({
colorBgElevated,
});
const animateRef = useRef<{ colorBgElevated: string }>({ colorBgElevated });
const startAnimationTheme = (clipPath: string[], isDark: boolean) => {
updateCSS(
@ -64,9 +60,14 @@ const useThemeAnimation = () => {
});
};
const toggleAnimationTheme = (event: MouseEvent, isDark: boolean) => {
const toggleAnimationTheme = (
event: React.MouseEvent<HTMLElement, MouseEvent>,
isDark: boolean,
) => {
// @ts-ignore
if (!(event && typeof document.startViewTransition === 'function')) return;
if (!(event && typeof document.startViewTransition === 'function')) {
return;
}
const x = event.clientX;
const y = event.clientY;
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));

View File

@ -83,8 +83,8 @@ const Group: React.FC<GroupProps> = (props) => {
</Typography.Title>
<Typography.Paragraph
style={{
marginBottom: isMobile ? token.marginXXL : token.marginFarXS,
color: titleColor,
marginBottom: isMobile ? token.marginXXL : (token as any).marginFarXS,
}}
>
{description}
@ -111,7 +111,7 @@ const Group: React.FC<GroupProps> = (props) => {
<GroupMask
disabled={!!background}
style={{
paddingBlock: token.marginFarSM,
paddingBlock: (token as any).marginFarSM,
}}
>
{childNode}

View File

@ -1,7 +1,8 @@
import { createStyles, css } from 'antd-style';
import React, { useMemo, useState } from 'react';
import { CSSMotionList } from 'rc-motion';
import { createStyles, css } from 'antd-style';
import classNames from 'classnames';
import { CSSMotionList } from 'rc-motion';
import { COLOR_IMAGES, getClosetColor } from './colorUtil';
export interface BackgroundImageProps {
@ -36,7 +37,7 @@ const BackgroundImage: React.FC<BackgroundImageProps> = ({ colorPrimary, isLight
const [keyList, setKeyList] = useState<string[]>([]);
React.useLayoutEffect(() => {
setKeyList([activeColor]);
setKeyList([activeColor as string]);
}, [activeColor]);
return (
@ -56,7 +57,7 @@ const BackgroundImage: React.FC<BackgroundImageProps> = ({ colorPrimary, isLight
const entity = COLOR_IMAGES.find((ent) => ent.color === color);
if (!entity || !entity.url) {
return null;
return null as unknown as React.ReactElement;
}
const { opacity } = style || {};

View File

@ -1,39 +1,47 @@
import { createStyles } from 'antd-style';
import type { FC } from 'react';
import React, { useEffect, useState } from 'react';
import classNames from 'classnames';
import { ColorPicker, Input, Space } from 'antd';
import type { Color, ColorPickerProps } from 'antd/es/color-picker';
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';
const useStyle = createStyles(({ token, css }) => ({
color: css`
width: ${token.controlHeightLG / 2}px;
height: ${token.controlHeightLG / 2}px;
border-radius: 100%;
cursor: pointer;
transition: all ${token.motionDurationFast};
display: inline-block;
width: ${token.controlHeightLG / 2}px;
height: ${token.controlHeightLG / 2}px;
border-radius: 100%;
cursor: pointer;
transition: all ${token.motionDurationFast};
display: inline-block;
& > input[type='radio'] {
width: 0;
height: 0;
opacity: 0;
}
& > input[type='radio'] {
width: 0;
height: 0;
opacity: 0;
}
&:focus-within {
// need
}
`,
&:focus-within {
// need
}
`,
colorActive: css`
box-shadow: 0 0 0 1px ${token.colorBgContainer},
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
`,
box-shadow:
0 0 0 1px ${token.colorBgContainer},
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
`,
}));
const DebouncedColorPicker: FC<ColorPickerProps> = ({ value: color, onChange, children }) => {
export interface ColorPickerProps {
children?: React.ReactNode;
value?: string | Color;
onChange?: (value?: Color | string) => void;
}
const DebouncedColorPicker: React.FC<ColorPickerProps> = (props) => {
const { value: color, children, onChange } = props;
const [value, setValue] = useState(color);
useEffect(() => {
@ -51,40 +59,24 @@ const DebouncedColorPicker: FC<ColorPickerProps> = ({ value: color, onChange, ch
<ColorPicker
value={value}
onChange={setValue}
presets={[
{
label: 'PresetColors',
colors: PRESET_COLORS,
},
]}
presets={[{ label: 'PresetColors', colors: PRESET_COLORS }]}
>
{children}
</ColorPicker>
);
};
export interface RadiusPickerProps {
value?: string | Color;
onChange?: (value: string) => void;
}
export default function ThemeColorPicker({ value, onChange }: RadiusPickerProps) {
const ThemeColorPicker: React.FC<ColorPickerProps> = ({ value, onChange }) => {
const { styles } = useStyle();
const matchColors = React.useMemo(() => {
const valueStr = generateColor(value).toRgbString();
const valueStr = generateColor(value || '').toRgbString();
let existActive = false;
const colors = PRESET_COLORS.map((color) => {
const colorStr = generateColor(color).toRgbString();
const active = colorStr === valueStr;
existActive = existActive || active;
return {
color,
active,
picker: false,
};
return { color, active, picker: false };
});
return [
@ -100,10 +92,8 @@ export default function ThemeColorPicker({ value, onChange }: RadiusPickerProps)
return (
<Space size="large">
<Input
value={typeof value === 'string' ? value : value.toHexString()}
onChange={(event) => {
onChange?.(event.target.value);
}}
value={typeof value === 'string' ? value : value?.toHexString()}
onChange={(event) => onChange?.(event.target.value)}
style={{ width: 120 }}
/>
@ -114,9 +104,7 @@ export default function ThemeColorPicker({ value, onChange }: RadiusPickerProps)
<label
key={color}
className={classNames(styles.color, active && styles.colorActive)}
style={{
background: color,
}}
style={{ background: color }}
onClick={() => {
if (!picker) {
onChange?.(color);
@ -149,4 +137,6 @@ export default function ThemeColorPicker({ value, onChange }: RadiusPickerProps)
</Space>
</Space>
);
}
};
export default ThemeColorPicker;

View File

@ -1,3 +1,4 @@
import type { Color } from 'antd/es/color-picker';
import { generateColor } from 'antd/es/color-picker/util';
export const DEFAULT_COLOR = '#1677FF';
@ -50,7 +51,8 @@ export const COLOR_IMAGES = [
export const PRESET_COLORS = COLOR_IMAGES.map(({ color }) => color);
const DISTANCE = 33;
export function getClosetColor(colorPrimary?: string | null) {
export function getClosetColor(colorPrimary?: Color | string | null) {
if (!colorPrimary) {
return null;
}

View File

@ -197,7 +197,7 @@ const useStyle = createStyles(({ token, cx }) => {
});
// ========================== Menu Config ==========================
const subMenuItems: MenuProps['items'] = [
const subMenuItems = [
{
key: `Design Values`,
label: `Design Values`,
@ -287,11 +287,12 @@ const ThemesInfo: Record<THEME, Partial<ThemeData>> = {
},
};
const normalize = (value: number) => value / 255;
function rgbToColorMatrix(color: string) {
const rgb = new TinyColor(color).toRgb();
const { r, g, b } = rgb;
const normalize = (value) => value / 255;
const invertValue = normalize(r) * 100;
const sepiaValue = 100;
const saturateValue = Math.max(normalize(r), normalize(g), normalize(b)) * 10000;
@ -360,10 +361,7 @@ export default function Theme() {
const isRootDark = useDark();
React.useEffect(() => {
onThemeChange(null, {
...themeData,
themeType: isRootDark ? 'dark' : 'default',
});
onThemeChange({}, { ...themeData, themeType: isRootDark ? 'dark' : 'default' });
}, [isRootDark]);
// ================================ Tokens ================================

View File

@ -62,12 +62,12 @@ function rehypeAntd(): UnifiedTransformer<HastRoot> {
(node.properties.className as string[]).push('component-api-table');
} else if (node.type === 'element' && (node.tagName === 'Link' || node.tagName === 'a')) {
const { tagName } = node;
node.properties.sourceType = tagName;
node.properties!.sourceType = tagName;
node.tagName = 'LocaleLink';
} else if (node.type === 'element' && node.tagName === 'video') {
node.tagName = 'VideoPlayer';
} else if (node.tagName === 'SourceCode') {
const { lang } = node.properties;
const { lang } = node.properties!;
if (typeof lang === 'string' && lang.startsWith('sandpack')) {
const code = (node.children[0] as any).value as string;

View File

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

View File

@ -1,10 +1,11 @@
import React, { useContext } from 'react';
import { theme as antdTheme, ConfigProvider } from 'antd';
import type { ThemeConfig } from 'antd';
import type { ThemeProviderProps } from 'antd-style';
import { ThemeProvider } from 'antd-style';
import type { FC } from 'react';
import React, { useContext } from 'react';
import { ConfigProvider, theme as antdTheme } from 'antd';
interface NewToken {
bannerHeight: number;
headerHeight: number;
menuItemBorder: number;
mobileMaxWidth: number;
@ -18,21 +19,13 @@ interface NewToken {
contentMarginTop: number;
}
// 通过给 antd-style 扩展 CustomToken 对象类型定义,可以为 useTheme 中增加相应的 token 对象
declare module 'antd-style' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CustomToken extends NewToken {}
}
const SiteThemeProvider: FC<ThemeProviderProps> = ({ children, theme, ...rest }) => {
const SiteThemeProvider: React.FC<ThemeProviderProps<any>> = ({ children, theme, ...rest }) => {
const { getPrefixCls, iconPrefixCls } = useContext(ConfigProvider.ConfigContext);
const rootPrefixCls = getPrefixCls();
const { token } = antdTheme.useToken();
React.useEffect(() => {
ConfigProvider.config({
theme,
});
ConfigProvider.config({ theme: theme as ThemeConfig });
}, [theme]);
return (

View File

@ -1,11 +1,12 @@
/* eslint-disable global-require */
import React, { useMemo } from 'react';
import { createStyles } from 'antd-style';
import { HistoryOutlined } from '@ant-design/icons';
import { Button, Drawer, Timeline, Typography } from 'antd';
import Link from '../Link';
import useLocale from '../../../hooks/useLocale';
import { createStyles } from 'antd-style';
import useFetch from '../../../hooks/useFetch';
import useLocale from '../../../hooks/useLocale';
import Link from '../Link';
const useStyle = createStyles(({ token, css }) => ({
history: css`
@ -46,7 +47,7 @@ function ParseChangelog(props: { changelog: string; refs: string[]; styles: any
const { changelog = '', refs = [], styles } = props;
const parsedChangelog = React.useMemo(() => {
const nodes: React.ReactElement[] = [];
const nodes: React.ReactNode[] = [];
let isQuota = false;
let lastStr = '';
@ -57,7 +58,7 @@ function ParseChangelog(props: { changelog: string; refs: string[]; styles: any
if (char !== '`') {
lastStr += char;
} else {
let node = lastStr;
let node: React.ReactNode = lastStr;
if (isQuota) {
node = <code>{node}</code>;
}
@ -88,8 +89,14 @@ function ParseChangelog(props: { changelog: string; refs: string[]; styles: any
);
}
function useChangelog(componentPath, lang) {
const data = useFetch(
type ChangelogInfo = {
version: string;
changelog: string;
refs: string[];
};
function useChangelog(componentPath: string, lang: 'cn' | 'en'): ChangelogInfo[] {
const data: any = useFetch(
lang === 'cn'
? {
key: 'component-changelog-cn',
@ -108,7 +115,7 @@ function useChangelog(componentPath, lang) {
(name) => name.toLowerCase() === component.toLowerCase(),
);
return data[componentName];
return data[componentName!];
}, [data, componentPath]);
}
@ -124,7 +131,7 @@ export default function ComponentChangelog(props: ComponentChangelogProps) {
const list = useChangelog(componentPath, lang);
const timelineItems = React.useMemo(() => {
const changelogMap = {};
const changelogMap: Record<string, ChangelogInfo[]> = {};
list?.forEach((info) => {
changelogMap[info.version] = changelogMap[info.version] || [];

View File

@ -3,12 +3,12 @@ import React, { forwardRef, useLayoutEffect, useMemo, useTransition } from 'reac
import { useLocation, useNavigate } from 'dumi';
import nprogress from 'nprogress';
export type LinkProps = {
export interface LinkProps {
to?: string | { pathname?: string; search?: string; hash?: string };
children?: React.ReactNode;
style?: React.CSSProperties;
className?: string;
};
}
const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
const { to, children, ...rest } = props;
@ -24,12 +24,14 @@ const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
}, [to]);
const handleClick = (e: MouseEvent<HTMLAnchorElement>) => {
if (!href.startsWith('http')) {
if (!href?.startsWith('http')) {
// Should support open in new tab
if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
e.preventDefault();
startTransition(() => {
navigate(href);
if (href) {
navigate(href);
}
});
}
}

View File

@ -12,10 +12,10 @@ import ThemeIcon from './ThemeIcon';
export type ThemeName = 'light' | 'dark' | 'compact' | 'motion-off' | 'happy-work';
export type ThemeSwitchProps = {
export interface ThemeSwitchProps {
value?: ThemeName[];
onChange: (value: ThemeName[]) => void;
};
}
const ThemeSwitch: React.FC<ThemeSwitchProps> = (props) => {
const { value = ['light'], onChange } = props;
@ -72,23 +72,6 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = (props) => {
}}
tooltip={<FormattedMessage id="app.theme.switch.compact" />}
/>
{/* Too many float button. Hide motion one */}
{/* <FloatButton
icon={<Motion />}
type={!isMotionOff ? 'primary' : 'default'}
onClick={() => {
if (isMotionOff) {
onChange(value.filter((theme) => theme !== 'motion-off'));
} else {
onChange([...value, 'motion-off']);
}
}}
tooltip={
<FormattedMessage
id={isMotionOff ? 'app.theme.switch.motion.off' : 'app.theme.switch.motion.on'}
/>
}
/> */}
<FloatButton
badge={{ dot: true }}
icon={<SmileOutlined />}

View File

@ -63,10 +63,6 @@ export default () => {
html {
scroll-padding-top: ${headerHeight + margin}px;
}
[data-prefers-color='dark'] {
color-scheme: dark;
}
`}
/>
);

View File

@ -26,6 +26,10 @@ const GlobalStyle: React.FC = () => {
.markdown img {
max-width: calc(100% - 32px);
max-height: 100%;
}
.markdown > a > img,
.markdown > img {
display: block;
margin: 0 auto;
}

View File

@ -1,4 +1,5 @@
import React, { useCallback, useEffect, useMemo, startTransition } from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import dayjs from 'dayjs';
import {
createCache,
extractStyle,
@ -9,15 +10,9 @@ import {
} from '@ant-design/cssinjs';
import { HappyProvider } from '@ant-design/happy-work-theme';
import { getSandpackCssText } from '@codesandbox/sandpack-react';
import { theme as antdTheme, App } from 'antd';
import { App, theme as antdTheme } from 'antd';
import type { DirectionType } from 'antd/es/config-provider';
import {
createSearchParams,
useOutlet,
useSearchParams,
useServerInsertedHTML,
usePrefersColor,
} from 'dumi';
import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi';
import { DarkContext } from '../../hooks/useDark';
import useLayoutState from '../../hooks/useLayoutState';
@ -32,6 +27,7 @@ type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][];
type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
const RESPONSIVE_MOBILE = 768;
export const ANT_DESIGN_NOT_SHOW_BANNER = 'ANT_DESIGN_NOT_SHOW_BANNER';
// const styleCache = createCache();
// if (typeof global !== 'undefined') {
@ -55,13 +51,12 @@ const GlobalLayout: React.FC = () => {
const outlet = useOutlet();
const { pathname } = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const [, , setPrefersColor] = usePrefersColor();
const [{ theme = [], direction, isMobile, bannerVisible = true }, setSiteState] =
const [{ theme = [], direction, isMobile, bannerVisible = false }, setSiteState] =
useLayoutState<SiteState>({
isMobile: false,
direction: 'ltr',
theme: [],
bannerVisible: true,
bannerVisible: false,
});
const updateSiteConfig = useCallback(
@ -81,13 +76,9 @@ const GlobalLayout: React.FC = () => {
}
}
if (key === 'theme') {
const _theme = value.filter((t) => t !== 'light');
nextSearchParams = createSearchParams({
...nextSearchParams,
theme: _theme,
});
startTransition(() => {
setPrefersColor(_theme.includes('dark') ? 'dark' : 'light');
theme: value.filter((t) => t !== 'light'),
});
}
});
@ -106,8 +97,16 @@ const GlobalLayout: React.FC = () => {
useEffect(() => {
const _theme = searchParams.getAll('theme') as ThemeName[];
const _direction = searchParams.get('direction') as DirectionType;
const storedBannerVisibleLastTime =
localStorage && localStorage.getItem(ANT_DESIGN_NOT_SHOW_BANNER);
const storedBannerVisible =
storedBannerVisibleLastTime && dayjs().diff(dayjs(storedBannerVisibleLastTime), 'day') >= 1;
setSiteState({ theme: _theme, direction: _direction === 'rtl' ? 'rtl' : 'ltr' });
setSiteState({
theme: _theme,
direction: _direction === 'rtl' ? 'rtl' : 'ltr',
bannerVisible: storedBannerVisibleLastTime ? storedBannerVisible : true,
});
// Handle isMobile
updateMobileMode();

View File

@ -1,21 +1,24 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { GithubOutlined, MenuOutlined } from '@ant-design/icons';
import { Alert, Col, Popover, Row, Select } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import dayjs from 'dayjs';
import { useLocation, useSiteData } from 'dumi';
import DumiSearchBar from 'dumi/theme-default/slots/SearchBar';
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { Alert, Col, Popover, Row, Select } from 'antd';
import useLocale from '../../../hooks/useLocale';
import DirectionIcon from '../../common/DirectionIcon';
import { ANT_DESIGN_NOT_SHOW_BANNER } from '../../layouts/GlobalLayout';
import * as utils from '../../utils';
import { getThemeConfig } from '../../utils';
import type { SiteContextProps } from '../SiteContext';
import SiteContext from '../SiteContext';
import type { SharedProps } from './interface';
import Logo from './Logo';
import More from './More';
import Navigation from './Navigation';
import SwitchBtn from './SwitchBtn';
import type { SharedProps } from './interface';
const RESPONSIVE_XS = 1120;
const RESPONSIVE_SM = 1200;
@ -133,6 +136,9 @@ const useStyle = createStyles(({ token, css }) => {
width: 22px;
height: 22px;
`,
message: css`
color: rgba(0, 0, 0, 0.88);
`,
};
});
@ -154,7 +160,8 @@ const Header: React.FC = () => {
windowWidth: 1400,
searching: false,
});
const { direction, isMobile, updateSiteConfig } = useContext<SiteContextProps>(SiteContext);
const { direction, isMobile, bannerVisible, updateSiteConfig } =
useContext<SiteContextProps>(SiteContext);
const pingTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const location = useLocation();
const { pathname, search } = location;
@ -178,6 +185,10 @@ const Header: React.FC = () => {
};
const onBannerClose = () => {
updateSiteConfig({ bannerVisible: false });
if (utils.isLocalStorageNameSupported()) {
localStorage.setItem(ANT_DESIGN_NOT_SHOW_BANNER, dayjs().toISOString());
}
};
useEffect(() => {
@ -352,7 +363,7 @@ const Header: React.FC = () => {
<MenuOutlined className="nav-phone-icon" onClick={handleShowMenu} />
</Popover>
)}
{isZhCN && (
{isZhCN && bannerVisible && (
<Alert
className={styles.banner}
message={
@ -362,7 +373,9 @@ const Header: React.FC = () => {
src="https://gw.alipayobjects.com/zos/rmsportal/XuVpGqBFxXplzvLjJBZB.svg"
alt="yuque"
/>
{isMobile ? locale.shortMessage : locale.message}
<span className={styles.message}>
{isMobile ? locale.shortMessage : locale.message}
</span>
<a
className={styles.link}
href="https://www.yuque.com/yuque/blog/welfare-edu?source=antd"

View File

@ -1,5 +1,6 @@
import flatten from 'lodash/flatten';
import flattenDeep from 'lodash/flattenDeep';
import themeConfig from './themeConfig';
interface Meta {

View File

@ -1,15 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"esModuleInterop": true,
"resolveJsonModule": true,
"baseUrl": "../",
"paths": {
"@@/*": [".dumi/tmp/*"],
"antd": ["components/index.tsx"],
"antd/es/*": ["components/*"],
"dumi/theme/*": [".dumi/theme/*"]
}
"resolveJsonModule": true
},
"include": ["./**/*", "../site/theme/template/Content"]
"include": ["**/*"]
}

View File

@ -16,6 +16,16 @@ tag: vVERSION
---
## 5.9.2
`2023-09-19`
- 🐞 Fix Table selection column not align in center when `size` is `small`. [#44922](https://github.com/ant-design/ant-design/pull/44922)
- 🐞 Fix Select style problem when `label` contains `div` element. [#44927](https://github.com/ant-design/ant-design/pull/44927)
- 🐞 Fix Modal broken style of buttons when custom `footer`. [#44929](https://github.com/ant-design/ant-design/pull/44929) [@Wxh16144](https://github.com/Wxh16144)
- 🐞 Fix notification wrong pop-up animation when `placement` is `bottom`. [#44918](https://github.com/ant-design/ant-design/pull/44918) [@linxianxi](https://github.com/linxianxi)
- 🐞 Fix missing inherited feedbackIcon in Form.Item with `noStyle`. [#44937](https://github.com/ant-design/ant-design/pull/44937)
## 5.9.1
`2023-09-15`

View File

@ -16,6 +16,16 @@ tag: vVERSION
---
## 5.9.2
`2023-09-19`
- 🐞 修复 Table `small` 尺寸时选择列没有居中对齐的问题。[#44922](https://github.com/ant-design/ant-design/pull/44922)
- 🐞 修复 Select 当 `label` 内使用了 `div` 块级元素时的样式问题。[#44927](https://github.com/ant-design/ant-design/pull/44927)
- 🐞 修复 Modal 自定义 `footer` 时按钮内容丢失的问题。[#44929](https://github.com/ant-design/ant-design/pull/44929) [@Wxh16144](https://github.com/Wxh16144)
- 🐞 修复 notification 底部弹出动画的问题。[#44918](https://github.com/ant-design/ant-design/pull/44918) [@linxianxi](https://github.com/linxianxi)
- 🐞 修复 Form.Item 有 `noStyle` 属性时没有继承上下文的反馈图标的问题。[#44937](https://github.com/ant-design/ant-design/pull/44937)
## 5.9.1
`2023-09-15`

View File

@ -1,4 +1,5 @@
import * as React from 'react';
import useForceUpdate from './useForceUpdate';
type UseSyncStateProps<T> = readonly [() => T, (newValue: T) => void];

View File

@ -62,7 +62,7 @@ const showInsetEffect: WaveConfig['showEffect'] = (node, { event, component }) =
// Motion
requestAnimationFrame(() => {
dot.ontransitionend = () => {
holder.parentElement?.removeChild(holder);
holder.remove();
};
dot.style.width = '200px';

View File

@ -50,8 +50,12 @@ export default function StatusProvider({
validateStatus,
);
const { isFormItemInput: parentIsFormItemInput, status: parentStatus } =
React.useContext(FormItemInputContext);
const {
isFormItemInput: parentIsFormItemInput,
status: parentStatus,
hasFeedback: parentHasFeedback,
feedbackIcon: parentFeedbackIcon,
} = React.useContext(FormItemInputContext);
// ====================== Context =======================
const formItemStatusContext = React.useMemo<FormItemStatusContextProps>(() => {
@ -75,23 +79,24 @@ export default function StatusProvider({
) : null;
}
let isFormItemInput: boolean | undefined = true;
let status: ValidateStatus = mergedValidateStatus || '';
// No style will follow parent context
if (noStyle) {
isFormItemInput = parentIsFormItemInput;
status = (mergedValidateStatus ?? parentStatus) || '';
}
return {
status,
const context: FormItemStatusContextProps = {
status: mergedValidateStatus || '',
errors,
warnings,
hasFeedback: !!hasFeedback,
feedbackIcon,
isFormItemInput,
isFormItemInput: true,
};
// No style will follow parent context
if (noStyle) {
context.status = (mergedValidateStatus ?? parentStatus) || '';
context.isFormItemInput = parentIsFormItemInput;
context.hasFeedback = !!(hasFeedback ?? parentHasFeedback);
context.feedbackIcon = hasFeedback !== undefined ? context.feedbackIcon : parentFeedbackIcon;
}
return context;
}, [mergedValidateStatus, hasFeedback, noStyle, parentIsFormItemInput, parentStatus]);
// ======================= Render =======================

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import classNames from 'classnames';
import { Field, FieldContext, ListContext } from 'rc-field-form';
import type { FieldProps } from 'rc-field-form/lib/Field';
import type { Meta } from 'rc-field-form/lib/interface';
import type { InternalNamePath, Meta } from 'rc-field-form/lib/interface';
import useState from 'rc-util/lib/hooks/useState';
import { supportRef } from 'rc-util/lib/ref';
@ -138,7 +138,7 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
// ========================= MISC =========================
// Get `noStyle` required info
const listContext = React.useContext(ListContext);
const fieldKeyPathRef = React.useRef<React.Key[]>();
const fieldKeyPathRef = React.useRef<InternalNamePath>();
// ======================== Errors ========================
// >>>>> Collect sub field errors

View File

@ -1418,15 +1418,15 @@ describe('Form', () => {
</Form.Item>
{/* should follow parent status */}
<Form.Item validateStatus="error">
<Form.Item validateStatus="error" hasFeedback>
<Form.Item noStyle>
<Select className="custom-select-b" />
</Form.Item>
</Form.Item>
{/* should follow child status */}
<Form.Item validateStatus="error">
<Form.Item noStyle validateStatus="warning">
<Form.Item validateStatus="error" hasFeedback>
<Form.Item noStyle validateStatus="warning" hasFeedback={false}>
<Select className="custom-select-c" />
</Form.Item>
</Form.Item>
@ -1455,9 +1455,19 @@ describe('Form', () => {
expect(container.querySelector('.custom-select-b')).toHaveClass('ant-select-status-error');
expect(container.querySelector('.custom-select-b')).toHaveClass('ant-select-in-form-item');
expect(
container
.querySelector('.custom-select-b')
?.querySelector('.ant-form-item-feedback-icon-error'),
).toBeTruthy();
expect(container.querySelector('.custom-select-c')).toHaveClass('ant-select-status-warning');
expect(container.querySelector('.custom-select-c')).toHaveClass('ant-select-in-form-item');
expect(
container
.querySelector('.custom-select-c')
?.querySelector('.ant-form-item-feedback-icon-warning'),
).toBeFalsy();
expect(container.querySelector('.custom-select-d')).toHaveClass('ant-select-status-warning');
expect(container.querySelector('.custom-select-d')).toHaveClass('ant-select-in-form-item');

View File

@ -69,7 +69,7 @@ Common props ref[Common props](/docs/react/common-props)
| component | Set the Form rendering element. Do not create a DOM node for `false` | ComponentType \| false | form | |
| fields | Control of form fields through state management (such as redux). Not recommended for non-strong demand. View [example](#components-form-demo-global-state) | [FieldData](#fielddata)\[] | - | |
| form | Form control instance created by `Form.useForm()`. Automatically created when not provided | [FormInstance](#forminstance) | - | |
| feedbackIcons | Can be passed custom icons while `Form.Item` element has `hasFeedback` | [FeedbackIcons](#feedbackicons) | - | |
| feedbackIcons | Can be passed custom icons while `Form.Item` element has `hasFeedback` | [FeedbackIcons](#feedbackicons) | - | 5.9.0 |
| initialValues | Set value by Form initialization or reset | object | - | |
| labelAlign | The text align of label of all items | `left` \| `right` | `right` | |
| labelWrap | whether label can be wrap | boolean | false | 4.18.0 |
@ -125,7 +125,7 @@ Form field component for data bidirectional binding, validation, layout, and so
| extra | The extra prompt message. It is similar to help. Usage example: to display error message and prompt message at the same time | ReactNode | - | |
| getValueFromEvent | Specify how to get value from event or other onChange arguments | (..args: any\[]) => any | - | |
| getValueProps | Additional props with sub component | (value: any) => any | - | 4.2.0 |
| hasFeedback | Used with `validateStatus`, this option specifies the validation status icon. Recommended to be used only with `Input`. Also, It can get feedback icons via icons prop. | boolean \| {icons:[FeedbackIcons](#feedbackicons)} | false | icons: 5.9.0 |
| hasFeedback | Used with `validateStatus`, this option specifies the validation status icon. Recommended to be used only with `Input`. Also, It can get feedback icons via icons prop. | boolean \| { icons: [FeedbackIcons](#feedbackicons) } | false | icons: 5.9.0 |
| help | The prompt message. If not provided, the prompt message will be generated by the validation rule. | ReactNode | - | |
| hidden | Whether to hide Form.Item (still collect and validate value) | boolean | false | 4.4.0 |
| htmlFor | Set sub label `htmlFor` | string | - | |
@ -164,7 +164,7 @@ Used when there are dependencies between fields. If a field has the `dependencie
### FeedbackIcons
`({status:ValidateStatus, errors: ReactNode, warnings: ReactNode}) => Record<ValidateStatus,ReactNode>`
`({ status: ValidateStatus, errors: ReactNode, warnings: ReactNode }) => Record<ValidateStatus, ReactNode>`
### shouldUpdate

View File

@ -70,7 +70,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ylFATY6w-ygAAA
| component | 设置 Form 渲染元素,为 `false` 则不创建 DOM 节点 | ComponentType \| false | form | |
| fields | 通过状态管理(如 redux控制表单字段如非强需求不推荐使用。查看[示例](#components-form-demo-global-state) | [FieldData](#fielddata)\[] | - | |
| form | 经 `Form.useForm()` 创建的 form 控制实例,不提供时会自动创建 | [FormInstance](#forminstance) | - | |
| feedbackIcons | Can be passed custom icons while `Form.Item` element has `hasFeedback` | ({status:ValidateStatus, errors: ReactNode, warnings: ReactNode}) => Record<ValidateStatus,ReactNode> | - | 5.9.0 |
| feedbackIcons | `Form.Item``hasFeedback` 属性时可以自定义图标 | [FeedbackIcons](#feedbackicons) | - | 5.9.0 |
| initialValues | 表单默认值,只有初始化以及重置时生效 | object | - | |
| labelAlign | label 标签的文本对齐方式 | `left` \| `right` | `right` | |
| labelWrap | label 标签的文本换行方式 | boolean | false | 4.18.0 |
@ -126,7 +126,7 @@ const validateMessages = {
| extra | 额外的提示信息,和 `help` 类似,当需要错误信息和提示文案同时出现时,可以使用这个。 | ReactNode | - | |
| getValueFromEvent | 设置如何将 event 的值转换成字段值 | (..args: any\[]) => any | - | |
| getValueProps | 为子元素添加额外的属性 | (value: any) => any | - | 4.2.0 |
| hasFeedback | 配合 `validateStatus` 属性使用,展示校验状态图标,建议只配合 Input 组件使用 此外,它还可以通过 Icons 属性获取反馈图标。 | boolean \| {icons:({status:ValidateStatus, errors: ReactNode, warnings: ReactNode}) => Record<ValidateStatus,ReactNode>} | false | |
| hasFeedback | 配合 `validateStatus` 属性使用,展示校验状态图标,建议只配合 Input 组件使用 此外,它还可以通过 Icons 属性获取反馈图标。 | boolean \| { icons: [FeedbackIcons](#feedbackicons) } | false | icons: 5.9.0 |
| help | 提示信息,如不设置,则会根据校验规则自动生成 | ReactNode | - | |
| hidden | 是否隐藏字段(依然会收集和校验字段) | boolean | false | 4.4.0 |
| htmlFor | 设置子元素 label `htmlFor` 属性 | string | - | |
@ -163,6 +163,10 @@ const validateMessages = {
`dependencies` 不应和 `shouldUpdate` 一起使用,因为这可能带来更新逻辑的混乱。
### FeedbackIcons
`({ status: ValidateStatus, errors: ReactNode, warnings: ReactNode }) => Record<ValidateStatus, ReactNode>`
### shouldUpdate
Form 通过增量更新方式,只更新被修改的字段相关组件以达到性能优化目的。大部分场景下,你只需要编写代码或者与 [`dependencies`](#dependencies) 属性配合校验即可。而在某些特定场景,例如修改某个字段值后出现新的字段选项、或者纯粹希望表单任意变化都对某一个区域进行渲染。你可以通过 `shouldUpdate` 修改 Form.Item 的更新逻辑。

View File

@ -2916,15 +2916,121 @@ Array [
unselectable="on"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select: none;"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>,
<div
class="ant-select ant-select-single ant-select-show-arrow"
style="width: 100px;"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-autocomplete="list"
aria-controls="rc_select_TEST_OR_SSR_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Search"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
readonly=""
role="combobox"
style="opacity: 0;"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-item"
title=""
/>
</div>
<div
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-select-dropdown-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div>
<div
id="rc_select_TEST_OR_SSR_list"
role="listbox"
style="height: 0px; width: 0px; overflow: hidden;"
>
<div
aria-label="Jack"
aria-selected="false"
id="rc_select_TEST_OR_SSR_list_0"
role="option"
>
jack
</div>
<div
aria-label="Lucy"
aria-selected="false"
id="rc_select_TEST_OR_SSR_list_1"
role="option"
>
lucy
</div>
</div>
<div
class="rc-virtual-list"
style="position: relative;"
>
<div
class="rc-virtual-list-holder"
style="max-height: 256px; overflow-y: auto;"
>
<div>
<div
class="rc-virtual-list-holder-inner"
style="display: flex; flex-direction: column;"
>
<div
aria-selected="false"
class="ant-select-item ant-select-item-option ant-select-item-option-disabled"
title="Disabled"
class="ant-select-item ant-select-item-option ant-select-item-option-active"
title="Jack"
>
<div
class="ant-select-item-option-content"
>
Disabled
Jack
</div>
<span
aria-hidden="true"
@ -2936,12 +3042,151 @@ Array [
<div
aria-selected="false"
class="ant-select-item ant-select-item-option"
title="yiminghe"
title="Lucy"
>
<div
class="ant-select-item-option-content"
>
yiminghe
Lucy
</div>
<span
aria-hidden="true"
class="ant-select-item-option-state"
style="user-select: none;"
unselectable="on"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select: none;"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>,
<div
class="ant-select ant-select-single ant-select-show-arrow"
style="width: 100px;"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-autocomplete="list"
aria-controls="rc_select_TEST_OR_SSR_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Search"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
readonly=""
role="combobox"
style="opacity: 0;"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<div
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-select-dropdown-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div>
<div
id="rc_select_TEST_OR_SSR_list"
role="listbox"
style="height: 0px; width: 0px; overflow: hidden;"
>
<div
aria-label="Jack"
aria-selected="false"
id="rc_select_TEST_OR_SSR_list_0"
role="option"
>
jack
</div>
<div
aria-label="Lucy"
aria-selected="false"
id="rc_select_TEST_OR_SSR_list_1"
role="option"
>
lucy
</div>
</div>
<div
class="rc-virtual-list"
style="position: relative;"
>
<div
class="rc-virtual-list-holder"
style="max-height: 256px; overflow-y: auto;"
>
<div>
<div
class="rc-virtual-list-holder-inner"
style="display: flex; flex-direction: column;"
>
<div
aria-selected="false"
class="ant-select-item ant-select-item-option ant-select-item-option-active"
title="Jack"
>
<div
class="ant-select-item-option-content"
>
Jack
</div>
<span
aria-hidden="true"
class="ant-select-item-option-state"
style="user-select: none;"
unselectable="on"
/>
</div>
<div
aria-selected="false"
class="ant-select-item ant-select-item-option"
title="Lucy"
>
<div
class="ant-select-item-option-content"
>
Lucy
</div>
<span
aria-hidden="true"

View File

@ -655,6 +655,123 @@ Array [
</span>
</span>
</div>,
<div
class="ant-select ant-select-single ant-select-show-arrow"
style="width:100px"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-autocomplete="list"
aria-controls="undefined_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Search"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-item"
title=""
/>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>,
<div
class="ant-select ant-select-single ant-select-show-arrow"
style="width:100px"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-autocomplete="list"
aria-controls="undefined_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Search"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>,
<div
class="ant-select ant-tree-select ant-select-single ant-select-show-arrow"
style="width:100px"

View File

@ -1 +1,7 @@
undefined
## zh-CN
默认对齐效果。
## en-US
Align without Space.

View File

@ -15,7 +15,6 @@ import {
} from 'antd';
const { Text } = Typography;
const { Option } = Select;
const { RangePicker } = DatePicker;
const narrowStyle: React.CSSProperties = {
@ -57,6 +56,11 @@ const options = [
},
];
const selectOptions = [
{ value: 'jack', label: 'Jack' },
{ value: 'lucy', label: 'Lucy' },
];
const App: React.FC = () => (
<>
<Mentions style={{ width: 100 }} rows={1} />
@ -69,14 +73,9 @@ const App: React.FC = () => (
<InputNumber style={{ width: 100 }} />
<DatePicker style={{ width: 100 }} />
<TimePicker style={{ width: 100 }} />
<Select style={{ width: 100 }} defaultValue="jack">
<Option value="jack">Jack</Option>
<Option value="lucy">Lucy</Option>
<Option value="disabled" disabled>
Disabled
</Option>
<Option value="Yiminghe">yiminghe</Option>
</Select>
<Select style={{ width: 100 }} defaultValue="jack" options={selectOptions} />
<Select style={{ width: 100 }} defaultValue="" options={selectOptions} />
<Select style={{ width: 100 }} options={selectOptions} />
<TreeSelect style={{ width: 100 }} />
<Cascader defaultValue={['zhejiang', 'hangzhou', 'xihu']} options={options} />
<RangePicker />

View File

@ -78884,7 +78884,7 @@ exports[`Locale Provider should display the text as fa 1`] = `
tabindex="-1"
type="button"
>
آذر
سپتامبر
</button>
<button
class="ant-picker-year-btn"
@ -80943,7 +80943,7 @@ exports[`Locale Provider should display the text as fa 1`] = `
tabindex="-1"
type="button"
>
آذر
سپتامبر
</button>
<button
class="ant-picker-year-btn"
@ -81481,7 +81481,7 @@ exports[`Locale Provider should display the text as fa 1`] = `
tabindex="-1"
type="button"
>
دی
اکتبر
</button>
<button
class="ant-picker-year-btn"
@ -82578,9 +82578,9 @@ exports[`Locale Provider should display the text as fa 1`] = `
</span>
<span
class="ant-select-selection-item"
title="آذر"
title="سپتامبر"
>
آذر
سپتامبر
</span>
</div>
<span

View File

@ -135,6 +135,18 @@ describe('Modal', () => {
expect(document.querySelector('.custom-footer')).toBeTruthy();
});
it('Should custom footer function second param work', () => {
const footerFn = jest.fn();
render(<Modal open footer={footerFn} />);
expect(footerFn).toHaveBeenCalled();
expect(footerFn.mock.calls[0][0]).toBeTruthy();
expect(footerFn.mock.calls[0][1]).toEqual({
OkBtn: expect.any(Function),
CancelBtn: expect.any(Function),
});
});
it('Should custom footer function work', () => {
render(
<Modal
@ -150,4 +162,24 @@ describe('Modal', () => {
);
expect(document.querySelector('.custom-footer-ele')).toBeTruthy();
});
// https://github.com/ant-design/ant-design/issues/
it('Both ways should be rendered normally on the page', () => {
render(
<Modal
open
footer={(origin, { OkBtn, CancelBtn }) => (
<>
<div className="first-origin">{origin}</div>
<div className="second-props-origin">
<OkBtn />
<CancelBtn />
</div>
</>
)}
/>,
);
expect(document.querySelector('.first-origin')).toMatchSnapshot();
expect(document.querySelector('.second-props-origin')).toMatchSnapshot();
});
});

View File

@ -1,5 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Modal Both ways should be rendered normally on the page 1`] = `
<div
class="first-origin"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Cancel
</span>
</button>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
OK
</span>
</button>
</div>
`;
exports[`Modal Both ways should be rendered normally on the page 2`] = `
<div
class="second-props-origin"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
OK
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Cancel
</span>
</button>
</div>
`;
exports[`Modal render correctly 1`] = `
<div>
<div>

View File

@ -71,17 +71,20 @@ export const Footer: React.FC<
let footerNode;
if (typeof footer === 'function' || typeof footer === 'undefined') {
footerNode = (
<ModalContextProvider value={btnCtxValueMemo}>
<>
<NormalCancelBtn />
<NormalOkBtn />
</ModalContextProvider>
</>
);
if (typeof footer === 'function') {
footerNode = footer(footerNode, {
OkBtn: NormalOkBtn,
CancelBtn: NormalCancelBtn,
});
}
footerNode = <ModalContextProvider value={btnCtxValueMemo}>{footerNode}</ModalContextProvider>;
} else {
footerNode = footer;
}

View File

@ -4,7 +4,7 @@ import type { NotificationToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
const genNotificationPlacementStyle: GenerateStyle<NotificationToken, CSSObject> = (token) => {
const { componentCls, notificationMarginEdge } = token;
const { componentCls, width, notificationMarginEdge, animationMaxHeight } = token;
const noticeCls = `${componentCls}-notice`;
@ -22,12 +22,12 @@ const genNotificationPlacementStyle: GenerateStyle<NotificationToken, CSSObject>
const topFadeIn = new Keyframes('antNotificationTopFadeIn', {
'0%': {
top: -token.animationMaxHeight,
bottom: -animationMaxHeight,
opacity: 0,
},
'100%': {
top: 0,
bottom: 0,
opacity: 1,
},
});

View File

@ -6071,12 +6071,13 @@ exports[`renders components/select/demo/option-label-center.tsx extend context c
<div
aria-selected="false"
class="ant-select-item ant-select-item-option"
title="normal"
>
<div
class="ant-select-item-option-content"
>
normal
<div>
normal
</div>
</div>
<span
aria-hidden="true"
@ -6264,12 +6265,13 @@ exports[`renders components/select/demo/option-label-center.tsx extend context c
<div
aria-selected="false"
class="ant-select-item ant-select-item-option"
title="normal"
>
<div
class="ant-select-item-option-content"
>
normal
<div>
normal
</div>
</div>
<span
aria-hidden="true"
@ -6312,6 +6314,457 @@ exports[`renders components/select/demo/option-label-center.tsx extend context c
</span>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-select ant-select-single ant-select-allow-clear ant-select-show-arrow"
style="width: 120px;"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-autocomplete="list"
aria-controls="rc_select_TEST_OR_SSR_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Search"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
readonly=""
role="combobox"
style="opacity: 0;"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-item"
>
<div>
normal
</div>
</span>
</div>
<div
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-select-dropdown-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div>
<div
id="rc_select_TEST_OR_SSR_list"
role="listbox"
style="height: 0px; width: 0px; overflow: hidden;"
>
<div
aria-selected="false"
id="rc_select_TEST_OR_SSR_list_0"
role="option"
>
long
</div>
<div
aria-selected="false"
id="rc_select_TEST_OR_SSR_list_1"
role="option"
>
short
</div>
</div>
<div
class="rc-virtual-list"
style="position: relative;"
>
<div
class="rc-virtual-list-holder"
style="max-height: 256px; overflow-y: auto;"
>
<div>
<div
class="rc-virtual-list-holder-inner"
style="display: flex; flex-direction: column;"
>
<div
aria-selected="false"
class="ant-select-item ant-select-item-option ant-select-item-option-active"
>
<div
class="ant-select-item-option-content"
>
<article
class="ant-typography"
>
long, long, long piece of text
</article>
</div>
<span
aria-hidden="true"
class="ant-select-item-option-state"
style="user-select: none;"
unselectable="on"
/>
</div>
<div
aria-selected="false"
class="ant-select-item ant-select-item-option"
>
<div
class="ant-select-item-option-content"
>
<article
class="ant-typography"
>
short
</article>
</div>
<span
aria-hidden="true"
class="ant-select-item-option-state"
style="user-select: none;"
unselectable="on"
/>
</div>
<div
aria-selected="true"
class="ant-select-item ant-select-item-option ant-select-item-option-selected"
>
<div
class="ant-select-item-option-content"
>
<div>
normal
</div>
</div>
<span
aria-hidden="true"
class="ant-select-item-option-state"
style="user-select: none;"
unselectable="on"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select: none;"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
<span
aria-hidden="true"
class="ant-select-clear"
style="user-select: none;"
unselectable="on"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm127.98 274.82h-.04l-.08.06L512 466.75 384.14 338.88c-.04-.05-.06-.06-.08-.06a.12.12 0 00-.07 0c-.03 0-.05.01-.09.05l-45.02 45.02a.2.2 0 00-.05.09.12.12 0 000 .07v.02a.27.27 0 00.06.06L466.75 512 338.88 639.86c-.05.04-.06.06-.06.08a.12.12 0 000 .07c0 .03.01.05.05.09l45.02 45.02a.2.2 0 00.09.05.12.12 0 00.07 0c.02 0 .04-.01.08-.05L512 557.25l127.86 127.87c.04.04.06.05.08.05a.12.12 0 00.07 0c.03 0 .05-.01.09-.05l45.02-45.02a.2.2 0 00.05-.09.12.12 0 000-.07v-.02a.27.27 0 00-.05-.06L557.25 512l127.87-127.86c.04-.04.05-.06.05-.08a.12.12 0 000-.07c0-.03-.01-.05-.05-.09l-45.02-45.02a.2.2 0 00-.09-.05.12.12 0 00-.07 0z"
/>
</svg>
</span>
</span>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-select ant-select-multiple ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
style="width: 120px;"
>
<div
class="ant-select-selector"
>
<div
class="ant-select-selection-overflow"
>
<div
class="ant-select-selection-overflow-item"
style="opacity: 1;"
>
<span
class="ant-select-selection-item"
>
<span
class="ant-select-selection-item-content"
>
<div>
normal
</div>
</span>
<span
aria-hidden="true"
class="ant-select-selection-item-remove"
style="user-select: none;"
unselectable="on"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
/>
</svg>
</span>
</span>
</span>
</div>
<div
class="ant-select-selection-overflow-item ant-select-selection-overflow-item-suffix"
style="opacity: 1;"
>
<div
class="ant-select-selection-search"
style="width: 0px;"
>
<input
aria-autocomplete="list"
aria-controls="rc_select_TEST_OR_SSR_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Search"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
readonly=""
role="combobox"
style="opacity: 0;"
type="search"
unselectable="on"
value=""
/>
<span
aria-hidden="true"
class="ant-select-selection-search-mirror"
>
</span>
</div>
</div>
</div>
</div>
<div
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-select-dropdown-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div>
<div
id="rc_select_TEST_OR_SSR_list"
role="listbox"
style="height: 0px; width: 0px; overflow: hidden;"
>
<div
aria-selected="false"
id="rc_select_TEST_OR_SSR_list_0"
role="option"
>
long
</div>
<div
aria-selected="false"
id="rc_select_TEST_OR_SSR_list_1"
role="option"
>
short
</div>
</div>
<div
class="rc-virtual-list"
style="position: relative;"
>
<div
class="rc-virtual-list-holder"
style="max-height: 256px; overflow-y: auto;"
>
<div>
<div
class="rc-virtual-list-holder-inner"
style="display: flex; flex-direction: column;"
>
<div
aria-selected="false"
class="ant-select-item ant-select-item-option ant-select-item-option-active"
>
<div
class="ant-select-item-option-content"
>
<article
class="ant-typography"
>
long, long, long piece of text
</article>
</div>
</div>
<div
aria-selected="false"
class="ant-select-item ant-select-item-option"
>
<div
class="ant-select-item-option-content"
>
<article
class="ant-typography"
>
short
</article>
</div>
</div>
<div
aria-selected="true"
class="ant-select-item ant-select-item-option ant-select-item-option-selected"
>
<div
class="ant-select-item-option-content"
>
<div>
normal
</div>
</div>
<span
aria-hidden="true"
class="ant-select-item-option-state"
style="user-select: none;"
unselectable="on"
>
<span
aria-label="check"
class="anticon anticon-check"
role="img"
>
<svg
aria-hidden="true"
data-icon="check"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
/>
</svg>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select: none;"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
<span
aria-hidden="true"
class="ant-select-clear"
style="user-select: none;"
unselectable="on"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm127.98 274.82h-.04l-.08.06L512 466.75 384.14 338.88c-.04-.05-.06-.06-.08-.06a.12.12 0 00-.07 0c-.03 0-.05.01-.09.05l-45.02 45.02a.2.2 0 00-.05.09.12.12 0 000 .07v.02a.27.27 0 00.06.06L466.75 512 338.88 639.86c-.05.04-.06.06-.06.08a.12.12 0 000 .07c0 .03.01.05.05.09l45.02 45.02a.2.2 0 00.09.05.12.12 0 00.07 0c.02 0 .04-.01.08-.05L512 557.25l127.86 127.87c.04.04.06.05.08.05a.12.12 0 00.07 0c.03 0 .05-.01.09-.05l45.02-45.02a.2.2 0 00.05-.09.12.12 0 000-.07v-.02a.27.27 0 00-.05-.06L557.25 512l127.87-127.86c.04-.04.05-.06.05-.08a.12.12 0 000-.07c0-.03-.01-.05-.05-.09l-45.02-45.02a.2.2 0 00-.09-.05.12.12 0 00-.07 0z"
/>
</svg>
</span>
</span>
</div>
</div>
<div
class="ant-space-item"
>
@ -6433,12 +6886,13 @@ exports[`renders components/select/demo/option-label-center.tsx extend context c
<div
aria-selected="false"
class="ant-select-item ant-select-item-option"
title="normal"
>
<div
class="ant-select-item-option-content"
>
normal
<div>
normal
</div>
</div>
</div>
</div>
@ -6561,12 +7015,13 @@ exports[`renders components/select/demo/option-label-center.tsx extend context c
class="ant-cascader-menu-item"
data-path-key="normal"
role="menuitemcheckbox"
title="normal"
>
<div
class="ant-cascader-menu-item-content"
>
normal
<div>
normal
</div>
</div>
</li>
</ul>
@ -6802,12 +7257,14 @@ exports[`renders components/select/demo/option-label-center.tsx extend context c
/>
<span
class="ant-select-tree-node-content-wrapper ant-select-tree-node-content-wrapper-normal"
title="normal"
title=""
>
<span
class="ant-select-tree-title"
>
normal
<div>
normal
</div>
</span>
</span>
</div>

View File

@ -1931,6 +1931,243 @@ exports[`renders components/select/demo/option-label-center.tsx correctly 1`] =
</span>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-select ant-select-single ant-select-allow-clear ant-select-show-arrow"
style="width:120px"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-autocomplete="list"
aria-controls="undefined_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Search"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-item"
>
<div>
normal
</div>
</span>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
<span
aria-hidden="true"
class="ant-select-clear"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm127.98 274.82h-.04l-.08.06L512 466.75 384.14 338.88c-.04-.05-.06-.06-.08-.06a.12.12 0 00-.07 0c-.03 0-.05.01-.09.05l-45.02 45.02a.2.2 0 00-.05.09.12.12 0 000 .07v.02a.27.27 0 00.06.06L466.75 512 338.88 639.86c-.05.04-.06.06-.06.08a.12.12 0 000 .07c0 .03.01.05.05.09l45.02 45.02a.2.2 0 00.09.05.12.12 0 00.07 0c.02 0 .04-.01.08-.05L512 557.25l127.86 127.87c.04.04.06.05.08.05a.12.12 0 00.07 0c.03 0 .05-.01.09-.05l45.02-45.02a.2.2 0 00.05-.09.12.12 0 000-.07v-.02a.27.27 0 00-.05-.06L557.25 512l127.87-127.86c.04-.04.05-.06.05-.08a.12.12 0 000-.07c0-.03-.01-.05-.05-.09l-45.02-45.02a.2.2 0 00-.09-.05.12.12 0 00-.07 0z"
/>
</svg>
</span>
</span>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-select ant-select-multiple ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
style="width:120px"
>
<div
class="ant-select-selector"
>
<div
class="ant-select-selection-overflow"
>
<div
class="ant-select-selection-overflow-item"
style="opacity:1"
>
<span
class="ant-select-selection-item"
>
<span
class="ant-select-selection-item-content"
>
<div>
normal
</div>
</span>
<span
aria-hidden="true"
class="ant-select-selection-item-remove"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
/>
</svg>
</span>
</span>
</span>
</div>
<div
class="ant-select-selection-overflow-item ant-select-selection-overflow-item-suffix"
style="opacity:1"
>
<div
class="ant-select-selection-search"
style="width:0"
>
<input
aria-autocomplete="list"
aria-controls="undefined_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Search"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
<span
aria-hidden="true"
class="ant-select-selection-search-mirror"
>
</span>
</div>
</div>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
<span
aria-hidden="true"
class="ant-select-clear"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm127.98 274.82h-.04l-.08.06L512 466.75 384.14 338.88c-.04-.05-.06-.06-.08-.06a.12.12 0 00-.07 0c-.03 0-.05.01-.09.05l-45.02 45.02a.2.2 0 00-.05.09.12.12 0 000 .07v.02a.27.27 0 00.06.06L466.75 512 338.88 639.86c-.05.04-.06.06-.06.08a.12.12 0 000 .07c0 .03.01.05.05.09l45.02 45.02a.2.2 0 00.09.05.12.12 0 00.07 0c.02 0 .04-.01.08-.05L512 557.25l127.86 127.87c.04.04.06.05.08.05a.12.12 0 00.07 0c.03 0 .05-.01.09-.05l45.02-45.02a.2.2 0 00.05-.09.12.12 0 000-.07v-.02a.27.27 0 00-.05-.06L557.25 512l127.87-127.86c.04-.04.05-.06.05-.08a.12.12 0 000-.07c0-.03-.01-.05-.05-.09l-45.02-45.02a.2.2 0 00-.09-.05.12.12 0 00-.07 0z"
/>
</svg>
</span>
</span>
</div>
</div>
<div
class="ant-space-item"
>

View File

@ -4,7 +4,7 @@ import { Select, Space, Cascader, Typography, TreeSelect } from 'antd';
const options = [
{ value: 'long', label: <Typography>long, long, long piece of text</Typography> },
{ value: 'short', label: <Typography>short</Typography> },
{ value: 'normal', label: 'normal' },
{ value: 'normal', label: <div>normal</div> },
];
const App: React.FC = () => (
@ -23,6 +23,23 @@ const App: React.FC = () => (
options={options}
/>
<Select
defaultValue="normal"
placeholder="Select a option"
style={{ width: 120 }}
allowClear
options={options}
/>
<Select
defaultValue={['normal']}
mode="multiple"
placeholder="Select a option"
style={{ width: 120 }}
allowClear
options={options}
/>
<Select
mode="multiple"
placeholder="Select a option"

View File

@ -55,9 +55,9 @@ function genSizeStyle(token: SelectToken, suffix?: string): CSSObject {
[[
'&:after',
/* For '' value baseline align */
`${componentCls}-selection-item:after`,
`${componentCls}-selection-item:empty:after`,
/* For undefined value baseline align */
`${componentCls}-selection-placeholder:after`,
`${componentCls}-selection-placeholder:empty:after`,
].join(',')]: {
display: 'inline-block',
width: 0,

View File

@ -1,10 +1,11 @@
/* eslint-disable no-unsafe-optional-chaining */
/* eslint-disable react/no-multi-comp */
import React, { useEffect, useState } from 'react';
import type { ColumnGroupType, ColumnType, TableProps } from '..';
import Table from '..';
import { act, fireEvent, render, waitFor } from '../../../tests/utils';
import { resetWarned } from '../../_util/warning';
import { act, fireEvent, render, waitFor } from '../../../tests/utils';
import Button from '../../button';
import ConfigProvider from '../../config-provider';
import Input from '../../input';

View File

@ -22,6 +22,7 @@ import Tree from '../../../tree';
import type {
ColumnFilterItem,
ColumnType,
FilterKey,
FilterSearchType,
FilterValue,
GetPopupContainer,
@ -31,7 +32,7 @@ import type {
import FilterSearch from './FilterSearch';
import FilterDropdownMenuWrapper from './FilterWrapper';
type FilterTreeDataNode = FieldDataNode<{ title: React.ReactNode; key: React.Key }>;
type FilterTreeDataNode = FieldDataNode<{ title: React.ReactNode; key: string }>;
interface FilterRestProps {
confirm?: Boolean;
@ -134,6 +135,10 @@ export interface FilterDropdownProps<RecordType> {
filterResetToDefaultFilteredValue?: boolean;
}
function wrapStringListType(keys?: FilterKey) {
return (keys as string[]) || [];
}
function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
const {
tablePrefixCls,
@ -196,20 +201,22 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
// ===================== Select Keys =====================
const propFilteredKeys = filterState?.filteredKeys;
const [getFilteredKeysSync, setFilteredKeysSync] = useSyncState(propFilteredKeys || []);
const [getFilteredKeysSync, setFilteredKeysSync] = useSyncState(
wrapStringListType(propFilteredKeys),
);
const onSelectKeys = ({ selectedKeys }: { selectedKeys: Key[] }) => {
const onSelectKeys = ({ selectedKeys }: { selectedKeys: string[] }) => {
setFilteredKeysSync(selectedKeys);
};
const onCheck = (
keys: Key[],
keys: string[],
{ node, checked }: { node: EventDataNode<FilterTreeDataNode>; checked: boolean },
) => {
if (!filterMultiple) {
onSelectKeys({ selectedKeys: checked && node.key ? [node.key] : [] });
} else {
onSelectKeys({ selectedKeys: keys as Key[] });
onSelectKeys({ selectedKeys: keys });
}
};
@ -217,7 +224,7 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
if (!visible) {
return;
}
onSelectKeys({ selectedKeys: propFilteredKeys || [] });
onSelectKeys({ selectedKeys: wrapStringListType(propFilteredKeys) });
}, [propFilteredKeys]);
// ====================== Open Keys ======================
@ -240,7 +247,7 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
}, [visible]);
// ======================= Submit ========================
const internalTriggerFilter = (keys?: Key[]) => {
const internalTriggerFilter = (keys?: string[]) => {
const mergedKeys = keys && keys.length ? keys : null;
if (mergedKeys === null && (!filterState || !filterState.filteredKeys)) {
return null;
@ -291,7 +298,7 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
const onVisibleChange = (newVisible: boolean) => {
if (newVisible && propFilteredKeys !== undefined) {
// Sync filteredKeys on appear in controlled mode (propFilteredKeys !== undefined)
setFilteredKeysSync(propFilteredKeys || []);
setFilteredKeysSync(wrapStringListType(propFilteredKeys));
}
triggerVisible(newVisible);
@ -321,7 +328,7 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
const key = String(filter.value);
const item: FilterTreeDataNode = {
title: filter.text,
key: filter.value !== undefined ? key : index,
key: filter.value !== undefined ? key : String(index),
};
if (filter.children) {
item.children = getTreeData({ filters: filter.children });
@ -340,7 +347,7 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
if (typeof column.filterDropdown === 'function') {
dropdownContent = column.filterDropdown({
prefixCls: `${dropdownPrefixCls}-custom`,
setSelectedKeys: (selectedKeys: Key[]) => onSelectKeys({ selectedKeys }),
setSelectedKeys: (selectedKeys: string[]) => onSelectKeys({ selectedKeys }),
selectedKeys: getFilteredKeysSync(),
confirm: doFilter,
clearFilters: onReset,
@ -439,7 +446,7 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
className={dropdownMenuClass}
onSelect={onSelectKeys}
onDeselect={onSelectKeys}
selectedKeys={selectedKeys as string[]}
selectedKeys={selectedKeys}
getPopupContainer={getPopupContainer}
openKeys={openKeys}
onOpenChange={onOpenChange}

View File

@ -9,6 +9,7 @@ import type {
FilterValue,
GetPopupContainer,
Key,
SafeKey,
TableLocale,
TransformColumns,
} from '../../interface';
@ -133,14 +134,17 @@ function generateFilterInfo<RecordType>(filterStates: FilterState<RecordType>[])
const currentFilters: Record<string, FilterValue | null> = {};
filterStates.forEach(({ key, filteredKeys, column }) => {
const keyAsString = key as SafeKey;
const { filters, filterDropdown } = column;
if (filterDropdown) {
currentFilters[key] = filteredKeys || null;
currentFilters[keyAsString] = filteredKeys || null;
} else if (Array.isArray(filteredKeys)) {
const keys = flattenKeys(filters);
currentFilters[key] = keys.filter((originKey) => filteredKeys.includes(String(originKey)));
currentFilters[keyAsString] = keys.filter((originKey) =>
filteredKeys.includes(String(originKey)),
);
} else {
currentFilters[key] = null;
currentFilters[keyAsString] = null;
}
});

View File

@ -1,3 +1,4 @@
import type * as React from 'react';
import type {
FixedType,
GetComponentProps,
@ -5,14 +6,14 @@ import type {
RenderedCell as RcRenderedCell,
} from 'rc-table/lib/interface';
import { ExpandableConfig, GetRowKey } from 'rc-table/lib/interface';
import type * as React from 'react';
import type { Breakpoint } from '../_util/responsiveObserver';
import type { AnyObject } from '../_util/type';
import type { CheckboxProps } from '../checkbox';
import type { PaginationProps } from '../pagination';
import type { TooltipProps } from '../tooltip';
import type { InternalTableProps, TableProps } from './InternalTable';
import type { INTERNAL_SELECTION_ITEM } from './hooks/useSelection';
import type { InternalTableProps, TableProps } from './InternalTable';
export type RefTable = <RecordType extends AnyObject = AnyObject>(
props: React.PropsWithChildren<TableProps<RecordType>> & { ref?: React.Ref<HTMLDivElement> },
@ -28,6 +29,8 @@ export { ExpandableConfig, GetRowKey };
export type Key = React.Key;
export type SafeKey = Exclude<Key, bigint>;
export type RowSelectionType = 'checkbox' | 'radio';
export type SelectionItemSelectFn = (currentRowKeys: Key[]) => void;
@ -57,13 +60,13 @@ export interface TableLocale {
export type SortOrder = 'descend' | 'ascend' | null;
const TableActions = ['paginate', 'sort', 'filter'] as const;
export type TableAction = (typeof TableActions)[number];
export type TableAction = typeof TableActions[number];
export type CompareFn<T> = (a: T, b: T, sortOrder?: SortOrder) => number;
export interface ColumnFilterItem {
text: React.ReactNode;
value: string | number | boolean;
value: React.Key | boolean;
children?: ColumnFilterItem[];
}
@ -82,7 +85,7 @@ export type ColumnTitle<RecordType> =
| ((props: ColumnTitleProps<RecordType>) => React.ReactNode);
export type FilterValue = (Key | boolean)[];
export type FilterKey = Key[] | null;
export type FilterKey = (string | number)[] | null;
export type FilterSearchType<RecordType = AnyObject> =
| boolean
| ((input: string, record: RecordType) => boolean);
@ -133,7 +136,7 @@ export interface ColumnType<RecordType> extends Omit<RcColumnType<RecordType>, '
filterIcon?: React.ReactNode | ((filtered: boolean) => React.ReactNode);
filterMode?: 'menu' | 'tree';
filterSearch?: FilterSearchType<ColumnFilterItem>;
onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
onFilter?: (value: React.Key | boolean, record: RecordType) => boolean;
filterDropdownOpen?: boolean;
onFilterDropdownOpenChange?: (visible: boolean) => void;
filterResetToDefaultFilteredValue?: boolean;

View File

@ -44,7 +44,7 @@ const genSizeStyle: GenerateStyle<TableToken, CSSObject> = (token) => {
},
// https://github.com/ant-design/ant-design/issues/35167
[`${componentCls}-selection-column`]: {
[`${componentCls}-selection-extra`]: {
paddingInlineStart: `${paddingHorizontal / 4}px`,
},
},

View File

@ -289,6 +289,8 @@ describe('Theme', () => {
expect(token2.colorLink).toEqual(token2.colorInfo);
expect(token2.colorLinkHover).toEqual(token2.colorInfoHover);
expect(token2.colorLinkActive).toEqual(token2.colorInfoActive);
// colorInfo should not follow colorPrimary
expect(token2.colorLink).not.toEqual('#189cff');
const token3 = getHookToken({ algorithm: [theme.darkAlgorithm] });
expect(token3.colorLink).toEqual(token3.colorInfo);

View File

@ -10,7 +10,7 @@ interface TreeTransferProps {
}
// Customize Table Transfer
const isChecked = (selectedKeys: (string | number)[], eventKey: string | number) =>
const isChecked = (selectedKeys: React.Key[], eventKey: React.Key) =>
selectedKeys.includes(eventKey);
const generateTree = (treeNodes: DataNode[] = [], checkedKeys: string[] = []): DataNode[] =>

View File

@ -1,6 +1,6 @@
import debounce from 'lodash/debounce';
import type RcTree from 'rc-tree';
import type { Key } from 'react';
import type { Key } from 'rc-tree/lib/interface';
import React from 'react';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';

View File

@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/no-shadow */
import { CarryOutOutlined } from '@ant-design/icons';
import type { DataNode } from 'rc-tree/lib/interface';
import React from 'react';
import { CarryOutOutlined } from '@ant-design/icons';
import type { TreeProps } from 'antd';
import { Switch, Tree } from 'antd';
import type { DataNode } from 'rc-tree/lib/interface';
const x = 3;
const y = 2;

View File

@ -79,8 +79,8 @@ const App: React.FC = () => {
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
setExpandedKeys(newExpandedKeys as React.Key[]);
.filter((item, i, self): item is React.Key => !!(item && self.indexOf(item) === i));
setExpandedKeys(newExpandedKeys);
setSearchValue(value);
setAutoExpandParent(true);
};

View File

@ -12,19 +12,19 @@ author: Redjue
### RGB 色彩模型
`RGB` 色彩模型是通过三原色(红、绿、蓝)的不同组合来表示色彩的,每个原色的取值范围是 0-255三原色的组合可以表示 256<sup>3</sup> 种颜色,这些颜色可以组成一个立方体,如下图所示: ![RGB](https://user-images.githubusercontent.com/21119589/266228482-f1ff94b1-e7ca-40c5-8512-1bc5ab79c388.jpg)
`RGB` 色彩模型是通过三原色(红、绿、蓝)的不同组合来表示色彩的,每个原色的取值范围是 0-255三原色的组合可以表示 256<sup>3</sup> 种颜色,这些颜色可以组成一个立方体,如下图所示: ![RGB](https://user-images.githubusercontent.com/21119589/268834307-79fca808-d3a3-4fe8-b370-ea1ec472023c.png)
`RGB` 色彩模型中,每个颜色都可以用一个三元组 `(R, G, B)` 来表示,其中 `R` 表示红色的取值,`G` 表示绿色的取值,`B` 表示蓝色的取值。例如,红色可以表示为 `(255, 0, 0)`,绿色可以表示为 `(0, 255, 0)`,蓝色可以表示为 `(0, 0, 255)`
### HSV/HSB 色彩模型
`HSV` 色彩模型是通过色相Hue、饱和度Saturation、明度Value来表示色彩的其中色相的取值范围是 0-360饱和度和明度的取值范围是 0-100。HSV 色彩模型可以用一个圆锥体来表示,如下图所示: ![HSV](https://user-images.githubusercontent.com/21119589/266231236-d68ad9d7-9654-4bc5-8489-7cc52f2aabb1.png)
`HSV` 色彩模型是通过色相Hue、饱和度Saturation、明度Value来表示色彩的其中色相的取值范围是 0-360饱和度和明度的取值范围是 0-100。HSV 色彩模型可以用一个圆锥体来表示,如下图所示: ![HSV](https://user-images.githubusercontent.com/21119589/268834741-83940b90-c709-492b-8a7e-f59d317411e9.png)
`HSV` 色彩模型中,每个颜色都可以用一个三元组 `(H, S, V)` 来表示,其中 `H` 表示色相的取值,`S` 表示饱和度的取值,`V` 表示明度的取值。例如,红色可以表示为 `(0, 100, 100)`,绿色可以表示为 `(120, 100, 100)`,蓝色可以表示为 `(240, 100, 100)`
### HEX 色彩模型
`HEX` 色彩模型是通过十六进制数来表示色彩的,其中前两位表示红色的取值,中间两位表示绿色的取值,后两位表示蓝色的取值。例如,红色可以表示为 `#FF0000`,绿色可以表示为 `#00FF00`,蓝色可以表示为 `#0000FF`。如下图所示: ![HEX](https://user-images.githubusercontent.com/21119589/266569791-7f6afedd-3b84-4ee1-8c98-d3d4b16e8317.png)
`HEX` 色彩模型是通过十六进制数来表示色彩的,其中前两位表示红色的取值,中间两位表示绿色的取值,后两位表示蓝色的取值。例如,红色可以表示为 `#FF0000`,绿色可以表示为 `#00FF00`,蓝色可以表示为 `#0000FF`。如下图所示: ![HEX](https://user-images.githubusercontent.com/21119589/268841812-1b8310f5-322b-45ec-b768-d4115cf7091d.png)
这也是我们最常见的颜色表示方式,因为它可以直接在 CSS 中使用。而且表示方式非常简单,只需要将 RGB 色彩模型中的三个数字转换为十六进制数即可。
@ -111,3 +111,11 @@ const alpha = (offset.x + centerOffsetX) / width;
## 总结
通过这次开发之旅,我对色彩模型有了更深入的了解,也对 **Ant Design** 的开发流程有了更深入的了解。感谢 **Ant Design** 团队给我这次机会,也感谢大家的阅读。如果对细节实现感兴趣的童鞋,可以移步 [@rc-component/color-picker](https://github.com/react-component/color-picker) 查看源码实现。
## 图片来源
https://zh.wikipedia.org/wiki/%E4%B8%89%E5%8E%9F%E8%89%B2%E5%85%89%E6%A8%A1%E5%BC%8F#/media/File:RGB_color_solid_cube.png
https://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4#/media/File:HSV_cone.png
https://zh.wikipedia.org/wiki/%E7%BD%91%E9%A1%B5%E9%A2%9C%E8%89%B2#/media/File:Web_Color_Charts.svg

View File

@ -211,6 +211,10 @@ Please ref document [Shadow Dom Usage](/docs/react/customize-theme#shadow-dom-us
Please ref dynamic theme document [SSR](/docs/react/customize-theme#server-side-render-ssr) part.
## What is the relationship between colorPrimary and colorInfo and colorLink in V5?
In the Ant Design Token system, `colorPrimary` and `colorInfo` are both [Seed Token](../react/customize-theme.en-US.md#seed-token), so they are independent of each other. `colorLink` is an [Alias Token](../react/customize-theme.en-US.md#alias-token), inherits `colorInfo` by default, and is independent of `colorPrimary`.
## How to spell Ant Design correctly?
- ✅ **Ant Design**: Capitalized with space, for the design language.

View File

@ -239,6 +239,10 @@ import { ConfigProvider } from 'antd';
请参考动态主题文档 [服务端渲染](/docs/react/customize-theme-cn#服务端渲染) 部分内容。
## V5 中 colorPrimary 和 colorInfo 及 colorLink 之间是什么关系?
在 Ant Design Token 系统中 `colorPrimary``colorInfo` 同属于 [基础变量Seed Token](../react/customize-theme.zh-CN.md#基础变量seed-token),所以两者是互相独立的。`colorLink` 则属于 [别名变量Alias Token](../react/customize-theme.zh-CN.md#别名变量alias-token), 默认继承 `colorInfo` 且和 `colorPrimary` 无关。
## 如何正确的拼写 Ant Design
- ✅ **Ant Design**:用空格分隔的首字母大写单词,指代设计语言。

View File

@ -175,8 +175,8 @@ import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
import type Entity from '@ant-design/cssinjs/es/Cache';
import { useServerInsertedHTML } from 'next/navigation';
const StyledComponentsRegistry = ({ children }: { children: React.ReactNode }) => {
const cache = React.useMemo<Entity>(() => createCache(), [createCache]);
const StyledComponentsRegistry = ({ children }: React.PropsWithChildren) => {
const cache = React.useMemo<Entity>(() => createCache(), []);
useServerInsertedHTML(() => (
<style id="antd" dangerouslySetInnerHTML={{ __html: extractStyle(cache, true) }} />
));
@ -203,7 +203,7 @@ export const metadata = {
description: 'Generated by create next app',
};
const RootLayout = ({ children }: { children: React.ReactNode }) => (
const RootLayout = ({ children }: React.PropsWithChildren) => (
<html lang="en">
<body className={inter.className}>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>

View File

@ -175,8 +175,8 @@ import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
import type Entity from '@ant-design/cssinjs/es/Cache';
import { useServerInsertedHTML } from 'next/navigation';
const StyledComponentsRegistry = ({ children }: { children: React.ReactNode }) => {
const cache = React.useMemo<Entity>(() => createCache(), [createCache]);
const StyledComponentsRegistry = ({ children }: React.PropsWithChildren) => {
const cache = React.useMemo<Entity>(() => createCache(), []);
useServerInsertedHTML(() => (
<style id="antd" dangerouslySetInnerHTML={{ __html: extractStyle(cache, true) }} />
));
@ -203,7 +203,7 @@ export const metadata = {
description: 'Generated by create next app',
};
const RootLayout = ({ children }: { children: React.ReactNode }) => (
const RootLayout = ({ children }: React.PropsWithChildren) => (
<html lang="en">
<body className={inter.className}>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>

View File

@ -1,6 +1,6 @@
{
"name": "antd",
"version": "5.9.1",
"version": "5.9.2",
"packageManager": "^npm@9.0.0",
"description": "An enterprise-class UI design language and React components implementation",
"title": "Ant Design",
@ -119,7 +119,6 @@
"@rc-component/mutate-observer": "^1.1.0",
"@rc-component/tour": "~1.10.0",
"@rc-component/trigger": "^1.15.6",
"@types/jsdom": "^21.1.2",
"classnames": "^2.2.6",
"copy-to-clipboard": "^3.2.0",
"dayjs": "^1.11.1",
@ -130,11 +129,11 @@
"rc-dialog": "~9.2.0",
"rc-drawer": "~6.4.1",
"rc-dropdown": "~4.1.0",
"rc-field-form": "~1.38.0",
"rc-field-form": "~1.38.1",
"rc-image": "~7.2.0",
"rc-input": "~1.1.1",
"rc-input-number": "~8.0.4",
"rc-mentions": "~2.7.0",
"rc-input": "~1.2.1",
"rc-input-number": "~8.1.0",
"rc-mentions": "~2.8.0",
"rc-menu": "~9.12.0",
"rc-motion": "^2.9.0",
"rc-notification": "~5.2.0",
@ -150,9 +149,9 @@
"rc-switch": "~4.1.0",
"rc-table": "~7.34.0",
"rc-tabs": "~12.12.1",
"rc-textarea": "~1.3.4",
"rc-textarea": "~1.4.0",
"rc-tooltip": "~6.0.1",
"rc-tree": "~5.7.10",
"rc-tree": "~5.7.12",
"rc-tree-select": "~5.12.1",
"rc-upload": "~4.3.4",
"rc-util": "^5.37.0",
@ -164,7 +163,7 @@
"@ant-design/happy-work-theme": "^1.0.0",
"@ant-design/tools": "^17.3.1",
"@antv/g6": "^4.8.13",
"@argos-ci/core": "^0.11.0",
"@argos-ci/core": "^0.12.0",
"@babel/eslint-plugin": "^7.19.1",
"@biomejs/biome": "^1.0.0",
"@codesandbox/sandpack-react": "^2.6.9",
@ -192,8 +191,10 @@
"@types/jest-environment-puppeteer": "^5.0.0",
"@types/jest-image-snapshot": "^6.1.0",
"@types/jquery": "^3.5.14",
"@types/jsdom": "^21.1.2",
"@types/lodash": "^4.14.139",
"@types/node": "^20.0.0",
"@types/nprogress": "^0.2.0",
"@types/prismjs": "^1.26.0",
"@types/progress": "^2.0.5",
"@types/qs": "^6.9.7",
@ -216,7 +217,7 @@
"cross-fetch": "^4.0.0",
"crypto": "^1.0.1",
"dekko": "^0.2.1",
"dumi": "^2.1.23",
"dumi": "^2.2.10",
"duplicate-package-checker-webpack-plugin": "^3.0.0",
"esbuild-loader": "^4.0.0",
"eslint": "^8.40.0",
@ -285,7 +286,7 @@
"react-router-dom": "^6.0.2",
"react-sticky-box": "^2.0.0",
"regenerator-runtime": "^0.14.0",
"remark": "^14.0.1",
"remark": "^15.0.1",
"remark-cli": "^11.0.0",
"remark-lint": "^9.0.0",
"remark-preset-lint-recommended": "^6.0.0",

View File

@ -8,7 +8,10 @@ const output = '.dumi/preset';
// Collect components
const componentNames = globSync(
path.join(process.cwd(), 'components/!(version|icon|col|row)/index.zh-CN.md').split(path.sep).join('/'),
path
.join(process.cwd(), 'components/!(version|icon|col|row)/index.zh-CN.md')
.split(path.sep)
.join('/'),
)
.map((filePath) => filePath.replace(/\\/g, '/').match(/components\/([^/]*)\//)![1])
.filter((name) => name !== 'overview');
@ -133,11 +136,16 @@ const miscKeys = [
let changelogLine = line.trim().replace('- ', '');
changelogLine = changelogLine
.replace(/\[([^\]]+)]\(([^)]+)\)/g, (...match) => {
const [, , ref] = match;
const [, title, ref] = match;
if (ref.includes('/pull/')) {
refs.push(ref);
}
return '';
if (title && (title[0] === '#' || title[0] === '@')) {
return '';
}
return title;
})
.trim();

View File

@ -46,6 +46,7 @@ const DEPRECIATED_VERSION = {
'https://github.com/ant-design/ant-design/issues/43684',
],
'5.8.0': ['https://github.com/ant-design/ant-design/issues/43943'],
'5.9.1': ['https://github.com/ant-design/ant-design/issues/44907'],
} as const;
function matchDeprecated(v: string) {

View File

@ -7,6 +7,7 @@ import { globSync } from 'glob';
import { configureToMatchImageSnapshot } from 'jest-image-snapshot';
import MockDate from 'mockdate';
import ReactDOMServer from 'react-dom/server';
import { App, ConfigProvider, theme } from '../../components';
const toMatchImageSnapshot = configureToMatchImageSnapshot({
@ -77,7 +78,6 @@ export default function imageTest(component: React.ReactElement, options: ImageT
const image = await page.screenshot({
fullPage: !options.onlyViewport,
optimizeForSpeed: true,
});
expect(image).toMatchImageSnapshot();

View File

@ -2,6 +2,7 @@
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@@/*": [".dumi/tmp/*"],
"antd": ["components/index.ts"],
"antd/es/*": ["components/*"],
"antd/lib/*": ["components/*"],
@ -23,5 +24,6 @@
"skipLibCheck": true,
"stripInternal": true
},
"include": [".dumirc.ts", "**/*"],
"exclude": ["node_modules", "lib", "es"]
}