chore: auto merge branches (#42276)

chore: master merge feature
This commit is contained in:
github-actions[bot] 2023-05-12 15:14:26 +00:00 committed by GitHub
commit 59c9119be1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
259 changed files with 38714 additions and 30231 deletions

View File

@ -2,6 +2,7 @@ import type { ReactNode } from 'react';
import React, { useMemo } from 'react';
import type { MenuProps } from 'antd';
import { useFullSidebarData, useSidebarData } from 'dumi';
import { Tag, theme } from 'antd';
import useLocation from './useLocation';
import Link from '../theme/common/Link';
@ -15,6 +16,7 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] =>
const { pathname, search } = useLocation();
const sidebarData = useSidebarData();
const { before, after } = options;
const { token } = theme.useToken();
const menuItems = useMemo<MenuProps['items']>(() => {
const sidebarItems = [...(sidebarData ?? [])];
@ -108,6 +110,11 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] =>
<span className="chinese" key="chinese">
{(item.frontmatter as any).subtitle}
</span>
{(item.frontmatter as any).tag && (
<Tag color="warning" style={{ marginLeft: token.marginXS }}>
{(item.frontmatter as any).tag}
</Tag>
)}
{after}
</Link>
),

View File

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

View File

@ -1,13 +1,13 @@
import React from 'react';
import { FloatButton } from 'antd';
import { FormattedMessage, Link, useLocation } from 'dumi';
import { DarkTheme, CompactTheme } from 'antd-token-previewer/es/icons';
import { BgColorsOutlined } from '@ant-design/icons';
import { FloatButton } from 'antd';
import { CompactTheme, DarkTheme, Motion } from 'antd-token-previewer/es/icons';
import { FormattedMessage, Link, useLocation } from 'dumi';
import React from 'react';
import useSiteToken from '../../../hooks/useSiteToken';
import { getLocalizedPathname, isZhCN } from '../../utils';
import ThemeIcon from './ThemeIcon';
export type ThemeName = 'light' | 'dark' | 'compact';
export type ThemeName = 'light' | 'dark' | 'compact' | 'motion-off';
export type ThemeSwitchProps = {
value?: ThemeName[];
@ -18,6 +18,9 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = (props: ThemeSwitchProps) => {
const { value = ['light'], onChange } = props;
const { token } = useSiteToken();
const { pathname, search } = useLocation();
const isMotionOff = value.includes('motion-off');
return (
<FloatButton.Group trigger="click" icon={<ThemeIcon />}>
<Link
@ -53,6 +56,22 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = (props: ThemeSwitchProps) => {
}}
tooltip={<FormattedMessage id="app.theme.switch.compact" />}
/>
<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.Group>
);
};

View File

