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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,15 @@ jobs:
if: github.event.pull_request.merged == true && github.repository == 'ant-design/ant-design' if: github.event.pull_request.merged == true && github.repository == 'ant-design/ant-design'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: 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 - name: Comment on PR
if: steps.get_commit_count.outputs.COUNT < 3
uses: actions-cool/maintain-one-comment@v3 uses: actions-cool/maintain-one-comment@v3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} 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 ## 5.15.3
`2024-03-17` `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 ## 5.15.3
`2024-03-17` `2024-03-17`

View File

@ -41,6 +41,11 @@ type TypeWarning = BaseTypeWarning & {
}; };
export interface WarningContextProps { 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; strict?: boolean;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -85,11 +85,21 @@ It accepts all props which native buttons support.
## FAQ ## 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? ### 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`. 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}> <ConfigProvider autoInsertSpaceInButton={false}>
<Button>按钮</Button> <Button>按钮</Button>
</ConfigProvider> </ConfigProvider>

View File

@ -90,11 +90,21 @@ group:
## FAQ ## 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}> <ConfigProvider autoInsertSpaceInButton={false}>
<Button>按钮</Button> <Button>按钮</Button>
</ConfigProvider> </ConfigProvider>

View File

@ -1,9 +1,5 @@
import type { CSSProperties, FC } from 'react'; import React, { useContext, useMemo, useRef } from 'react';
import React, { useContext, useMemo, useRef, useState } from 'react'; import type { HsbaColorType } from '@rc-component/color-picker';
import type {
HsbaColorType,
ColorPickerProps as RcColorPickerProps,
} from '@rc-component/color-picker';
import classNames from 'classnames'; import classNames from 'classnames';
import useMergedState from 'rc-util/lib/hooks/useMergedState'; 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 DisabledContext from '../config-provider/DisabledContext';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import useSize from '../config-provider/hooks/useSize'; import useSize from '../config-provider/hooks/useSize';
import type { SizeType } from '../config-provider/SizeContext';
import { FormItemInputContext, NoFormStyle } from '../form/context'; import { FormItemInputContext, NoFormStyle } from '../form/context';
import type { PopoverProps } from '../popover'; import type { PopoverProps } from '../popover';
import Popover from '../popover'; import Popover from '../popover';
@ -23,50 +18,10 @@ import type { Color } from './color';
import ColorPickerPanel from './ColorPickerPanel'; import ColorPickerPanel from './ColorPickerPanel';
import ColorTrigger from './components/ColorTrigger'; import ColorTrigger from './components/ColorTrigger';
import useColorState from './hooks/useColorState'; import useColorState from './hooks/useColorState';
import type { import type { ColorPickerBaseProps, ColorPickerProps, TriggerPlacement } from './interface';
ColorFormat,
ColorPickerBaseProps,
ColorValueType,
PresetsItem,
TriggerPlacement,
TriggerType,
} from './interface';
import useStyle from './style'; import useStyle from './style';
import { genAlphaColor, generateColor, getAlphaColor } from './util'; 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> & { type CompoundedComponent = React.FC<ColorPickerProps> & {
_InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel; _InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel;
}; };
@ -109,7 +64,7 @@ const ColorPicker: CompoundedComponent = (props) => {
const contextDisabled = useContext(DisabledContext); const contextDisabled = useContext(DisabledContext);
const mergedDisabled = disabled ?? contextDisabled; const mergedDisabled = disabled ?? contextDisabled;
const [colorValue, setColorValue] = useColorState('', { const [colorValue, setColorValue, prevValue] = useColorState('', {
value, value,
defaultValue, defaultValue,
}); });
@ -124,8 +79,6 @@ const ColorPicker: CompoundedComponent = (props) => {
onChange: onFormatChange, onChange: onFormatChange,
}); });
const [colorCleared, setColorCleared] = useState(!value && !defaultValue);
const prefixCls = getPrefixCls('color-picker', customizePrefixCls); const prefixCls = getPrefixCls('color-picker', customizePrefixCls);
const isAlphaColor = useMemo(() => getAlphaColor(colorValue) < 100, [colorValue]); const isAlphaColor = useMemo(() => getAlphaColor(colorValue) < 100, [colorValue]);
@ -138,19 +91,19 @@ const ColorPicker: CompoundedComponent = (props) => {
const rootCls = useCSSVarCls(prefixCls); const rootCls = useCSSVarCls(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls); const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const rtlCls = { [`${prefixCls}-rtl`]: direction }; const rtlCls = { [`${prefixCls}-rtl`]: direction };
const mergeRootCls = classNames(rootClassName, cssVarCls, rootCls, rtlCls); const mergedRootCls = classNames(rootClassName, cssVarCls, rootCls, rtlCls);
const mergeCls = classNames( const mergedCls = classNames(
getStatusClassNames(prefixCls, contextStatus), getStatusClassNames(prefixCls, contextStatus),
{ {
[`${prefixCls}-sm`]: mergedSize === 'small', [`${prefixCls}-sm`]: mergedSize === 'small',
[`${prefixCls}-lg`]: mergedSize === 'large', [`${prefixCls}-lg`]: mergedSize === 'large',
}, },
colorPicker?.className, colorPicker?.className,
mergeRootCls, mergedRootCls,
className, className,
hashId, hashId,
); );
const mergePopupCls = classNames(prefixCls, mergeRootCls); const mergedPopupCls = classNames(prefixCls, mergedRootCls);
const popupAllowCloseRef = useRef(true); const popupAllowCloseRef = useRef(true);
@ -167,14 +120,16 @@ const ColorPicker: CompoundedComponent = (props) => {
const handleChange = (data: Color, type?: HsbaColorType, pickColor?: boolean) => { const handleChange = (data: Color, type?: HsbaColorType, pickColor?: boolean) => {
let color: Color = generateColor(data); let color: Color = generateColor(data);
// If color is cleared, reset alpha to 100
const isNull = value === null || (!value && defaultValue === null); const isNull = value === null || (!value && defaultValue === null);
if (colorCleared || isNull) { if (prevValue.current?.cleared || isNull) {
setColorCleared(false);
// ignore alpha slider // ignore alpha slider
if (getAlphaColor(colorValue) === 0 && type !== 'alpha') { if (getAlphaColor(colorValue) === 0 && type !== 'alpha') {
color = genAlphaColor(color); color = genAlphaColor(color);
} }
} }
// ignore alpha color // ignore alpha color
if (disabledAlpha && isAlphaColor) { if (disabledAlpha && isAlphaColor) {
color = genAlphaColor(color); color = genAlphaColor(color);
@ -192,7 +147,6 @@ const ColorPicker: CompoundedComponent = (props) => {
}; };
const handleClear = () => { const handleClear = () => {
setColorCleared(true);
onClear?.(); onClear?.();
}; };
@ -221,7 +175,6 @@ const ColorPicker: CompoundedComponent = (props) => {
prefixCls, prefixCls,
color: colorValue, color: colorValue,
allowClear, allowClear,
colorCleared,
disabled: mergedDisabled, disabled: mergedDisabled,
disabledAlpha, disabledAlpha,
presets, presets,
@ -254,21 +207,20 @@ const ColorPicker: CompoundedComponent = (props) => {
/> />
</NoFormStyle> </NoFormStyle>
} }
overlayClassName={mergePopupCls} overlayClassName={mergedPopupCls}
{...popoverProps} {...popoverProps}
> >
{children || ( {children || (
<ColorTrigger <ColorTrigger
open={popupOpen} open={popupOpen}
className={mergeCls} className={mergedCls}
style={mergedStyle} style={mergedStyle}
color={value ? generateColor(value) : colorValue}
prefixCls={prefixCls} prefixCls={prefixCls}
disabled={mergedDisabled} disabled={mergedDisabled}
colorCleared={colorCleared}
showText={showText} showText={showText}
format={formatValue} format={formatValue}
{...rest} {...rest}
color={colorValue}
/> />
)} )}
</Popover>, </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 { createEvent, fireEvent, render } from '@testing-library/react';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
@ -11,8 +11,9 @@ import ConfigProvider from '../../config-provider';
import Form from '../../form'; import Form from '../../form';
import theme from '../../theme'; import theme from '../../theme';
import type { Color } from '../color'; import type { Color } from '../color';
import type { ColorPickerProps } from '../ColorPicker';
import ColorPicker from '../ColorPicker'; import ColorPicker from '../ColorPicker';
import type { ColorPickerProps, ColorValueType } from '../interface';
import { generateColor } from '../util';
function doMouseMove( function doMouseMove(
container: HTMLElement, container: HTMLElement,
@ -607,4 +608,94 @@ describe('ColorPicker', () => {
const { container } = render(<ColorPicker />); const { container } = render(<ColorPicker />);
expect(container.querySelector('.ant-color-picker-clear')).toBeTruthy(); 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< extends Pick<
RcColor, RcColor,
'toHsb' | 'toHsbString' | 'toHex' | 'toHexString' | 'toRgb' | 'toRgbString' 'toHsb' | 'toHsbString' | 'toHex' | 'toHexString' | 'toRgb' | 'toRgbString'
> {} > {
cleared: boolean | 'controlled';
}
export class ColorFactory { export class ColorFactory implements Color {
/** Original Color object */ /** Original Color object */
private metaColor: RcColor; private metaColor: RcColor;
public cleared: boolean = false;
constructor(color: ColorGenInput<Color>) { constructor(color: ColorGenInput<Color>) {
this.metaColor = new RcColor(color as ColorGenInput); this.metaColor = new RcColor(color as ColorGenInput);
if (!color) { if (!color) {
this.metaColor.setAlpha(0); this.metaColor.setAlpha(0);
this.cleared = true;
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
import type { ReactNode } from 'react'; import type { CSSProperties, FC, ReactNode } from 'react';
import type { ColorPickerProps } from './ColorPicker';
import type { Color } from './color'; 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 { export enum ColorFormat {
hex = 'hex', hex = 'hex',
@ -32,7 +34,6 @@ export interface ColorPickerBaseProps {
prefixCls: string; prefixCls: string;
format?: keyof typeof ColorFormat; format?: keyof typeof ColorFormat;
allowClear?: boolean; allowClear?: boolean;
colorCleared?: boolean;
disabled?: boolean; disabled?: boolean;
disabledAlpha?: boolean; disabledAlpha?: boolean;
presets?: PresetsItem[]; presets?: PresetsItem[];
@ -42,3 +43,36 @@ export interface ColorPickerBaseProps {
} }
export type ColorValueType = Color | string | null; 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 { export interface ThemeConfig {
/**
* @descCN Design Token
* @descEN Modify Design Token.
*/
token?: Partial<AliasToken>; token?: Partial<AliasToken>;
/**
* @descCN Component Token Alias Token
* @descEN Modify Component Token and Alias Token applied to components.
*/
components?: ComponentsConfig; components?: ComponentsConfig;
/**
* @descCN Seed Token Map Token
* @descEN Modify the algorithms of theme.
* @default defaultAlgorithm
*/
algorithm?: MappingAlgorithm | MappingAlgorithm[]; algorithm?: MappingAlgorithm | MappingAlgorithm[];
/**
* @descCN `ConfigProvider`
* @descEN Whether to inherit the theme configured in the outer layer `ConfigProvider`.
* @default true
*/
inherit?: boolean; inherit?: boolean;
/** /**
* @descCN `hashed` antd `false` `ture` * @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. defaults to `true`. * @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; hashed?: boolean;
/** /**
* @descCN `cssVar` CSS `false` * @descCN `cssVar` CSS
* @descEN Enable CSS variable mode through `cssVar` configuration, This configuration will be inherited. defaults to `false`. * @descEN Enable CSS variable mode through `cssVar` configuration, This configuration will be inherited.
* @default false
* @since 5.12.0
*/ */
cssVar?: cssVar?:
| { | {
/** /**
* Prefix for css variable, default to `ant`. * @descCN css
* @descEN Prefix for css variable.
* @default ant
*/ */
prefix?: string; 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; key?: string;
} }
@ -144,7 +169,16 @@ export type SpaceConfig = ComponentStyleConfig & Pick<SpaceProps, 'size' | 'clas
export type PopupOverflow = 'viewport' | 'scroll'; export type PopupOverflow = 'viewport' | 'scroll';
export interface WaveConfig { export interface WaveConfig {
/**
* @descCN `false`
* @descEN Whether to use wave effect. If it needs to close, set to `false`.
* @default true
*/
disabled?: boolean; disabled?: boolean;
/**
* @descCN
* @descEN Customized wave effect.
*/
showEffect?: ShowWaveEffect; showEffect?: ShowWaveEffect;
} }
@ -155,6 +189,10 @@ export interface ConfigConsumerProps {
iconPrefixCls: string; iconPrefixCls: string;
getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => string; getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => string;
renderEmpty?: RenderEmptyHandler; 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; csp?: CSPConfig;
autoInsertSpaceInButton?: boolean; autoInsertSpaceInButton?: boolean;
input?: InputConfig; input?: InputConfig;

View File

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

View File

@ -56,7 +56,7 @@ export default Demo;
| autoInsertSpaceInButton | 设置为 `false` 时,移除按钮中 2 个汉字之间的空格 | boolean | true | | | autoInsertSpaceInButton | 设置为 `false` 时,移除按钮中 2 个汉字之间的空格 | boolean | true | |
| componentDisabled | 设置 antd 组件禁用状态 | boolean | - | 4.21.0 | | componentDisabled | 设置 antd 组件禁用状态 | boolean | - | 4.21.0 |
| componentSize | 设置 antd 组件大小 | `small` \| `middle` \| `large` | - | | | 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` | | | direction | 设置文本展示方向。 [示例](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
| getPopupContainer | 弹出框Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | function(triggerNode) | () => document.body | | | getPopupContainer | 弹出框Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | function(triggerNode) | () => document.body | |
| getTargetContainer | 配置 Affix、Anchor 滚动监听容器。 | () => HTMLElement | () => window | 4.2.0 | | 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). 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 ```jsx
// The default locale is en-US, if you want to use other locale, just set locale in entry file globally. // 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) // 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'; import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
<ConfigProvider locale={locale}> <ConfigProvider locale={locale}>
<DatePicker defaultValue={dayjs('2015-01-01', 'YYYY-MM-DD')} /> <DatePicker defaultValue={dayjs('2015-01-01', 'YYYY-MM-DD')} />
</ConfigProvider>; </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 ### Common API
The following APIs are shared by DatePicker, RangePicker. 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)。 如有特殊需求(仅修改单一组件的语言),请使用 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 ```jsx
// 默认语言为 en-US如果你需要设置其他语言推荐在入口文件全局设置 locale // 默认语言为 en-US如果你需要设置其他语言推荐在入口文件全局设置 locale
// 确保还导入相关的 dayjs 文件,否则所有文本的区域设置都不会更改(例如范围选择器月份) // 确保还导入相关的 dayjs 文件,否则所有文本的区域设置都不会更改(例如范围选择器月份)
@ -87,11 +74,18 @@ import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn'; import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
<ConfigProvider locale={locale}> <ConfigProvider locale={locale}>
<DatePicker defaultValue={dayjs('2015-01-01', 'YYYY-MM-DD')} /> <DatePicker defaultValue={dayjs('2015-01-01', 'YYYY-MM-DD')} />
</ConfigProvider>; </ConfigProvider>;
``` ```
<!-- prettier-ignore -->
:::warning
在搭配 Next.js 的 App Router 使用时,注意在引入 dayjs 的 locale 文件时加上 `'use client'`。这是由于 Ant Design 的组件都是客户端组件,在 RSC 中引入 dayjs 的 locale 文件将不会在客户端生效。
:::
### 共同的 API ### 共同的 API
以下 API 为 DatePicker、 RangePicker 共享的 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 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 ( return (
<CSSMotion visible={visible} motionName={`${rootPrefixCls}-fade`}> <CSSMotion visible={visible} motionName={`${rootPrefixCls}-fade`}>

View File

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

View File

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

View File

@ -4,6 +4,8 @@ import DatePicker from '../date-picker/locale/is_IS';
import type { Locale } from '.'; import type { Locale } from '.';
import TimePicker from '../time-picker/locale/is_IS'; import TimePicker from '../time-picker/locale/is_IS';
const typeTemplate = '${label} er ekki gilt ${type}';
const localeValues: Locale = { const localeValues: Locale = {
locale: 'is', locale: 'is',
Pagination, Pagination,
@ -42,6 +44,56 @@ const localeValues: Locale = {
Empty: { Empty: {
description: 'Engin gögn', 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; export default localeValues;

View File

@ -1961,11 +1961,11 @@ Array [
tabindex="0" tabindex="0"
> >
<li <li
class="ant-menu-submenu ant-menu-submenu-inline ant-menu-submenu-open" class="ant-menu-submenu ant-menu-submenu-inline"
role="none" role="none"
> >
<div <div
aria-expanded="true" aria-expanded="false"
aria-haspopup="true" aria-haspopup="true"
class="ant-menu-submenu-title" class="ant-menu-submenu-title"
role="menuitem" role="menuitem"
@ -2000,6 +2000,47 @@ Array [
class="ant-menu-submenu-arrow" class="ant-menu-submenu-arrow"
/> />
</div> </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 <ul
class="ant-menu ant-menu-sub ant-menu-inline" class="ant-menu ant-menu-sub ant-menu-inline"
data-menu-list="true" data-menu-list="true"
@ -2030,72 +2071,93 @@ Array [
</span> </span>
</li> </li>
<li <li
class="ant-menu-item ant-menu-item-only-child" class="ant-menu-submenu ant-menu-submenu-inline ant-menu-submenu-open"
role="menuitem" role="none"
style="padding-left:48px"
tabindex="-1"
> >
<span <div
class="ant-menu-title-content" 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>
<li <li
class="ant-menu-item ant-menu-item-only-child" class="ant-menu-submenu ant-menu-submenu-inline"
role="menuitem" role="none"
style="padding-left:48px"
tabindex="-1"
> >
<span <div
class="ant-menu-title-content" 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> </li>
</ul> </ul>
</li> </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 <li
class="ant-menu-submenu ant-menu-submenu-inline" class="ant-menu-submenu ant-menu-submenu-inline"
role="none" role="none"

View File

@ -22,44 +22,84 @@ function getItem(
} }
const items: MenuItem[] = [ const items: MenuItem[] = [
getItem('Navigation One', 'sub1', <MailOutlined />, [ getItem('Navigation One', '1', <MailOutlined />, [
getItem('Option 1', '1'), getItem('Option 1', '11'),
getItem('Option 2', '2'), getItem('Option 2', '12'),
getItem('Option 3', '3'), getItem('Option 3', '13'),
getItem('Option 4', '4'), getItem('Option 4', '14'),
]), ]),
getItem('Navigation Two', 'sub2', <AppstoreOutlined />, [ getItem('Navigation Two', '2', <AppstoreOutlined />, [
getItem('Option 5', '5'), getItem('Option 1', '21'),
getItem('Option 6', '6'), getItem('Option 2', '22'),
getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]), 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('Navigation Three', '3', <SettingOutlined />, [
getItem('Option 9', '9'), getItem('Option 1', '31'),
getItem('Option 10', '10'), getItem('Option 2', '32'),
getItem('Option 11', '11'), getItem('Option 3', '33'),
getItem('Option 12', '12'), getItem('Option 4', '34'),
]), ]),
]; ];
// submenu keys of first level interface LevelKeysProps {
const rootSubmenuKeys = ['sub1', 'sub2', 'sub4']; 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 App: React.FC = () => {
const [openKeys, setOpenKeys] = useState(['sub1']); const [stateOpenKeys, setStateOpenKeys] = useState(['2', '23']);
const onOpenChange: MenuProps['onOpenChange'] = (keys) => { const onOpenChange: MenuProps['onOpenChange'] = (openKeys) => {
const latestOpenKey = keys.find((key) => openKeys.indexOf(key) === -1); const currentOpenKey = openKeys.find((key) => stateOpenKeys.indexOf(key) === -1);
if (latestOpenKey && rootSubmenuKeys.indexOf(latestOpenKey!) === -1) { // open
setOpenKeys(keys); 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 { } else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : []); // close
setStateOpenKeys(openKeys);
} }
}; };
return ( return (
<Menu <Menu
mode="inline" mode="inline"
openKeys={openKeys} defaultSelectedKeys={['231']}
openKeys={stateOpenKeys}
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
style={{ width: 256 }} style={{ width: 256 }}
items={items} items={items}

View File

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

View File

@ -54,6 +54,12 @@ Consult [Tooltip's documentation](/components/tooltip/#api) to find more APIs.
<ComponentTokenTable component="Popconfirm"></ComponentTokenTable> <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 ## Note
Please ensure that the child node of `Popconfirm` accepts `onMouseEnter`, `onMouseLeave`, `onFocus`, `onClick` events. 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> <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` 事件。 请确保 `Popconfirm` 的子元素能接受 `onMouseEnter`、`onMouseLeave`、`onFocus`、`onClick` 事件。

View File

@ -211,7 +211,7 @@ exports[`renders components/qr-code/demo/download.tsx extend context correctly 1
> >
<div <div
class="ant-qrcode" 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 <canvas
height="160" height="160"
@ -235,7 +235,7 @@ exports[`renders components/qr-code/demo/errorlevel.tsx extend context correctly
Array [ Array [
<div <div
class="ant-qrcode" 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 <canvas
height="160" height="160"

View File

@ -169,7 +169,7 @@ exports[`renders components/qr-code/demo/download.tsx correctly 1`] = `
> >
<div <div
class="ant-qrcode" 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 <canvas
height="160" height="160"
@ -191,7 +191,7 @@ exports[`renders components/qr-code/demo/errorlevel.tsx correctly 1`] = `
Array [ Array [
<div <div
class="ant-qrcode" 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 <canvas
height="160" height="160"

View File

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

View File

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

View File

@ -1,7 +1,7 @@
## zh-CN ## zh-CN
你可以通过 `Table.EXPAND_COLUMN``Table.SELECT_COLUMN` 来控制选择和展开列的顺序。 你可以通过 `Table.EXPAND_COLUMN``Table.SELECTION_COLUMN` 来控制选择和展开列的顺序。
## en-US ## 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); setColumns(newColumns);
}; };
const mergeColumns: TableColumnsType<DataType> = columns.map((col, index) => ({ const mergedColumns = columns.map<TableColumnsType<DataType>[number]>((col, index) => ({
...col, ...col,
onHeaderCell: (column: TableColumnsType<DataType>[number]) => ({ onHeaderCell: (column: TableColumnsType<DataType>[number]) => ({
width: column.width, width: column.width,
@ -124,7 +124,7 @@ const App: React.FC = () => {
cell: ResizableTitle, cell: ResizableTitle,
}, },
}} }}
columns={mergeColumns} columns={mergedColumns}
dataSource={data} 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`] = ` exports[`renders components/tag/demo/checkable.tsx extend context correctly 1`] = `
<div <div
class="ant-flex ant-flex-wrap-wrap ant-flex-align-center" class="ant-flex ant-flex-wrap-wrap ant-flex-align-center"
style="gap: 4px 0;" style="gap: 4px;"
> >
<span> <span>
Categories: 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`] = ` exports[`renders components/tag/demo/checkable.tsx correctly 1`] = `
<div <div
class="ant-flex ant-flex-wrap-wrap ant-flex-align-center" class="ant-flex ant-flex-wrap-wrap ant-flex-align-center"
style="gap:4px 0" style="gap:4px"
> >
<span> <span>
Categories: Categories:

View File

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

View File

@ -1,4 +1,5 @@
import type { PresetColorType } from './presetColors'; import type { PresetColorType } from './presetColors';
// ====================================================================== // ======================================================================
// == Seed Token == // == Seed Token ==
// ====================================================================== // ======================================================================
@ -273,7 +274,7 @@ export interface SeedToken extends PresetColorType {
* @nameEN Motion Style * @nameEN Motion Style
* @desc `false` * @desc `false`
* @descEN Used to configure the motion effect, when it is `false`, the motion is turned off * @descEN Used to configure the motion effect, when it is `false`, the motion is turned off
* @default false * @default true
*/ */
motion: boolean; 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; const coverNode = isValidNode(cover) ? <div className={`${prefixCls}-cover`}>{cover}</div> : null;
let mergeIndicatorNode: ReactNode; let mergedIndicatorNode: ReactNode;
if (indicatorsRender) { if (indicatorsRender) {
mergeIndicatorNode = indicatorsRender(current, total); mergedIndicatorNode = indicatorsRender(current, total);
} else { } else {
mergeIndicatorNode = [...Array.from({ length: total }).keys()].map<ReactNode>( mergedIndicatorNode = [...Array.from({ length: total }).keys()].map<ReactNode>(
(stepItem, index) => ( (stepItem, index) => (
<span <span
key={stepItem} key={stepItem}
@ -121,7 +121,7 @@ const TourPanel: React.FC<TourPanelProps> = (props) => {
{headerNode} {headerNode}
{descriptionNode} {descriptionNode}
<div className={`${prefixCls}-footer`}> <div className={`${prefixCls}-footer`}>
{total > 1 && <div className={`${prefixCls}-indicators`}>{mergeIndicatorNode}</div>} {total > 1 && <div className={`${prefixCls}-indicators`}>{mergedIndicatorNode}</div>}
<div className={`${prefixCls}-buttons`}> <div className={`${prefixCls}-buttons`}>
{current !== 0 ? ( {current !== 0 ? (
<Button <Button

View File

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

View File

@ -8,7 +8,7 @@ import {
PictureTwoTone, PictureTwoTone,
PlusOutlined, PlusOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Modal, Upload } from 'antd'; import { Image, Upload } from 'antd';
import type { GetProp, UploadFile, UploadProps } from 'antd'; import type { GetProp, UploadFile, UploadProps } from 'antd';
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]; type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
@ -54,8 +54,6 @@ const App: React.FC = () => {
}, },
]); ]);
const handleCancel = () => setPreviewOpen(false);
const handlePreview = async (file: UploadFile) => { const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) { if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as FileType); file.preview = await getBase64(file.originFileObj as FileType);
@ -113,9 +111,17 @@ const App: React.FC = () => {
> >
{fileList.length >= 8 ? null : uploadButton} {fileList.length >= 8 ? null : uploadButton}
</Upload> </Upload>
<Modal open={previewOpen} footer={null} onCancel={handleCancel}> {previewImage && (
<img alt="example" style={{ width: '100%' }} src={previewImage} /> <Image
</Modal> 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 React, { useState } from 'react';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import { Modal, Upload } from 'antd'; import { Image, Upload } from 'antd';
import type { GetProp, UploadFile, UploadProps } from 'antd'; import type { GetProp, UploadFile, UploadProps } from 'antd';
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]; type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
@ -16,7 +16,6 @@ const getBase64 = (file: FileType): Promise<string> =>
const App: React.FC = () => { const App: React.FC = () => {
const [previewOpen, setPreviewOpen] = useState(false); const [previewOpen, setPreviewOpen] = useState(false);
const [previewImage, setPreviewImage] = useState(''); const [previewImage, setPreviewImage] = useState('');
const [previewTitle, setPreviewTitle] = useState('');
const [fileList, setFileList] = useState<UploadFile[]>([ const [fileList, setFileList] = useState<UploadFile[]>([
{ {
uid: '-1', uid: '-1',
@ -56,8 +55,6 @@ const App: React.FC = () => {
}, },
]); ]);
const handleCancel = () => setPreviewOpen(false);
const handlePreview = async (file: UploadFile) => { const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) { if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as FileType); file.preview = await getBase64(file.originFileObj as FileType);
@ -65,7 +62,6 @@ const App: React.FC = () => {
setPreviewImage(file.url || (file.preview as string)); setPreviewImage(file.url || (file.preview as string));
setPreviewOpen(true); setPreviewOpen(true);
setPreviewTitle(file.name || file.url!.substring(file.url!.lastIndexOf('/') + 1));
}; };
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) =>
@ -88,9 +84,17 @@ const App: React.FC = () => {
> >
{fileList.length >= 8 ? null : uploadButton} {fileList.length >= 8 ? null : uploadButton}
</Upload> </Upload>
<Modal open={previewOpen} title={previewTitle} footer={null} onCancel={handleCancel}> {previewImage && (
<img alt="example" style={{ width: '100%' }} src={previewImage} /> <Image
</Modal> 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 React, { useState } from 'react';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import { Modal, Upload } from 'antd'; import { Image, Upload } from 'antd';
import type { GetProp, UploadFile, UploadProps } from 'antd'; import type { GetProp, UploadFile, UploadProps } from 'antd';
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]; type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
@ -16,7 +16,6 @@ const getBase64 = (file: FileType): Promise<string> =>
const App: React.FC = () => { const App: React.FC = () => {
const [previewOpen, setPreviewOpen] = useState(false); const [previewOpen, setPreviewOpen] = useState(false);
const [previewImage, setPreviewImage] = useState(''); const [previewImage, setPreviewImage] = useState('');
const [previewTitle, setPreviewTitle] = useState('');
const [fileList, setFileList] = useState<UploadFile[]>([ const [fileList, setFileList] = useState<UploadFile[]>([
{ {
uid: '-1', uid: '-1',
@ -38,8 +37,6 @@ const App: React.FC = () => {
}, },
]); ]);
const handleCancel = () => setPreviewOpen(false);
const handlePreview = async (file: UploadFile) => { const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) { if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as FileType); file.preview = await getBase64(file.originFileObj as FileType);
@ -47,7 +44,6 @@ const App: React.FC = () => {
setPreviewImage(file.url || (file.preview as string)); setPreviewImage(file.url || (file.preview as string));
setPreviewOpen(true); setPreviewOpen(true);
setPreviewTitle(file.name || file.url!.substring(file.url!.lastIndexOf('/') + 1));
}; };
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) =>
@ -70,9 +66,17 @@ const App: React.FC = () => {
> >
{fileList.length >= 8 ? null : uploadButton} {fileList.length >= 8 ? null : uploadButton}
</Upload> </Upload>
<Modal open={previewOpen} title={previewTitle} footer={null} onCancel={handleCancel}> {previewImage && (
<img alt="example" style={{ width: '100%' }} src={previewImage} /> <Image
</Modal> 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. Please check whether you have imported dayjs locale correctly.
```jsx ```jsx
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn'; import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn'); dayjs.locale('zh-cn');

View File

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

View File

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