Merge pull request #48133 from ant-design/master

chore: merge master into feature
This commit is contained in:
lijianan 2024-03-28 10:54:52 +08:00 committed by GitHub
commit 405394a8e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 1606 additions and 532 deletions

View File

@ -17,6 +17,7 @@ import CodeSandboxIcon from '../../common/CodeSandboxIcon';
import EditButton from '../../common/EditButton';
import ExternalLinkIcon from '../../common/ExternalLinkIcon';
import RiddleIcon from '../../common/RiddleIcon';
import DemoContext from '../../slots/DemoContext';
import type { SiteContextProps } from '../../slots/SiteContext';
import SiteContext from '../../slots/SiteContext';
import { ping } from '../../utils';
@ -107,6 +108,7 @@ const CodePreviewer: React.FC<AntdPreviewerProps> = (props) => {
clientOnly,
pkgDependencyList,
} = props;
const { showDebug } = useContext(DemoContext);
const { pkg } = useSiteData();
const location = useLocation();
@ -469,26 +471,28 @@ createRoot(document.getElementById('container')).render(<Demo />);
<CodePenIcon className="code-box-codepen" />
</Tooltip>
</form>
<form
className="code-box-code-action"
action="https://codesandbox.io/api/v1/sandboxes/define"
method="POST"
target="_blank"
ref={codeSandboxIconRef}
onClick={() => {
track({ type: 'codesandbox', demo: asset.id });
codeSandboxIconRef.current?.submit();
}}
>
<input
type="hidden"
name="parameters"
value={compress(JSON.stringify(codesanboxPrefillConfig))}
/>
<Tooltip title={<FormattedMessage id="app.demo.codesandbox" />}>
<CodeSandboxIcon className="code-box-codesandbox" />
</Tooltip>
</form>
{showDebug && (
<form
className="code-box-code-action"
action="https://codesandbox.io/api/v1/sandboxes/define"
method="POST"
target="_blank"
ref={codeSandboxIconRef}
onClick={() => {
track({ type: 'codesandbox', demo: asset.id });
codeSandboxIconRef.current?.submit();
}}
>
<input
type="hidden"
name="parameters"
value={compress(JSON.stringify(codesanboxPrefillConfig))}
/>
<Tooltip title={<FormattedMessage id="app.demo.codesandbox" />}>
<CodeSandboxIcon className="code-box-codesandbox" />
</Tooltip>
</form>
)}
<Tooltip title={<FormattedMessage id="app.demo.separate" />}>
<a
className="code-box-code-action"

View File

