mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
Merge pull request #48133 from ant-design/master
chore: merge master into feature
This commit is contained in:
commit
405394a8e4
@ -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"
|
||||
|
@ -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};
|
||||
|
@ -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 }) => {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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}
|
||||
|
8
.github/workflows/pr-contributor-welcome.yml
vendored
8
.github/workflows/pr-contributor-welcome.yml
vendored
@ -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 }}
|
||||
|
@ -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`
|
||||
|
@ -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`
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -25,7 +25,7 @@ const MyPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<Space wrap>
|
||||
<Button type="primary" onClick={showMessage}>
|
||||
Open message
|
||||
</Button>
|
||||
|
@ -17,7 +17,7 @@ const MyPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<Space wrap>
|
||||
<Button type="primary" onClick={showMessage}>
|
||||
Message for only one
|
||||
</Button>
|
||||
|
@ -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>,
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>,
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -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 = () => {
|
||||
|
@ -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}
|
||||
|
@ -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());
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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'>;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 |
|
||||
|
@ -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.
|
||||
|
@ -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。
|
||||
|
@ -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`}>
|
||||
|
@ -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',
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -1,6 +1,6 @@
|
||||
## zh-CN
|
||||
|
||||
通过 `notification.useNotification` 创建支持读取 context 的 `contextHolder`。请注意,我们推荐通过顶层注册的方式代替 `message` 静态方法,因为静态方法无法消费上下文,因而 ConfigProvider 的数据也不会生效。
|
||||
通过 `notification.useNotification` 创建支持读取 context 的 `contextHolder`。请注意,我们推荐通过顶层注册的方式代替 `notification` 静态方法,因为静态方法无法消费上下文,因而 ConfigProvider 的数据也不会生效。
|
||||
|
||||
## en-US
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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` 事件。
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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 />}
|
||||
|
@ -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`.
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
) {
|
||||
|
@ -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', () => {
|
||||
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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');
|
||||
|
@ -163,6 +163,8 @@ type InputRef = GetRef<typeof Input>;
|
||||
请检查是否正确设置了 dayjs 语言包。
|
||||
|
||||
```js
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import 'dayjs/locale/zh-cn';
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
|
24
package.json
24
package.json
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user