@ -9,13 +9,13 @@ import { App, theme as antdTheme } from 'antd';
import type { DirectionType } from 'antd/es/config-provider';
import { createSearchParams, useOutlet, useSearchParams } from 'dumi';
import React, { useCallback, useEffect, useMemo } from 'react';
import useLayoutState from '../../hooks/useLayoutState';
import SiteThemeProvider from '../SiteThemeProvider';
import useLocation from '../../hooks/useLocation';
import type { ThemeName } from '../common/ThemeSwitch';
import ThemeSwitch from '../common/ThemeSwitch';
import type { SiteContextProps } from '../slots/SiteContext';
import SiteContext from '../slots/SiteContext';
import useLayoutState from '../../hooks/useLayoutState';
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][];
type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
@ -42,10 +42,10 @@ const GlobalLayout: React.FC = () => {
const outlet = useOutlet();
const { pathname } = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const [{ theme, direction, isMobile }, setSiteState] = useLayoutState<SiteState>({
const [{ theme = [], direction, isMobile }, setSiteState] = useLayoutState<SiteState>({
isMobile: false,
direction: 'ltr',
theme: ['light'],
theme: ['light', 'motion-off'],
});
const updateSiteConfig = useCallback(
@ -116,6 +116,9 @@ const GlobalLayout: React.FC = () => {
<SiteThemeProvider
theme={{
algorithm: getAlgorithm(theme),
token: {
motion: !theme.includes('motion-off'),
},
}}
>
<App>

View File

@ -3,6 +3,8 @@
"app.theme.switch.default": "Default theme",
"app.theme.switch.dark": "Dark theme",
"app.theme.switch.compact": "Compact theme",
"app.theme.switch.motion.on": "Motion On",
"app.theme.switch.motion.off": "Motion Off",
"app.header.search": "Search...",
"app.header.menu.documentation": "Docs",
"app.header.menu.more": "More",

View File

@ -3,6 +3,8 @@
"app.theme.switch.default": "默认主题",
"app.theme.switch.dark": "暗黑主题",
"app.theme.switch.compact": "紧凑主题",
"app.theme.switch.motion.on": "动画开启",
"app.theme.switch.motion.off": "动画关闭",
"app.header.search": "全文本搜索...",
"app.header.menu.documentation": "文档",
"app.header.menu.more": "更多",

2
.gitignore vendored
View File

@ -64,3 +64,5 @@ __image_snapshots__/
.devcontainer*
.husky/prepare-commit-msg
.eslintcache

View File

@ -19,6 +19,7 @@ exports[`antd exports modules correctly 1`] = `
"Checkbox",
"Col",
"Collapse",
"ColorPicker",
"ConfigProvider",
"DatePicker",
"Descriptions",

View File

@ -78,9 +78,7 @@ export default function genPurePanel<ComponentProps extends BaseProps>(
<ConfigProvider
theme={{
token: {
motionDurationFast: '0.01s',
motionDurationMid: '0.01s',
motionDurationSlow: '0.01s',
motion: false,
},
}}
>
@ -89,7 +87,6 @@ export default function genPurePanel<ComponentProps extends BaseProps>(
style={{
paddingBottom: popupHeight,
position: 'relative',
width: 'fit-content',
minWidth: popupWidth,
}}
>

View File

@ -135,23 +135,27 @@ exports[`renders components/auto-complete/demo/certain-category.tsx extend conte
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
</button>
</span>
@ -1406,23 +1410,27 @@ exports[`renders components/auto-complete/demo/form-debug.tsx extend context cor
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
</button>
</span>
@ -1632,23 +1640,27 @@ exports[`renders components/auto-complete/demo/form-debug.tsx extend context cor
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
</button>
</span>
@ -1677,23 +1689,27 @@ exports[`renders components/auto-complete/demo/form-debug.tsx extend context cor
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
<span>
Search
@ -2193,23 +2209,27 @@ exports[`renders components/auto-complete/demo/uncertain-category.tsx extend con
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
</button>
</span>

View File

@ -108,23 +108,27 @@ exports[`renders components/auto-complete/demo/certain-category.tsx correctly 1`
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
</button>
</span>
@ -791,23 +795,27 @@ exports[`renders components/auto-complete/demo/form-debug.tsx correctly 1`] = `
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
</button>
</span>
@ -946,23 +954,27 @@ exports[`renders components/auto-complete/demo/form-debug.tsx correctly 1`] = `
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
</button>
</span>
@ -979,23 +991,27 @@ exports[`renders components/auto-complete/demo/form-debug.tsx correctly 1`] = `
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
<span>
Search
@ -1112,7 +1128,7 @@ exports[`renders components/auto-complete/demo/render-panel.tsx correctly 1`] =
class="ant-space-item"
>
<div
style="padding-bottom:0;position:relative;width:fit-content;min-width:0"
style="padding-bottom:0;position:relative;min-width:0"
>
<div
class="ant-select ant-select-auto-complete ant-select-single ant-select-show-search"
@ -1259,23 +1275,27 @@ exports[`renders components/auto-complete/demo/uncertain-category.tsx correctly
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
</button>
</span>

View File

@ -11,6 +11,10 @@ import type { BaseSelectRef } from 'rc-select';
import toArray from 'rc-util/lib/Children/toArray';
import omit from 'rc-util/lib/omit';
import * as React from 'react';
import genPurePanel from '../_util/PurePanel';
import { isValidElement } from '../_util/reactNode';
import type { InputStatus } from '../_util/statusUtils';
import warning from '../_util/warning';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import type {
@ -20,10 +24,6 @@ import type {
RefSelectProps,
} from '../select';
import Select from '../select';
import genPurePanel from '../_util/PurePanel';
import { isValidElement } from '../_util/reactNode';
import type { InputStatus } from '../_util/statusUtils';
import warning from '../_util/warning';
const { Option } = Select;
@ -45,6 +45,9 @@ export interface AutoCompleteProps<
popupClassName?: string;
/** @deprecated Please use `popupClassName` instead */
dropdownClassName?: string;
/** @deprecated Please use `popupMatchSelectWidth` instead */
dropdownMatchSelectWidth?: boolean | number;
popupMatchSelectWidth?: boolean | number;
}
function isSelectOptionOrSelectOptGroup(child: any): Boolean {

View File

@ -114,7 +114,7 @@ const InternalAvatar: React.ForwardRefRenderFunction<HTMLSpanElement, AvatarProp
['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].includes(key),
);
const screens = useBreakpoint(needResponsive);
const responsiveSizeStyle: React.CSSProperties = React.useMemo(() => {
const responsiveSizeStyle = React.useMemo<React.CSSProperties>(() => {
if (typeof size !== 'object') {
return {};
}

View File

@ -158,23 +158,27 @@ exports[`renders components/badge/demo/change.tsx extend context correctly 1`] =
type="button"
>
<span
aria-label="minus"
class="anticon anticon-minus"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="minus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="minus"
class="anticon anticon-minus"
role="img"
>
<path
d="M872 474H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="minus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M872 474H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
</span>
</button>
<button
@ -182,29 +186,33 @@ exports[`renders components/badge/demo/change.tsx extend context correctly 1`] =
type="button"
>
<span
aria-label="plus"
class="anticon anticon-plus"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="plus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="plus"
class="anticon anticon-plus"
role="img"
>
<defs>
<style />
</defs>
<path
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="plus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<defs>
<style />
</defs>
<path
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</span>
</span>
</button>
<button
@ -212,23 +220,27 @@ exports[`renders components/badge/demo/change.tsx extend context correctly 1`] =
type="button"
>
<span
aria-label="question"
class="anticon anticon-question"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="question"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="question"
class="anticon anticon-question"
role="img"
>
<path
d="M764 280.9c-14-30.6-33.9-58.1-59.3-81.6C653.1 151.4 584.6 125 512 125s-141.1 26.4-192.7 74.2c-25.4 23.6-45.3 51-59.3 81.7-14.6 32-22 65.9-22 100.9v27c0 6.2 5 11.2 11.2 11.2h54c6.2 0 11.2-5 11.2-11.2v-27c0-99.5 88.6-180.4 197.6-180.4s197.6 80.9 197.6 180.4c0 40.8-14.5 79.2-42 111.2-27.2 31.7-65.6 54.4-108.1 64-24.3 5.5-46.2 19.2-61.7 38.8a110.85 110.85 0 00-23.9 68.6v31.4c0 6.2 5 11.2 11.2 11.2h54c6.2 0 11.2-5 11.2-11.2v-31.4c0-15.7 10.9-29.5 26-32.9 58.4-13.2 111.4-44.7 149.3-88.7 19.1-22.3 34-47.1 44.3-74 10.7-27.9 16.1-57.2 16.1-87 0-35-7.4-69-22-100.9zM512 787c-30.9 0-56 25.1-56 56s25.1 56 56 56 56-25.1 56-56-25.1-56-56-56z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="question"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M764 280.9c-14-30.6-33.9-58.1-59.3-81.6C653.1 151.4 584.6 125 512 125s-141.1 26.4-192.7 74.2c-25.4 23.6-45.3 51-59.3 81.7-14.6 32-22 65.9-22 100.9v27c0 6.2 5 11.2 11.2 11.2h54c6.2 0 11.2-5 11.2-11.2v-27c0-99.5 88.6-180.4 197.6-180.4s197.6 80.9 197.6 180.4c0 40.8-14.5 79.2-42 111.2-27.2 31.7-65.6 54.4-108.1 64-24.3 5.5-46.2 19.2-61.7 38.8a110.85 110.85 0 00-23.9 68.6v31.4c0 6.2 5 11.2 11.2 11.2h54c6.2 0 11.2-5 11.2-11.2v-31.4c0-15.7 10.9-29.5 26-32.9 58.4-13.2 111.4-44.7 149.3-88.7 19.1-22.3 34-47.1 44.3-74 10.7-27.9 16.1-57.2 16.1-87 0-35-7.4-69-22-100.9zM512 787c-30.9 0-56 25.1-56 56s25.1 56 56 56 56-25.1 56-56-25.1-56-56-56z"
/>
</svg>
</span>
</span>
</button>
</div>

View File

@ -158,23 +158,27 @@ exports[`renders components/badge/demo/change.tsx correctly 1`] = `
type="button"
>
<span
aria-label="minus"
class="anticon anticon-minus"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="minus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="minus"
class="anticon anticon-minus"
role="img"
>
<path
d="M872 474H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="minus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M872 474H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
</span>
</button>
<button
@ -182,29 +186,33 @@ exports[`renders components/badge/demo/change.tsx correctly 1`] = `
type="button"
>
<span
aria-label="plus"
class="anticon anticon-plus"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="plus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="plus"
class="anticon anticon-plus"
role="img"
>
<defs>
<style />
</defs>
<path
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="plus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<defs>
<style />
</defs>
<path
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</span>
</span>
</button>
<button
@ -212,23 +220,27 @@ exports[`renders components/badge/demo/change.tsx correctly 1`] = `
type="button"
>
<span
aria-label="question"
class="anticon anticon-question"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="question"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="question"
class="anticon anticon-question"
role="img"
>
<path
d="M764 280.9c-14-30.6-33.9-58.1-59.3-81.6C653.1 151.4 584.6 125 512 125s-141.1 26.4-192.7 74.2c-25.4 23.6-45.3 51-59.3 81.7-14.6 32-22 65.9-22 100.9v27c0 6.2 5 11.2 11.2 11.2h54c6.2 0 11.2-5 11.2-11.2v-27c0-99.5 88.6-180.4 197.6-180.4s197.6 80.9 197.6 180.4c0 40.8-14.5 79.2-42 111.2-27.2 31.7-65.6 54.4-108.1 64-24.3 5.5-46.2 19.2-61.7 38.8a110.85 110.85 0 00-23.9 68.6v31.4c0 6.2 5 11.2 11.2 11.2h54c6.2 0 11.2-5 11.2-11.2v-31.4c0-15.7 10.9-29.5 26-32.9 58.4-13.2 111.4-44.7 149.3-88.7 19.1-22.3 34-47.1 44.3-74 10.7-27.9 16.1-57.2 16.1-87 0-35-7.4-69-22-100.9zM512 787c-30.9 0-56 25.1-56 56s25.1 56 56 56 56-25.1 56-56-25.1-56-56-56z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="question"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M764 280.9c-14-30.6-33.9-58.1-59.3-81.6C653.1 151.4 584.6 125 512 125s-141.1 26.4-192.7 74.2c-25.4 23.6-45.3 51-59.3 81.7-14.6 32-22 65.9-22 100.9v27c0 6.2 5 11.2 11.2 11.2h54c6.2 0 11.2-5 11.2-11.2v-27c0-99.5 88.6-180.4 197.6-180.4s197.6 80.9 197.6 180.4c0 40.8-14.5 79.2-42 111.2-27.2 31.7-65.6 54.4-108.1 64-24.3 5.5-46.2 19.2-61.7 38.8a110.85 110.85 0 00-23.9 68.6v31.4c0 6.2 5 11.2 11.2 11.2h54c6.2 0 11.2-5 11.2-11.2v-31.4c0-15.7 10.9-29.5 26-32.9 58.4-13.2 111.4-44.7 149.3-88.7 19.1-22.3 34-47.1 44.3-74 10.7-27.9 16.1-57.2 16.1-87 0-35-7.4-69-22-100.9zM512 787c-30.9 0-56 25.1-56 56s25.1 56 56 56 56-25.1 56-56-25.1-56-56-56z"
/>
</svg>
</span>
</span>
</button>
</div>

View File

@ -112,121 +112,119 @@ exports[`renders components/breadcrumb/demo/debug-routes.tsx extend context corr
</svg>
</span>
</span>
<div>
<div
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up"
style="opacity: 0;"
<div
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-placement-bottom"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-0"
role="menuitem"
tabindex="-1"
>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-0"
role="menuitem"
tabindex="-1"
<span
class="ant-dropdown-menu-title-content"
>
<span
class="ant-dropdown-menu-title-content"
<a
href="#/home/user/user1"
>
<a
href="#/home/user/user1"
>
User1
</a>
</span>
</li>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-1"
role="menuitem"
tabindex="-1"
>
<span
class="ant-dropdown-menu-title-content"
>
<a
href="#/home/user/user2"
>
User2
</a>
</span>
</li>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</ul>
User1
</a>
</span>
</li>
<div
aria-hidden="true"
style="display: none;"
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
class="ant-tooltip-inner"
role="tooltip"
/>
<div
class="ant-tooltip-content"
</div>
</div>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-1"
role="menuitem"
tabindex="-1"
>
<span
class="ant-dropdown-menu-title-content"
>
<a
href="#/home/user/user2"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
User2
</a>
</span>
</li>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</ul>
<div
aria-hidden="true"
style="display: none;"
>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</div>
@ -304,177 +302,175 @@ exports[`renders components/breadcrumb/demo/overlay.tsx extend context correctly
</svg>
</span>
</span>
<div>
<div
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up"
style="opacity: 0;"
<div
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-dropdown-placement-bottom"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-1"
role="menuitem"
tabindex="-1"
>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-1"
role="menuitem"
tabindex="-1"
<span
class="ant-dropdown-menu-title-content"
>
<span
class="ant-dropdown-menu-title-content"
<a
href="http://www.alipay.com/"
rel="noopener noreferrer"
target="_blank"
>
<a
href="http://www.alipay.com/"
rel="noopener noreferrer"
target="_blank"
>
General
</a>
</span>
</li>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-2"
role="menuitem"
tabindex="-1"
>
<span
class="ant-dropdown-menu-title-content"
>
<a
href="http://www.taobao.com/"
rel="noopener noreferrer"
target="_blank"
>
Layout
</a>
</span>
</li>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-3"
role="menuitem"
tabindex="-1"
>
<span
class="ant-dropdown-menu-title-content"
>
<a
href="http://www.tmall.com/"
rel="noopener noreferrer"
target="_blank"
>
Navigation
</a>
</span>
</li>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</ul>
General
</a>
</span>
</li>
<div
aria-hidden="true"
style="display: none;"
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
class="ant-tooltip-inner"
role="tooltip"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</div>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-2"
role="menuitem"
tabindex="-1"
>
<span
class="ant-dropdown-menu-title-content"
>
<a
href="http://www.taobao.com/"
rel="noopener noreferrer"
target="_blank"
>
Layout
</a>
</span>
</li>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
class="ant-tooltip-inner"
role="tooltip"
/>
<div
class="ant-tooltip-content"
</div>
</div>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-3"
role="menuitem"
tabindex="-1"
>
<span
class="ant-dropdown-menu-title-content"
>
<a
href="http://www.tmall.com/"
rel="noopener noreferrer"
target="_blank"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
Navigation
</a>
</span>
</li>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</ul>
<div
aria-hidden="true"
style="display: none;"
>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-tooltip-arrow"
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</div>

View File

@ -0,0 +1,23 @@
import React, { forwardRef } from 'react';
import classNames from 'classnames';
export type IconWrapperProps = {
prefixCls: string;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
};
const IconWrapper = forwardRef<HTMLSpanElement, IconWrapperProps>((props, ref) => {
const { className, style, children, prefixCls } = props;
const iconWrapperCls = classNames(`${prefixCls}-icon`, className);
return (
<span ref={ref} className={iconWrapperCls} style={style}>
{children}
</span>
);
});
export default IconWrapper;

View File

@ -1,11 +1,34 @@
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
import CSSMotion from 'rc-motion';
import React from 'react';
import React, { forwardRef } from 'react';
import classNames from 'classnames';
import IconWrapper from './IconWrapper';
type InnerLoadingIconProps = {
prefixCls: string;
className?: string;
style?: React.CSSProperties;
iconClassName?: string;
};
const InnerLoadingIcon = forwardRef<HTMLSpanElement, InnerLoadingIconProps>(
({ prefixCls, className, style, iconClassName }, ref) => {
const mergedIconCls = classNames(`${prefixCls}-loading-icon`, className);
return (
<IconWrapper prefixCls={prefixCls} className={mergedIconCls} style={style} ref={ref}>
<LoadingOutlined className={iconClassName} />
</IconWrapper>
);
},
);
export interface LoadingIconProps {
prefixCls: string;
existIcon: boolean;
loading?: boolean | object;
className?: string;
style?: React.CSSProperties;
}
const getCollapsedWidth = (): React.CSSProperties => ({
@ -20,15 +43,17 @@ const getRealWidth = (node: HTMLElement): React.CSSProperties => ({
transform: 'scale(1)',
});
const LoadingIcon: React.FC<LoadingIconProps> = ({ prefixCls, loading, existIcon }) => {
const LoadingIcon: React.FC<LoadingIconProps> = ({
prefixCls,
loading,
existIcon,
className,
style,
}) => {
const visible = !!loading;
if (existIcon) {
return (
<span className={`${prefixCls}-loading-icon`}>
<LoadingOutlined />
</span>
);
return <InnerLoadingIcon prefixCls={prefixCls} className={className} style={style} />;
}
return (
@ -44,10 +69,20 @@ const LoadingIcon: React.FC<LoadingIconProps> = ({ prefixCls, loading, existIcon
onLeaveStart={getRealWidth}
onLeaveActive={getCollapsedWidth}
>
{({ className, style }: { className?: string; style?: React.CSSProperties }, ref: any) => (
<span className={`${prefixCls}-loading-icon`} style={style} ref={ref}>
<LoadingOutlined className={className} />
</span>
{(
{
className: motionCls,
style: motionStyle,
}: { className?: string; style?: React.CSSProperties },
ref: any,
) => (
<InnerLoadingIcon
prefixCls={prefixCls}
className={className}
style={{ ...style, ...motionStyle }}
ref={ref}
iconClassName={motionCls}
/>
)}
</CSSMotion>
);

File diff suppressed because it is too large Load Diff

View File

@ -44,23 +44,27 @@ exports[`Button renders Chinese characters correctly 2`] = `
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
<span>
按钮
@ -104,23 +108,27 @@ exports[`Button renders Chinese characters correctly 4`] = `
type="button"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
<span>
按钮
@ -134,7 +142,7 @@ exports[`Button renders Chinese characters correctly 5`] = `
type="button"
>
<span
class="ant-btn-loading-icon"
class="ant-btn-icon ant-btn-loading-icon"
>
<span
aria-label="loading"
@ -168,7 +176,7 @@ exports[`Button renders Chinese characters correctly 6`] = `
type="button"
>
<span
class="ant-btn-loading-icon"
class="ant-btn-icon ant-btn-loading-icon"
style="width: 0px; opacity: 0; transform: scale(0);"
>
<span
@ -304,6 +312,73 @@ exports[`Button should render empty button without errors 1`] = `
/>
`;
exports[`Button should support custom icon className 1`] = `
<div>
<button
class="ant-btn ant-btn-primary ant-btn-icon-only"
type="button"
>
<span
class="ant-btn-icon custom-icon"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
</button>
</div>
`;
exports[`Button should support custom icon styles 1`] = `
<div>
<button
class="ant-btn ant-btn-primary ant-btn-icon-only"
type="button"
>
<span
class="ant-btn-icon"
style="color: red;"
>
<span
aria-label="search"
class="anticon anticon-search"
role="img"
>
<svg
aria-hidden="true"
data-icon="search"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</span>
</span>
</button>
</div>
`;
exports[`Button should support link button 1`] = `
<a
class="ant-btn ant-btn-default"

View File

@ -161,6 +161,21 @@ describe('Button', () => {
expect(wrapper.container.firstChild).not.toHaveClass('ant-btn-loading');
});
it('should support custom icon className', () => {
const { container } = render(
<Button type="primary" icon={<SearchOutlined />} classNames={{ icon: 'custom-icon' }} />,
);
expect(container.querySelectorAll('.custom-icon').length).toBe(1);
expect(container).toMatchSnapshot();
});
it('should support custom icon styles', () => {
const { container } = render(
<Button type="primary" icon={<SearchOutlined />} styles={{ icon: { color: 'red' } }} />,
);
expect(container).toMatchSnapshot();
});
it('reset when loading back of delay', () => {
jest.useFakeTimers();
const { rerender, container } = render(<Button loading={{ delay: 1000 }} />);

View File

@ -23,6 +23,7 @@ import Group, { GroupSizeContext } from './button-group';
import type { ButtonHTMLType, ButtonShape, ButtonType } from './buttonHelpers';
import { isTwoCNChar, isUnBorderedButtonType, spaceChildren } from './buttonHelpers';
import useStyle from './style';
import IconWrapper from './IconWrapper';
export type LegacyButtonType = ButtonType | 'danger';
@ -48,6 +49,8 @@ export interface BaseButtonProps {
block?: boolean;
children?: React.ReactNode;
[key: `data-${string}`]: string;
classNames?: { icon: string };
styles?: { icon: React.CSSProperties };
}
export type AnchorButtonProps = {
@ -107,6 +110,7 @@ const InternalButton: React.ForwardRefRenderFunction<
danger,
shape = 'default',
size: customizeSize,
styles,
disabled: customDisabled,
className,
rootClassName,
@ -116,6 +120,7 @@ const InternalButton: React.ForwardRefRenderFunction<
block = false,
// React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`.
htmlType = 'button',
classNames: customClassNames,
...rest
} = props;
@ -237,7 +242,9 @@ const InternalButton: React.ForwardRefRenderFunction<
const iconNode =
icon && !innerLoading ? (
icon
<IconWrapper prefixCls={prefixCls} className={customClassNames?.icon} style={styles?.icon}>
{icon}
</IconWrapper>
) : (
<LoadingIcon existIcon={!!icon} prefixCls={prefixCls} loading={!!innerLoading} />
);

View File

@ -55,6 +55,7 @@ Different button styles can be generated by setting Button properties. The recom
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| block | Option to fit button width to its parent width | boolean | false | |
| classNames | Semantic DOM class | Record<SemanticDOM, string> | - | 5.4.0 |
| danger | Set the danger status of button | boolean | false | |
| disabled | Disabled state of button | boolean | false | |
| ghost | Make background transparent and invert text and border colors | boolean | false | |
@ -64,12 +65,19 @@ Different button styles can be generated by setting Button properties. The recom
| loading | Set the loading status of button | boolean \| { delay: number } | false | |
| shape | Can be set button shape | `default` \| `circle` \| `round` | `default` | |
| size | Set the size of button | `large` \| `middle` \| `small` | `middle` | |
| styles | Semantic DOM style | Record<SemanticDOM, CSSProperties> | - | 5.4.0 |
| target | Same as target attribute of a, works when href is specified | string | - | |
| type | Can be set to `primary` `ghost` `dashed` `link` `text` `default` | string | `default` | |
| onClick | Set the handler to handle `click` event | (event: MouseEvent) => void | - | |
It accepts all props which native buttons support.
### `styles` and `classNames` attribute
| Property | Description | Version |
| -------- | ----------------- | ------- |
| icon | set `icon`element | 5.5.0 |
## Design Token
<ComponentTokenTable component="Button"></ComponentTokenTable>

View File

@ -60,6 +60,7 @@ group:
| 属性 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| block | 将按钮宽度调整为其父宽度的选项 | boolean | false | |
| classNames | 语义化结构 class | Record<SemanticDOM, string> | - | 5.4.0 |
| danger | 设置危险按钮 | boolean | false | |
| disabled | 设置按钮失效状态 | boolean | false | |
| ghost | 幽灵属性,使按钮背景透明 | boolean | false | |
@ -69,12 +70,19 @@ group:
| loading | 设置按钮载入状态 | boolean \| { delay: number } | false | |
| shape | 设置按钮形状 | `default` \| `circle` \| `round` | `default` | |
| size | 设置按钮大小 | `large` \| `middle` \| `small` | `middle` | |
| styles | 语义化结构 style | Record<SemanticDOM, CSSProperties> | - | 5.4.0 |
| target | 相当于 a 链接的 target 属性href 存在时生效 | string | - | |
| type | 设置按钮类型 | `primary` \| `ghost` \| `dashed` \| `link` \| `text` \| `default` | `default` | |
| onClick | 点击按钮时的回调 | (event: MouseEvent) => void | - | |
支持原生 button 的其他所有属性。
### `styles``classNames` 属性
| 名称 | 说明 | 版本 |
| ---- | ------------ | ----- |
| icon | 设置图标元素 | 5.5.0 |
## Design Token
<ComponentTokenTable component="Button"></ComponentTokenTable>

View File

@ -1,30 +1,31 @@
import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import genGroupStyle from './group';
import { genFocusStyle } from '../../style';
import { genCompactItemStyle } from '../../style/compact-item';
import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import genGroupStyle from './group';
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {}
export interface ButtonToken extends FullToken<'Button'> {
// FIXME: should be removed
colorOutlineDefault: string;
buttonPaddingHorizontal: number;
buttonIconOnlyFontSize: number;
buttonFontWeight: number;
}
// ============================== Shared ==============================
const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSSObject => {
const { componentCls, iconCls } = token;
const { componentCls, iconCls, buttonFontWeight } = token;
return {
[componentCls]: {
outline: 'none',
position: 'relative',
display: 'inline-block',
fontWeight: 400,
fontWeight: buttonFontWeight,
whiteSpace: 'nowrap',
textAlign: 'center',
backgroundImage: 'none',
@ -41,8 +42,19 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
display: 'inline-block',
},
[`${componentCls}-icon`]: {
lineHeight: 0,
},
// Leave a space between icon and text.
[`> ${iconCls} + span, > span + ${iconCls}`]: {
[`&:not(${componentCls}-icon-only) > ${componentCls}-icon`]: {
[`&${componentCls}-loading-icon, &:not(:last-child)`]: {
marginInlineEnd: token.marginXS,
},
},
// Special case for anticon after children
[`> span + ${iconCls}`]: {
marginInlineStart: token.marginXS,
},
@ -397,13 +409,13 @@ const genTypeButtonStyle: GenerateStyle<ButtonToken> = (token) => {
const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSSInterpolation => {
const {
componentCls,
iconCls,
controlHeight,
fontSize,
lineHeight,
lineWidth,
borderRadius,
buttonPaddingHorizontal,
iconCls,
} = token;
const paddingVertical = Math.max(0, (controlHeight - fontSize * lineHeight) / 2 - lineWidth);
@ -427,8 +439,8 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
[`&${componentCls}-round`]: {
width: 'auto',
},
'> span': {
transform: 'scale(1.143)', // 14px -> 16px
[iconCls]: {
fontSize: token.buttonIconOnlyFontSize,
},
},
@ -441,10 +453,6 @@ const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls: string = ''): CSS
[`${componentCls}-loading-icon`]: {
transition: `width ${token.motionDurationSlow} ${token.motionEaseInOut}, opacity ${token.motionDurationSlow} ${token.motionEaseInOut}`,
},
[`&:not(${iconOnlyCls}) ${componentCls}-loading-icon > ${iconCls}`]: {
marginInlineEnd: token.marginXS,
},
},
},
@ -466,6 +474,7 @@ const genSizeSmallButtonStyle: GenerateStyle<ButtonToken> = (token) => {
padding: token.paddingXS,
buttonPaddingHorizontal: 8, // Fixed padding
borderRadius: token.borderRadiusSM,
buttonIconOnlyFontSize: token.fontSizeLG - 2,
});
return genSizeButtonStyle(smallToken, `${token.componentCls}-sm`);
@ -476,6 +485,7 @@ const genSizeLargeButtonStyle: GenerateStyle<ButtonToken> = (token) => {
controlHeight: token.controlHeightLG,
fontSize: token.fontSizeLG,
borderRadius: token.borderRadiusLG,
buttonIconOnlyFontSize: token.fontSizeLG + 2,
});
return genSizeButtonStyle(largeToken, `${token.componentCls}-lg`);
@ -499,6 +509,8 @@ export default genComponentStyleHook('Button', (token) => {
const buttonToken = mergeToken<ButtonToken>(token, {
colorOutlineDefault: controlTmpOutline,
buttonPaddingHorizontal: paddingContentHorizontal,
buttonIconOnlyFontSize: token.fontSizeLG,
buttonFontWeight: 400,
});
return [

View File

@ -2837,3 +2837,949 @@ exports[`Calendar rtl render component should be rendered correctly in RTL direc
</div>
</div>
`;
exports[`Calendar support Calendar.generateCalendar 1`] = `
<div
class="ant-picker-calendar ant-picker-calendar-full"
>
<div
class="ant-picker-calendar-header"
>
<div
class="ant-select ant-picker-calendar-year-select ant-select-single ant-select-show-arrow"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="rc_select_TEST_OR_SSR_list_0"
aria-autocomplete="list"
aria-controls="rc_select_TEST_OR_SSR_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
readonly=""
role="combobox"
style="opacity: 0;"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-item"
title="2000"
>
2000
</span>
</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-picker-calendar-month-select ant-select-single ant-select-show-arrow"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="rc_select_TEST_OR_SSR_list_0"
aria-autocomplete="list"
aria-controls="rc_select_TEST_OR_SSR_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
readonly=""
role="combobox"
style="opacity: 0;"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-item"
title="Jan"
>
Jan
</span>
</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-radio-group ant-radio-group-outline ant-picker-calendar-mode-switch"
>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
>
<span
class="ant-radio-button ant-radio-button-checked"
>
<input
checked=""
class="ant-radio-button-input"
type="radio"
value="month"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Month
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="year"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Year
</span>
</label>
</div>
</div>
<div
class="ant-picker-panel"
tabindex="0"
>
<div
class="ant-picker-date-panel"
>
<div
class="ant-picker-body"
>
<table
class="ant-picker-content"
>
<thead>
<tr>
<th>
Su
</th>
<th>
Mo
</th>
<th>
Tu
</th>
<th>
We
</th>
<th>
Th
</th>
<th>
Fr
</th>
<th>
Sa
</th>
</tr>
</thead>
<tbody>
<tr>
<td
class="ant-picker-cell"
title="1999-12-26"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
26
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="1999-12-27"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
27
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="1999-12-28"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
28
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="1999-12-29"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
29
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="1999-12-30"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
30
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-end"
title="1999-12-31"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
31
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-start ant-picker-cell-in-view ant-picker-cell-today ant-picker-cell-selected"
title="2000-01-01"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date ant-picker-calendar-date-today"
>
<div
class="ant-picker-calendar-date-value"
>
01
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
<tr>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-02"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
02
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-03"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
03
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-04"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
04
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-05"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
05
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-06"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
06
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-07"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
07
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-08"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
08
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
<tr>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-09"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
09
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-10"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
10
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-11"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
11
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-12"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
12
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-13"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
13
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-14"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
14
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-15"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
15
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
<tr>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-16"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
16
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-17"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
17
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-18"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
18
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-19"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
19
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-20"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
20
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-21"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
21
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-22"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
22
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
<tr>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-23"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
23
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-24"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
24
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-25"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
25
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-26"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
26
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-27"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
27
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-28"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
28
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-29"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
29
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
<tr>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-01-30"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
30
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-end ant-picker-cell-in-view"
title="2000-01-31"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
31
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-start"
title="2000-02-01"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
01
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-02-02"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
02
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-02-03"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
03
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-02-04"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
04
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-02-05"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
05
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
`;

View File

@ -538,4 +538,14 @@ describe('Calendar', () => {
expect(container.querySelector('.bar')).toBeTruthy();
errSpy.mockRestore();
});
it('support Calendar.generateCalendar', () => {
jest.useFakeTimers().setSystemTime(new Date('2000-01-01'));
const MyCalendar = Calendar.generateCalendar(dayjsGenerateConfig);
const { container } = render(<MyCalendar />);
expect(container.firstChild).toMatchSnapshot();
jest.useRealTimers();
});
});

View File

@ -5,5 +5,11 @@ import generateCalendar from './generateCalendar';
const Calendar = generateCalendar<Dayjs>(dayjsGenerateConfig);
export type CalendarType = typeof Calendar & {
generateCalendar: typeof generateCalendar;
};
(Calendar as CalendarType).generateCalendar = generateCalendar;
export type { CalendarProps };
export default Calendar;
export default Calendar as CalendarType;

View File

@ -836,24 +836,22 @@ Array [
</svg>
</span>
</button>
<div>
<div
class="ant-tabs-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-tabs-dropdown-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<ul
aria-label="expanded dropdown"
class="ant-tabs-dropdown-menu ant-tabs-dropdown-menu-root ant-tabs-dropdown-menu-vertical"
data-menu-list="true"
id="rc-tabs-test-more-popup"
role="listbox"
tabindex="-1"
/>
<div
class="ant-tabs-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up"
style="opacity: 0;"
>
<ul
aria-label="expanded dropdown"
class="ant-tabs-dropdown-menu ant-tabs-dropdown-menu-root ant-tabs-dropdown-menu-vertical"
data-menu-list="true"
id="rc-tabs-test-more-popup"
role="listbox"
tabindex="-1"
/>
<div
aria-hidden="true"
style="display: none;"
/>
</div>
aria-hidden="true"
style="display: none;"
/>
</div>
</div>
</div>
@ -996,24 +994,22 @@ Array [
</svg>
</span>
</button>
<div>
<div
class="ant-tabs-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-tabs-dropdown-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<ul
aria-label="expanded dropdown"
class="ant-tabs-dropdown-menu ant-tabs-dropdown-menu-root ant-tabs-dropdown-menu-vertical"
data-menu-list="true"
id="rc-tabs-test-more-popup"
role="listbox"
tabindex="-1"
/>
<div
class="ant-tabs-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up"
style="opacity: 0;"
>
<ul
aria-label="expanded dropdown"
class="ant-tabs-dropdown-menu ant-tabs-dropdown-menu-root ant-tabs-dropdown-menu-vertical"
data-menu-list="true"
id="rc-tabs-test-more-popup"
role="listbox"
tabindex="-1"
/>
<div
aria-hidden="true"
style="display: none;"
/>
</div>
aria-hidden="true"
style="display: none;"
/>
</div>
</div>
<div

View File

@ -848,7 +848,7 @@ Array [
exports[`renders components/cascader/demo/render-panel.tsx correctly 1`] = `
<div
style="padding-bottom:0;position:relative;width:fit-content;min-width:0"
style="padding-bottom:0;position:relative;min-width:0"
>
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"

View File

@ -681,4 +681,38 @@ describe('Cascader', () => {
expect(selectedValue!.join(',')).toBe('zhejiang');
});
});
it('should be correct expression with disableCheckbox', () => {
const { container } = render(
<Cascader
multiple
options={[
{
label: '台湾',
value: 'tw',
children: [
{
label: '福建',
value: 'fj',
disableCheckbox: true,
},
{
label: '兰州',
value: 'lz',
},
{ label: '北京', value: 'bj' },
],
},
]}
/>,
);
fireEvent.mouseDown(container.querySelector('.ant-select-selector')!);
// disabled className
fireEvent.click(container.querySelector('.ant-cascader-menu-item')!);
expect(container.querySelectorAll('.ant-cascader-checkbox-disabled')).toHaveLength(1);
// Check all children except disableCheckbox When the parent checkbox is checked
expect(container.querySelectorAll('.ant-cascader-checkbox')).toHaveLength(4);
fireEvent.click(container.querySelector('.ant-cascader-checkbox')!);
expect(container.querySelectorAll('.ant-cascader-checkbox-checked')).toHaveLength(3);
});
});

View File

@ -1,7 +1,7 @@
## zh-CN
一次性选择多个选项。
一次性选择多个选项。通过添加 `disableCheckbox` 属性,选择具体某一个`checkbox`禁用 。可以通过类名修改禁用的样式。
## en-US
Select multiple options
Select multiple options. Disable the `checkbox` by adding the `disableCheckbox` property and selecting a specific item. The style of the disable can be modified by the className.

View File

@ -1,10 +1,11 @@
import React from 'react';
import { Cascader } from 'antd';
import React from 'react';
interface Option {
value: string | number;
label: string;
children?: Option[];
disableCheckbox?: boolean;
}
const options: Option[] = [
@ -26,6 +27,7 @@ const options: Option[] = [
{
label: 'Toy Fish',
value: 'fish',
disableCheckbox: true,
},
{
label: 'Toy Cards',

View File

@ -159,8 +159,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
getPrefixCls,
renderEmpty,
direction: rootDirection,
// virtual,
// dropdownMatchSelectWidth,
popupOverflow,
} = React.useContext(ConfigContext);
const mergedDirection = direction || rootDirection;
@ -273,7 +272,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
return isRtl ? 'bottomRight' : 'bottomLeft';
}, [placement, isRtl]);
const mergedBuiltinPlacements = useBuiltinPlacements(builtinPlacements);
const mergedBuiltinPlacements = useBuiltinPlacements(builtinPlacements, popupOverflow);
// ==================== Render =====================
const renderNode = (

View File

@ -0,0 +1,168 @@
import type {
ColorPickerPanelProps as RcColorPickerPanelProps,
TriggerPlacement,
TriggerType,
} from '@rc-component/color-picker';
import classNames from 'classnames';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import type { CSSProperties } from 'react';
import React, { useContext, useState } from 'react';
import type { ConfigConsumerProps } from '../config-provider/context';
import { ConfigContext } from '../config-provider/context';
import type { PopoverProps } from '../popover';
import Popover from '../popover';
import theme from '../theme';
import ColorPickerPanel from './ColorPickerPanel';
import type { Color } from './color';
import ColorTrigger from './components/ColorTrigger';
import useColorState from './hooks/useColorState';
import type { ColorFormat, ColorPickerBaseProps, PresetsItem } from './interface';
import useStyle from './style/index';
import { customizePrefixCls, generateColor } from './util';
import genPurePanel from '../_util/PurePanel';
export interface ColorPickerProps
extends Omit<
RcColorPickerPanelProps,
'onChange' | 'arrow' | 'value' | 'defaultValue' | 'children' | 'panelRender'
> {
value?: Color | string;
defaultValue?: Color | string;
children?: React.ReactElement;
open?: boolean;
disabled?: boolean;
placement?: TriggerPlacement;
trigger?: TriggerType;
format?: keyof typeof ColorFormat;
allowClear?: boolean;
presets?: PresetsItem[];
arrow?: boolean | { pointAtCenter: boolean };
prefixCls?: string;
className?: string;
style?: CSSProperties;
styles?: { popup?: CSSProperties };
rootClassName?: string;
onOpenChange?: (open: boolean) => void;
onFormatChange?: (format: ColorFormat) => void;
onChange?: (value: Color, hex: string) => void;
getPopupContainer?: PopoverProps['getPopupContainer'];
}
type CompoundedComponent = React.FC<ColorPickerProps> & {
_InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel;
};
const ColorPicker: CompoundedComponent = (props) => {
const {
value,
defaultValue,
format,
allowClear = false,
presets,
children,
trigger = 'click',
open,
disabled,
placement = 'bottomLeft',
arrow = true,
style,
className,
rootClassName,
styles,
onFormatChange,
onChange,
onOpenChange,
getPopupContainer,
} = props;
const { getPrefixCls, direction } = useContext<ConfigConsumerProps>(ConfigContext);
const { token } = theme.useToken();
const [colorValue, setColorValue] = useColorState(token.colorPrimary, {
value,
defaultValue,
});
const [popupOpen, setPopupOpen] = useMergedState(false, {
value: open,
postState: (openData) => !disabled && openData,
onChange: onOpenChange,
});
const [clearColor, setClearColor] = useState(false);
const prefixCls = getPrefixCls('color-picker', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls);
const mergeRootCls = classNames(rootClassName, {
[`${prefixCls}-rtl`]: direction,
});
const mergeCls = classNames(mergeRootCls, className, hashId);
const handleChange = (data: Color) => {
const color: Color = generateColor(data);
if (clearColor && color.toHsb().a > 0) {
setClearColor(false);
}
if (!value) {
setColorValue(color);
}
onChange?.(color, color.toHexString());
};
const handleClear = (clear: boolean) => {
setClearColor(clear);
};
const popoverProps: PopoverProps = {
open: popupOpen,
trigger,
placement,
arrow,
rootClassName,
getPopupContainer,
};
const colorBaseProps: ColorPickerBaseProps = {
prefixCls,
color: colorValue,
allowClear,
clearColor,
disabled,
presets,
format,
onFormatChange,
};
return wrapSSR(
<Popover
style={styles?.popup}
onOpenChange={setPopupOpen}
content={
<ColorPickerPanel {...colorBaseProps} onChange={handleChange} onClear={handleClear} />
}
overlayClassName={prefixCls}
{...popoverProps}
>
{children || (
<ColorTrigger
open={popupOpen}
className={mergeCls}
style={style}
color={colorValue}
prefixCls={prefixCls}
clearColor={clearColor}
disabled={disabled}
/>
)}
</Popover>,
);
};
if (process.env.NODE_ENV !== 'production') {
ColorPicker.displayName = 'ColorPicker';
}
const PurePanel = genPurePanel(ColorPicker, 'color-picker', (prefixCls) => prefixCls);
ColorPicker._InternalPanelDoNotUseOrYouWillBeFired = PurePanel;
export default ColorPicker;

View File

@ -0,0 +1,63 @@
import { ColorPickerPanel as RcColorPickerPanel } from '@rc-component/color-picker';
import type { FC } from 'react';
import React from 'react';
import Divider from '../divider';
import type { Color } from './color';
import ColorClear from './components/ColorClear';
import ColorInput from './components/ColorInput';
import ColorPresets from './components/ColorPresets';
import type { ColorPickerBaseProps } from './interface';
interface ColorPickerPanelProps extends ColorPickerBaseProps {
onChange?: (value?: Color) => void;
onClear?: (clear?: boolean) => void;
}
const ColorPickerPanel: FC<ColorPickerPanelProps> = (props) => {
const { prefixCls, allowClear, presets, onChange, onClear, color, ...injectProps } = props;
const colorPickerPanelPrefixCls = `${prefixCls}-inner-panel`;
const extraPanelRender = (panel: React.ReactElement) => (
<div className={colorPickerPanelPrefixCls}>
{allowClear && (
<ColorClear
prefixCls={prefixCls}
value={color}
onChange={(clearColor) => {
onChange?.(clearColor);
onClear?.(true);
}}
{...injectProps}
/>
)}
{panel}
<ColorInput
value={color}
onChange={(value) => onChange?.(value)}
prefixCls={prefixCls}
{...injectProps}
/>
{Array.isArray(presets) && (
<>
<Divider className={`${colorPickerPanelPrefixCls}-divider`} />
<ColorPresets
value={color}
presets={presets}
onChange={(value) => onChange?.(value)}
prefixCls={prefixCls}
/>
</>
)}
</div>
);
return (
<RcColorPickerPanel
prefixCls={prefixCls}
value={color?.toHsb()}
onChange={onChange}
panelRender={extraPanelRender}
/>
);
};
export default ColorPickerPanel;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,248 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders components/color-picker/demo/allowClear.tsx correctly 1`] = `
<div
class="ant-color-picker-trigger"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22, 119, 255)"
/>
</div>
</div>
`;
exports[`renders components/color-picker/demo/base.tsx correctly 1`] = `
<div
class="ant-color-picker-trigger"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22, 119, 255)"
/>
</div>
</div>
`;
exports[`renders components/color-picker/demo/disabled.tsx correctly 1`] = `
<div
class="ant-color-picker-trigger ant-color-picker-trigger-disabled"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22, 119, 255)"
/>
</div>
</div>
`;
exports[`renders components/color-picker/demo/format.tsx correctly 1`] = `
<div
class="ant-space ant-space-vertical"
style="display:flex"
>
<div
class="ant-space-item"
style="margin-bottom:16px"
>
<div
class="ant-row ant-row-middle"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<div
class="ant-col"
>
<div
class="ant-color-picker-trigger"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22, 119, 255)"
/>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-col"
>
HEX:
<span>
#1677ff
</span>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
style="margin-bottom:16px"
>
<div
class="ant-row ant-row-middle"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<div
class="ant-col"
>
<div
class="ant-color-picker-trigger"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(23, 120, 255)"
/>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-col"
>
HSB:
<span>
hsb(215,91%,100%)
</span>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-row ant-row-middle"
>
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<div
class="ant-col"
>
<div
class="ant-color-picker-trigger"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22, 119, 255)"
/>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-col"
>
RGB:
<span>
rgb(22,119,255)
</span>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders components/color-picker/demo/presets.tsx correctly 1`] = `
<div
class="ant-color-picker-trigger"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22, 119, 255)"
/>
</div>
</div>
`;
exports[`renders components/color-picker/demo/pure-panel.tsx correctly 1`] = `
<div
style="padding-bottom:0;position:relative;min-width:0"
>
<div
class="ant-color-picker-trigger"
style="margin:0"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22, 119, 255)"
/>
</div>
</div>
</div>
`;
exports[`renders components/color-picker/demo/trigger.tsx correctly 1`] = `
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<div
style="width:20px;height:20px;border-radius:4px;background:#1677ff"
/>
</div>
<div
class="ant-space-item"
>
<span>
#1677ff
</span>
</div>
</div>
`;

View File

@ -0,0 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ColorPicker Should disabled work 1`] = `
<div>
<div
class="ant-color-picker-trigger ant-color-picker-trigger-disabled"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background: rgb(22, 119, 255);"
/>
</div>
</div>
</div>
`;
exports[`ColorPicker Should render trigger work 1`] = `
<div>
<div
class="trigger"
/>
</div>
`;
exports[`ColorPicker rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-color-picker-trigger ant-color-picker-rtl"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background: rgb(22, 119, 255);"
/>
</div>
</div>
`;

View File

@ -0,0 +1,96 @@
import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import ColorAlphaInput from '../components/ColorAlphaInput';
import ColorHexInput from '../components/ColorHexInput';
import ColorHsbInput from '../components/ColorHsbInput';
import ColorRgbInput from '../components/ColorRgbInput';
import ColorSteppers from '../components/ColorSteppers';
describe('ColorPicker Components test', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('Should ColorSteppers work correct', () => {
const handleAlphaChange = jest.fn();
const { container } = render(<ColorSteppers prefixCls="test" onChange={handleAlphaChange} />);
expect(container.querySelector('.test-steppers')).toBeTruthy();
fireEvent.change(container.querySelector('.test-steppers input')!, {
target: { value: 1 },
});
expect(container.querySelector('.test-steppers input')?.getAttribute('value')).toEqual('1');
expect(handleAlphaChange).toHaveBeenCalledTimes(1);
});
it('Should ColorAlphaInput work correct', () => {
const handleAlphaChange = jest.fn();
const { container } = render(<ColorAlphaInput prefixCls="test" onChange={handleAlphaChange} />);
expect(container.querySelector('.test-alpha-input')).toBeTruthy();
fireEvent.change(container.querySelector('.test-alpha-input input')!, {
target: { value: 1 },
});
expect(container.querySelector('.test-alpha-input input')?.getAttribute('value')).toEqual('1%');
expect(handleAlphaChange).toHaveBeenCalledTimes(1);
});
it('Should ColorHexInput work correct', () => {
const handleAlphaChange = jest.fn();
const { container } = render(<ColorHexInput prefixCls="test" onChange={handleAlphaChange} />);
expect(container.querySelector('.test-hex-input')).toBeTruthy();
fireEvent.change(container.querySelector('.test-hex-input input')!, {
target: { value: 631515 },
});
expect(container.querySelector('.test-hex-input input')?.getAttribute('value')).toEqual(
'631515',
);
expect(handleAlphaChange).toHaveBeenCalledTimes(1);
});
it('Should ColorHsbInput work correct', () => {
const handleAlphaChange = jest.fn();
const { container } = render(<ColorHsbInput prefixCls="test" onChange={handleAlphaChange} />);
expect(container.querySelector('.test-hsb-input')).toBeTruthy();
const hsbInputEls = container.querySelectorAll('.test-hsb-input input');
fireEvent.change(hsbInputEls[0], {
target: { value: 139 },
});
expect(hsbInputEls[0]?.getAttribute('value')).toEqual('139');
fireEvent.change(hsbInputEls[1], {
target: { value: 78 },
});
expect(hsbInputEls[1]?.getAttribute('value')).toEqual('78%');
fireEvent.change(hsbInputEls[2], {
target: { value: 39 },
});
expect(hsbInputEls[2]?.getAttribute('value')).toEqual('39%');
expect(handleAlphaChange).toHaveBeenCalledTimes(3);
});
it('Should ColorRgbInput work correct', () => {
const handleAlphaChange = jest.fn();
const { container } = render(<ColorRgbInput prefixCls="test" onChange={handleAlphaChange} />);
expect(container.querySelector('.test-rgb-input')).toBeTruthy();
const rgbInputEls = container.querySelectorAll('.test-rgb-input input');
fireEvent.change(rgbInputEls[0], {
target: { value: 99 },
});
expect(rgbInputEls[0]?.getAttribute('value')).toEqual('99');
fireEvent.change(rgbInputEls[1], {
target: { value: 21 },
});
expect(rgbInputEls[1]?.getAttribute('value')).toEqual('21');
fireEvent.change(rgbInputEls[2], {
target: { value: 21 },
});
expect(rgbInputEls[2]?.getAttribute('value')).toEqual('21');
expect(handleAlphaChange).toHaveBeenCalledTimes(3);
});
});

View File

@ -0,0 +1,3 @@
import { extendTest } from '../../../tests/shared/demoTest';
extendTest('color-picker');

View File

@ -0,0 +1,3 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('color-picker');

View File

@ -0,0 +1,5 @@
import { imageDemoTest } from '../../../tests/shared/imageTest';
describe('ColorPicker image', () => {
imageDemoTest('color-picker');
});

View File

@ -0,0 +1,249 @@
import { fireEvent, render } from '@testing-library/react';
import React, { useMemo, useState } from 'react';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { waitFakeTimer } from '../../../tests/utils';
import ColorPicker from '../ColorPicker';
import type { Color } from '../color';
describe('ColorPicker', () => {
mountTest(ColorPicker);
rtlTest(ColorPicker);
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('Should component render correct', () => {
const { container } = render(<ColorPicker />);
expect(container.querySelector('.ant-color-picker-trigger')).toBeTruthy();
});
it('Should component defaultValue work', () => {
const { container } = render(<ColorPicker defaultValue="#000000" />);
expect(
container.querySelector('.ant-color-picker-color-block-inner')?.getAttribute('style'),
).toEqual('background: rgb(0, 0, 0);');
});
it('Should component custom trigger work', async () => {
const App = () => {
const [color, setColor] = useState<Color | string>('hsb(215, 91%, 100%)');
const colorString = useMemo(
() => (typeof color === 'string' ? color : color.toHsbString()),
[color],
);
return (
<ColorPicker value={color} onChange={setColor} format="hsb">
<span className="custom-trigger">{colorString}</span>
</ColorPicker>
);
};
const { container } = render(<App />);
expect(container.querySelector('.custom-trigger')).toBeTruthy();
fireEvent.click(container.querySelector('.custom-trigger')!);
await waitFakeTimer();
expect(container.querySelector('.ant-color-picker')).toBeTruthy();
const hsbInputEls = container.querySelectorAll('.ant-color-picker-hsb-input input');
fireEvent.change(hsbInputEls[0], {
target: { value: 0 },
});
fireEvent.change(hsbInputEls[1], {
target: { value: 78 },
});
fireEvent.change(hsbInputEls[2], {
target: { value: 39 },
});
expect(container.querySelector('.custom-trigger')?.innerHTML).toEqual('hsb(0, 78%, 39%)');
});
it('Should popup open work', async () => {
const { container } = render(<ColorPicker />);
fireEvent.click(container.querySelector('.ant-color-picker-trigger')!);
await waitFakeTimer();
expect(container.querySelector('.ant-color-picker')).toBeTruthy();
fireEvent.click(container.querySelector('.ant-color-picker-trigger')!);
await waitFakeTimer();
expect(container.querySelector('.ant-popover-hidden')).toBeTruthy();
});
it('Should disabled work', async () => {
const { container } = render(<ColorPicker disabled />);
expect(container.querySelector('.ant-color-picker-trigger-disabled')).toBeTruthy();
expect(container).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-color-picker-trigger')!);
await waitFakeTimer();
expect(container.querySelector('.ant-color-picker')).toBeFalsy();
});
it('Should allowClear work', async () => {
const { container } = render(<ColorPicker allowClear />);
fireEvent.click(container.querySelector('.ant-color-picker-trigger')!);
await waitFakeTimer();
expect(container.querySelector('.ant-color-picker-clear')).toBeTruthy();
fireEvent.click(container.querySelector('.ant-color-picker-clear')!);
expect(
container.querySelector('.ant-color-picker-alpha-input input')?.getAttribute('value'),
).toEqual('0%');
expect(
container.querySelector('.ant-color-picker-trigger .ant-color-picker-clear'),
).toBeTruthy();
fireEvent.change(container.querySelector('.ant-color-picker-alpha-input input')!, {
target: { value: 1 },
});
expect(
container.querySelector('.ant-color-picker-trigger .ant-color-picker-clear'),
).toBeFalsy();
});
it('Should render trigger work', async () => {
const { container } = render(
<ColorPicker>
<div className="trigger" />
</ColorPicker>,
);
expect(container.querySelector('.trigger')).toBeTruthy();
expect(container).toMatchSnapshot();
fireEvent.click(container.querySelector('.trigger')!);
await waitFakeTimer();
expect(container.querySelector('.ant-color-picker')).toBeTruthy();
fireEvent.click(container.querySelector('.trigger')!);
await waitFakeTimer();
expect(container.querySelector('.ant-popover-hidden')).toBeTruthy();
});
it('Should preset color work', async () => {
const handleColorChange = jest.fn();
const { container } = render(
<ColorPicker
onChange={handleColorChange}
presets={[
{
label: 'Recommended',
colors: [
'#000000',
'#000000E0',
'#000000A6',
'#00000073',
'#00000040',
'#00000026',
'#0000001A',
'#00000012',
'#0000000A',
'#00000005',
],
},
{
label: 'Recent',
colors: [],
},
]}
/>,
);
fireEvent.click(container.querySelector('.ant-color-picker-trigger')!);
await waitFakeTimer();
const presetsColors = container
.querySelector('.ant-collapse-content')
?.querySelectorAll('.ant-color-picker-presets-color')!;
expect(container.querySelector('.ant-color-picker-presets')).toBeTruthy();
expect(presetsColors.length).toBe(10);
expect(
container
.querySelectorAll('.ant-collapse-content')[1]
.querySelector('.ant-color-picker-presets-empty'),
).toBeTruthy();
fireEvent.click(presetsColors[0]);
expect(
presetsColors[0].classList.contains('ant-color-picker-presets-color-bright'),
).toBeFalsy();
expect(
container.querySelector('.ant-color-picker-hex-input input')?.getAttribute('value'),
).toEqual('000000');
fireEvent.click(presetsColors[9]);
expect(
presetsColors[9].classList.contains('ant-color-picker-presets-color-bright'),
).toBeTruthy();
expect(
container.querySelector('.ant-color-picker-hex-input input')?.getAttribute('value'),
).toEqual('000000');
expect(
container.querySelector('.ant-color-picker-alpha-input input')?.getAttribute('value'),
).toEqual('2%');
expect(handleColorChange).toHaveBeenCalledTimes(2);
});
it('Should format change work', async () => {
const { container } = render(<ColorPicker />);
fireEvent.click(container.querySelector('.ant-color-picker-trigger')!);
await waitFakeTimer();
expect(container.querySelector('.ant-color-picker-hex-input')).toBeTruthy();
fireEvent.mouseDown(
container.querySelector('.ant-color-picker-format-select .ant-select-selector')!,
);
await waitFakeTimer();
fireEvent.click(container.querySelector('.ant-select-item[title="HSB"]')!);
await waitFakeTimer();
expect(container.querySelector('.ant-color-picker-hsb-input')).toBeTruthy();
fireEvent.mouseDown(
container.querySelector('.ant-color-picker-format-select .ant-select-selector')!,
);
await waitFakeTimer();
fireEvent.click(container.querySelector('.ant-select-item[title="RGB"]')!);
await waitFakeTimer();
expect(container.querySelector('.ant-color-picker-rgb-input')).toBeTruthy();
});
it('Should hex input work', async () => {
const { container } = render(<ColorPicker open format="hex" />);
fireEvent.change(container.querySelector('.ant-color-picker-hex-input input')!, {
target: { value: 631515 },
});
expect(
container.querySelector('.ant-color-picker-color-block-inner')?.getAttribute('style'),
).toEqual('background: rgb(99, 21, 21);');
});
it('Should rgb input work', async () => {
const { container } = render(<ColorPicker open format="rgb" />);
const rgbInputEls = container.querySelectorAll('.ant-color-picker-rgb-input input');
fireEvent.change(rgbInputEls[0], {
target: { value: 99 },
});
fireEvent.change(rgbInputEls[1], {
target: { value: 21 },
});
fireEvent.change(rgbInputEls[2], {
target: { value: 21 },
});
expect(
container.querySelector('.ant-color-picker-color-block-inner')?.getAttribute('style'),
).toEqual('background: rgb(99, 21, 21);');
});
it('Should hsb input work', async () => {
const { container } = render(<ColorPicker open format="hsb" />);
const hsbInputEls = container.querySelectorAll('.ant-color-picker-hsb-input input');
fireEvent.change(hsbInputEls[0], {
target: { value: 0 },
});
fireEvent.change(hsbInputEls[1], {
target: { value: 78 },
});
fireEvent.change(hsbInputEls[2], {
target: { value: 39 },
});
expect(
container.querySelector('.ant-color-picker-color-block-inner')?.getAttribute('style'),
).toEqual('background: rgb(99, 22, 22);');
});
});

View File

@ -0,0 +1,45 @@
/* eslint-disable class-methods-use-this */
import type { ColorGenInput } from '@rc-component/color-picker';
import { Color as RcColor } from '@rc-component/color-picker';
import { getHex } from './util';
export interface Color
extends Pick<
RcColor,
'toHsb' | 'toHsbString' | 'toHex' | 'toHexString' | 'toRgb' | 'toRgbString'
> {}
export class ColorFactory {
/** Original Color object */
private metaColor: RcColor;
constructor(color: ColorGenInput<Color>) {
this.metaColor = new RcColor(color as ColorGenInput);
}
toHsb() {
return this.metaColor.toHsb();
}
toHsbString() {
return this.metaColor.toHsbString();
}
toHex() {
return getHex(this.toHexString(), this.metaColor.getAlpha() < 1);
}
toHexString() {
return this.metaColor.getAlpha() === 1
? this.metaColor.toHexString()
: this.metaColor.toHex8String();
}
toRgb() {
return this.metaColor.toRgb();
}
toRgbString() {
return this.metaColor.toRgbString();
}
}

View File

@ -0,0 +1,45 @@
import type { FC } from 'react';
import React, { useEffect, useState } from 'react';
import type { Color } from '../color';
import type { ColorPickerBaseProps } from '../interface';
import { generateColor, getAlphaColor } from '../util';
import ColorSteppers from './ColorSteppers';
interface ColorAlphaInputProps extends Pick<ColorPickerBaseProps, 'prefixCls'> {
value?: Color;
onChange?: (value: Color) => void;
}
const ColorAlphaInput: FC<ColorAlphaInputProps> = ({ prefixCls, value, onChange }) => {
const colorAlphaInputPrefixCls = `${prefixCls}-alpha-input`;
const [alphaValue, setAlphaValue] = useState(generateColor(value || '#000'));
// Update step value
useEffect(() => {
if (value) {
setAlphaValue(value);
}
}, [value]);
const handleAlphaChange = (step: number) => {
const hsba = alphaValue.toHsb();
hsba.a = (step || 0) / 100;
const genColor = generateColor(hsba);
if (!value) {
setAlphaValue(genColor);
}
onChange?.(genColor);
};
return (
<ColorSteppers
value={getAlphaColor(alphaValue)}
prefixCls={prefixCls}
formatter={(step) => `${step}%`}
className={colorAlphaInputPrefixCls}
onChange={handleAlphaChange}
/>
);
};
export default ColorAlphaInput;

View File

@ -0,0 +1,24 @@
import type { FC } from 'react';
import React from 'react';
import type { Color } from '../color';
import type { ColorPickerBaseProps } from '../interface';
import { generateColor } from '../util';
interface ColorClearProps extends Pick<ColorPickerBaseProps, 'prefixCls'> {
value?: Color;
onChange?: (value: Color) => void;
}
const ColorClear: FC<ColorClearProps> = ({ prefixCls, value, onChange }) => {
const handleClick = () => {
if (value) {
const hsba = value.toHsb();
hsba.a = 0;
const genColor = generateColor(hsba);
onChange?.(genColor);
}
};
return <div className={`${prefixCls}-clear`} onClick={handleClick} />;
};
export default ColorClear;

View File

@ -0,0 +1,47 @@
import type { FC } from 'react';
import React, { useEffect, useState } from 'react';
import Input from '../../input';
import type { Color } from '../color';
import type { ColorPickerBaseProps } from '../interface';
import { generateColor, toHexFormat } from '../util';
interface ColorHexInputProps extends Pick<ColorPickerBaseProps, 'prefixCls'> {
value?: Color;
onChange?: (value: Color) => void;
}
const hexReg = /(^#[\da-f]{6}$)|(^#[\da-f]{8}$)/i;
const isHexString = (hex?: string) => hexReg.test(`#${hex}`);
const ColorHexInput: FC<ColorHexInputProps> = ({ prefixCls, value, onChange }) => {
const colorHexInputPrefixCls = `${prefixCls}-hex-input`;
const [hexValue, setHexValue] = useState(value?.toHex());
// Update step value
useEffect(() => {
const hex = value?.toHex();
if (isHexString(hex) && value) {
setHexValue(toHexFormat(hex));
}
}, [value]);
const handleHexChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const originValue = e.target.value;
setHexValue(toHexFormat(originValue));
if (isHexString(toHexFormat(originValue, true))) {
onChange?.(generateColor(originValue));
}
};
return (
<Input
className={colorHexInputPrefixCls}
value={hexValue?.toUpperCase()}
prefix="#"
onChange={handleHexChange}
size="small"
/>
);
};
export default ColorHexInput;

View File

@ -0,0 +1,69 @@
import type { HSB } from '@rc-component/color-picker';
import { getRoundNumber } from '@rc-component/color-picker/lib/util';
import type { FC } from 'react';
import React, { useEffect, useState } from 'react';
import type { Color } from '../color';
import type { ColorPickerBaseProps } from '../interface';
import { generateColor } from '../util';
import ColorSteppers from './ColorSteppers';
interface ColorHsbInputProps extends Pick<ColorPickerBaseProps, 'prefixCls'> {
value?: Color;
onChange?: (value: Color) => void;
}
const ColorHsbInput: FC<ColorHsbInputProps> = ({ prefixCls, value, onChange }) => {
const colorHsbInputPrefixCls = `${prefixCls}-hsb-input`;
const [hsbValue, setHsbValue] = useState(generateColor(value || '#000'));
// Update step value
useEffect(() => {
if (value) {
setHsbValue(value);
}
}, [value]);
const handleHsbChange = (step: number, type: keyof HSB) => {
const hsb = hsbValue.toHsb();
hsb[type] = type === 'h' ? step : (step || 0) / 100;
const genColor = generateColor(hsb);
if (!value) {
setHsbValue(genColor);
}
onChange?.(genColor);
};
return (
<div className={colorHsbInputPrefixCls}>
<ColorSteppers
max={360}
min={0}
value={Number(hsbValue.toHsb().h)}
prefixCls={prefixCls}
className={colorHsbInputPrefixCls}
formatter={(step) => getRoundNumber(step || 0).toString()}
onChange={(step) => handleHsbChange(Number(step), 'h')}
/>
<ColorSteppers
max={100}
min={0}
value={Number(hsbValue.toHsb().s) * 100}
prefixCls={prefixCls}
className={colorHsbInputPrefixCls}
formatter={(step) => `${getRoundNumber(step || 0)}%`}
onChange={(step) => handleHsbChange(Number(step), 's')}
/>
<ColorSteppers
max={100}
min={0}
value={Number(hsbValue.toHsb().b) * 100}
prefixCls={prefixCls}
className={colorHsbInputPrefixCls}
formatter={(step) => `${getRoundNumber(step || 0)}%`}
onChange={(step) => handleHsbChange(Number(step), 'b')}
/>
</div>
);
};
export default ColorHsbInput;

View File

@ -0,0 +1,75 @@
import type { FC } from 'react';
import React from 'react';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import Select from '../../select';
import type { ColorPickerBaseProps } from '../interface';
import { ColorFormat } from '../interface';
import type { Color } from '../color';
import ColorAlphaInput from './ColorAlphaInput';
import ColorHexInput from './ColorHexInput';
import ColorHsbInput from './ColorHsbInput';
import ColorRgbInput from './ColorRgbInput';
interface ColorInputProps
extends Pick<ColorPickerBaseProps, 'prefixCls' | 'format' | 'onFormatChange'> {
value?: Color;
onChange?: (value: Color) => void;
}
const ColorInput: FC<ColorInputProps> = (props) => {
const { prefixCls, format, onFormatChange, value, onChange } = props;
const [colorFormat, setColorFormat] = useMergedState('hex', {
value: format,
onChange: onFormatChange,
});
const colorInputPrefixCls = `${prefixCls}-input`;
const handleFormatChange = (newFormat: ColorFormat) => {
setColorFormat(newFormat);
};
const steppersRender = () => {
switch (colorFormat) {
case ColorFormat.hsb:
return <ColorHsbInput value={value} onChange={onChange} prefixCls={prefixCls} />;
case ColorFormat.rgb:
return <ColorRgbInput value={value} onChange={onChange} prefixCls={prefixCls} />;
case ColorFormat.hex:
default:
return <ColorHexInput value={value} onChange={onChange} prefixCls={prefixCls} />;
}
};
return (
<div className={`${colorInputPrefixCls}-container`}>
<Select
value={colorFormat}
bordered={false}
getPopupContainer={(current) => current}
popupMatchSelectWidth={68}
placement="bottomRight"
onChange={handleFormatChange}
className={`${prefixCls}-format-select`}
size="small"
options={[
{
label: ColorFormat.hex.toLocaleUpperCase(),
value: ColorFormat.hex,
},
{
label: ColorFormat.hsb.toLocaleUpperCase(),
value: ColorFormat.hsb,
},
{
label: ColorFormat.rgb.toLocaleUpperCase(),
value: ColorFormat.rgb,
},
]}
/>
<div className={colorInputPrefixCls}>{steppersRender()}</div>
<ColorAlphaInput prefixCls={prefixCls} value={value} onChange={onChange} />
</div>
);
};
export default ColorInput;

View File

@ -0,0 +1,84 @@
import classNames from 'classnames';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import type { FC } from 'react';
import React, { useMemo } from 'react';
import { ColorBlock } from '@rc-component/color-picker';
import Collapse from '../../collapse';
import { useLocale } from '../../locale';
import type { Color } from '../color';
import type { ColorPickerBaseProps, PresetsItem } from '../interface';
import { generateColor } from '../util';
const { Panel } = Collapse;
interface ColorPresetsProps extends Pick<ColorPickerBaseProps, 'prefixCls'> {
presets: PresetsItem[];
value?: Color;
onChange?: (value: Color) => void;
}
const genPresetColor = (list: PresetsItem[]) =>
list.map((value) => {
value.colors = value.colors.map((color) => generateColor(color));
return value;
});
const isBright = (value: Color) => {
const { r, g, b, a } = value.toRgb();
if (a <= 0.5) {
return true;
}
return r * 0.299 + g * 0.587 + b * 0.114 > 192;
};
const ColorPresets: FC<ColorPresetsProps> = ({ prefixCls, presets, value: color, onChange }) => {
const [locale] = useLocale('ColorPicker');
const [presetsValue] = useMergedState(genPresetColor(presets), {
value: genPresetColor(presets),
postState: (item) => genPresetColor(item),
});
const colorPresetsPrefixCls = `${prefixCls}-presets`;
const activeKey = useMemo(
() => presetsValue.map((preset) => `panel-${preset.label}`),
[presetsValue],
);
const handleClick = (colorValue: Color) => {
onChange?.(colorValue);
};
return (
<div className={colorPresetsPrefixCls}>
<Collapse defaultActiveKey={activeKey} ghost>
{presetsValue.map((preset) => (
<Panel
header={<div className={`${colorPresetsPrefixCls}-label`}>{preset?.label}</div>}
key={`panel-${preset?.label}`}
>
<div className={`${colorPresetsPrefixCls}-items`}>
{Array.isArray(preset?.colors) && preset?.colors.length > 0 ? (
preset.colors.map((presetColor: Color) => (
<ColorBlock
key={`preset-${presetColor.toHexString()}`}
color={generateColor(presetColor).toRgbString()}
prefixCls={prefixCls}
className={classNames(`${colorPresetsPrefixCls}-color`, {
[`${colorPresetsPrefixCls}-color-checked`]:
presetColor.toHexString() === color?.toHexString(),
[`${colorPresetsPrefixCls}-color-bright`]: isBright(presetColor),
})}
onClick={() => handleClick(presetColor)}
/>
))
) : (
<span className={`${colorPresetsPrefixCls}-empty`}>{locale.presetEmpty}</span>
)}
</div>
</Panel>
))}
</Collapse>
</div>
);
};
export default ColorPresets;

View File

@ -0,0 +1,65 @@
import type { RGB } from '@rc-component/color-picker';
import type { FC } from 'react';
import React, { useEffect, useState } from 'react';
import type { Color } from '../color';
import type { ColorPickerBaseProps } from '../interface';
import { generateColor } from '../util';
import ColorSteppers from './ColorSteppers';
interface ColorRgbInputProps extends Pick<ColorPickerBaseProps, 'prefixCls'> {
value?: Color;
onChange?: (value: Color) => void;
}
const ColorRgbInput: FC<ColorRgbInputProps> = ({ prefixCls, value, onChange }) => {
const colorRgbInputPrefixCls = `${prefixCls}-rgb-input`;
const [rgbValue, setRgbValue] = useState(generateColor(value || '#000'));
// Update step value
useEffect(() => {
if (value) {
setRgbValue(value);
}
}, [value]);
const handleRgbChange = (step: number | null, type: keyof RGB) => {
const rgb = rgbValue.toRgb();
rgb[type] = step || 0;
const genColor = generateColor(rgb);
if (!value) {
setRgbValue(genColor);
}
onChange?.(genColor);
};
return (
<div className={colorRgbInputPrefixCls}>
<ColorSteppers
max={255}
min={0}
value={Number(rgbValue.toRgb().r)}
prefixCls={prefixCls}
className={colorRgbInputPrefixCls}
onChange={(step) => handleRgbChange(Number(step), 'r')}
/>
<ColorSteppers
max={255}
min={0}
value={Number(rgbValue.toRgb().g)}
prefixCls={prefixCls}
className={colorRgbInputPrefixCls}
onChange={(step) => handleRgbChange(Number(step), 'g')}
/>
<ColorSteppers
max={255}
min={0}
value={Number(rgbValue.toRgb().b)}
prefixCls={prefixCls}
className={colorRgbInputPrefixCls}
onChange={(step) => handleRgbChange(Number(step), 'b')}
/>
</div>
);
};
export default ColorRgbInput;

View File

@ -0,0 +1,55 @@
import classNames from 'classnames';
import type { FC } from 'react';
import React, { useEffect, useState } from 'react';
import type { InputNumberProps } from '../../input-number';
import InputNumber from '../../input-number';
import type { ColorPickerBaseProps } from '../interface';
interface ColorSteppersProps extends Pick<ColorPickerBaseProps, 'prefixCls'> {
value?: number;
min?: number;
max?: number;
onChange?: (value: number | null) => void;
className?: string;
prefix?: (prefixCls: string) => React.ReactNode;
formatter?: InputNumberProps<number>['formatter'];
}
const ColorSteppers: FC<ColorSteppersProps> = ({
prefixCls,
min = 0,
max = 100,
value,
onChange,
className,
formatter,
}) => {
const colorSteppersPrefixCls = `${prefixCls}-steppers`;
const [stepValue, setStepValue] = useState(value);
// Update step value
useEffect(() => {
if (!Number.isNaN(value)) {
setStepValue(value);
}
}, [value]);
return (
<InputNumber
className={classNames(colorSteppersPrefixCls, className)}
min={min}
max={max}
value={stepValue}
formatter={formatter}
size="small"
onChange={(step) => {
if (!value) {
setStepValue(step || 0);
}
onChange?.(step);
}}
/>
);
};
export default ColorSteppers;

View File

@ -0,0 +1,46 @@
import classNames from 'classnames';
import type { CSSProperties, MouseEventHandler } from 'react';
import React, { forwardRef, useMemo } from 'react';
import { ColorBlock } from '@rc-component/color-picker';
import type { ColorPickerBaseProps } from '../interface';
import ColorClear from './ColorClear';
interface colorTriggerProps
extends Pick<ColorPickerBaseProps, 'prefixCls' | 'clearColor' | 'disabled'> {
color: Exclude<ColorPickerBaseProps['color'], undefined>;
open?: boolean;
className?: string;
style?: CSSProperties;
onClick?: MouseEventHandler<HTMLDivElement>;
onMouseEnter?: MouseEventHandler<HTMLDivElement>;
onMouseLeave?: MouseEventHandler<HTMLDivElement>;
}
const ColorTrigger = forwardRef<HTMLDivElement, colorTriggerProps>((props, ref) => {
const { color, prefixCls, open, clearColor, disabled, className, ...rest } = props;
const colorTriggerPrefixCls = `${prefixCls}-trigger`;
const containerRender = useMemo(
() =>
clearColor ? (
<ColorClear prefixCls={prefixCls} />
) : (
<ColorBlock color={color.toRgbString()} prefixCls={prefixCls} />
),
[color, clearColor],
);
return (
<div
ref={ref}
className={classNames(colorTriggerPrefixCls, className, {
[`${colorTriggerPrefixCls}-active`]: open,
[`${colorTriggerPrefixCls}-disabled`]: disabled,
})}
{...rest}
>
{containerRender}
</div>
);
});
export default ColorTrigger;

View File

@ -0,0 +1,7 @@
## zh-CN
清除已选择的颜色。
## en-US
Clear Color.

View File

@ -0,0 +1,4 @@
import { ColorPicker } from 'antd';
import React from 'react';
export default () => <ColorPicker allowClear />;

View File

@ -0,0 +1,7 @@
## zh-CN
最简单的使用方法。
## en-US
Basic Usage.

View File

@ -0,0 +1,10 @@
import { ColorPicker, theme } from 'antd';
import type { Color } from 'antd/lib/color-picker';
import React, { useState } from 'react';
export default () => {
const { token } = theme.useToken();
const [color, setColor] = useState<Color | string>(token.colorPrimary);
return <ColorPicker value={color} onChange={setColor} />;
};

View File

@ -0,0 +1,7 @@
## zh-CN
设置为禁用状态。
## en-US
Set to disabled state.

View File

@ -0,0 +1,4 @@
import { ColorPicker } from 'antd';
import React from 'react';
export default () => <ColorPicker disabled />;

View File

@ -0,0 +1,7 @@
## zh-CN
编码格式,支持`HEX`、`HSB`、`RGB`。
## en-US
Encoding formats, support `HEX`, `HSB`, `RGB`.

View File

@ -0,0 +1,57 @@
import { Col, ColorPicker, Row, Space } from 'antd';
import type { Color } from 'antd/lib/color-picker';
import React, { useMemo, useState } from 'react';
export default () => {
const [colorHex, setColorHex] = useState<Color | string>('#1677ff');
const [colorHsb, setColorHsb] = useState<Color | string>('hsb(215,91%,100%)');
const [colorRgb, setColorRgb] = useState<Color | string>('rgb(22,119,255)');
const hexString = useMemo(
() => (typeof colorHex === 'string' ? colorHex : colorHex.toHexString()),
[colorHex],
);
const hsbString = useMemo(
() => (typeof colorHsb === 'string' ? colorHsb : colorHsb.toHsbString()),
[colorHsb],
);
const rgbString = useMemo(
() => (typeof colorRgb === 'string' ? colorRgb : colorRgb.toRgbString()),
[colorRgb],
);
return (
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
<Row align="middle">
<Space>
<Col>
<ColorPicker format="hex" value={colorHex} onChange={setColorHex} />
</Col>
<Col>
HEX: <span>{hexString}</span>
</Col>
</Space>
</Row>
<Row align="middle">
<Space>
<Col>
<ColorPicker format="hsb" value={colorHsb} onChange={setColorHsb} />
</Col>
<Col>
HSB: <span>{hsbString}</span>
</Col>
</Space>
</Row>
<Row align="middle">
<Space>
<Col>
<ColorPicker format="rgb" value={colorRgb} onChange={setColorRgb} />
</Col>
<Col>
RGB: <span>{rgbString}</span>
</Col>
</Space>
</Row>
</Space>
);
};

View File

@ -0,0 +1,7 @@
## zh-CN
设置颜色选择器的预设颜色。
## en-US
Set the presets color of the color picker.

View File

@ -0,0 +1,48 @@
import { ColorPicker } from 'antd';
import React from 'react';
export default () => (
<ColorPicker
presets={[
{
label: 'Recommended',
colors: [
'#000000',
'#000000E0',
'#000000A6',
'#00000073',
'#00000040',
'#00000026',
'#0000001A',
'#00000012',
'#0000000A',
'#00000005',
'#F5222D',
'#FA8C16',
'#FADB14',
'#8BBB11',
'#52C41A',
'#13A8A8',
'#1677FF',
'#2F54EB',
'#722ED1',
'#EB2F96',
'#F5222D4D',
'#FA8C164D',
'#FADB144D',
'#8BBB114D',
'#52C41A4D',
'#13A8A84D',
'#1677FF4D',
'#2F54EB4D',
'#722ED14D',
'#EB2F964D',
],
},
{
label: 'Recent',
colors: [],
},
]}
/>
);

View File

@ -0,0 +1,7 @@
## zh-CN
Pure Panel
## en-US
Pure Panel

View File

@ -0,0 +1,12 @@
import { ColorPicker, theme } from 'antd';
import type { Color } from 'antd/lib/color-picker';
import React, { useState } from 'react';
const PureRenderColorPicker = ColorPicker._InternalPanelDoNotUseOrYouWillBeFired;
export default () => {
const { token } = theme.useToken();
const [color, setColor] = useState<Color | string>(token.colorPrimary);
return <PureRenderColorPicker value={color} onChange={setColor} />;
};

View File

@ -0,0 +1,7 @@
## zh-CN
自定义颜色面板的触发器。
## en-US
Triggers for customizing color panels.

View File

@ -0,0 +1,28 @@
import { ColorPicker, Space, theme } from 'antd';
import type { Color } from 'antd/lib/color-picker';
import React, { useMemo, useState } from 'react';
export default () => {
const { token } = theme.useToken();
const [color, setColor] = useState<Color | string>(token.colorPrimary);
const genColor = useMemo(
() => (typeof color === 'string' ? color : color.toHexString()),
[color],
);
return (
<ColorPicker value={color} onChange={setColor}>
<Space>
<div
style={{
width: 20,
height: 20,
borderRadius: 4,
background: genColor,
}}
/>
<span>{genColor}</span>
</Space>
</ColorPicker>
);
};

View File

@ -0,0 +1,39 @@
import { useEffect, useState } from 'react';
import type { Color } from '../color';
import { generateColor } from '../util';
function hasValue(value: Color | string | undefined) {
return value !== undefined;
}
const useColorState = (
defaultStateValue: Color | string,
option: {
defaultValue?: Color | string;
value?: Color | string;
},
): [Color, React.Dispatch<React.SetStateAction<Color>>] => {
const { defaultValue, value } = option;
const [colorValue, setColorValue] = useState(() => {
let mergeState;
if (hasValue(value)) {
mergeState = value;
} else if (hasValue(defaultValue)) {
mergeState = defaultValue;
} else {
mergeState = defaultStateValue;
}
const genColor = generateColor(mergeState || '');
return genColor;
});
useEffect(() => {
if (value) {
setColorValue(generateColor(value));
}
}, [value]);
return [colorValue, setColorValue];
};
export default useColorState;

View File

@ -0,0 +1,68 @@
---
category: Components
title: ColorPicker
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*PpY4RYNM8UcAAAAAAAAAAAAADrJ8AQ/original
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*EHL-QYJofZsAAAAAAAAAAAAADrJ8AQ/original
tag: New
demo:
cols: 2
group:
title: Data Entry
---
Components providing color selection
## When To Use
Used when the user needs to customize the color selection
## Examples
<!-- prettier-ignore -->
<code src="./demo/base.tsx">Basic Usage</code>
<code src="./demo/disabled.tsx" debug>Disable</code>
<code src="./demo/allowClear.tsx">Clear Color</code>
<code src="./demo/trigger.tsx">Custom Trigger</code>
<code src="./demo/format.tsx">Color Format</code>
<code src="./demo/presets.tsx">Preset Colors</code>
<code src="./demo/pure-panel.tsx" debug>Pure Render</code>
## API
> This component is available since antd@5.5.0
<!-- prettier-ignore -->
| Property | Description | Type | Default |
| :-- | :-- | :-- | :-- |
| format | Format of color | `rgb` \| `hex` \| `hsb` | `hex` |
| onFormatChange | Callback when `format` is changed | `(format: 'hex' \| 'rgb' \| 'hsb') => void` | - |
| value | Value of color | string \| `Color` | - |
| defaultValue | Default value of color | string \| `Color` | - |
| onChange | Callback when `value` is changed | `(value: Color, hex: string) => void` | - |
| allowClear | Allow clearing color selected | boolean | false |
| presets | Preset colors | `{ label: ReactNode, colors: Array<string \| Color> }[]` | - |
| children | Trigger of ColorPicker | ReactElement | - |
| trigger | ColorPicker trigger mode | `hover` \| `click` | `click` |
| open | Whether to show popup | boolean | - |
| onOpenChange | Callback when `open` is changed | `(open: boolean) => void` | - |
| disabled | Disable ColorPicker | boolean | - |
| placement | Placement of popup | `top` \| `topLeft` \| `topRight` \| `bottom` \| `bottomLeft` \| `bottomRight` | `bottomLeft` |
| arrow | Configuration for popup arrow | boolean | `{ pointAtCenter: boolean }` | - |
### Color
<!-- prettier-ignore -->
| Property | Description | Type | Default |
| :-- | :-- | :-- | :-- |
| toHex | Convert to `hex` format characters | `() => string` | - |
| toHexString | Convert to `hex` format color string | `() => string` | - |
| toHsb | Convert to `hsb` object | `() => ({ h: number, s: number, b: number, a number })` | - |
| toHsbString | Convert to `hsb` format color string | `() => string` | - |
| toRgb | Convert to `rgb` object | `() => ({ r: number, g: number, b: number, a number })` | - |
| toRgbString | Convert to `rgb` format color string | `() => string` | - |
## FAQ
### Questions about color assignment
The value of the color selector supports both string color values and selector-generated `Color` objects. However, since there is a precision error when converting color strings of different formats to each other, it is recommended to use selector-generated `Color` objects for assignment operations in controlled scenarios, so that the precision problem can be avoided and the values are guaranteed to be accurate and the selector can work as expected.

View File

@ -0,0 +1,6 @@
import ColorPicker from './ColorPicker';
export type { Color } from './color';
export type { ColorPickerProps } from './ColorPicker';
export default ColorPicker;

View File

@ -0,0 +1,69 @@
---
category: Components
subtitle: 颜色选择器
title: ColorPicker
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*PpY4RYNM8UcAAAAAAAAAAAAADrJ8AQ/original
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*EHL-QYJofZsAAAAAAAAAAAAADrJ8AQ/original
tag: 新
demo:
cols: 2
group:
title: 数据录入
---
提供颜色选取的组件
## 何时使用
当用户需要自定义颜色选择的时候使用。
## 代码演示
<!-- prettier-ignore -->
<code src="./demo/base.tsx">基本使用</code>
<code src="./demo/disabled.tsx" debug>禁用</code>
<code src="./demo/allowClear.tsx">清除颜色</code>
<code src="./demo/trigger.tsx">自定义触发器</code>
<code src="./demo/format.tsx">颜色编码</code>
<code src="./demo/presets.tsx">预设颜色</code>
<code src="./demo/pure-panel.tsx" debug>Pure Render</code>
## API
> 自 `antd@5.5.0` 版本开始提供该组件。
<!-- prettier-ignore -->
| 参数 | 说明 | 类型 | 默认值 |
| :-- | :-- | :-- | :-- |
| format | 颜色格式 | `rgb` \| `hex` \| `hsb` | `hex` |
| onFormatChange | 颜色格式变化的回调 | `(format: 'hex' \| 'rgb' \| 'hsb') => void` | - |
| value | 颜色的值 | string \| `Color` | - |
| defaultValue | 颜色默认的值 | string \| `Color` | - |
| onChange | 颜色变化的回调 | `(value: Color, hex: string) => void` | - |
| allowClear | 允许清除选择的颜色 | boolean | false |
| presets | 预设的颜色 | `{ label: ReactNode, colors: Array<string \| Color> }[]` | - |
| children | 颜色选择器的触发器 | ReactElement | - |
| trigger | 颜色选择器的触发模式 | `hover` \| `click` | `click` |
| open | 是否显示弹出窗口 | boolean | - |
| onOpenChange | 当 `open` 被改变时的回调 | `(open: boolean) => void` | - |
| disabled | 禁用颜色选择器 | boolean | - |
| placement | 弹出窗口的位置 | `top` \| `topLeft` \| `topRight` \| `bottom` \| `bottomLeft` \| `bottomRight` | `bottomLeft` |
| arrow | 配置弹出的箭头 | boolean | `{ pointAtCenter: boolean }` | - |
### Color
<!-- prettier-ignore -->
| 参数 | 说明 | 类型 | 默认值 |
| :-- | :-- | :-- | :-- |
| toHex | 转换成 `hex` 格式字符 | `() => string` | - |
| toHexString | 转换成 `hex` 格式颜色字符串 | `() => string` | - |
| toHsb | 转换成 `hsb` 对象 | `() => ({ h: number, s: number, b: number, a number })` | - |
| toHsbString | 转换成 `hsb` 格式颜色字符串 | `() => string` | - |
| toRgb | 转换成 `rgb` 对象 | `() => ({ r: number, g: number, b: number, a number })` | - |
| toRgbString | 转换成 `rgb` 格式颜色字符串 | `() => string` | - |
## FAQ
### 关于颜色赋值的问题
颜色选择器的值同时支持字符串色值和选择器生成的 `Color` 对象,但由于不同格式的颜色字符串互相转换会有精度误差问题,所以受控场景推荐使用选择器生成的 `Color` 对象来进行赋值操作,这样可以避免精度问题,保证取值是精准的,选择器也可以按照预期工作。

View File

@ -0,0 +1,22 @@
import type { ReactNode } from 'react';
import type { ColorPickerProps } from './ColorPicker';
import type { Color } from './color';
export enum ColorFormat {
hex = 'hex',
rgb = 'rgb',
hsb = 'hsb',
}
export type PresetsItem = { label: ReactNode; colors: Array<string | Color> };
export interface ColorPickerBaseProps {
color?: Color;
prefixCls: string;
format?: keyof typeof ColorFormat;
allowClear?: boolean;
clearColor?: boolean;
disabled?: boolean;
presets?: PresetsItem[];
onFormatChange?: ColorPickerProps['onFormatChange'];
}

View File

@ -0,0 +1,27 @@
import type { CSSObject } from '@ant-design/cssinjs';
import type { ColorPickerToken } from './index';
const genColorBlockStyle = (token: ColorPickerToken, size: number): CSSObject => {
const { componentCls, borderRadiusSM, colorPickerInsetShadow, lineWidth, colorFillSecondary } =
token;
return {
[`${componentCls}-color-block`]: {
position: 'relative',
borderRadius: borderRadiusSM,
width: size,
height: size,
boxShadow: colorPickerInsetShadow,
backgroundSize: '100%',
backgroundImage:
'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAFpJREFUWAntljEKADAIA23p6v//qQ+wfUEcCu1yriEgp0FHRJSJcnehmmWm1Dv/lO4HIg1AAAKjTqm03ea88zMCCEDgO4HV5bS757f+7wRoAAIQ4B9gByAAgQ3pfiDmXmAeEwAAAABJRU5ErkJggg==")',
[`${componentCls}-color-block-inner`]: {
width: '100%',
height: '100%',
border: `${lineWidth}px solid ${colorFillSecondary}`,
borderRadius: 'inherit',
},
},
};
};
export default genColorBlockStyle;

View File

@ -0,0 +1,153 @@
import type { CSSObject } from '@ant-design/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import genPickerStyle from './picker';
import genInputStyle from './input';
import genPresetsStyle from './presets';
import genColorBlockStyle from './color-block';
export interface ComponentToken {}
export interface ColorPickerToken extends FullToken<'ColorPicker'> {
colorPickerWidth: number;
colorPickerInsetShadow: string;
colorPickerHandlerSize: number;
colorPickerHandlerSizeSM: number;
colorPickerSliderHeight: number;
colorPickerPreviewSize: number;
colorPickerAlphaInputWidth: number;
colorPickerInputNumberHandleWidth: number;
colorPickerPresetColorSize: number;
}
export const genActiveStyle = (token: ColorPickerToken) => ({
boxShadow: `0 0 0 ${token.controlOutlineWidth}px ${token.controlOutline}`,
borderInlineEndWidth: token.lineWidth,
outline: 0,
});
const genClearStyle = (token: ColorPickerToken, size: number): CSSObject => {
const { componentCls, borderRadiusSM, lineWidth, colorSplit, red6 } = token;
return {
[`${componentCls}-clear`]: {
width: size,
height: size,
borderRadius: borderRadiusSM,
border: `${lineWidth}px solid ${colorSplit}`,
position: 'relative',
cursor: 'pointer',
overflow: 'hidden',
'&::after': {
content: '""',
position: 'absolute',
insetInlineEnd: lineWidth,
top: 0,
display: 'block',
width: 40, // maximum
height: 2, // fixed
transformOrigin: 'right',
transform: 'rotate(-45deg)',
backgroundColor: red6,
},
},
};
};
const genColorPickerStyle: GenerateStyle<ColorPickerToken> = (token) => {
const {
componentCls,
colorPickerWidth,
colorPrimary,
motionDurationMid,
colorBgElevated,
colorTextDisabled,
colorBgContainerDisabled,
borderRadius,
marginXS,
marginSM,
controlHeight,
controlHeightSM,
colorBgTextActive,
colorPickerPresetColorSize,
lineWidth,
colorBorder,
} = token;
return [
{
[componentCls]: {
[`${componentCls}-panel`]: {
display: 'flex',
flexDirection: 'column',
width: colorPickerWidth,
[`${componentCls}-inner-panel`]: {
[`${componentCls}-clear`]: {
marginInlineStart: 'auto',
marginBottom: marginXS,
},
'&-divider': {
margin: `${marginSM}px 0 ${marginXS}px`,
},
},
...genPickerStyle(token),
...genInputStyle(token),
...genPresetsStyle(token),
...genClearStyle(token, colorPickerPresetColorSize),
},
'&-trigger': {
width: controlHeight,
height: controlHeight,
borderRadius,
border: `${lineWidth}px solid ${colorBorder}`,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: `all ${motionDurationMid}`,
background: colorBgElevated,
'&-active': {
...genActiveStyle(token),
borderColor: colorPrimary,
},
'&:hover': {
borderColor: colorPrimary,
},
'&-disabled': {
color: colorTextDisabled,
background: colorBgContainerDisabled,
cursor: 'not-allowed',
'&:hover': {
borderColor: colorBgTextActive,
},
},
...genClearStyle(token, controlHeightSM),
...genColorBlockStyle(token, controlHeightSM),
},
},
},
];
};
export default genComponentStyleHook('ColorPicker', (token) => {
const { colorTextQuaternary, marginSM } = token;
const colorPickerSliderHeight = 8;
const ColorPickerToken = mergeToken<ColorPickerToken>(token, {
colorPickerWidth: 234,
colorPickerHandlerSize: 16,
colorPickerHandlerSizeSM: 12,
colorPickerAlphaInputWidth: 44,
colorPickerInputNumberHandleWidth: 16,
colorPickerPresetColorSize: 18,
colorPickerInsetShadow: `inset 0 0 1px 0 ${colorTextQuaternary}`,
colorPickerSliderHeight,
colorPickerPreviewSize: colorPickerSliderHeight * 2 + marginSM,
});
return [genColorPickerStyle(ColorPickerToken)];
});

View File

@ -0,0 +1,99 @@
import type { CSSObject } from '@ant-design/cssinjs';
import type { GenerateStyle } from '../../theme/internal';
import type { ColorPickerToken } from './index';
const genInputStyle: GenerateStyle<ColorPickerToken, CSSObject> = (token) => {
const {
componentCls,
antCls,
fontSizeSM,
lineHeightSM,
colorPickerAlphaInputWidth,
marginXXS,
paddingXXS,
controlHeightSM,
marginXS,
fontSizeIcon,
paddingXS,
colorTextPlaceholder,
colorPickerInputNumberHandleWidth,
lineWidth,
} = token;
return {
[`${componentCls}-input-container`]: {
display: 'flex',
[`${componentCls}-steppers${antCls}-input-number`]: {
fontSize: fontSizeSM,
lineHeight: lineHeightSM,
[`${antCls}-input-number-input`]: {
paddingInlineStart: paddingXXS,
paddingInlineEnd: 0,
},
[`${antCls}-input-number-handler-wrap`]: {
width: colorPickerInputNumberHandleWidth,
},
},
[`${componentCls}-steppers${componentCls}-alpha-input`]: {
flex: `0 0 ${colorPickerAlphaInputWidth}px`,
marginInlineStart: marginXXS,
},
[`${componentCls}-format-select${antCls}-select`]: {
marginInlineEnd: marginXS,
'&-single': {
[`${antCls}-select-selector`]: {
padding: 0,
border: 0,
},
[`${antCls}-select-arrow`]: {
insetInlineEnd: 0,
},
[`${antCls}-select-selection-item`]: {
paddingInlineEnd: fontSizeIcon + marginXXS,
fontSize: fontSizeSM,
lineHeight: `${controlHeightSM}px`,
},
[`${antCls}-select-item-option-content`]: {
fontSize: fontSizeSM,
lineHeight: lineHeightSM,
},
[`${antCls}-select-dropdown`]: {
[`${antCls}-select-item`]: {
minHeight: 'auto',
},
},
},
},
[`${componentCls}-input`]: {
gap: marginXXS,
alignItems: 'center',
flex: 1,
width: 0,
[`${componentCls}-hsb-input,${componentCls}-rgb-input`]: {
display: 'flex',
gap: marginXXS,
alignItems: 'center',
},
[`${componentCls}-steppers`]: {
flex: 1,
},
[`${componentCls}-hex-input${antCls}-input-affix-wrapper`]: {
flex: 1,
padding: `0 ${paddingXS}px`,
[`${antCls}-input`]: {
fontSize: fontSizeSM,
lineHeight: `${controlHeightSM - 2 * lineWidth}px`,
},
[`${antCls}-input-prefix`]: {
color: colorTextPlaceholder,
},
},
},
},
};
};
export default genInputStyle;

View File

@ -0,0 +1,80 @@
import type { CSSObject } from '@ant-design/cssinjs';
import type { GenerateStyle } from '../../theme/internal';
import type { ColorPickerToken } from './index';
import genColorBlockStyle from './color-block';
const genPickerStyle: GenerateStyle<ColorPickerToken, CSSObject> = (token) => {
const {
componentCls,
controlHeightLG,
borderRadiusSM,
colorPickerInsetShadow,
marginSM,
colorBgElevated,
colorFillSecondary,
lineWidthBold,
colorPickerHandlerSize,
colorPickerHandlerSizeSM,
colorPickerSliderHeight,
colorPickerPreviewSize,
} = token;
return {
[`${componentCls}-select`]: {
[`${componentCls}-palette`]: {
minHeight: controlHeightLG * 4,
overflow: 'hidden',
borderRadius: borderRadiusSM,
},
[`${componentCls}-saturation`]: {
position: 'absolute',
borderRadius: 'inherit',
boxShadow: colorPickerInsetShadow,
inset: 0,
},
marginBottom: marginSM,
},
[`${componentCls}-handler`]: {
width: colorPickerHandlerSize,
height: colorPickerHandlerSize,
border: `${lineWidthBold}px solid ${colorBgElevated}`,
position: 'relative',
borderRadius: '50%',
boxShadow: `${colorPickerInsetShadow}, 0 0 0 1px ${colorFillSecondary}`,
'&-sm': {
width: colorPickerHandlerSizeSM,
height: colorPickerHandlerSizeSM,
},
},
[`${componentCls}-slider`]: {
borderRadius: colorPickerSliderHeight / 2,
[`${componentCls}-palette`]: {
height: colorPickerSliderHeight,
},
[`${componentCls}-gradient`]: {
borderRadius: colorPickerSliderHeight / 2,
boxShadow: colorPickerInsetShadow,
},
'&-alpha': {
backgroundSize: colorPickerSliderHeight * 2,
backgroundImage:
'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAFpJREFUWAntljEKADAIA23p6v//qQ+wfUEcCu1yriEgp0FHRJSJcnehmmWm1Dv/lO4HIg1AAAKjTqm03ea88zMCCEDgO4HV5bS757f+7wRoAAIQ4B9gByAAgQ3pfiDmXmAeEwAAAABJRU5ErkJggg==")',
},
marginBottom: marginSM,
},
[`${componentCls}-slider-container`]: {
display: 'flex',
gap: marginSM,
[`${componentCls}-slider-group`]: {
flex: 1,
},
},
...genColorBlockStyle(token, colorPickerPreviewSize),
};
};
export default genPickerStyle;

View File

@ -0,0 +1,113 @@
import type { CSSObject } from '@ant-design/cssinjs';
import type { GenerateStyle } from '../../theme/internal';
import type { ColorPickerToken } from './index';
const genPresetsStyle: GenerateStyle<ColorPickerToken, CSSObject> = (token) => {
const {
componentCls,
antCls,
colorTextQuaternary,
paddingXXS,
colorPickerPresetColorSize,
fontSizeSM,
colorText,
lineHeightSM,
lineWidth,
borderRadius,
colorFill,
colorWhite,
colorTextTertiary,
marginXXS,
paddingXS,
} = token;
return {
[`${componentCls}-presets`]: {
[`${antCls}-collapse-item > ${antCls}-collapse-header`]: {
padding: 0,
[`${antCls}-collapse-expand-icon`]: {
height: fontSizeSM * lineHeightSM,
color: colorTextQuaternary,
paddingInlineEnd: paddingXXS,
},
},
[`${antCls}-collapse`]: {
display: 'flex',
flexDirection: 'column',
gap: marginXXS,
},
[`${antCls}-collapse-item > ${antCls}-collapse-content > ${antCls}-collapse-content-box`]: {
padding: `${paddingXS}px 0`,
},
'&-label': {
fontSize: fontSizeSM,
color: colorText,
lineHeight: lineHeightSM,
},
'&-items': {
display: 'flex',
flexWrap: 'wrap',
gap: marginXXS * 1.5,
[`${componentCls}-presets-color`]: {
position: 'relative',
cursor: 'pointer',
width: colorPickerPresetColorSize,
height: colorPickerPresetColorSize,
'&::before': {
content: '""',
pointerEvents: 'none',
width: colorPickerPresetColorSize + 4 * lineWidth,
height: colorPickerPresetColorSize + 4 * lineWidth,
position: 'absolute',
top: -2 * lineWidth,
insetInlineStart: -2 * lineWidth,
borderRadius,
border: `${lineWidth}px solid transparent`,
transition: `border-color ${token.motionDurationMid} ${token.motionEaseInBack}`,
},
'&:hover::before': {
borderColor: colorFill,
},
'&::after': {
boxSizing: 'border-box',
position: 'absolute',
top: '50%',
insetInlineStart: '21.5%',
display: 'table',
width: (colorPickerPresetColorSize / 13) * 5,
height: (colorPickerPresetColorSize / 13) * 8,
border: `${token.lineWidthBold}px solid ${token.colorWhite}`,
borderTop: 0,
borderInlineStart: 0,
transform: 'rotate(45deg) scale(0) translate(-50%,-50%)',
opacity: 0,
content: '""',
transition: `all ${token.motionDurationFast} ${token.motionEaseInBack}, opacity ${token.motionDurationFast}`,
},
[`&${componentCls}-presets-color-checked`]: {
'&::after': {
opacity: 1,
borderColor: colorWhite,
transform: 'rotate(45deg) scale(1) translate(-50%,-50%)',
transition: `transform ${token.motionDurationMid} ${token.motionEaseOutBack} ${token.motionDurationFast}`,
},
[`&${componentCls}-presets-color-bright`]: {
'&::after': {
borderColor: colorTextTertiary,
},
},
},
},
},
'&-empty': {
fontSize: fontSizeSM,
color: colorTextQuaternary,
},
},
};
};
export default genPresetsStyle;

View File

@ -0,0 +1,20 @@
import type { ColorGenInput } from '@rc-component/color-picker';
import { getRoundNumber } from '@rc-component/color-picker/lib/util';
import type { Color } from './color';
import { ColorFactory } from './color';
export const customizePrefixCls = 'ant-color-picker';
export const generateColor = (color: ColorGenInput<Color>): Color => {
if (color instanceof ColorFactory) {
return color;
}
return new ColorFactory(color);
};
export const getAlphaColor = (color: Color) => getRoundNumber(color.toHsb().a * 100);
export const toHexFormat = (value?: string, alpha?: boolean) =>
value?.replace(/[^\w/]/gi, '').slice(0, alpha ? 8 : 6) || '';
export const getHex = (value?: string, alpha?: boolean) => (value ? toHexFormat(value, alpha) : '');

View File

@ -0,0 +1,22 @@
import { Provider as MotionProvider } from 'rc-motion';
import * as React from 'react';
import { useToken } from '../theme/internal';
export interface MotionWrapperProps {
children?: React.ReactNode;
}
export default function MotionWrapper(props: MotionWrapperProps): React.ReactElement {
const { children } = props;
const [, token] = useToken();
const { motion } = token;
const needWrapMotionProviderRef = React.useRef(false);
needWrapMotionProviderRef.current = needWrapMotionProviderRef.current || motion === false;
if (needWrapMotionProviderRef.current) {
return <MotionProvider motion={motion}>{children}</MotionProvider>;
}
return children as React.ReactElement;
}

View File

@ -0,0 +1,532 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ConfigProvider.Popup disable virtual if dropdownMatchSelectWidth is false 1`] = `
<div>
<div
class="ant-select ant-select-single ant-select-show-arrow ant-select-open"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="rc_select_TEST_OR_SSR_list_0"
aria-autocomplete="list"
aria-controls="rc_select_TEST_OR_SSR_list"
aria-expanded="true"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
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;"
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 ant-select-open"
>
<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="true"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
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;"
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-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-open"
>
<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="true"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
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;"
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>
`;
exports[`ConfigProvider.Popup disable virtual if is false 1`] = `
<div>
<div
class="ant-select ant-select-single ant-select-show-arrow ant-select-open"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="rc_select_TEST_OR_SSR_list_0"
aria-autocomplete="list"
aria-controls="rc_select_TEST_OR_SSR_list"
aria-expanded="true"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
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;"
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 ant-select-open"
>
<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="true"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
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;"
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-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-open"
>
<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="true"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
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;"
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>
`;
exports[`ConfigProvider.Popup disable virtual if popupMatchSelectWidth is false 1`] = `
<div>
<div
class="ant-select ant-select-single ant-select-show-arrow ant-select-open"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="rc_select_TEST_OR_SSR_list_0"
aria-autocomplete="list"
aria-controls="rc_select_TEST_OR_SSR_list"
aria-expanded="true"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
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;"
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 ant-select-open"
>
<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="true"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
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;"
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-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-open"
>
<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="true"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
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;"
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>
`;

View File

@ -122,15 +122,6 @@ describe('ConfigProvider', () => {
);
expect(isArray ? container.children : container.firstChild).toMatchSnapshot();
});
it('configProvider virtual and dropdownMatchSelectWidth', () => {
const { container } = render(
<ConfigProvider virtual={false} dropdownMatchSelectWidth={false}>
{renderComponent({})}
</ConfigProvider>,
);
expect(isArray ? container.children : container.firstChild).toMatchSnapshot();
});
});
}

View File

@ -0,0 +1,111 @@
import type { TriggerProps } from '@rc-component/trigger';
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import React from 'react';
import ConfigProvider from '..';
import { render } from '../../../tests/utils';
import Cascader from '../../cascader';
import Select from '../../select';
import TreeSelect from '../../tree-select';
dayjs.extend(customParseFormat);
jest.mock('rc-util/lib/Portal');
function triggerProps(): TriggerProps {
return (global as any).triggerProps;
}
jest.mock('@rc-component/trigger', () => {
const R = jest.requireActual('react');
const Trigger = jest.requireActual('@rc-component/trigger').default;
return R.forwardRef((props: any, ref: any) => {
(global as any).triggerProps = props;
return <Trigger {...props} ref={ref} />;
});
});
describe('ConfigProvider.Popup', () => {
beforeEach(() => {
(global as any).triggerProps = null;
});
const selectLikeNodes = (
<>
<Select
open
options={new Array(20).fill(null).map((_, index) => ({ value: index, label: index }))}
/>
<TreeSelect
open
treeData={new Array(20).fill(null).map((_, index) => ({ value: index, title: index }))}
/>
<Cascader
open
options={new Array(20).fill(null).map((_, index) => ({ value: index, label: index }))}
/>
</>
);
it('disable virtual if is false', () => {
const { container } = render(
<ConfigProvider virtual={false}>{selectLikeNodes}</ConfigProvider>,
);
expect(container).toMatchSnapshot();
});
it('disable virtual if dropdownMatchSelectWidth is false', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { container } = render(
<ConfigProvider dropdownMatchSelectWidth={false}>{selectLikeNodes}</ConfigProvider>,
);
expect(container).toMatchSnapshot();
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: ConfigProvider] `dropdownMatchSelectWidth` is deprecated. Please use `popupMatchSelectWidth` instead.',
);
errSpy.mockRestore();
});
it('disable virtual if popupMatchSelectWidth is false', () => {
const { container } = render(
<ConfigProvider popupMatchSelectWidth={false}>{selectLikeNodes}</ConfigProvider>,
);
expect(container).toMatchSnapshot();
});
describe('config popupOverflow', () => {
it('Select', () => {
render(
<ConfigProvider popupOverflow="scroll">
<Select open />
</ConfigProvider>,
);
expect(triggerProps().builtinPlacements!.topLeft!.htmlRegion).toBe('scroll');
});
it('TreeSelect', () => {
render(
<ConfigProvider popupOverflow="scroll">
<TreeSelect open />
</ConfigProvider>,
);
expect(triggerProps().builtinPlacements!.topLeft!.htmlRegion).toBe('scroll');
});
it('Cascader', () => {
render(
<ConfigProvider popupOverflow="scroll">
<Cascader open />
</ConfigProvider>,
);
expect(triggerProps().builtinPlacements!.topLeft!.htmlRegion).toBe('scroll');
});
});
});

View File

@ -34,6 +34,8 @@ export interface ThemeConfig {
inherit?: boolean;
}
export type PopupOverflow = 'viewport' | 'scroll';
export interface ConfigConsumerProps {
getTargetContainer?: () => HTMLElement;
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
@ -58,7 +60,8 @@ export interface ConfigConsumerProps {
size?: SizeType | number;
};
virtual?: boolean;
dropdownMatchSelectWidth?: boolean;
popupMatchSelectWidth?: boolean;
popupOverflow?: PopupOverflow;
form?: {
requiredMark?: RequiredMark;
colon?: boolean;

View File

@ -55,7 +55,8 @@ Some components use dynamic style to support wave effect. You can config `csp` p
| componentSize | Config antd component size | `small` \| `middle` \| `large` | - | |
| csp | Set [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) config | { nonce: string } | - | |
| direction | Set direction of layout. See [demo](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | - | 4.3.0 |
| popupMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | - | 5.5.0 |
| popupOverflow | Select like component popup logic. Can set to show in viewport or follow window scroll | 'viewport' \| 'scroll' <InlinePopover previewURL="https://user-images.githubusercontent.com/5378891/230344474-5b9f7e09-0a5d-49e8-bae8-7d2abed6c837.png"></InlinePopover> | 'viewport' | 5.5.0 |
| form | Set Form common props | { validateMessages?: [ValidateMessages](/components/form/#validatemessages), requiredMark?: boolean \| `optional`, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options) } | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0 |
| getPopupContainer | To set the container of the popup element. The default is to create a `div` element in `body` | function(triggerNode) | () => document.body | |
| getTargetContainer | Config Affix, Anchor scroll target container | () => HTMLElement | () => window | 4.2.0 |

View File

@ -7,6 +7,7 @@ import useMemo from 'rc-util/lib/hooks/useMemo';
import type { ReactElement } from 'react';
import * as React from 'react';
import type { Options } from 'scroll-into-view-if-needed';
import warning from '../_util/warning';
import type { RequiredMark } from '../form/Form';
import type { Locale } from '../locale';
import LocaleProvider, { ANT_MARK } from '../locale';
@ -15,14 +16,21 @@ import LocaleContext from '../locale/context';
import defaultLocale from '../locale/en_US';
import { DesignTokenContext } from '../theme/internal';
import defaultSeedToken from '../theme/themes/seed';
import warning from '../_util/warning';
import type { ConfigConsumerProps, CSPConfig, DirectionType, Theme, ThemeConfig } from './context';
import type {
ConfigConsumerProps,
CSPConfig,
DirectionType,
PopupOverflow,
Theme,
ThemeConfig,
} from './context';
import { ConfigConsumer, ConfigContext, defaultIconPrefixCls } from './context';
import { registerTheme } from './cssVariables';
import type { RenderEmptyHandler } from './defaultRenderEmpty';
import { DisabledContextProvider } from './DisabledContext';
import useConfig from './hooks/useConfig';
import useTheme from './hooks/useTheme';
import MotionWrapper from './MotionWrapper';
import type { SizeType } from './SizeContext';
import SizeContext, { SizeContextProvider } from './SizeContext';
import useStyle from './style';
@ -115,7 +123,10 @@ export interface ConfigProviderProps {
size?: SizeType | number;
};
virtual?: boolean;
/** @deprecated Please use `popupMatchSelectWidth` instead */
dropdownMatchSelectWidth?: boolean;
popupMatchSelectWidth?: boolean;
popupOverflow?: PopupOverflow;
theme?: ThemeConfig;
}
@ -182,6 +193,8 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
space,
virtual,
dropdownMatchSelectWidth,
popupMatchSelectWidth,
popupOverflow,
legacyLocale,
parentContext,
iconPrefixCls: customIconPrefixCls,
@ -189,6 +202,16 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
componentDisabled,
} = props;
// =================================== Warning ===================================
if (process.env.NODE_ENV !== 'production') {
warning(
dropdownMatchSelectWidth === undefined,
'ConfigProvider',
'`dropdownMatchSelectWidth` is deprecated. Please use `popupMatchSelectWidth` instead.',
);
}
// =================================== Context ===================================
const getPrefixCls = React.useCallback(
(suffixCls: string, customizePrefixCls?: string) => {
const { prefixCls } = props;
@ -221,7 +244,8 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
direction,
space,
virtual,
dropdownMatchSelectWidth,
popupMatchSelectWidth: popupMatchSelectWidth ?? dropdownMatchSelectWidth,
popupOverflow,
getPrefixCls,
iconPrefixCls,
theme: mergedTheme,
@ -300,6 +324,9 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
childNode = <SizeContextProvider size={componentSize}>{childNode}</SizeContextProvider>;
}
// =================================== Motion ===================================
childNode = <MotionWrapper>{childNode}</MotionWrapper>;
// ================================ Dynamic theme ================================
const memoTheme = React.useMemo(() => {
const { algorithm, token, ...rest } = mergedTheme || {};

View File

@ -56,7 +56,8 @@ export default Demo;
| componentSize | 设置 antd 组件大小 | `small` \| `middle` \| `large` | - | |
| csp | 设置 [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 配置 | { nonce: string } | - | |
| direction | 设置文本展示方向。 [示例](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。`false` 时会关闭虚拟滚动 | boolean \| number | - | 4.3.0 |
| popupMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。`false` 时会关闭虚拟滚动 | boolean \| number | - | 5.5.0 |
| popupOverflow | Select 类组件弹层展示逻辑,默认为可视区域滚动,可配置成滚动区域滚动 | 'viewport' \| 'scroll' <InlinePopover previewURL="https://user-images.githubusercontent.com/5378891/230344474-5b9f7e09-0a5d-49e8-bae8-7d2abed6c837.png"></InlinePopover> | 'viewport' | 5.5.0 |
| form | 设置 Form 组件的通用属性 | { validateMessages?: [ValidateMessages](/components/form-cn#validatemessages), requiredMark?: boolean \| `optional`, colon?: boolean, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options)} | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0 |
| getPopupContainer | 弹出框Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | function(triggerNode) | () => document.body | |
| getTargetContainer | 配置 Affix、Anchor 滚动监听容器。 | () => HTMLElement | () => window | 4.2.0 |

View File

@ -2,13 +2,14 @@ import dayjs from 'dayjs';
import 'dayjs/locale/mk'; // to test local in 'prop locale should works' test case
import customParseFormat from 'dayjs/plugin/customParseFormat';
import MockDate from 'mockdate';
import React from 'react';
import dayJsGenerateConfig from 'rc-picker/lib/generate/dayjs';
import type { TriggerProps } from 'rc-trigger';
import { fireEvent, render } from '../../../tests/utils';
import React from 'react';
import DatePicker from '..';
import focusTest from '../../../tests/shared/focusTest';
import type { PickerLocale } from '../generatePicker';
import { fireEvent, render } from '../../../tests/utils';
import { resetWarned } from '../../_util/warning';
import type { PickerLocale } from '../generatePicker';
dayjs.extend(customParseFormat);
@ -288,4 +289,10 @@ describe('DatePicker', () => {
errSpy.mockRestore();
});
it('support DatePicker.generatePicker', () => {
const MyDatePicker = DatePicker.generatePicker(dayJsGenerateConfig);
const { container } = render(<MyDatePicker />);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@ -1224,3 +1224,45 @@ Array [
</div>,
]
`;
exports[`DatePicker support DatePicker.generatePicker 1`] = `
<div
class="ant-picker"
>
<div
class="ant-picker-input"
>
<input
autocomplete="off"
placeholder="Select date"
readonly=""
size="12"
title=""
value=""
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
</div>
`;

View File

@ -3305,7 +3305,7 @@ exports[`renders components/date-picker/demo/range-picker.tsx correctly 1`] = `
exports[`renders components/date-picker/demo/render-panel.tsx correctly 1`] = `
<div
style="padding-bottom:0;position:relative;width:fit-content;min-width:0"
style="padding-bottom:0;position:relative;min-width:0"
>
<div
class="ant-picker"

View File

@ -1,7 +1,7 @@
## zh-CN
这里举例如何用 `onCalendarChange``disabledDate` 来限制动态的日期区间选择。
使用 `changeOnBlur` 配合 `onCalendarChange``disabledDate` 来限制动态的日期区间选择。
## en-US
A example shows how to select a dynamic range by using `onCalendarChange` and `disabledDate`.
Using `changeOnBlur` work with `onCalendarChange` and `disabledDate` to limit date selection.

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { DatePicker } from 'antd';
import type { Dayjs } from 'dayjs';
import React, { useState } from 'react';
const { RangePicker } = DatePicker;
@ -31,9 +31,14 @@ const App: React.FC = () => {
<RangePicker
value={dates || value}
disabledDate={disabledDate}
onCalendarChange={(val) => setDates(val)}
onChange={(val) => setValue(val)}
onCalendarChange={(val) => {
setDates(val);
}}
onChange={(val) => {
setValue(val);
}}
onOpenChange={onOpenChange}
changeOnBlur
/>
);
};

View File

@ -82,6 +82,7 @@ The following APIs are shared by DatePicker, RangePicker.
| bordered | Whether has border style | boolean | true | |
| className | The picker className | string | - | |
| dateRender | Custom rendering function for date cells, >= 5.4.0 use `cellRender` instead. | function(currentDate: dayjs, today: dayjs) => React.ReactNode | - | < 5.4.0 |
| changeOnBlur | Trigger `change` when blur. e.g. datetime picker no need click confirm button | boolean | false | 5.5.0 |
| cellRender | Custom rendering function for picker cells | function(current: dayjs, today: dayjs, info: { originNode: React.ReactElement,today: DateType, range?: 'start' \| 'end', type: PanelMode, locale?: Locale, subType?: 'hour' \| 'minute' \| 'second' \| 'meridiem' }) => React.ReactNode | - | 5.4.0 |
| disabled | Determine whether the DatePicker is disabled | boolean | false | |
| disabledDate | Specify the date that cannot be selected | (currentDate: dayjs) => boolean | - | |

View File

@ -16,6 +16,12 @@ export type RangePickerProps = BaseRangePickerProps<Dayjs>;
const DatePicker = generatePicker<Dayjs>(dayjsGenerateConfig);
export type DatePickerType = typeof DatePicker & {
_InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel;
_InternalRangePanelDoNotUseOrYouWillBeFired: typeof PureRangePanel;
generatePicker: typeof generatePicker;
};
function postPureProps(props: DatePickerProps) {
const dropdownAlign = transPlacement2DropdownAlign(props.direction, props.placement);
@ -31,11 +37,9 @@ function postPureProps(props: DatePickerProps) {
// We don't care debug panel
/* istanbul ignore next */
const PurePanel = genPurePanel(DatePicker, 'picker', null, postPureProps);
(DatePicker as any)._InternalPanelDoNotUseOrYouWillBeFired = PurePanel;
(DatePicker as DatePickerType)._InternalPanelDoNotUseOrYouWillBeFired = PurePanel;
const PureRangePanel = genPurePanel(DatePicker.RangePicker, 'picker', null, postPureProps);
(DatePicker as any)._InternalRangePanelDoNotUseOrYouWillBeFired = PureRangePanel;
(DatePicker as DatePickerType)._InternalRangePanelDoNotUseOrYouWillBeFired = PureRangePanel;
(DatePicker as DatePickerType).generatePicker = generatePicker;
export default DatePicker as typeof DatePicker & {
_InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel;
_InternalRangePanelDoNotUseOrYouWillBeFired: typeof PureRangePanel;
};
export default DatePicker as DatePickerType;

View File

@ -83,6 +83,7 @@ import locale from 'antd/locale/zh_CN';
| bordered | 是否有边框 | boolean | true | |
| className | 选择器 className | string | - | |
| dateRender | 自定义日期单元格的内容5.4.0 起用 `cellRender` 代替 | function(currentDate: dayjs, today: dayjs) => React.ReactNode | - | < 5.4.0 |
| changeOnBlur | 失去焦点时触发 `change` 事件,例如 datetime 下不再需要点击确认按钮 | boolean | false | 5.5.0 |
| cellRender | 自定义单元格的内容 | function(current: dayjs, today: dayjs, info: { originNode: React.ReactElement,today: DateType, range?: 'start' \| 'end', type: PanelMode, locale?: Locale, subType?: 'hour' \| 'minute' \| 'second' \| 'meridiem' }) => React.ReactNode | - | 5.4.0 |
| disabled | 禁用 | boolean | false | |
| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | - | |

View File

@ -99,6 +99,70 @@ Array [
]
`;
exports[`renders components/drawer/demo/component-token.tsx extend context correctly 1`] = `
<div
style="padding: 32px; background: rgb(230, 230, 230);"
>
<div
class="ant-drawer ant-drawer-pure ant-drawer-right"
style="height: 300px;"
>
<div
class="ant-drawer-wrapper-body"
>
<div
class="ant-drawer-header"
>
<div
class="ant-drawer-header-title"
>
<button
aria-label="Close"
class="ant-drawer-close"
type="button"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
/>
</svg>
</span>
</button>
<div
class="ant-drawer-title"
>
Hello Title
</div>
</div>
</div>
<div
class="ant-drawer-body"
>
Hello Content
</div>
<div
class="ant-drawer-footer"
>
Footer!
</div>
</div>
</div>
</div>
`;
exports[`renders components/drawer/demo/config-provider.tsx extend context correctly 1`] = `
<div
class="site-drawer-render-in-current-wrapper"
@ -434,29 +498,33 @@ Array [
type="button"
>
<span
aria-label="plus"
class="anticon anticon-plus"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="plus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="plus"
class="anticon anticon-plus"
role="img"
>
<defs>
<style />
</defs>
<path
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="plus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<defs>
<style />
</defs>
<path
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</span>
</span>
<span>
New account

View File

@ -11,6 +11,70 @@ exports[`renders components/drawer/demo/basic-right.tsx correctly 1`] = `
</button>
`;
exports[`renders components/drawer/demo/component-token.tsx correctly 1`] = `
<div
style="padding:32px;background:#e6e6e6"
>
<div
class="ant-drawer ant-drawer-pure ant-drawer-right"
style="height:300px"
>
<div
class="ant-drawer-wrapper-body"
>
<div
class="ant-drawer-header"
>
<div
class="ant-drawer-header-title"
>
<button
aria-label="Close"
class="ant-drawer-close"
type="button"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
/>
</svg>
</span>
</button>
<div
class="ant-drawer-title"
>
Hello Title
</div>
</div>
</div>
<div
class="ant-drawer-body"
>
Hello Content
</div>
<div
class="ant-drawer-footer"
>
Footer!
</div>
</div>
</div>
</div>
`;
exports[`renders components/drawer/demo/config-provider.tsx correctly 1`] = `
<div
class="site-drawer-render-in-current-wrapper"
@ -137,29 +201,33 @@ exports[`renders components/drawer/demo/form-in-drawer.tsx correctly 1`] = `
type="button"
>
<span
aria-label="plus"
class="anticon anticon-plus"
role="img"
class="ant-btn-icon"
>
<svg
aria-hidden="true"
data-icon="plus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="plus"
class="anticon anticon-plus"
role="img"
>
<defs>
<style />
</defs>
<path
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="plus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<defs>
<style />
</defs>
<path
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</span>
</span>
<span>
New account

View File

@ -0,0 +1,7 @@
## zh-CN
Component Token Debug.
## en-US
Component Token Debug.

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