@ -26,7 +26,8 @@ const useStyle = createStyles(({ token, css }) => {
backdrop-filter: blur(8px);
border-radius: ${token.borderRadius}px;
box-sizing: border-box;
z-index: 1000;
margin-inline-end: calc(16px - 100vw + 100%);
z-index: 10;
.toc-debug {
color: ${token.purple6};

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import { createStyles } from 'antd-style';
import { Link, useLocation } from 'dumi';
import * as React from 'react';
import * as utils from '../../utils';
const useStyle = createStyles(({ token, css }) => {

View File

@ -1,13 +1,14 @@
import * as React from 'react';
import { FormattedMessage, useFullSidebarData, useLocation } from 'dumi';
import { MenuOutlined } from '@ant-design/icons';
import { createStyles, css } from 'antd-style';
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
import * as utils from '../../utils';
import type { SharedProps } from './interface';
import { createStyles, css } from 'antd-style';
import { FormattedMessage, useFullSidebarData, useLocation } from 'dumi';
import useLocale from '../../../hooks/useLocale';
import Link from '../../common/Link';
import * as utils from '../../utils';
import type { SharedProps } from './interface';
// ============================= Theme =============================
const locales = {
@ -65,7 +66,7 @@ const useStyle = createStyles(({ token }) => {
position: absolute;
inset: 0;
background-color: transparent;
content: "";
content: '';
}
}
@ -114,14 +115,8 @@ export interface NavigationProps extends SharedProps {
onDirectionChange: () => void;
}
export default ({
isZhCN,
isMobile,
responsive,
directionText,
onLangChange,
onDirectionChange,
}: NavigationProps) => {
const HeaderNavigation: React.FC<NavigationProps> = (props) => {
const { isZhCN, isMobile, responsive, directionText, onLangChange, onDirectionChange } = props;
const { pathname, search } = useLocation();
const [locale] = useLocale(locales);
@ -132,11 +127,7 @@ export default ({
const menuMode = isMobile ? 'inline' : 'horizontal';
const module = pathname
.split('/')
.filter((path) => path)
.slice(0, -1)
.join('/');
const module = pathname.split('/').filter(Boolean).slice(0, -1).join('/');
let activeMenuItem = module || 'home';
if (pathname.startsWith('/changelog')) {
activeMenuItem = 'docs/react';
@ -287,7 +278,8 @@ export default ({
className={styles.nav}
disabledOverflow
items={items}
style={{ borderRight: 0 }}
/>
);
};
export default HeaderNavigation;

View File

@ -1,7 +1,7 @@
import { createStyles } from 'antd-style';
import React from 'react';
import classNames from 'classnames';
import { Tooltip } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
export interface LangBtnProps {
label1: React.ReactNode;

View File

@ -39,7 +39,6 @@ const locales = {
const useStyle = createStyles(({ token, css }) => {
const searchIconColor = '#ced4d9';
return {
header: css`
position: sticky;
@ -102,12 +101,10 @@ const useStyle = createStyles(({ token, css }) => {
display: flex;
align-items: center;
margin: 0;
column-gap: 12px;
> * {
flex: none;
margin: 0;
margin-inline-end: 12px;
&:last-child {
margin-inline-end: 40px;
}
@ -118,7 +115,6 @@ const useStyle = createStyles(({ token, css }) => {
`,
popoverMenu: {
width: 300,
[`${token.antCls}-popover-inner-content`]: {
padding: 0,
},
@ -130,16 +126,19 @@ const useStyle = createStyles(({ token, css }) => {
user-select: none;
`,
link: css`
margin-left: 10px;
margin-inline-start: 10px;
@media only screen and (max-width: ${token.mobileMaxWidth}px) {
margin-left: 0;
margin-inline-start: 0;
}
`,
icon: css`
margin-right: 10px;
width: 22px;
height: 22px;
versionSelect: css`
min-width: 90px;
.rc-virtual-list {
.rc-virtual-list-holder {
scrollbar-width: thin;
scrollbar-color: unset;
}
}
`,
};
});
@ -299,8 +298,8 @@ const Header: React.FC = () => {
navigationNode,
<Select
key="version"
className="version"
size="small"
className={styles.versionSelect}
defaultValue={pkg.version}
onChange={handleVersionChange}
dropdownStyle={getDropdownStyle}

View File

@ -19,7 +19,15 @@ jobs:
if: github.event.pull_request.merged == true && github.repository == 'ant-design/ant-design'
runs-on: ubuntu-latest
steps:
- name: get commit count
id: get_commit_count
run: |
PR_AUTHOR=$(echo "${{ github.event.pull_request.user.login }}")
RESULT_DATA=$(curl -s "https://api.github.com/repos/${{ github.repository }}/commits?author=${PR_AUTHOR}&per_page=5")
DATA_LENGTH=$(echo $RESULT_DATA | jq 'if type == "array" then length else 0 end')
echo "COUNT=$DATA_LENGTH" >> $GITHUB_OUTPUT
- name: Comment on PR
if: steps.get_commit_count.outputs.COUNT < 3
uses: actions-cool/maintain-one-comment@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -16,6 +16,17 @@ tag: vVERSION
---
## 5.15.4
`2024-03-25`
- 💄 Fix QRCode that custom style would be overrided by internal style. [#48053](https://github.com/ant-design/ant-design/pull/48053) [@li-jia-nan](https://github.com/li-jia-nan)
- 💄 Fix Radio disabled hover style. [#47972](https://github.com/ant-design/ant-design/pull/47972) [@madocto](https://github.com/madocto)
- 🐞 Fix Watermark sometime repeat re-render when browser set scale. [#47895](https://github.com/ant-design/ant-design/pull/47895)
- TypeScript
- 🤖 Affix Export AffixRef interface. [#47982](https://github.com/ant-design/ant-design/pull/47982) [@li-jia-nan](https://github.com/li-jia-nan)
- 🤖 MISC: Fix GetRef ts util can not get correct ref type for some component. [#47983](https://github.com/ant-design/ant-design/pull/47983)
## 5.15.3
`2024-03-17`

View File

@ -16,6 +16,17 @@ tag: vVERSION
---
## 5.15.4
`2024-03-25`
- 💄 修复 QRCode 组件自定义样式会被内部样式覆盖的问题。[#48053](https://github.com/ant-design/ant-design/pull/48053) [@li-jia-nan](https://github.com/li-jia-nan)
- 💄 修复 Radio 禁用状态 hover 样式。[#47972](https://github.com/ant-design/ant-design/pull/47972) [@madocto](https://github.com/madocto)
- 🐞 修复 Watermark 在特定屏幕缩放下会不断重复渲染的问题。[#47895](https://github.com/ant-design/ant-design/pull/47895)
- TypeScript
- 🤖 Affix 导出 AffixRef 类型。[#47982](https://github.com/ant-design/ant-design/pull/47982) [@li-jia-nan](https://github.com/li-jia-nan)
- 🤖 MISC: 修复 GetRef 工具类型不能正确获得某些组件 ref 类型的问题。[#47983](https://github.com/ant-design/ant-design/pull/47983)
## 5.15.3
`2024-03-17`

View File

@ -41,6 +41,11 @@ type TypeWarning = BaseTypeWarning & {
};
export interface WarningContextProps {
/**
* @descCN `false`
* @descEN Set the warning level. When set to `false`, discard related information will be aggregated into a single message.
* @since 5.10.0
*/
strict?: boolean;
}

View File

@ -1,10 +1,11 @@
import * as React from 'react';
import classNames from 'classnames';
import CSSMotion from 'rc-motion';
import { render, unmount } from 'rc-util/lib/React/render';
import raf from 'rc-util/lib/raf';
import * as React from 'react';
import { render, unmount } from 'rc-util/lib/React/render';
import { TARGET_CLS, type ShowWaveEffect } from './interface';
import { getTargetWaveColor } from './util';
import { type ShowWaveEffect, TARGET_CLS } from './interface';
function validateNum(value: number) {
return Number.isNaN(value) ? 0 : value;

View File

@ -12,7 +12,7 @@ import useWave from './useWave';
export interface WaveProps {
disabled?: boolean;
children?: React.ReactNode;
component?: string;
component?: 'Tag' | 'Button' | 'Checkbox' | 'Radio' | 'Switch';
}
const Wave: React.FC<WaveProps> = (props) => {
@ -48,7 +48,6 @@ const Wave: React.FC<WaveProps> = (props) => {
) {
return;
}
showWave(e);
};

View File

@ -1,16 +1,17 @@
import * as React from 'react';
import { useEvent } from 'rc-util';
import raf from 'rc-util/lib/raf';
import showWaveEffect from './WaveEffect';
import { ConfigContext } from '../../config-provider';
import useToken from '../../theme/useToken';
import { TARGET_CLS, type ShowWave } from './interface';
import showWaveEffect from './WaveEffect';
export default function useWave(
const useWave = (
nodeRef: React.RefObject<HTMLElement>,
className: string,
component?: string,
) {
component?: 'Tag' | 'Button' | 'Checkbox' | 'Radio' | 'Switch',
) => {
const { wave } = React.useContext(ConfigContext);
const [, token, hashId] = useToken();
@ -41,4 +42,6 @@ export default function useWave(
};
return showDebounceWave;
}
};
export default useWave;

View File

@ -203,7 +203,7 @@ const Alert: React.FC<AlertProps> = (props) => {
return alert?.closeIcon;
}, [closeIcon, closable, closeText, alert?.closeIcon]);
const mergeAriaProps = React.useMemo(() => {
const mergedAriaProps = React.useMemo<React.AriaAttributes>(() => {
const merged = closable ?? alert?.closable;
if (typeof merged === 'object') {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -252,7 +252,7 @@ const Alert: React.FC<AlertProps> = (props) => {
prefixCls={prefixCls}
closeIcon={mergedCloseIcon}
handleClose={handleClose}
ariaProps={mergeAriaProps}
ariaProps={mergedAriaProps}
/>
</div>
)}

View File

@ -6,6 +6,7 @@ exports[`renders components/app/demo/basic.tsx extend context correctly 1`] = `
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap: wrap;"
>
<div
class="ant-space-item"
@ -55,6 +56,7 @@ exports[`renders components/app/demo/config.tsx extend context correctly 1`] = `
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap: wrap;"
>
<div
class="ant-space-item"

View File

@ -6,6 +6,7 @@ exports[`renders components/app/demo/basic.tsx correctly 1`] = `
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap:wrap"
>
<div
class="ant-space-item"
@ -53,6 +54,7 @@ exports[`renders components/app/demo/config.tsx correctly 1`] = `
>
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
style="flex-wrap:wrap"
>
<div
class="ant-space-item"

View File

@ -25,7 +25,7 @@ const MyPage = () => {
};
return (
<Space>
<Space wrap>
<Button type="primary" onClick={showMessage}>
Open message
</Button>

View File

@ -17,7 +17,7 @@ const MyPage = () => {
};
return (
<Space>
<Space wrap>
<Button type="primary" onClick={showMessage}>
Message for only one
</Button>

View File

@ -7,12 +7,12 @@ import type { Breakpoint } from '../_util/responsiveObserver';
import { responsiveArray } from '../_util/responsiveObserver';
import { devUseWarning } from '../_util/warning';
import { ConfigContext } from '../config-provider';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import useSize from '../config-provider/hooks/useSize';
import useBreakpoint from '../grid/hooks/useBreakpoint';
import type { AvatarContextType, AvatarSize } from './AvatarContext';
import AvatarContext from './AvatarContext';
import useStyle from './style';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
export interface AvatarProps {
/** Shape of avatar, options: `circle`, `square` */
@ -53,7 +53,7 @@ const InternalAvatar: React.ForwardRefRenderFunction<HTMLSpanElement, AvatarProp
const avatarNodeRef = React.useRef<HTMLSpanElement>(null);
const avatarChildrenRef = React.useRef<HTMLSpanElement>(null);
const avatarNodeMergeRef = composeRef<HTMLSpanElement>(ref, avatarNodeRef);
const avatarNodeMergedRef = composeRef<HTMLSpanElement>(ref, avatarNodeRef);
const { getPrefixCls, avatar } = React.useContext(ConfigContext);
@ -234,7 +234,7 @@ const InternalAvatar: React.ForwardRefRenderFunction<HTMLSpanElement, AvatarProp
{...others}
style={{ ...sizeStyle, ...responsiveSizeStyle, ...avatar?.style, ...others.style }}
className={classString}
ref={avatarNodeMergeRef}
ref={avatarNodeMergedRef}
>
{childrenToRender}
</span>,

View File

@ -85,11 +85,21 @@ It accepts all props which native buttons support.
## FAQ
### How to close the click wave effect?
If you don't need this feature, you can set `disabled` of `wave` in [ConfigProvider](/components/config-provider#api) as `true`.
```jsx
<ConfigProvider wave={{ disabled: true }}>
<Button>click</Button>
</ConfigProvider>
```
### How to remove space between 2 chinese characters?
Following the Ant Design specification, we will add one space between if Button (exclude Text button and Link button) contains two Chinese characters only. If you don't need that, you can use [ConfigProvider](/components/config-provider/#api) to set `autoInsertSpaceInButton` as `false`.
```tsx
```jsx
<ConfigProvider autoInsertSpaceInButton={false}>
<Button>按钮</Button>
</ConfigProvider>

View File

@ -90,11 +90,21 @@ group:
## FAQ
### 如何关闭点击波纹效果?
如果你不需要这个特性,可以设置 [ConfigProvider](/components/config-provider-cn#api) 的 `wave``disabled``true`
```jsx
<ConfigProvider wave={{ disabled: true }}>
<Button>click</Button>
</ConfigProvider>
```
### 如何移除两个汉字之间的空格?
根据 Ant Design 设计规范要求,我们会在按钮内(文本按钮和链接按钮除外)只有两个汉字时自动添加空格,如果你不需要这个特性,可以设置 [ConfigProvider](/components/config-provider-cn#api) 的 `autoInsertSpaceInButton``false`
根据 Ant Design 设计规范要求,我们会在按钮内(文本按钮和链接按钮除外)只有两个汉字时自动添加空格,如果你不需要这个特性,可以设置 [ConfigProvider](/components/config-provider-cn#api) 的 `autoInsertSpaceInButton``false`
```tsx
```jsx
<ConfigProvider autoInsertSpaceInButton={false}>
<Button>按钮</Button>
</ConfigProvider>

View File

@ -1,9 +1,5 @@
import type { CSSProperties, FC } from 'react';
import React, { useContext, useMemo, useRef, useState } from 'react';
import type {
HsbaColorType,
ColorPickerProps as RcColorPickerProps,
} from '@rc-component/color-picker';
import React, { useContext, useMemo, useRef } from 'react';
import type { HsbaColorType } from '@rc-component/color-picker';
import classNames from 'classnames';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
@ -15,7 +11,6 @@ import { ConfigContext } from '../config-provider/context';
import DisabledContext from '../config-provider/DisabledContext';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import useSize from '../config-provider/hooks/useSize';
import type { SizeType } from '../config-provider/SizeContext';
import { FormItemInputContext, NoFormStyle } from '../form/context';
import type { PopoverProps } from '../popover';
import Popover from '../popover';
@ -23,50 +18,10 @@ import type { Color } from './color';
import ColorPickerPanel from './ColorPickerPanel';
import ColorTrigger from './components/ColorTrigger';
import useColorState from './hooks/useColorState';
import type {
ColorFormat,
ColorPickerBaseProps,
ColorValueType,
PresetsItem,
TriggerPlacement,
TriggerType,
} from './interface';
import type { ColorPickerBaseProps, ColorPickerProps, TriggerPlacement } from './interface';
import useStyle from './style';
import { genAlphaColor, generateColor, getAlphaColor } from './util';
export type ColorPickerProps = Omit<
RcColorPickerProps,
'onChange' | 'value' | 'defaultValue' | 'panelRender' | 'disabledAlpha' | 'onChangeComplete'
> & {
value?: ColorValueType;
defaultValue?: ColorValueType;
children?: React.ReactNode;
open?: boolean;
disabled?: boolean;
placement?: TriggerPlacement;
trigger?: TriggerType;
format?: keyof typeof ColorFormat;
defaultFormat?: keyof typeof ColorFormat;
allowClear?: boolean;
presets?: PresetsItem[];
arrow?: boolean | { pointAtCenter: boolean };
panelRender?: (
panel: React.ReactNode,
extra: { components: { Picker: FC; Presets: FC } },
) => React.ReactNode;
showText?: boolean | ((color: Color) => React.ReactNode);
size?: SizeType;
styles?: { popup?: CSSProperties; popupOverlayInner?: CSSProperties };
rootClassName?: string;
disabledAlpha?: boolean;
[key: `data-${string}`]: string;
onOpenChange?: (open: boolean) => void;
onFormatChange?: (format: ColorFormat) => void;
onChange?: (value: Color, hex: string) => void;
onClear?: () => void;
onChangeComplete?: (value: Color) => void;
} & Pick<PopoverProps, 'getPopupContainer' | 'autoAdjustOverflow' | 'destroyTooltipOnHide'>;
type CompoundedComponent = React.FC<ColorPickerProps> & {
_InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel;
};
@ -109,7 +64,7 @@ const ColorPicker: CompoundedComponent = (props) => {
const contextDisabled = useContext(DisabledContext);
const mergedDisabled = disabled ?? contextDisabled;
const [colorValue, setColorValue] = useColorState('', {
const [colorValue, setColorValue, prevValue] = useColorState('', {
value,
defaultValue,
});
@ -124,8 +79,6 @@ const ColorPicker: CompoundedComponent = (props) => {
onChange: onFormatChange,
});
const [colorCleared, setColorCleared] = useState(!value && !defaultValue);
const prefixCls = getPrefixCls('color-picker', customizePrefixCls);
const isAlphaColor = useMemo(() => getAlphaColor(colorValue) < 100, [colorValue]);
@ -138,19 +91,19 @@ const ColorPicker: CompoundedComponent = (props) => {
const rootCls = useCSSVarCls(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const rtlCls = { [`${prefixCls}-rtl`]: direction };
const mergeRootCls = classNames(rootClassName, cssVarCls, rootCls, rtlCls);
const mergeCls = classNames(
const mergedRootCls = classNames(rootClassName, cssVarCls, rootCls, rtlCls);
const mergedCls = classNames(
getStatusClassNames(prefixCls, contextStatus),
{
[`${prefixCls}-sm`]: mergedSize === 'small',
[`${prefixCls}-lg`]: mergedSize === 'large',
},
colorPicker?.className,
mergeRootCls,
mergedRootCls,
className,
hashId,
);
const mergePopupCls = classNames(prefixCls, mergeRootCls);
const mergedPopupCls = classNames(prefixCls, mergedRootCls);
const popupAllowCloseRef = useRef(true);
@ -167,14 +120,16 @@ const ColorPicker: CompoundedComponent = (props) => {
const handleChange = (data: Color, type?: HsbaColorType, pickColor?: boolean) => {
let color: Color = generateColor(data);
// If color is cleared, reset alpha to 100
const isNull = value === null || (!value && defaultValue === null);
if (colorCleared || isNull) {
setColorCleared(false);
if (prevValue.current?.cleared || isNull) {
// ignore alpha slider
if (getAlphaColor(colorValue) === 0 && type !== 'alpha') {
color = genAlphaColor(color);
}
}
// ignore alpha color
if (disabledAlpha && isAlphaColor) {
color = genAlphaColor(color);
@ -192,7 +147,6 @@ const ColorPicker: CompoundedComponent = (props) => {
};
const handleClear = () => {
setColorCleared(true);
onClear?.();
};
@ -221,7 +175,6 @@ const ColorPicker: CompoundedComponent = (props) => {
prefixCls,
color: colorValue,
allowClear,
colorCleared,
disabled: mergedDisabled,
disabledAlpha,
presets,
@ -254,21 +207,20 @@ const ColorPicker: CompoundedComponent = (props) => {
/>
</NoFormStyle>
}
overlayClassName={mergePopupCls}
overlayClassName={mergedPopupCls}
{...popoverProps}
>
{children || (
<ColorTrigger
open={popupOpen}
className={mergeCls}
className={mergedCls}
style={mergedStyle}
color={value ? generateColor(value) : colorValue}
prefixCls={prefixCls}
disabled={mergedDisabled}
colorCleared={colorCleared}
showText={showText}
format={formatValue}
{...rest}
color={colorValue}
/>
)}
</Popover>,

View File

@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { createEvent, fireEvent, render } from '@testing-library/react';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
@ -11,8 +11,9 @@ import ConfigProvider from '../../config-provider';
import Form from '../../form';
import theme from '../../theme';
import type { Color } from '../color';
import type { ColorPickerProps } from '../ColorPicker';
import ColorPicker from '../ColorPicker';
import type { ColorPickerProps, ColorValueType } from '../interface';
import { generateColor } from '../util';
function doMouseMove(
container: HTMLElement,
@ -607,4 +608,94 @@ describe('ColorPicker', () => {
const { container } = render(<ColorPicker />);
expect(container.querySelector('.ant-color-picker-clear')).toBeTruthy();
});
['', null].forEach((value) => {
it(`When controlled and without an initial value, then changing the controlled value to valid color should be reflected correctly on the DOM. [${String(
value,
)}]`, async () => {
const Demo = () => {
const [color, setColor] = useState<ColorValueType>(value);
useEffect(() => {
setColor(generateColor('red'));
}, []);
return <ColorPicker value={color} />;
};
const { container } = render(<Demo />);
await waitFakeTimer();
expect(container.querySelector('.ant-color-picker-color-block-inner')).toHaveStyle({
background: 'rgb(255, 0, 0)',
});
});
it(`When controlled and has an initial value, then changing the controlled value to cleared color should be reflected correctly on the DOM. [${String(
value,
)}]`, async () => {
const Demo = () => {
const [color, setColor] = useState<ColorValueType>(generateColor('red'));
useEffect(() => {
setColor(value);
}, []);
return <ColorPicker value={color} />;
};
const { container } = render(<Demo />);
await waitFakeTimer();
expect(container.querySelector('.ant-color-picker-clear')).toBeTruthy();
});
});
it('Controlled string value should work with allowClear correctly', async () => {
const Demo = (props: any) => {
const [color, setColor] = useState<ColorValueType>(generateColor('red'));
useEffect(() => {
if (typeof props.value !== 'undefined') {
setColor(props.value);
}
}, [props.value]);
return (
<ColorPicker value={color} onChange={(e) => setColor(e.toHexString())} open allowClear />
);
};
const { container, rerender } = render(<Demo />);
await waitFakeTimer();
expect(
container.querySelector('.ant-color-picker-trigger .ant-color-picker-clear'),
).toBeFalsy();
fireEvent.click(container.querySelector('.ant-color-picker-clear')!);
expect(
container.querySelector('.ant-color-picker-trigger .ant-color-picker-clear'),
).toBeTruthy();
rerender(<Demo value="#1677ff" />);
expect(
container.querySelector('.ant-color-picker-trigger .ant-color-picker-clear'),
).toBeFalsy();
});
it('Controlled value should work with allowClear correctly', async () => {
const Demo = (props: any) => {
const [color, setColor] = useState<ColorValueType>(generateColor('red'));
useEffect(() => {
if (typeof props.value !== 'undefined') {
setColor(props.value);
}
}, [props.value]);
return <ColorPicker value={color} onChange={(e) => setColor(e)} open allowClear />;
};
const { container, rerender } = render(<Demo />);
await waitFakeTimer();
expect(
container.querySelector('.ant-color-picker-trigger .ant-color-picker-clear'),
).toBeFalsy();
fireEvent.click(container.querySelector('.ant-color-picker-clear')!);
expect(
container.querySelector('.ant-color-picker-trigger .ant-color-picker-clear'),
).toBeTruthy();
rerender(<Demo value="#1677ff" />);
expect(
container.querySelector('.ant-color-picker-trigger .ant-color-picker-clear'),
).toBeFalsy();
});
});

View File

@ -11,16 +11,21 @@ export interface Color
extends Pick<
RcColor,
'toHsb' | 'toHsbString' | 'toHex' | 'toHexString' | 'toRgb' | 'toRgbString'
> {}
> {
cleared: boolean | 'controlled';
}
export class ColorFactory {
export class ColorFactory implements Color {
/** Original Color object */
private metaColor: RcColor;
public cleared: boolean = false;
constructor(color: ColorGenInput<Color>) {
this.metaColor = new RcColor(color as ColorGenInput);
if (!color) {
this.metaColor.setAlpha(0);
this.cleared = true;
}
}

View File

@ -4,17 +4,18 @@ import type { Color } from '../color';
import type { ColorPickerBaseProps } from '../interface';
import { generateColor } from '../util';
interface ColorClearProps extends Pick<ColorPickerBaseProps, 'prefixCls' | 'colorCleared'> {
interface ColorClearProps extends Pick<ColorPickerBaseProps, 'prefixCls'> {
value?: Color;
onChange?: (value: Color) => void;
}
const ColorClear: FC<ColorClearProps> = ({ prefixCls, value, colorCleared, onChange }) => {
const ColorClear: FC<ColorClearProps> = ({ prefixCls, value, onChange }) => {
const handleClick = () => {
if (value && !colorCleared) {
if (value && !value.cleared) {
const hsba = value.toHsb();
hsba.a = 0;
const genColor = generateColor(hsba);
genColor.cleared = true;
onChange?.(genColor);
}
};

View File

@ -2,14 +2,13 @@ import { ColorBlock } from '@rc-component/color-picker';
import classNames from 'classnames';
import type { CSSProperties, MouseEventHandler } from 'react';
import React, { forwardRef, useMemo } from 'react';
import type { ColorPickerProps } from '../ColorPicker';
import type { ColorPickerBaseProps } from '../interface';
import type { ColorPickerProps, ColorPickerBaseProps } from '../interface';
import { getAlphaColor } from '../util';
import ColorClear from './ColorClear';
interface colorTriggerProps
extends Pick<ColorPickerBaseProps, 'prefixCls' | 'colorCleared' | 'disabled' | 'format'> {
color: Exclude<ColorPickerBaseProps['color'], undefined>;
export interface ColorTriggerProps
extends Pick<ColorPickerBaseProps, 'prefixCls' | 'disabled' | 'format'> {
color: NonNullable<ColorPickerBaseProps['color']>;
open?: boolean;
showText?: ColorPickerProps['showText'];
className?: string;
@ -19,19 +18,18 @@ interface colorTriggerProps
onMouseLeave?: MouseEventHandler<HTMLDivElement>;
}
const ColorTrigger = forwardRef<HTMLDivElement, colorTriggerProps>((props, ref) => {
const { color, prefixCls, open, colorCleared, disabled, format, className, showText, ...rest } =
props;
const ColorTrigger = forwardRef<HTMLDivElement, ColorTriggerProps>((props, ref) => {
const { color, prefixCls, open, disabled, format, className, showText, ...rest } = props;
const colorTriggerPrefixCls = `${prefixCls}-trigger`;
const containerNode = useMemo<React.ReactNode>(
() =>
colorCleared ? (
color.cleared ? (
<ColorClear prefixCls={prefixCls} />
) : (
<ColorBlock prefixCls={prefixCls} color={color.toRgbString()} />
),
[color, colorCleared, prefixCls],
[color, prefixCls],
);
const genColorString = () => {

View File

@ -7,11 +7,12 @@ import { PanelPickerContext } from '../context';
import type { ColorPickerBaseProps } from '../interface';
import ColorClear from './ColorClear';
import ColorInput from './ColorInput';
import { generateColor } from '../util';
export interface PanelPickerProps
extends Pick<
ColorPickerBaseProps,
'prefixCls' | 'colorCleared' | 'allowClear' | 'disabledAlpha' | 'onChangeComplete'
'prefixCls' | 'allowClear' | 'disabledAlpha' | 'onChangeComplete'
> {
value?: Color;
onChange?: (value?: Color, type?: HsbaColorType, pickColor?: boolean) => void;
@ -21,7 +22,6 @@ export interface PanelPickerProps
const PanelPicker: FC = () => {
const {
prefixCls,
colorCleared,
allowClear,
value,
disabledAlpha,
@ -36,7 +36,6 @@ const PanelPicker: FC = () => {
<ColorClear
prefixCls={prefixCls}
value={value}
colorCleared={colorCleared}
onChange={(clearColor) => {
onChange?.(clearColor);
onClear?.();
@ -48,8 +47,12 @@ const PanelPicker: FC = () => {
prefixCls={prefixCls}
value={value?.toHsb()}
disabledAlpha={disabledAlpha}
onChange={(colorValue, type) => onChange?.(colorValue, type, true)}
onChangeComplete={onChangeComplete}
onChange={(colorValue, type) => {
onChange?.(generateColor(colorValue), type, true);
}}
onChangeComplete={(colorValue) => {
onChangeComplete?.(generateColor(colorValue));
}}
/>
<ColorInput
value={value}

View File

@ -1,4 +1,16 @@
import React from 'react';
import { ColorPicker } from 'antd';
export default () => <ColorPicker defaultValue="#1677ff" allowClear />;
export default () => {
const [color, setColor] = React.useState<string>('#1677ff');
return (
<ColorPicker
value={color}
allowClear
onChange={(c) => {
setColor(c.toHexString());
}}
/>
);
};

View File

@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import type { Color } from '../color';
import type { ColorValueType } from '../interface';
import { generateColor } from '../util';
@ -10,27 +11,39 @@ function hasValue(value?: ColorValueType) {
const useColorState = (
defaultStateValue: ColorValueType,
option: { defaultValue?: ColorValueType; value?: ColorValueType },
): readonly [Color, React.Dispatch<React.SetStateAction<Color>>] => {
) => {
const { defaultValue, value } = option;
const [colorValue, setColorValue] = useState<Color>(() => {
let mergeState: ColorValueType | undefined;
const prevColor = useRef<Color>(generateColor(''));
const [colorValue, _setColorValue] = useState<Color>(() => {
let mergedState: ColorValueType | undefined;
if (hasValue(value)) {
mergeState = value;
mergedState = value;
} else if (hasValue(defaultValue)) {
mergeState = defaultValue;
mergedState = defaultValue;
} else {
mergeState = defaultStateValue;
mergedState = defaultStateValue;
}
return generateColor(mergeState || '');
const color = generateColor(mergedState || '');
prevColor.current = color;
return color;
});
const setColorValue: typeof _setColorValue = (color: Color) => {
_setColorValue(color);
prevColor.current = color;
};
useEffect(() => {
if (value) {
setColorValue(generateColor(value));
if (hasValue(value)) {
const newColor = generateColor(value || '');
if (prevColor.current.cleared === true) {
newColor.cleared = 'controlled';
}
setColorValue(newColor);
}
}, [value]);
return [colorValue, setColorValue] as const;
return [colorValue, setColorValue, prevColor] as const;
};
export default useColorState;

View File

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

View File

@ -1,6 +1,8 @@
import type { ReactNode } from 'react';
import type { ColorPickerProps } from './ColorPicker';
import type { CSSProperties, FC, ReactNode } from 'react';
import type { Color } from './color';
import type { ColorPickerProps as RcColorPickerProps } from '@rc-component/color-picker';
import type { SizeType } from '../config-provider/SizeContext';
import type { PopoverProps } from '../popover';
export enum ColorFormat {
hex = 'hex',
@ -32,7 +34,6 @@ export interface ColorPickerBaseProps {
prefixCls: string;
format?: keyof typeof ColorFormat;
allowClear?: boolean;
colorCleared?: boolean;
disabled?: boolean;
disabledAlpha?: boolean;
presets?: PresetsItem[];
@ -42,3 +43,36 @@ export interface ColorPickerBaseProps {
}
export type ColorValueType = Color | string | null;
export type ColorPickerProps = Omit<
RcColorPickerProps,
'onChange' | 'value' | 'defaultValue' | 'panelRender' | 'disabledAlpha' | 'onChangeComplete'
> & {
value?: ColorValueType;
defaultValue?: ColorValueType;
children?: React.ReactNode;
open?: boolean;
disabled?: boolean;
placement?: TriggerPlacement;
trigger?: TriggerType;
format?: keyof typeof ColorFormat;
defaultFormat?: keyof typeof ColorFormat;
allowClear?: boolean;
presets?: PresetsItem[];
arrow?: boolean | { pointAtCenter: boolean };
panelRender?: (
panel: React.ReactNode,
extra: { components: { Picker: FC; Presets: FC } },
) => React.ReactNode;
showText?: boolean | ((color: Color) => React.ReactNode);
size?: SizeType;
styles?: { popup?: CSSProperties; popupOverlayInner?: CSSProperties };
rootClassName?: string;
disabledAlpha?: boolean;
[key: `data-${string}`]: string;
onOpenChange?: (open: boolean) => void;
onFormatChange?: (format: ColorFormat) => void;
onChange?: (value: Color, hex: string) => void;
onClear?: () => void;
onChangeComplete?: (value: Color) => void;
} & Pick<PopoverProps, 'getPopupContainer' | 'autoAdjustOverflow' | 'destroyTooltipOnHide'>;

View File

@ -51,27 +51,52 @@ type ComponentsConfig = {
};
export interface ThemeConfig {
/**
* @descCN Design Token
* @descEN Modify Design Token.
*/
token?: Partial<AliasToken>;
/**
* @descCN Component Token Alias Token
* @descEN Modify Component Token and Alias Token applied to components.
*/
components?: ComponentsConfig;
/**
* @descCN Seed Token Map Token
* @descEN Modify the algorithms of theme.
* @default defaultAlgorithm
*/
algorithm?: MappingAlgorithm | MappingAlgorithm[];
/**
* @descCN `ConfigProvider`
* @descEN Whether to inherit the theme configured in the outer layer `ConfigProvider`.
* @default true
*/
inherit?: boolean;
/**
* @descCN `hashed` antd `false` `ture`
* @descEN Whether to enable the `hashed` attribute. If there is only one version of antd in your application, you can set `false` to reduce the bundle size. defaults to `true`.
* @descCN `hashed` antd `false`
* @descEN Whether to enable the `hashed` attribute. If there is only one version of antd in your application, you can set `false` to reduce the bundle size.
* @default true
* @since 5.12.0
*/
hashed?: boolean;
/**
* @descCN `cssVar` CSS `false`
* @descEN Enable CSS variable mode through `cssVar` configuration, This configuration will be inherited. defaults to `false`.
* @descCN `cssVar` CSS
* @descEN Enable CSS variable mode through `cssVar` configuration, This configuration will be inherited.
* @default false
* @since 5.12.0
*/
cssVar?:
| {
/**
* Prefix for css variable, default to `ant`.
* @descCN css
* @descEN Prefix for css variable.
* @default ant
*/
prefix?: string;
/**
* Unique key for theme, should be set manually < react@18.
* @descCN key react@18
* @descEN Unique key for theme, should be set manually < react@18.
*/
key?: string;
}
@ -144,7 +169,16 @@ export type SpaceConfig = ComponentStyleConfig & Pick<SpaceProps, 'size' | 'clas
export type PopupOverflow = 'viewport' | 'scroll';
export interface WaveConfig {
/**
* @descCN `false`
* @descEN Whether to use wave effect. If it needs to close, set to `false`.
* @default true
*/
disabled?: boolean;
/**
* @descCN
* @descEN Customized wave effect.
*/
showEffect?: ShowWaveEffect;
}
@ -155,6 +189,10 @@ export interface ConfigConsumerProps {
iconPrefixCls: string;
getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => string;
renderEmpty?: RenderEmptyHandler;
/**
* @descCN [Content Security Policy](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP) 配置。
* @descEN Set the [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) config.
*/
csp?: CSPConfig;
autoInsertSpaceInButton?: boolean;
input?: InputConfig;

View File

@ -128,11 +128,25 @@ export interface ConfigProviderProps {
textArea?: TextAreaConfig;
select?: SelectConfig;
pagination?: PaginationConfig;
/**
* @descCN `antd/locale`
* @descEN Language package setting, you can find the packages in `antd/locale`.
*/
locale?: Locale;
componentSize?: SizeType;
componentDisabled?: boolean;
/**
* @descCN
* @descEN Set direction of layout.
* @default ltr
*/
direction?: DirectionType;
space?: SpaceConfig;
/**
* @descCN `false`
* @descEN Close the virtual scrolling when setting `false`.
* @default true
*/
virtual?: boolean;
/** @deprecated Please use `popupMatchSelectWidth` instead */
dropdownMatchSelectWidth?: boolean;

View File

@ -56,7 +56,7 @@ export default Demo;
| autoInsertSpaceInButton | 设置为 `false` 时,移除按钮中 2 个汉字之间的空格 | boolean | true | |
| componentDisabled | 设置 antd 组件禁用状态 | boolean | - | 4.21.0 |
| componentSize | 设置 antd 组件大小 | `small` \| `middle` \| `large` | - | |
| csp | 设置 [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 配置 | { nonce: string } | - | |
| csp | 设置 [Content Security Policy](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP) 配置 | { nonce: string } | - | |
| direction | 设置文本展示方向。 [示例](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
| getPopupContainer | 弹出框Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | function(triggerNode) | () => document.body | |
| getTargetContainer | 配置 Affix、Anchor 滚动监听容器。 | () => HTMLElement | () => window | 4.2.0 |

View File

@ -65,19 +65,6 @@ The default locale is en-US, if you need to use other languages, recommend to us
If there are special needs (only modifying single component language), Please use the property: local. Example: [default](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json).
<!-- prettier-ignore -->
:::warning
When use with Next.js App Router, make sure to add `'use client'` before import locale file of dayjs. It's because all components of Ant Design only works in client, importing locale in RSC will not work.
:::
```jsx
import locale from 'antd/es/date-picker/locale/zh_CN';
import 'dayjs/locale/zh-cn';
<DatePicker locale={locale} />;
```
```jsx
// The default locale is en-US, if you want to use other locale, just set locale in entry file globally.
// Make sure you import the relevant dayjs file as well, otherwise the locale won't change for all texts (e.g. range picker months)
@ -86,11 +73,18 @@ import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
<ConfigProvider locale={locale}>
<DatePicker defaultValue={dayjs('2015-01-01', 'YYYY-MM-DD')} />
</ConfigProvider>;
```
<!-- prettier-ignore -->
:::warning
When use with Next.js App Router, make sure to add `'use client'` before import locale file of dayjs. It's because all components of Ant Design only works in client, importing locale in RSC will not work.
:::
### Common API
The following APIs are shared by DatePicker, RangePicker.

View File

@ -66,19 +66,6 @@ demo:
如有特殊需求(仅修改单一组件的语言),请使用 locale 参数,参考:[默认配置](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json)。
<!-- prettier-ignore -->
:::warning
在搭配 Next.js 的 App Router 使用时,注意在引入 dayjs 的 locale 文件时加上 `'use client'`。这是由于 Ant Design 的组件都是客户端组件,在 RSC 中引入 dayjs 的 locale 文件将不会在客户端生效。
:::
```jsx
import locale from 'antd/es/date-picker/locale/zh_CN';
import 'dayjs/locale/zh-cn';
<DatePicker locale={locale} />;
```
```jsx
// 默认语言为 en-US如果你需要设置其他语言推荐在入口文件全局设置 locale
// 确保还导入相关的 dayjs 文件,否则所有文本的区域设置都不会更改(例如范围选择器月份)
@ -87,11 +74,18 @@ import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
<ConfigProvider locale={locale}>
<DatePicker defaultValue={dayjs('2015-01-01', 'YYYY-MM-DD')} />
</ConfigProvider>;
```
<!-- prettier-ignore -->
:::warning
在搭配 Next.js 的 App Router 使用时,注意在引入 dayjs 的 locale 文件时加上 `'use client'`。这是由于 Ant Design 的组件都是客户端组件,在 RSC 中引入 dayjs 的 locale 文件将不会在客户端生效。
:::
### 共同的 API
以下 API 为 DatePicker、 RangePicker 共享的 API。

View File

@ -75,9 +75,15 @@ const BackTop = React.forwardRef<FloatButtonRef, BackTopProps>((props, ref) => {
const groupShape = useContext<FloatButtonShape | undefined>(FloatButtonGroupContext);
const mergeShape = groupShape || shape;
const mergedShape = groupShape || shape;
const contentProps: FloatButtonProps = { prefixCls, icon, type, shape: mergeShape, ...restProps };
const contentProps: FloatButtonProps = {
prefixCls,
icon,
type,
shape: mergedShape,
...restProps,
};
return (
<CSSMotion visible={visible} motionName={`${rootPrefixCls}-fade`}>

View File

@ -6,6 +6,7 @@ import { devUseWarning } from '../_util/warning';
import Badge from '../badge';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import Tooltip from '../tooltip';
import FloatButtonGroupContext from './context';
import Content from './FloatButtonContent';
@ -18,7 +19,6 @@ import type {
FloatButtonShape,
} from './interface';
import useStyle from './style';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
export const floatButtonPrefixCls = 'float-btn';
@ -41,7 +41,7 @@ const FloatButton = React.forwardRef<FloatButtonElement, FloatButtonProps>((prop
const rootCls = useCSSVarCls(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const mergeShape = groupShape || shape;
const mergedShape = groupShape || shape;
const classString = classNames(
hashId,
@ -51,7 +51,7 @@ const FloatButton = React.forwardRef<FloatButtonElement, FloatButtonProps>((prop
className,
rootClassName,
`${prefixCls}-${type}`,
`${prefixCls}-${mergeShape}`,
`${prefixCls}-${mergedShape}`,
{
[`${prefixCls}-rtl`]: direction === 'rtl',
},

View File

@ -36,10 +36,13 @@ export interface RowProps extends React.HTMLAttributes<HTMLDivElement> {
wrap?: boolean;
}
function useMergePropByScreen(oriProp: RowProps['align'] | RowProps['justify'], screen: ScreenMap) {
function useMergedPropByScreen(
oriProp: RowProps['align'] | RowProps['justify'],
screen: ScreenMap,
) {
const [prop, setProp] = React.useState(typeof oriProp === 'string' ? oriProp : '');
const calcMergeAlignOrJustify = () => {
const calcMergedAlignOrJustify = () => {
if (typeof oriProp === 'string') {
setProp(oriProp);
}
@ -61,7 +64,7 @@ function useMergePropByScreen(oriProp: RowProps['align'] | RowProps['justify'],
};
React.useEffect(() => {
calcMergeAlignOrJustify();
calcMergedAlignOrJustify();
}, [JSON.stringify(oriProp), screen]);
return prop;
@ -101,9 +104,9 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
});
// ================================== calc responsive data ==================================
const mergeAlign = useMergePropByScreen(align, curScreens);
const mergedAlign = useMergedPropByScreen(align, curScreens);
const mergeJustify = useMergePropByScreen(justify, curScreens);
const mergedJustify = useMergedPropByScreen(justify, curScreens);
const gutterRef = React.useRef<Gutter | [Gutter, Gutter]>(gutter);
@ -154,8 +157,8 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
prefixCls,
{
[`${prefixCls}-no-wrap`]: wrap === false,
[`${prefixCls}-${mergeJustify}`]: mergeJustify,
[`${prefixCls}-${mergeAlign}`]: mergeAlign,
[`${prefixCls}-${mergedJustify}`]: mergedJustify,
[`${prefixCls}-${mergedAlign}`]: mergedAlign,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,

View File

@ -4,6 +4,8 @@ import DatePicker from '../date-picker/locale/is_IS';
import type { Locale } from '.';
import TimePicker from '../time-picker/locale/is_IS';
const typeTemplate = '${label} er ekki gilt ${type}';
const localeValues: Locale = {
locale: 'is',
Pagination,
@ -42,6 +44,56 @@ const localeValues: Locale = {
Empty: {
description: 'Engin gögn',
},
Form: {
optional: 'Valfrjálst',
defaultValidateMessages: {
default: 'Villa við staðfestingu reits ${label}',
required: 'gjörðu svo vel að koma inn ${label}',
enum: '${label} verður að vera einn af [${enum}]',
whitespace: '${label} getur ekki verið tómur stafur',
date: {
format: '${label} dagsetningarsnið er ógilt',
parse: 'Ekki er hægt að breyta ${label} í dag',
invalid: '${label} er ógild dagsetning',
},
types: {
string: typeTemplate,
method: typeTemplate,
array: typeTemplate,
object: typeTemplate,
number: typeTemplate,
date: typeTemplate,
boolean: typeTemplate,
integer: typeTemplate,
float: typeTemplate,
regexp: typeTemplate,
email: typeTemplate,
url: typeTemplate,
hex: typeTemplate,
},
string: {
len: '${label} verður að vera ${len} stafir',
min: '${label} er að minnsta kosti ${min} stafir að lengd',
max: '${label} getur verið allt að ${max} stafir',
range: '${label} verður að vera á milli ${min}-${max} stafir',
},
number: {
len: '${label} verður að vera jafngildi ${len}',
min: 'Lágmarksgildi ${label} er ${mín}',
max: 'Hámarksgildi ${label} er ${max}',
range: '${label} verður að vera á milli ${min}-${max}',
},
array: {
len: 'Verður að vera ${len}${label}',
min: 'Að minnsta kosti ${min}${label}',
max: 'Í mesta lagi ${max}${label}',
range: 'Magn ${label} verður að vera á milli ${min}-${max}',
},
pattern: {
mismatch: '${label} passar ekki við mynstur ${pattern}',
},
},
},
};
export default localeValues;

View File

@ -1961,11 +1961,11 @@ Array [
tabindex="0"
>
<li
class="ant-menu-submenu ant-menu-submenu-inline ant-menu-submenu-open"
class="ant-menu-submenu ant-menu-submenu-inline"
role="none"
>
<div
aria-expanded="true"
aria-expanded="false"
aria-haspopup="true"
class="ant-menu-submenu-title"
role="menuitem"
@ -2000,6 +2000,47 @@ Array [
class="ant-menu-submenu-arrow"
/>
</div>
</li>
<li
class="ant-menu-submenu ant-menu-submenu-inline ant-menu-submenu-open"
role="none"
>
<div
aria-expanded="true"
aria-haspopup="true"
class="ant-menu-submenu-title"
role="menuitem"
style="padding-left:24px"
tabindex="-1"
>
<span
aria-label="appstore"
class="anticon anticon-appstore ant-menu-item-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="appstore"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z"
/>
</svg>
</span>
<span
class="ant-menu-title-content"
>
Navigation Two
</span>
<i
class="ant-menu-submenu-arrow"
/>
</div>
<ul
class="ant-menu ant-menu-sub ant-menu-inline"
data-menu-list="true"
@ -2030,72 +2071,93 @@ Array [
</span>
</li>
<li
class="ant-menu-item ant-menu-item-only-child"
role="menuitem"
style="padding-left:48px"
tabindex="-1"
class="ant-menu-submenu ant-menu-submenu-inline ant-menu-submenu-open"
role="none"
>
<span
class="ant-menu-title-content"
<div
aria-expanded="true"
aria-haspopup="true"
class="ant-menu-submenu-title"
role="menuitem"
style="padding-left:48px"
tabindex="-1"
>
Option 3
</span>
<span
class="ant-menu-title-content"
>
Submenu
</span>
<i
class="ant-menu-submenu-arrow"
/>
</div>
<ul
class="ant-menu ant-menu-sub ant-menu-inline"
data-menu-list="true"
role="menu"
>
<li
class="ant-menu-item ant-menu-item-selected ant-menu-item-only-child"
role="menuitem"
style="padding-left:72px"
tabindex="-1"
>
<span
class="ant-menu-title-content"
>
Option 1
</span>
</li>
<li
class="ant-menu-item ant-menu-item-only-child"
role="menuitem"
style="padding-left:72px"
tabindex="-1"
>
<span
class="ant-menu-title-content"
>
Option 2
</span>
</li>
<li
class="ant-menu-item ant-menu-item-only-child"
role="menuitem"
style="padding-left:72px"
tabindex="-1"
>
<span
class="ant-menu-title-content"
>
Option 3
</span>
</li>
</ul>
</li>
<li
class="ant-menu-item ant-menu-item-only-child"
role="menuitem"
style="padding-left:48px"
tabindex="-1"
class="ant-menu-submenu ant-menu-submenu-inline"
role="none"
>
<span
class="ant-menu-title-content"
<div
aria-expanded="false"
aria-haspopup="true"
class="ant-menu-submenu-title"
role="menuitem"
style="padding-left:48px"
tabindex="-1"
>
Option 4
</span>
<span
class="ant-menu-title-content"
>
Submenu 2
</span>
<i
class="ant-menu-submenu-arrow"
/>
</div>
</li>
</ul>
</li>
<li
class="ant-menu-submenu ant-menu-submenu-inline"
role="none"
>
<div
aria-expanded="false"
aria-haspopup="true"
class="ant-menu-submenu-title"
role="menuitem"
style="padding-left:24px"
tabindex="-1"
>
<span
aria-label="appstore"
class="anticon anticon-appstore ant-menu-item-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="appstore"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z"
/>
</svg>
</span>
<span
class="ant-menu-title-content"
>
Navigation Two
</span>
<i
class="ant-menu-submenu-arrow"
/>
</div>
</li>
<li
class="ant-menu-submenu ant-menu-submenu-inline"
role="none"

View File

@ -22,44 +22,84 @@ function getItem(
}
const items: MenuItem[] = [
getItem('Navigation One', 'sub1', <MailOutlined />, [
getItem('Option 1', '1'),
getItem('Option 2', '2'),
getItem('Option 3', '3'),
getItem('Option 4', '4'),
getItem('Navigation One', '1', <MailOutlined />, [
getItem('Option 1', '11'),
getItem('Option 2', '12'),
getItem('Option 3', '13'),
getItem('Option 4', '14'),
]),
getItem('Navigation Two', 'sub2', <AppstoreOutlined />, [
getItem('Option 5', '5'),
getItem('Option 6', '6'),
getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]),
getItem('Navigation Two', '2', <AppstoreOutlined />, [
getItem('Option 1', '21'),
getItem('Option 2', '22'),
getItem('Submenu', '23', null, [
getItem('Option 1', '231'),
getItem('Option 2', '232'),
getItem('Option 3', '233'),
]),
getItem('Submenu 2', '24', null, [
getItem('Option 1', '241'),
getItem('Option 2', '242'),
getItem('Option 3', '243'),
]),
]),
getItem('Navigation Three', 'sub4', <SettingOutlined />, [
getItem('Option 9', '9'),
getItem('Option 10', '10'),
getItem('Option 11', '11'),
getItem('Option 12', '12'),
getItem('Navigation Three', '3', <SettingOutlined />, [
getItem('Option 1', '31'),
getItem('Option 2', '32'),
getItem('Option 3', '33'),
getItem('Option 4', '34'),
]),
];
// submenu keys of first level
const rootSubmenuKeys = ['sub1', 'sub2', 'sub4'];
interface LevelKeysProps {
key?: string;
children?: LevelKeysProps[];
}
const getLevelKeys = (items1: LevelKeysProps[]) => {
const key: Record<string, number> = {};
const func = (items2: LevelKeysProps[], level = 1) => {
items2.forEach((item) => {
if (item.key) {
key[item.key] = level;
}
if (item.children) {
return func(item.children, level + 1);
}
});
};
func(items1);
return key;
};
const levelKeys = getLevelKeys(items as LevelKeysProps[]);
const App: React.FC = () => {
const [openKeys, setOpenKeys] = useState(['sub1']);
const [stateOpenKeys, setStateOpenKeys] = useState(['2', '23']);
const onOpenChange: MenuProps['onOpenChange'] = (keys) => {
const latestOpenKey = keys.find((key) => openKeys.indexOf(key) === -1);
if (latestOpenKey && rootSubmenuKeys.indexOf(latestOpenKey!) === -1) {
setOpenKeys(keys);
const onOpenChange: MenuProps['onOpenChange'] = (openKeys) => {
const currentOpenKey = openKeys.find((key) => stateOpenKeys.indexOf(key) === -1);
// open
if (currentOpenKey !== undefined) {
const repeatIndex = openKeys
.filter((key) => key !== currentOpenKey)
.findIndex((key) => levelKeys[key] === levelKeys[currentOpenKey]);
setStateOpenKeys(
openKeys
// remove repeat key
.filter((_, index) => index !== repeatIndex)
// remove current level all child
.filter((key) => levelKeys[key] <= levelKeys[currentOpenKey]),
);
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
// close
setStateOpenKeys(openKeys);
}
};
return (
<Menu
mode="inline"
openKeys={openKeys}
defaultSelectedKeys={['231']}
openKeys={stateOpenKeys}
onOpenChange={onOpenChange}
style={{ width: 256 }}
items={items}

View File

@ -1,6 +1,6 @@
## zh-CN
通过 `notification.useNotification` 创建支持读取 context 的 `contextHolder`。请注意,我们推荐通过顶层注册的方式代替 `message` 静态方法,因为静态方法无法消费上下文,因而 ConfigProvider 的数据也不会生效。
通过 `notification.useNotification` 创建支持读取 context 的 `contextHolder`。请注意,我们推荐通过顶层注册的方式代替 `notification` 静态方法,因为静态方法无法消费上下文,因而 ConfigProvider 的数据也不会生效。
## en-US

View File

@ -54,6 +54,12 @@ Consult [Tooltip's documentation](/components/tooltip/#api) to find more APIs.
<ComponentTokenTable component="Popconfirm"></ComponentTokenTable>
## FAQ
### Why does the warning findDOMNode is deprecated some times appear in strict mode?
This is due to the implementation of `rc-trigger`. `rc-trigger` forces children to accept ref, otherwise it will fall back to findDOMNode, so children need to be native html tags. If not, you need to use `React.forwardRef` transparently passes `ref` to native html tags.
## Note
Please ensure that the child node of `Popconfirm` accepts `onMouseEnter`, `onMouseLeave`, `onFocus`, `onClick` events.

View File

@ -55,6 +55,12 @@ demo:
<ComponentTokenTable component="Popconfirm"></ComponentTokenTable>
## FAQ
### 为何在严格模式中有时候会出现 findDOMNode is deprecated 这个警告?
这是由于 `rc-trigger` 的实现方式导致的,`rc-trigger` 强制要求 children 能够接受 ref否则就会 fallback 到 findDOMNode所以 children 需要是原生 html 标签,如果不是,则需要使用 `React.forwardRef``ref` 透传到原生 html 标签。
## 注意
请确保 `Popconfirm` 的子元素能接受 `onMouseEnter`、`onMouseLeave`、`onFocus`、`onClick` 事件。

View File

@ -211,7 +211,7 @@ exports[`renders components/qr-code/demo/download.tsx extend context correctly 1
>
<div
class="ant-qrcode"
style="margin-bottom: 16px; width: 160px; height: 160px; background-color: rgb(255, 255, 255);"
style="width: 160px; height: 160px; background-color: rgb(255, 255, 255); margin-bottom: 16px;"
>
<canvas
height="160"
@ -235,7 +235,7 @@ exports[`renders components/qr-code/demo/errorlevel.tsx extend context correctly
Array [
<div
class="ant-qrcode"
style="margin-bottom: 16px; width: 160px; height: 160px; background-color: transparent;"
style="width: 160px; height: 160px; background-color: transparent; margin-bottom: 16px;"
>
<canvas
height="160"

View File

@ -169,7 +169,7 @@ exports[`renders components/qr-code/demo/download.tsx correctly 1`] = `
>
<div
class="ant-qrcode"
style="margin-bottom:16px;width:160px;height:160px;background-color:#fff"
style="width:160px;height:160px;background-color:#fff;margin-bottom:16px"
>
<canvas
height="160"
@ -191,7 +191,7 @@ exports[`renders components/qr-code/demo/errorlevel.tsx correctly 1`] = `
Array [
<div
class="ant-qrcode"
style="margin-bottom:16px;width:160px;height:160px;background-color:transparent"
style="width:160px;height:160px;background-color:transparent;margin-bottom:16px"
>
<canvas
height="160"

View File

@ -87,4 +87,11 @@ describe('QRCode test', () => {
);
errSpy.mockRestore();
});
it('correct style order', () => {
const { container } = render(<QRCode value="test" size={80} style={{ width: 100 }} />);
expect(container.querySelector<HTMLDivElement>('.ant-qrcode')).toHaveStyle(
'width: 100px; height: 80px',
);
});
});

View File

@ -1,6 +1,9 @@
import React, { useState } from 'react';
import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
import { QRCode, Button } from 'antd';
import { Button, QRCode } from 'antd';
const MIN_SIZE = 48;
const MAX_SIZE = 300;
const App: React.FC = () => {
const [size, setSize] = useState<number>(160);
@ -8,8 +11,8 @@ const App: React.FC = () => {
const increase = () => {
setSize((prevSize) => {
const newSize = prevSize + 10;
if (newSize > 300) {
return 300;
if (newSize >= MAX_SIZE) {
return MAX_SIZE;
}
return newSize;
});
@ -18,8 +21,8 @@ const App: React.FC = () => {
const decline = () => {
setSize((prevSize) => {
const newSize = prevSize - 10;
if (newSize < 48) {
return 48;
if (newSize <= MIN_SIZE) {
return MIN_SIZE;
}
return newSize;
});
@ -28,10 +31,10 @@ const App: React.FC = () => {
return (
<>
<Button.Group style={{ marginBottom: 16 }}>
<Button onClick={decline} disabled={size <= 48} icon={<MinusOutlined />}>
<Button onClick={decline} disabled={size <= MIN_SIZE} icon={<MinusOutlined />}>
Smaller
</Button>
<Button onClick={increase} disabled={size >= 300} icon={<PlusOutlined />}>
<Button onClick={increase} disabled={size >= MAX_SIZE} icon={<PlusOutlined />}>
Larger
</Button>
</Button.Group>

View File

@ -78,11 +78,15 @@ const QRCode: React.FC<QRCodeProps> = (props) => {
[`${prefixCls}-borderless`]: !bordered,
});
const mergedStyle: React.CSSProperties = {
width: size,
height: size,
backgroundColor: bgColor,
...style,
};
return wrapCSSVar(
<div
className={mergedCls}
style={{ ...style, width: size, height: size, backgroundColor: bgColor }}
>
<div className={mergedCls} style={mergedStyle}>
{status !== 'active' && (
<div className={`${prefixCls}-mask`}>
{status === 'loading' && <Spin />}

View File

@ -1,7 +1,7 @@
## zh-CN
你可以通过 `Table.EXPAND_COLUMN``Table.SELECT_COLUMN` 来控制选择和展开列的顺序。
你可以通过 `Table.EXPAND_COLUMN``Table.SELECTION_COLUMN` 来控制选择和展开列的顺序。
## en-US
You can control the order of the expand and select columns by using `Table.EXPAND_COLUMN` and `Table.SELECT_COLUMN`.
You can control the order of the expand and select columns by using `Table.EXPAND_COLUMN` and `Table.SELECTION_COLUMN`.

View File

@ -108,7 +108,7 @@ const App: React.FC = () => {
setColumns(newColumns);
};
const mergeColumns: TableColumnsType<DataType> = columns.map((col, index) => ({
const mergedColumns = columns.map<TableColumnsType<DataType>[number]>((col, index) => ({
...col,
onHeaderCell: (column: TableColumnsType<DataType>[number]) => ({
width: column.width,
@ -124,7 +124,7 @@ const App: React.FC = () => {
cell: ResizableTitle,
},
}}
columns={mergeColumns}
columns={mergedColumns}
dataSource={data}
/>
);

View File

@ -508,7 +508,7 @@ exports[`renders components/tag/demo/borderlessLayout.tsx extend context correct
exports[`renders components/tag/demo/checkable.tsx extend context correctly 1`] = `
<div
class="ant-flex ant-flex-wrap-wrap ant-flex-align-center"
style="gap: 4px 0;"
style="gap: 4px;"
>
<span>
Categories:

View File

@ -500,7 +500,7 @@ exports[`renders components/tag/demo/borderlessLayout.tsx correctly 1`] = `
exports[`renders components/tag/demo/checkable.tsx correctly 1`] = `
<div
class="ant-flex ant-flex-wrap-wrap ant-flex-align-center"
style="gap:4px 0"
style="gap:4px"
>
<span>
Categories:

View File

@ -14,7 +14,7 @@ const App: React.FC = () => {
};
return (
<Flex gap="4px 0" wrap="wrap" align="center">
<Flex gap={4} wrap="wrap" align="center">
<span>Categories:</span>
{tagsData.map<React.ReactNode>((tag) => (
<Tag.CheckableTag

View File

@ -1,4 +1,5 @@
import type { PresetColorType } from './presetColors';
// ======================================================================
// == Seed Token ==
// ======================================================================
@ -273,7 +274,7 @@ export interface SeedToken extends PresetColorType {
* @nameEN Motion Style
* @desc `false`
* @descEN Used to configure the motion effect, when it is `false`, the motion is turned off
* @default false
* @default true
*/
motion: boolean;
}

View File

@ -86,12 +86,12 @@ const TourPanel: React.FC<TourPanelProps> = (props) => {
const coverNode = isValidNode(cover) ? <div className={`${prefixCls}-cover`}>{cover}</div> : null;
let mergeIndicatorNode: ReactNode;
let mergedIndicatorNode: ReactNode;
if (indicatorsRender) {
mergeIndicatorNode = indicatorsRender(current, total);
mergedIndicatorNode = indicatorsRender(current, total);
} else {
mergeIndicatorNode = [...Array.from({ length: total }).keys()].map<ReactNode>(
mergedIndicatorNode = [...Array.from({ length: total }).keys()].map<ReactNode>(
(stepItem, index) => (
<span
key={stepItem}
@ -121,7 +121,7 @@ const TourPanel: React.FC<TourPanelProps> = (props) => {
{headerNode}
{descriptionNode}
<div className={`${prefixCls}-footer`}>
{total > 1 && <div className={`${prefixCls}-indicators`}>{mergeIndicatorNode}</div>}
{total > 1 && <div className={`${prefixCls}-indicators`}>{mergedIndicatorNode}</div>}
<div className={`${prefixCls}-buttons`}>
{current !== 0 ? (
<Button

View File

@ -137,6 +137,7 @@ const InternalUpload: React.ForwardRefRenderFunction<UploadRef, UploadProps> = (
if (
!exceedMaxCount ||
file.status === 'removed' ||
// We should ignore event if current file is exceed `maxCount`
cloneList.some((f) => f.uid === file.uid)
) {

View File

@ -815,6 +815,53 @@ describe('Upload', () => {
}),
);
});
it('should trigger onChange when defaultFileList.length is longer than maxCount ', async () => {
const onChange = jest.fn();
const { container } = render(
<Upload
onChange={onChange}
maxCount={3}
defaultFileList={[
{
uid: 'bamboo',
name: 'bamboo.png',
},
{
uid: 'little',
name: 'little.png',
},
{
uid: 'foo',
name: 'foo.png',
},
{
uid: 'bar',
name: 'bar.png',
},
{
uid: 'bar1',
name: 'bar1.png',
},
]}
showUploadList
>
<button type="button">upload</button>
</Upload>,
);
fireEvent.click(container.querySelector('.ant-upload-list-item-action')!);
await waitFakeTimer();
// Click delete
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
// Have 3 file
fileList: [expect.anything(), expect.anything(), expect.anything()],
}),
);
});
});
it('auto fill file uid', () => {

View File

@ -8,7 +8,7 @@ import {
PictureTwoTone,
PlusOutlined,
} from '@ant-design/icons';
import { Modal, Upload } from 'antd';
import { Image, Upload } from 'antd';
import type { GetProp, UploadFile, UploadProps } from 'antd';
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
@ -54,8 +54,6 @@ const App: React.FC = () => {
},
]);
const handleCancel = () => setPreviewOpen(false);
const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as FileType);
@ -113,9 +111,17 @@ const App: React.FC = () => {
>
{fileList.length >= 8 ? null : uploadButton}
</Upload>
<Modal open={previewOpen} footer={null} onCancel={handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
{previewImage && (
<Image
wrapperStyle={{ display: 'none' }}
preview={{
visible: previewOpen,
onVisibleChange: (visible) => setPreviewOpen(visible),
afterOpenChange: (visible) => !visible && setPreviewImage(''),
}}
src={previewImage}
/>
)}
</>
);
};

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { PlusOutlined } from '@ant-design/icons';
import { Modal, Upload } from 'antd';
import { Image, Upload } from 'antd';
import type { GetProp, UploadFile, UploadProps } from 'antd';
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
@ -16,7 +16,6 @@ const getBase64 = (file: FileType): Promise<string> =>
const App: React.FC = () => {
const [previewOpen, setPreviewOpen] = useState(false);
const [previewImage, setPreviewImage] = useState('');
const [previewTitle, setPreviewTitle] = useState('');
const [fileList, setFileList] = useState<UploadFile[]>([
{
uid: '-1',
@ -56,8 +55,6 @@ const App: React.FC = () => {
},
]);
const handleCancel = () => setPreviewOpen(false);
const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as FileType);
@ -65,7 +62,6 @@ const App: React.FC = () => {
setPreviewImage(file.url || (file.preview as string));
setPreviewOpen(true);
setPreviewTitle(file.name || file.url!.substring(file.url!.lastIndexOf('/') + 1));
};
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) =>
@ -88,9 +84,17 @@ const App: React.FC = () => {
>
{fileList.length >= 8 ? null : uploadButton}
</Upload>
<Modal open={previewOpen} title={previewTitle} footer={null} onCancel={handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
{previewImage && (
<Image
wrapperStyle={{ display: 'none' }}
preview={{
visible: previewOpen,
onVisibleChange: (visible) => setPreviewOpen(visible),
afterOpenChange: (visible) => !visible && setPreviewImage(''),
}}
src={previewImage}
/>
)}
</>
);
};

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { PlusOutlined } from '@ant-design/icons';
import { Modal, Upload } from 'antd';
import { Image, Upload } from 'antd';
import type { GetProp, UploadFile, UploadProps } from 'antd';
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
@ -16,7 +16,6 @@ const getBase64 = (file: FileType): Promise<string> =>
const App: React.FC = () => {
const [previewOpen, setPreviewOpen] = useState(false);
const [previewImage, setPreviewImage] = useState('');
const [previewTitle, setPreviewTitle] = useState('');
const [fileList, setFileList] = useState<UploadFile[]>([
{
uid: '-1',
@ -38,8 +37,6 @@ const App: React.FC = () => {
},
]);
const handleCancel = () => setPreviewOpen(false);
const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as FileType);
@ -47,7 +44,6 @@ const App: React.FC = () => {
setPreviewImage(file.url || (file.preview as string));
setPreviewOpen(true);
setPreviewTitle(file.name || file.url!.substring(file.url!.lastIndexOf('/') + 1));
};
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) =>
@ -70,9 +66,17 @@ const App: React.FC = () => {
>
{fileList.length >= 8 ? null : uploadButton}
</Upload>
<Modal open={previewOpen} title={previewTitle} footer={null} onCancel={handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
{previewImage && (
<Image
wrapperStyle={{ display: 'none' }}
preview={{
visible: previewOpen,
onVisibleChange: (visible) => setPreviewOpen(visible),
afterOpenChange: (visible) => !visible && setPreviewImage(''),
}}
src={previewImage}
/>
)}
</>
);
};

View File

@ -135,6 +135,8 @@ type InputRef = GetRef<typeof Input>;
Please check whether you have imported dayjs locale correctly.
```jsx
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');

View File

@ -163,6 +163,8 @@ type InputRef = GetRef<typeof Input>;
请检查是否正确设置了 dayjs 语言包。
```js
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');

View File

@ -1,6 +1,6 @@
{
"name": "antd",
"version": "5.15.3",
"version": "5.15.4",
"description": "An enterprise-class UI design language and React components implementation",
"keywords": [
"ant",
@ -170,8 +170,8 @@
"@ant-design/tools": "^18.0.2",
"@antv/g6": "^4.8.24",
"@babel/eslint-plugin": "^7.23.5",
"@biomejs/biome": "^1.6.2",
"@codesandbox/sandpack-react": "^2.13.5",
"@biomejs/biome": "^1.6.3",
"@codesandbox/sandpack-react": "^2.13.7",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
@ -208,7 +208,7 @@
"@types/prismjs": "^1.26.3",
"@types/progress": "^2.0.7",
"@types/qs": "^6.9.14",
"@types/react": "^18.2.67",
"@types/react": "^18.2.72",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.2.22",
"@types/react-highlight-words": "^0.16.7",
@ -217,11 +217,11 @@
"@types/tar": "^6.1.11",
"@types/throttle-debounce": "^5.0.2",
"@types/warning": "^3.0.3",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"ali-oss": "^6.20.0",
"antd-img-crop": "^4.21.0",
"antd-style": "^3.6.1",
"antd-style": "^3.6.2",
"antd-token-previewer": "^2.0.8",
"chalk": "^4.1.2",
"cheerio": "1.0.0-rc.12",
@ -287,14 +287,14 @@
"pretty-format": "^29.7.0",
"prismjs": "^1.29.0",
"progress": "^2.0.3",
"puppeteer": "^22.6.0",
"puppeteer": "^22.6.1",
"qs": "^6.12.0",
"rc-footer": "^0.6.8",
"rc-tween-one": "^3.0.6",
"rc-virtual-list": "^3.11.4",
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-countup": "^6.5.2",
"react-countup": "^6.5.3",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.6",
"react-fast-marquee": "^1.6.4",
@ -314,17 +314,17 @@
"remark-preset-lint-recommended": "^6.1.3",
"runes2": "^1.1.4",
"semver": "^7.6.0",
"sharp": "^0.33.2",
"sharp": "^0.33.3",
"simple-git": "^3.23.0",
"size-limit": "^11.1.2",
"stylelint": "^16.2.1",
"stylelint": "^16.3.1",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^36.0.0",
"stylelint-prettier": "^5.0.0",
"sylvanas": "^0.6.1",
"tar": "^6.2.1",
"tar-fs": "^3.0.5",
"terser": "^5.29.2",
"terser": "^5.30.0",
"tsx": "^4.7.1",
"typedoc": "^0.25.12",
"typescript": "~5.4.3",