mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-27 12:39:49 +08:00
Merge branch 'master' into fix-bug-#51152
This commit is contained in:
commit
37cef7fe97
@ -8,9 +8,6 @@ import SiteContext from '../SiteContext';
|
||||
import ContributorAvatar from './ContributorAvatar';
|
||||
|
||||
const useStyle = createStyles(({ token, css }) => ({
|
||||
contributorsList: css`
|
||||
margin-top: 120px !important;
|
||||
`,
|
||||
listMobile: css`
|
||||
margin: 1em 0 !important;
|
||||
`,
|
||||
@ -50,7 +47,7 @@ const Contributors: React.FC<ContributorsProps> = ({ filename }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.contributorsList, { [styles.listMobile]: isMobile })}>
|
||||
<div className={classNames({ [styles.listMobile]: isMobile })}>
|
||||
<div className={styles.title}>{formatMessage({ id: 'app.content.contributors' })}</div>
|
||||
<ContributorsList
|
||||
cache
|
||||
|
@ -94,9 +94,11 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
juejinLink={meta.frontmatter.juejin_url}
|
||||
/>
|
||||
</InViewSuspense>
|
||||
<InViewSuspense fallback={<div style={{ height: 50, marginTop: 120 }} />}>
|
||||
<Contributors filename={meta.frontmatter.filename} />
|
||||
</InViewSuspense>
|
||||
<div style={{ marginTop: 120 }}>
|
||||
<InViewSuspense fallback={<div style={{ height: 50 }} />}>
|
||||
<Contributors filename={meta.frontmatter.filename} />
|
||||
</InViewSuspense>
|
||||
</div>
|
||||
</article>
|
||||
<InViewSuspense fallback={null}>
|
||||
<PrevAndNext rtl={isRTL} />
|
||||
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -133,7 +133,7 @@ jobs:
|
||||
bunx nyc report --reporter text -t coverage --report-dir coverage
|
||||
rm -rf persist-coverage
|
||||
- name: Upload coverage to codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
# use own token to upload coverage reports
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
@ -58,7 +58,13 @@ jobs:
|
||||
if (comment.body.includes('VISUAL_DIFF_FAILED')) {
|
||||
hasDiffFailed = true;
|
||||
}
|
||||
if (comment.body.includes('- [x] Visual diff is acceptable')) {
|
||||
|
||||
// https://regex101.com/r/kLjudz/1
|
||||
const RE = /(?<=\>\s\[!IMPORTANT\].*?- \[ \])/s;
|
||||
if (
|
||||
comment.body.includes('- [x] Visual diff is acceptable') &&
|
||||
comment.body.match(RE) == null /** 检查 IMPORTANT 是否存在未勾选的 */
|
||||
) {
|
||||
hasMemberApprove = true;
|
||||
}
|
||||
}
|
||||
|
@ -62,5 +62,17 @@
|
||||
"https://github.com/ant-design/ant-design/issues/50960",
|
||||
"https://github.com/ant-design/ant-design/issues/50969"
|
||||
],
|
||||
"5.22.0": ["https://github.com/ant-design/ant-design/issues/51601"]
|
||||
"5.21.6": [
|
||||
"https://github.com/ant-design/ant-design/issues/51420",
|
||||
"https://github.com/ant-design/ant-design/issues/51430"
|
||||
],
|
||||
"5.22.0": [
|
||||
"https://github.com/ant-design/ant-design/issues/51601",
|
||||
"https://github.com/ant-design/ant-design/issues/51420",
|
||||
"https://github.com/ant-design/ant-design/issues/51430"
|
||||
],
|
||||
"5.22.1": [
|
||||
"https://github.com/ant-design/ant-design/issues/51420",
|
||||
"https://github.com/ant-design/ant-design/issues/51430"
|
||||
]
|
||||
}
|
||||
|
@ -15,6 +15,21 @@ tag: vVERSION
|
||||
|
||||
---
|
||||
|
||||
## 5.22.2
|
||||
|
||||
`2024-11-19`
|
||||
|
||||
- 🐞 Fix Input.OTP focus from advancing when previous input is empty. [#51664](https://github.com/ant-design/ant-design/pull/51664) [@thecodesalim](https://github.com/thecodesalim)
|
||||
- 🐞 Adjust Modal function call not to scroll the confirm button when it get auto focused. [#51647](https://github.com/ant-design/ant-design/pull/51647) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Fix Form `rules` with same error content will cause React render warning. [#51636](https://github.com/ant-design/ant-design/pull/51636) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Refactor Button `focus` logic trigger with `useEffect` to resolve some async load case not get `autoFocus`. [#51624](https://github.com/ant-design/ant-design/pull/51624) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 Fix Button custom icon not center-aligned. [#51652](https://github.com/ant-design/ant-design/pull/51652) [@afc163](https://github.com/afc163)
|
||||
- 🐞 Fix Table `getCheckboxProps` event handlers being overridden by internal selection logic. [#51661](https://github.com/ant-design/ant-design/pull/51661) [@Zyf665](https://github.com/Zyf665)
|
||||
- 🐞 Fix Tree that `onCheck` and `onSelect` were not properly triggered. [#51448](https://github.com/ant-design/ant-design/pull/51448) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 🌐 Localization
|
||||
- 🇷🇺 Add support for Russian translation. [#51619](https://github.com/ant-design/ant-design/pull/51619) [@avvakumovid](https://github.com/avvakumovid)
|
||||
- 🇮🇹 Add support for Italian translation in TimePicker. [#51685](https://github.com/ant-design/ant-design/pull/51685) [@LorenzoCardinali](https://github.com/LorenzoCardinali)
|
||||
|
||||
## 5.22.1
|
||||
|
||||
`2024-11-13`
|
||||
|
@ -15,6 +15,21 @@ tag: vVERSION
|
||||
|
||||
---
|
||||
|
||||
## 5.22.2
|
||||
|
||||
`2024-11-19`
|
||||
|
||||
- 🐞 修复 Input.OTP 组件在有非法输入时仍会切换到下一个输入框的问题。[#51664](https://github.com/ant-design/ant-design/pull/51664) [@thecodesalim](https://github.com/thecodesalim)
|
||||
- 🐞 调整 Modal 确认函数,使其在弹出后聚焦确认按钮时不要滚动窗体。[#51647](https://github.com/ant-design/ant-design/pull/51647) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 修复 Form `rules` 生成多条相同错误时会报 React 渲染错误的问题。[#51636](https://github.com/ant-design/ant-design/pull/51636) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 调整 Button 使用 `useEffect` 来触发 `autoFocus` 逻辑,以解决一些异步渲染场景下 Button 无法自动聚焦的问题。[#51624](https://github.com/ant-design/ant-design/pull/51624) [@zombieJ](https://github.com/zombieJ)
|
||||
- 🐞 修复 Button 中使用自定义三方图标库时图标未居中的问题。[#51652](https://github.com/ant-design/ant-design/pull/51652) [@afc163](https://github.com/afc163)
|
||||
- 🐞 修复 Table 组件 `getCheckboxProps` 中的事件处理器被内部选择逻辑覆盖的问题。[#51661](https://github.com/ant-design/ant-design/pull/51661) [@Zyf665](https://github.com/Zyf665)
|
||||
- 🐞 修复 Tree 组件的 `onCheck` 和 `onSelect` 事件没有被正确触发的问题。[#51448](https://github.com/ant-design/ant-design/pull/51448) [@Wxh16144](https://github.com/Wxh16144)
|
||||
- 🌐 本地化
|
||||
- 🇷🇺 添加了俄语翻译支持。[#51619](https://github.com/ant-design/ant-design/pull/51619) [@avvakumovid](https://github.com/avvakumovid)
|
||||
- 🇮🇹 为 TimePicker 添加了意大利语翻译。[#51685](https://github.com/ant-design/ant-design/pull/51685) [@LorenzoCardinali](https://github.com/LorenzoCardinali)
|
||||
|
||||
## 5.22.1
|
||||
|
||||
`2024-11-13`
|
||||
|
@ -52,7 +52,9 @@ const ActionButton: React.FC<ActionButtonProps> = (props) => {
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
if (autoFocus) {
|
||||
timeoutId = setTimeout(() => {
|
||||
buttonRef.current?.focus();
|
||||
buttonRef.current?.focus({
|
||||
preventScroll: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
|
@ -474,4 +474,10 @@ describe('Button', () => {
|
||||
'--ant-button-solid-text-color': '#000',
|
||||
});
|
||||
});
|
||||
|
||||
it('autoFocus should work', () => {
|
||||
const { container } = render(<Button autoFocus>button</Button>);
|
||||
|
||||
expect(container.querySelector('button')).toBe(document.activeElement);
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { Children, createRef, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import React, { Children, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import omit from 'rc-util/lib/omit';
|
||||
import { composeRef } from 'rc-util/lib/ref';
|
||||
import { useComposeRef } from 'rc-util/lib/ref';
|
||||
|
||||
import { devUseWarning } from '../_util/warning';
|
||||
import Wave from '../_util/wave';
|
||||
@ -22,7 +22,7 @@ import { isTwoCNChar, isUnBorderedButtonVariant, spaceChildren } from './buttonH
|
||||
import IconWrapper from './IconWrapper';
|
||||
import LoadingIcon from './LoadingIcon';
|
||||
import useStyle from './style';
|
||||
import CompactCmp from './style/compactCmp';
|
||||
import Compact from './style/compact';
|
||||
|
||||
export type LegacyButtonType = ButtonType | 'danger';
|
||||
|
||||
@ -119,6 +119,7 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
classNames: customClassNames,
|
||||
style: customStyle = {},
|
||||
autoInsertSpace,
|
||||
autoFocus,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@ -162,13 +163,15 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
|
||||
const [hasTwoCNChar, setHasTwoCNChar] = useState<boolean>(false);
|
||||
|
||||
const internalRef = createRef<HTMLButtonElement | HTMLAnchorElement>();
|
||||
const buttonRef = useRef<HTMLButtonElement | HTMLAnchorElement>();
|
||||
|
||||
const buttonRef = composeRef(ref, internalRef);
|
||||
const mergedRef = useComposeRef(ref, buttonRef);
|
||||
|
||||
const needInserted =
|
||||
Children.count(children) === 1 && !icon && !isUnBorderedButtonVariant(mergedVariant);
|
||||
|
||||
// ========================= Effect =========================
|
||||
// Loading
|
||||
useEffect(() => {
|
||||
let delayTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
if (loadingOrDelay.delay > 0) {
|
||||
@ -190,12 +193,13 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
return cleanupTimer;
|
||||
}, [loadingOrDelay]);
|
||||
|
||||
// Two chinese characters check
|
||||
useEffect(() => {
|
||||
// FIXME: for HOC usage like <FormatMessage />
|
||||
if (!buttonRef || !(buttonRef as any).current || !mergedInsertSpace) {
|
||||
if (!buttonRef.current || !mergedInsertSpace) {
|
||||
return;
|
||||
}
|
||||
const buttonText = (buttonRef as any).current.textContent;
|
||||
const buttonText = buttonRef.current.textContent || '';
|
||||
if (needInserted && isTwoCNChar(buttonText)) {
|
||||
if (!hasTwoCNChar) {
|
||||
setHasTwoCNChar(true);
|
||||
@ -203,8 +207,16 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
} else if (hasTwoCNChar) {
|
||||
setHasTwoCNChar(false);
|
||||
}
|
||||
}, [buttonRef]);
|
||||
});
|
||||
|
||||
// Auto focus
|
||||
useEffect(() => {
|
||||
if (autoFocus && buttonRef.current) {
|
||||
buttonRef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
// ========================= Events =========================
|
||||
const handleClick = React.useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement, MouseEvent>) => {
|
||||
// FIXME: https://github.com/ant-design/ant-design/issues/30207
|
||||
@ -217,6 +229,7 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
[props.onClick, innerLoading, mergedDisabled],
|
||||
);
|
||||
|
||||
// ========================== Warn ==========================
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warning = devUseWarning('Button');
|
||||
|
||||
@ -233,6 +246,7 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
);
|
||||
}
|
||||
|
||||
// ========================== Size ==========================
|
||||
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
|
||||
|
||||
const sizeClassNameMap = { large: 'lg', small: 'sm', middle: undefined };
|
||||
@ -245,6 +259,7 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
|
||||
const linkButtonRestProps = omit(rest as ButtonProps & { navigate: any }, ['navigate']);
|
||||
|
||||
// ========================= Render =========================
|
||||
const classes = classNames(
|
||||
prefixCls,
|
||||
hashId,
|
||||
@ -301,7 +316,7 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
href={mergedDisabled ? undefined : linkButtonRestProps.href}
|
||||
style={fullStyle}
|
||||
onClick={handleClick}
|
||||
ref={buttonRef as React.Ref<HTMLAnchorElement>}
|
||||
ref={mergedRef as React.Ref<HTMLAnchorElement>}
|
||||
tabIndex={mergedDisabled ? -1 : 0}
|
||||
>
|
||||
{iconNode}
|
||||
@ -318,12 +333,11 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
style={fullStyle}
|
||||
onClick={handleClick}
|
||||
disabled={mergedDisabled}
|
||||
ref={buttonRef as React.Ref<HTMLButtonElement>}
|
||||
ref={mergedRef as React.Ref<HTMLButtonElement>}
|
||||
>
|
||||
{iconNode}
|
||||
{kids}
|
||||
{/* Styles: compact */}
|
||||
{!!compactItemClassnames && <CompactCmp key="compact" prefixCls={prefixCls} />}
|
||||
{compactItemClassnames && <Compact prefixCls={prefixCls} />}
|
||||
</button>
|
||||
);
|
||||
|
||||
|
50
components/button/style/compact.ts
Normal file
50
components/button/style/compact.ts
Normal file
@ -0,0 +1,50 @@
|
||||
// Style as inline component
|
||||
import type { CSSObject } from '@ant-design/cssinjs';
|
||||
|
||||
import { genCompactItemStyle } from '../../style/compact-item';
|
||||
import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical';
|
||||
import type { GenerateStyle } from '../../theme/internal';
|
||||
import { genSubStyleComponent } from '../../theme/internal';
|
||||
import type { ButtonToken } from './token';
|
||||
import { prepareComponentToken, prepareToken } from './token';
|
||||
|
||||
const genButtonCompactStyle: GenerateStyle<ButtonToken> = (token) => {
|
||||
const { componentCls, colorPrimaryHover, lineWidth, calc } = token;
|
||||
const insetOffset = calc(lineWidth).mul(-1).equal();
|
||||
const getCompactBorderStyle = (vertical?: boolean) =>
|
||||
({
|
||||
[`${componentCls}-compact${vertical ? '-vertical' : ''}-item${componentCls}-primary:not([disabled])`]:
|
||||
{
|
||||
'& + &::before': {
|
||||
position: 'absolute',
|
||||
top: vertical ? insetOffset : 0,
|
||||
insetInlineStart: vertical ? 0 : insetOffset,
|
||||
backgroundColor: colorPrimaryHover,
|
||||
content: '""',
|
||||
width: vertical ? '100%' : lineWidth,
|
||||
height: vertical ? lineWidth : '100%',
|
||||
},
|
||||
},
|
||||
}) as CSSObject;
|
||||
// Special styles for Primary Button
|
||||
return {
|
||||
...getCompactBorderStyle(),
|
||||
...getCompactBorderStyle(true),
|
||||
};
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genSubStyleComponent(
|
||||
['Button', 'compact'],
|
||||
(token) => {
|
||||
const buttonToken = prepareToken(token);
|
||||
|
||||
return [
|
||||
// Space Compact
|
||||
genCompactItemStyle(buttonToken),
|
||||
genCompactItemVerticalStyle(buttonToken),
|
||||
genButtonCompactStyle(buttonToken),
|
||||
];
|
||||
},
|
||||
prepareComponentToken,
|
||||
);
|
@ -1,72 +0,0 @@
|
||||
// Style as inline component
|
||||
import { unit } from '@ant-design/cssinjs';
|
||||
|
||||
import { genCompactItemStyle } from '../../style/compact-item';
|
||||
import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical';
|
||||
import type { GenerateStyle } from '../../theme/internal';
|
||||
import { genSubStyleComponent } from '../../theme/internal';
|
||||
import type { ButtonToken } from './token';
|
||||
import { prepareComponentToken, prepareToken } from './token';
|
||||
|
||||
const genButtonCompactStyle: GenerateStyle<ButtonToken> = (token) => {
|
||||
const { componentCls, calc } = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
// Special styles for Primary Button
|
||||
[`&-compact-item${componentCls}-primary`]: {
|
||||
[`&:not([disabled]) + ${componentCls}-compact-item${componentCls}-primary:not([disabled])`]:
|
||||
{
|
||||
position: 'relative',
|
||||
|
||||
'&:before': {
|
||||
position: 'absolute',
|
||||
top: calc(token.lineWidth).mul(-1).equal(),
|
||||
insetInlineStart: calc(token.lineWidth).mul(-1).equal(),
|
||||
display: 'inline-block',
|
||||
width: token.lineWidth,
|
||||
height: `calc(100% + ${unit(token.lineWidth)} * 2)`,
|
||||
backgroundColor: token.colorPrimaryHover,
|
||||
content: '""',
|
||||
},
|
||||
},
|
||||
},
|
||||
// Special styles for Primary Button
|
||||
'&-compact-vertical-item': {
|
||||
[`&${componentCls}-primary`]: {
|
||||
[`&:not([disabled]) + ${componentCls}-compact-vertical-item${componentCls}-primary:not([disabled])`]:
|
||||
{
|
||||
position: 'relative',
|
||||
|
||||
'&:before': {
|
||||
position: 'absolute',
|
||||
top: calc(token.lineWidth).mul(-1).equal(),
|
||||
insetInlineStart: calc(token.lineWidth).mul(-1).equal(),
|
||||
display: 'inline-block',
|
||||
width: `calc(100% + ${unit(token.lineWidth)} * 2)`,
|
||||
height: token.lineWidth,
|
||||
backgroundColor: token.colorPrimaryHover,
|
||||
content: '""',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genSubStyleComponent(
|
||||
['Button', 'compact'],
|
||||
(token) => {
|
||||
const buttonToken = prepareToken(token);
|
||||
|
||||
return [
|
||||
// Space Compact
|
||||
genCompactItemStyle(buttonToken),
|
||||
genCompactItemVerticalStyle(buttonToken),
|
||||
genButtonCompactStyle(buttonToken),
|
||||
];
|
||||
},
|
||||
prepareComponentToken,
|
||||
);
|
@ -38,12 +38,8 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
|
||||
'> span': {
|
||||
display: 'inline-block',
|
||||
},
|
||||
|
||||
[`${componentCls}-icon`]: {
|
||||
lineHeight: 1,
|
||||
[`> span, ${componentCls}-icon`]: {
|
||||
display: 'inline-flex',
|
||||
},
|
||||
|
||||
'> a': {
|
||||
@ -529,10 +525,11 @@ const genButtonStyle = (token: ButtonToken, prefixCls = ''): CSSInterpolation =>
|
||||
buttonPaddingHorizontal,
|
||||
iconCls,
|
||||
buttonPaddingVertical,
|
||||
motionDurationSlow,
|
||||
motionEaseInOut,
|
||||
buttonIconOnlyFontSize,
|
||||
opacityLoading,
|
||||
} = token;
|
||||
|
||||
const iconOnlyCls = `${componentCls}-icon-only`;
|
||||
|
||||
return [
|
||||
{
|
||||
[prefixCls]: {
|
||||
@ -542,7 +539,7 @@ const genButtonStyle = (token: ButtonToken, prefixCls = ''): CSSInterpolation =>
|
||||
padding: `${unit(buttonPaddingVertical!)} ${unit(buttonPaddingHorizontal!)}`,
|
||||
borderRadius,
|
||||
|
||||
[`&${iconOnlyCls}`]: {
|
||||
[`&${componentCls}-icon-only`]: {
|
||||
width: controlHeight,
|
||||
paddingInline: 0,
|
||||
|
||||
@ -556,22 +553,21 @@ const genButtonStyle = (token: ButtonToken, prefixCls = ''): CSSInterpolation =>
|
||||
},
|
||||
|
||||
[iconCls]: {
|
||||
fontSize: token.buttonIconOnlyFontSize,
|
||||
fontSize: buttonIconOnlyFontSize,
|
||||
},
|
||||
},
|
||||
|
||||
// Loading
|
||||
[`&${componentCls}-loading`]: {
|
||||
opacity: token.opacityLoading,
|
||||
opacity: opacityLoading,
|
||||
cursor: 'default',
|
||||
},
|
||||
|
||||
[`${componentCls}-loading-icon`]: {
|
||||
transition: `width ${token.motionDurationSlow} ${token.motionEaseInOut}, opacity ${token.motionDurationSlow} ${token.motionEaseInOut}`,
|
||||
transition: `width ${motionDurationSlow} ${motionEaseInOut}, opacity ${motionDurationSlow} ${motionEaseInOut}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Shape - patch prefixCls again to override solid border radius style
|
||||
{
|
||||
[`${componentCls}${componentCls}-circle${prefixCls}`]: genCircleButtonStyle(token),
|
||||
|
@ -31,7 +31,7 @@ demo:
|
||||
<code src="./demo/search.tsx">Search</code>
|
||||
<code src="./demo/lazy.tsx">Load Options Lazily</code>
|
||||
<code src="./demo/fields-name.tsx">Custom Field Names</code>
|
||||
<code src="./demo/suffix.tsx" debug>Prefix and Suffix</code>
|
||||
<code src="./demo/suffix.tsx" version="5.22.0">Prefix and Suffix</code>
|
||||
<code src="./demo/custom-dropdown.tsx">Custom dropdown</code>
|
||||
<code src="./demo/placement.tsx">Placement</code>
|
||||
<code src="./demo/status.tsx">Status</code>
|
||||
@ -67,7 +67,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| maxTagCount | Max tag count to show. `responsive` will cost render performance | number \| `responsive` | - | 4.17.0 |
|
||||
| maxTagPlaceholder | Placeholder for not showing tags | ReactNode \| function(omittedValues) | - | 4.17.0 |
|
||||
| maxTagTextLength | Max tag text length to show | number | - | 4.17.0 |
|
||||
| notFoundContent | Specify content to show when no result matches | string | `Not Found` | |
|
||||
| notFoundContent | Specify content to show when no result matches | ReactNode | `Not Found` | |
|
||||
| open | Set visible of cascader popup | boolean | - | 4.17.0 |
|
||||
| options | The data options of cascade | [Option](#option)\[] | - | |
|
||||
| placeholder | The input placeholder | string | - | |
|
||||
|
@ -32,7 +32,7 @@ demo:
|
||||
<code src="./demo/search.tsx">搜索</code>
|
||||
<code src="./demo/lazy.tsx">动态加载选项</code>
|
||||
<code src="./demo/fields-name.tsx">自定义字段名</code>
|
||||
<code src="./demo/suffix.tsx" debug>前后缀</code>
|
||||
<code src="./demo/suffix.tsx" version="5.22.0">前后缀</code>
|
||||
<code src="./demo/custom-dropdown.tsx">扩展菜单</code>
|
||||
<code src="./demo/placement.tsx">弹出位置</code>
|
||||
<code src="./demo/status.tsx">自定义状态</code>
|
||||
@ -68,7 +68,7 @@ demo:
|
||||
| maxTagCount | 最多显示多少个 tag,响应式模式会对性能产生损耗 | number \| `responsive` | - | 4.17.0 |
|
||||
| maxTagPlaceholder | 隐藏 tag 时显示的内容 | ReactNode \| function(omittedValues) | - | 4.17.0 |
|
||||
| maxTagTextLength | 最大显示的 tag 文本长度 | number | - | 4.17.0 |
|
||||
| notFoundContent | 当下拉列表为空时显示的内容 | string | `Not Found` | |
|
||||
| notFoundContent | 当下拉列表为空时显示的内容 | ReactNode | `Not Found` | |
|
||||
| open | 控制浮层显隐 | boolean | - | 4.17.0 |
|
||||
| options | 可选项数据源 | [Option](#option)\[] | - | |
|
||||
| placeholder | 输入框占位文本 | string | - | |
|
||||
|
@ -78,6 +78,17 @@ const ErrorList: React.FC<ErrorListProps> = ({
|
||||
];
|
||||
}, [help, helpStatus, debounceErrors, debounceWarnings]);
|
||||
|
||||
const filledKeyFullKeyList = React.useMemo<ErrorEntity[]>(() => {
|
||||
const keysCount: Record<string, number> = {};
|
||||
fullKeyList.forEach(({ key }) => {
|
||||
keysCount[key] = (keysCount[key] || 0) + 1;
|
||||
});
|
||||
return fullKeyList.map((entity, index) => ({
|
||||
...entity,
|
||||
key: keysCount[entity.key] > 1 ? `${entity.key}-fallback-${index}` : entity.key,
|
||||
}));
|
||||
}, [fullKeyList]);
|
||||
|
||||
const helpProps: { id?: string } = {};
|
||||
|
||||
if (fieldId) {
|
||||
@ -88,7 +99,7 @@ const ErrorList: React.FC<ErrorListProps> = ({
|
||||
<CSSMotion
|
||||
motionDeadline={collapseMotion.motionDeadline}
|
||||
motionName={`${prefixCls}-show-help`}
|
||||
visible={!!fullKeyList.length}
|
||||
visible={!!filledKeyFullKeyList.length}
|
||||
onVisibleChanged={onVisibleChanged}
|
||||
>
|
||||
{(holderProps) => {
|
||||
@ -109,7 +120,7 @@ const ErrorList: React.FC<ErrorListProps> = ({
|
||||
role="alert"
|
||||
>
|
||||
<CSSMotionList
|
||||
keys={fullKeyList}
|
||||
keys={filledKeyFullKeyList}
|
||||
{...initCollapseMotion(prefixCls)}
|
||||
motionName={`${prefixCls}-show-help-item`}
|
||||
component={false}
|
||||
|
@ -2459,4 +2459,60 @@ describe('Form', () => {
|
||||
fireEvent.click(container.querySelector('input')!);
|
||||
expect(container.querySelector('input')?.checked).toBeTruthy();
|
||||
});
|
||||
|
||||
it('not warning for react key', async () => {
|
||||
const MockInput = (props: { onChange?: (value: number[]) => void }) => (
|
||||
<Input
|
||||
onChange={({ target: { value } }) => {
|
||||
props.onChange?.(value.split(',').map(Number));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const { container } = render(
|
||||
<Form>
|
||||
<Form.Item>
|
||||
<Form.Item
|
||||
name="test"
|
||||
rules={[
|
||||
{
|
||||
type: 'array',
|
||||
defaultField: {
|
||||
type: 'number',
|
||||
min: 10,
|
||||
message: 'LESS_THAN_10',
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<MockInput />
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
|
||||
function expectErrors(errors: string[]) {
|
||||
expect(container.querySelectorAll('.ant-form-item-explain-error')).toHaveLength(
|
||||
errors.length,
|
||||
);
|
||||
errors.forEach((error, index) => {
|
||||
expect(container.querySelectorAll('.ant-form-item-explain-error')[index]).toHaveTextContent(
|
||||
error,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// user type something and clear
|
||||
await changeValue(0, '1');
|
||||
expectErrors(['LESS_THAN_10']);
|
||||
|
||||
await changeValue(0, '1,1');
|
||||
expectErrors(['LESS_THAN_10', 'LESS_THAN_10']);
|
||||
|
||||
await changeValue(0, '1');
|
||||
expectErrors(['LESS_THAN_10']);
|
||||
|
||||
await changeValue(0, '100');
|
||||
expectErrors([]);
|
||||
});
|
||||
});
|
||||
|
@ -208,7 +208,7 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
const nextCells = patchValue(index, txt);
|
||||
|
||||
const nextIndex = Math.min(index + txt.length, length - 1);
|
||||
if (nextIndex !== index) {
|
||||
if (nextIndex !== index && nextCells[index] !== undefined) {
|
||||
refs.current[nextIndex]?.focus();
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { TextAreaRef as RcTextAreaRef } from 'rc-textarea';
|
||||
import type { TextAreaRef as RcTextAreaRef, TextAreaProps as RcTextAreaProps } from 'rc-textarea';
|
||||
import RcTextArea from 'rc-textarea';
|
||||
import type { TextAreaProps as RcTextAreaProps } from 'rc-textarea/lib/interface';
|
||||
|
||||
import getAllowClear from '../_util/getAllowClear';
|
||||
import type { InputStatus } from '../_util/statusUtils';
|
||||
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
||||
|
@ -25,17 +25,17 @@ const ContainerHeight = 400;
|
||||
const App: React.FC = () => {
|
||||
const [data, setData] = useState<UserItem[]>([]);
|
||||
|
||||
const appendData = () => {
|
||||
const appendData = (showMessage = true) => {
|
||||
fetch(fakeDataUrl)
|
||||
.then((res) => res.json())
|
||||
.then((body) => {
|
||||
setData(data.concat(body.results));
|
||||
message.success(`${body.results.length} more items loaded!`);
|
||||
showMessage && message.success(`${body.results.length} more items loaded!`);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
appendData();
|
||||
appendData(false);
|
||||
}, []);
|
||||
|
||||
const onScroll = (e: React.UIEvent<HTMLElement, UIEvent>) => {
|
||||
|
@ -80,6 +80,7 @@ const localeValues: Locale = {
|
||||
copy: 'Копировать',
|
||||
copied: 'Скопировано',
|
||||
expand: 'Раскрыть',
|
||||
collapse: "Свернуть",
|
||||
},
|
||||
Form: {
|
||||
optional: '(необязательно)',
|
||||
@ -138,6 +139,12 @@ const localeValues: Locale = {
|
||||
expired: 'QR-код устарел',
|
||||
refresh: 'Обновить',
|
||||
},
|
||||
ColorPicker: {
|
||||
presetEmpty: 'Пустой',
|
||||
transparent: 'Прозрачный',
|
||||
singleColor: 'Один цвет',
|
||||
gradientColor: 'Градиент',
|
||||
}
|
||||
};
|
||||
|
||||
export default localeValues;
|
||||
|
@ -52,7 +52,7 @@ return (
|
||||
<code src="./demo/label-in-value.tsx">Get value of selected item</code>
|
||||
<code src="./demo/automatic-tokenization.tsx">Automatic tokenization</code>
|
||||
<code src="./demo/select-users.tsx">Search and Select Users</code>
|
||||
<code src="./demo/suffix.tsx" debug>Prefix and Suffix</code>
|
||||
<code src="./demo/suffix.tsx" version="5.22.0">Prefix and Suffix</code>
|
||||
<code src="./demo/custom-dropdown-menu.tsx">Custom dropdown</code>
|
||||
<code src="./demo/hide-selected.tsx">Hide Already Selected</code>
|
||||
<code src="./demo/variant.tsx" version="5.13.0">Variants</code>
|
||||
|
@ -53,7 +53,7 @@ return (
|
||||
<code src="./demo/label-in-value.tsx">获得选项的文本</code>
|
||||
<code src="./demo/automatic-tokenization.tsx">自动分词</code>
|
||||
<code src="./demo/select-users.tsx">搜索用户</code>
|
||||
<code src="./demo/suffix.tsx" debug>前后缀</code>
|
||||
<code src="./demo/suffix.tsx" version="5.22.0">前后缀</code>
|
||||
<code src="./demo/custom-dropdown-menu.tsx">扩展菜单</code>
|
||||
<code src="./demo/hide-selected.tsx">隐藏已选择选项</code>
|
||||
<code src="./demo/variant.tsx" version="5.13.0">多种形态</code>
|
||||
|
@ -1905,4 +1905,76 @@ describe('Table.rowSelection', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should trigger both custom and internal checkbox events', () => {
|
||||
const onClickMock = jest.fn();
|
||||
const onChangeMock = jest.fn();
|
||||
|
||||
const getCheckboxProps = () => ({
|
||||
onClick: onClickMock,
|
||||
onChange: onChangeMock,
|
||||
});
|
||||
|
||||
const { container } = render(
|
||||
<Table
|
||||
rowSelection={{
|
||||
type: 'checkbox',
|
||||
getCheckboxProps,
|
||||
}}
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
/>,
|
||||
);
|
||||
|
||||
const firstRowCheckbox = container.querySelector('tbody tr:first-child input[type="checkbox"]');
|
||||
expect(firstRowCheckbox).toBeTruthy();
|
||||
|
||||
fireEvent.click(firstRowCheckbox!);
|
||||
|
||||
expect(onClickMock).toHaveBeenCalled();
|
||||
expect(onClickMock.mock.calls.length).toBe(1);
|
||||
|
||||
expect(onChangeMock).toHaveBeenCalled();
|
||||
expect(onChangeMock.mock.calls.length).toBe(1);
|
||||
|
||||
const changeEvent = onChangeMock.mock.calls[0][0];
|
||||
expect(changeEvent).toHaveProperty('target');
|
||||
expect(changeEvent.target).toHaveProperty('checked');
|
||||
});
|
||||
|
||||
it('should trigger both custom and internal radio events', () => {
|
||||
const onClickMock = jest.fn();
|
||||
const onChangeMock = jest.fn();
|
||||
|
||||
const getCheckboxProps = () => ({
|
||||
onClick: onClickMock,
|
||||
onChange: onChangeMock,
|
||||
});
|
||||
|
||||
const { container } = render(
|
||||
<Table
|
||||
rowSelection={{
|
||||
type: 'radio',
|
||||
getCheckboxProps,
|
||||
}}
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
/>,
|
||||
);
|
||||
|
||||
const firstRowRadio = container.querySelector('tbody tr:first-child input[type="radio"]');
|
||||
expect(firstRowRadio).toBeTruthy();
|
||||
|
||||
fireEvent.click(firstRowRadio!);
|
||||
|
||||
expect(onClickMock).toHaveBeenCalled();
|
||||
expect(onClickMock.mock.calls.length).toBe(1);
|
||||
|
||||
expect(onChangeMock).toHaveBeenCalled();
|
||||
expect(onChangeMock.mock.calls.length).toBe(1);
|
||||
|
||||
const changeEvent = onChangeMock.mock.calls[0][0];
|
||||
expect(changeEvent).toHaveProperty('target');
|
||||
expect(changeEvent.target).toHaveProperty('checked');
|
||||
});
|
||||
});
|
||||
|
@ -496,17 +496,21 @@ const useSelection = <RecordType extends AnyObject = AnyObject>(
|
||||
renderCell = (_, record, index) => {
|
||||
const key = getRowKey(record, index);
|
||||
const checked = keySet.has(key);
|
||||
|
||||
const checkboxProps = checkboxPropsMap.get(key);
|
||||
return {
|
||||
node: (
|
||||
<Radio
|
||||
{...checkboxPropsMap.get(key)}
|
||||
{...checkboxProps}
|
||||
checked={checked}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
checkboxProps?.onClick?.(e);
|
||||
}}
|
||||
onChange={(event) => {
|
||||
if (!keySet.has(key)) {
|
||||
triggerSingleSelection(key, true, [key], event.nativeEvent);
|
||||
}
|
||||
checkboxProps?.onChange?.(event);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
@ -538,8 +542,12 @@ const useSelection = <RecordType extends AnyObject = AnyObject>(
|
||||
indeterminate={mergedIndeterminate}
|
||||
checked={checked}
|
||||
skipGroup
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={({ nativeEvent }) => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
checkboxProps?.onClick?.(e);
|
||||
}}
|
||||
onChange={(event) => {
|
||||
const { nativeEvent } = event;
|
||||
const { shiftKey } = nativeEvent;
|
||||
const currentSelectedIndex = recordKeys.findIndex((item) => item === key);
|
||||
const isMultiple = derivedSelectedKeys.some((item) => recordKeys.includes(item));
|
||||
@ -595,6 +603,7 @@ const useSelection = <RecordType extends AnyObject = AnyObject>(
|
||||
} else {
|
||||
updatePrevSelectedIndex(currentSelectedIndex);
|
||||
}
|
||||
checkboxProps?.onChange?.(event);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
|
@ -2,6 +2,7 @@ import type { TimePickerLocale } from '../index';
|
||||
|
||||
const locale: TimePickerLocale = {
|
||||
placeholder: "Selezionare l'orario",
|
||||
rangePlaceholder: ['Inizio orario', 'Fine orario'],
|
||||
};
|
||||
|
||||
export default locale;
|
||||
|
@ -52,13 +52,13 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
|
||||
### Timeline
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| mode | By sending `alternate` the timeline will distribute the nodes to the left and right | `left` \| `alternate` \| `right` | - |
|
||||
| pending | Set the last ghost node's existence or its content | ReactNode | false |
|
||||
| pendingDot | Set the dot of the last ghost node when pending is true | ReactNode | <LoadingOutlined /> |
|
||||
| reverse | Whether reverse nodes or not | boolean | false |
|
||||
| items | Each node of timeline | [Items](#Items)[] | 5.2.0 |
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| mode | By sending `alternate` the timeline will distribute the nodes to the left and right | `left` \| `alternate` \| `right` | - | |
|
||||
| pending | Set the last ghost node's existence or its content | ReactNode | false | |
|
||||
| pendingDot | Set the dot of the last ghost node when pending is true | ReactNode | <LoadingOutlined /> | |
|
||||
| reverse | Whether reverse nodes or not | boolean | false | |
|
||||
| items | Each node of timeline | [Items](#Items)[] | - | 5.2.0 |
|
||||
|
||||
### Items
|
||||
|
||||
|
@ -53,13 +53,13 @@ return (
|
||||
|
||||
### Timeline
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| mode | 通过设置 `mode` 可以改变时间轴和内容的相对位置 | `left` \| `alternate` \| `right` | - |
|
||||
| pending | 指定最后一个幽灵节点是否存在或内容 | ReactNode | false |
|
||||
| pendingDot | 当最后一个幽灵节点存在時,指定其时间图点 | ReactNode | <LoadingOutlined /> |
|
||||
| reverse | 节点排序 | boolean | false |
|
||||
| items | 选项配置 | [Items](#Items)[] | 5.2.0 |
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| mode | 通过设置 `mode` 可以改变时间轴和内容的相对位置 | `left` \| `alternate` \| `right` | - | |
|
||||
| pending | 指定最后一个幽灵节点是否存在或内容 | ReactNode | false | |
|
||||
| pendingDot | 当最后一个幽灵节点存在時,指定其时间图点 | ReactNode | <LoadingOutlined /> | |
|
||||
| reverse | 节点排序 | boolean | false | |
|
||||
| items | 选项配置 | [Items](#Items)[] | - | 5.2.0 |
|
||||
|
||||
### Items
|
||||
|
||||
|
@ -24,7 +24,7 @@ demo:
|
||||
<code src="./demo/treeLine.tsx">Show Tree Line</code>
|
||||
<code src="./demo/placement.tsx">Placement</code>
|
||||
<code src="./demo/status.tsx">Status</code>
|
||||
<code src="./demo/suffix.tsx" debug>Prefix and Suffix</code>
|
||||
<code src="./demo/suffix.tsx" version="5.22.0">Prefix and Suffix</code>
|
||||
<code src="./demo/render-panel.tsx" debug>_InternalPanelDoNotUseOrYouWillBeFired</code>
|
||||
<code src="./demo/component-token.tsx" debug>Component Token</code>
|
||||
|
||||
|
@ -25,7 +25,7 @@ demo:
|
||||
<code src="./demo/treeLine.tsx">线性样式</code>
|
||||
<code src="./demo/placement.tsx">弹出位置</code>
|
||||
<code src="./demo/status.tsx">自定义状态</code>
|
||||
<code src="./demo/suffix.tsx" debug>前后缀</code>
|
||||
<code src="./demo/suffix.tsx" version="5.22.0">前后缀</code>
|
||||
<code src="./demo/render-panel.tsx" debug>\_InternalPanelDoNotUseOrYouWillBeFired</code>
|
||||
<code src="./demo/component-token.tsx" debug>组件 Token</code>
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { extendTest } from '../../../tests/shared/demoTest';
|
||||
|
||||
extendTest('tree', { skip: ['big-data.tsx', 'virtual-scroll.tsx', 'component-token.tsx'] });
|
||||
extendTest('tree', {
|
||||
skip: ['big-data.tsx', 'virtual-scroll.tsx', 'component-token.tsx', 'directory-debug.tsx'],
|
||||
});
|
||||
|
@ -1,3 +1,5 @@
|
||||
import demoTest from '../../../tests/shared/demoTest';
|
||||
|
||||
demoTest('tree', { skip: ['big-data.tsx', 'virtual-scroll.tsx', 'component-token.tsx'] });
|
||||
demoTest('tree', {
|
||||
skip: ['big-data.tsx', 'virtual-scroll.tsx', 'component-token.tsx', 'directory-debug.tsx'],
|
||||
});
|
||||
|
7
components/tree/demo/directory-debug.md
Normal file
7
components/tree/demo/directory-debug.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
调试 [#51210](https://github.com/ant-design/ant-design/pull/51210), [#51448](https://github.com/ant-design/ant-design/pull/51448#issuecomment-2449144872)
|
||||
|
||||
## en-US
|
||||
|
||||
Debugging [#51210](https://github.com/ant-design/ant-design/pull/51210), [#51448](https://github.com/ant-design/ant-design/pull/51448#issuecomment-2449144872)
|
65
components/tree/demo/directory-debug.tsx
Normal file
65
components/tree/demo/directory-debug.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { Flex, Tree } from 'antd';
|
||||
import type { GetProps, TreeDataNode } from 'antd';
|
||||
|
||||
const { DirectoryTree } = Tree;
|
||||
|
||||
const treeData: TreeDataNode[] = [
|
||||
{
|
||||
title: 'parent 0',
|
||||
key: '0-0',
|
||||
children: [
|
||||
{ title: 'leaf 0-0', key: '0-0-0', isLeaf: true },
|
||||
{ title: 'leaf 0-1', key: '0-0-1', isLeaf: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'parent 1',
|
||||
key: '0-1',
|
||||
children: [
|
||||
{ title: 'leaf 1-0', key: '0-1-0', isLeaf: true },
|
||||
{ title: 'leaf 1-1', key: '0-1-1', isLeaf: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const sharedProps: GetProps<typeof DirectoryTree> = {
|
||||
treeData,
|
||||
defaultExpandAll: true,
|
||||
onSelect: (keys, info) => {
|
||||
console.log('Trigger Select', keys, info);
|
||||
},
|
||||
onExpand: (keys, info) => {
|
||||
console.log('Trigger Expand', keys, info);
|
||||
},
|
||||
};
|
||||
|
||||
const DemoOne = () => <DirectoryTree draggable defaultSelectedKeys={['0-0-0']} />;
|
||||
|
||||
const DemoTwo = () => <DirectoryTree {...sharedProps} checkable defaultSelectedKeys={['0-1-0']} />;
|
||||
|
||||
const DemoThree = () => (
|
||||
<DirectoryTree {...sharedProps} draggable checkable defaultSelectedKeys={['0-1']} />
|
||||
);
|
||||
|
||||
const BasicDemo = () => <DirectoryTree {...sharedProps} multiple treeData={treeData} />;
|
||||
|
||||
const NormalDemo = () => <Tree {...sharedProps} defaultSelectedKeys={['0-1']} />;
|
||||
|
||||
const NormalCheckDemo = () => <Tree {...sharedProps} checkable defaultSelectedKeys={['0-1']} />;
|
||||
|
||||
const NormalDragDemo = () => <Tree {...sharedProps} draggable defaultSelectedKeys={['0-1-0']} />;
|
||||
|
||||
const App = () => (
|
||||
<Flex wrap gap="large">
|
||||
<DemoOne />
|
||||
<DemoTwo />
|
||||
<DemoThree />
|
||||
<BasicDemo />
|
||||
<NormalDemo />
|
||||
<NormalCheckDemo />
|
||||
<NormalDragDemo />
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default App;
|
@ -24,6 +24,7 @@ Almost anything can be represented in a tree structure. Examples include directo
|
||||
<code src="./demo/line.tsx">Tree with line</code>
|
||||
<code src="./demo/customized-icon.tsx">Customize Icon</code>
|
||||
<code src="./demo/directory.tsx">directory</code>
|
||||
<code src="./demo/directory-debug.tsx" debug>Directory Debug</code>
|
||||
<code src="./demo/switcher-icon.tsx">Customize collapse/expand icon</code>
|
||||
<code src="./demo/virtual-scroll.tsx">Virtual scroll</code>
|
||||
<code src="./demo/drag-debug.tsx" debug>Drag Debug</code>
|
||||
|
@ -25,6 +25,7 @@ demo:
|
||||
<code src="./demo/line.tsx">连接线</code>
|
||||
<code src="./demo/customized-icon.tsx">自定义图标</code>
|
||||
<code src="./demo/directory.tsx">目录</code>
|
||||
<code src="./demo/directory-debug.tsx" debug>目录 Debug</code>
|
||||
<code src="./demo/switcher-icon.tsx">自定义展开/折叠图标</code>
|
||||
<code src="./demo/virtual-scroll.tsx">虚拟滚动</code>
|
||||
<code src="./demo/drag-debug.tsx" debug>Drag Debug</code>
|
||||
|
@ -38,15 +38,14 @@ export const genDirectoryStyle = ({
|
||||
},
|
||||
},
|
||||
|
||||
[`${treeCls}-switcher`]: {
|
||||
marginInlineEnd: 0,
|
||||
[`${treeCls}-switcher, ${treeCls}-checkbox, ${treeCls}-draggable-icon`]: {
|
||||
zIndex: 1,
|
||||
},
|
||||
|
||||
// ============= Selected =============
|
||||
'&-selected': {
|
||||
[`${treeCls}-switcher, ${treeCls}-draggable-icon`]: {
|
||||
color: directoryNodeSelectedColor,
|
||||
zIndex: 1,
|
||||
},
|
||||
|
||||
// >>> Title
|
||||
|
@ -120,7 +120,6 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
|
||||
nodeHoverBg,
|
||||
colorTextQuaternary,
|
||||
} = token;
|
||||
const treeCheckBoxMarginHorizontal = token.marginXXS;
|
||||
|
||||
return {
|
||||
[treeCls]: {
|
||||
@ -253,6 +252,14 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
|
||||
visibility: 'hidden',
|
||||
},
|
||||
|
||||
// Switcher / Checkbox
|
||||
[`${treeCls}-switcher, ${treeCls}-checkbox`]: {
|
||||
marginInlineEnd: token
|
||||
.calc(token.calc(titleHeight).sub(token.controlInteractiveSize))
|
||||
.div(2)
|
||||
.equal(),
|
||||
},
|
||||
|
||||
// >>> Switcher
|
||||
[`${treeCls}-switcher`]: {
|
||||
...getSwitchStyle(prefixCls, token),
|
||||
@ -260,15 +267,10 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
|
||||
flex: 'none',
|
||||
alignSelf: 'stretch',
|
||||
width: titleHeight,
|
||||
margin: 0,
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
transition: `all ${token.motionDurationSlow}`,
|
||||
marginInlineEnd: token
|
||||
.calc(token.calc(titleHeight).sub(token.controlInteractiveSize))
|
||||
.div(2)
|
||||
.equal(),
|
||||
|
||||
'&-noop': {
|
||||
cursor: 'unset',
|
||||
@ -329,14 +331,6 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
|
||||
},
|
||||
},
|
||||
|
||||
// >>> Checkbox
|
||||
[`${treeCls}-checkbox`]: {
|
||||
top: 'initial',
|
||||
marginInlineEnd: treeCheckBoxMarginHorizontal,
|
||||
alignSelf: 'flex-start',
|
||||
marginTop: token.marginXXS,
|
||||
},
|
||||
|
||||
// >>> Title
|
||||
// add `${treeCls}-checkbox + span` to cover checkbox `${checkboxCls} + span`
|
||||
[`${treeCls}-node-content-wrapper`]: {
|
||||
|
@ -5,11 +5,10 @@ import { clearFix, textEllipsis } from '../../style';
|
||||
import type { GenerateStyle } from '../../theme/internal';
|
||||
|
||||
const genListStyle: GenerateStyle<UploadToken> = (token) => {
|
||||
const { componentCls, antCls, iconCls, fontSize, lineHeight, calc } = token;
|
||||
const { componentCls, iconCls, fontSize, lineHeight, calc } = token;
|
||||
const itemCls = `${componentCls}-list-item`;
|
||||
const actionsCls = `${itemCls}-actions`;
|
||||
const actionCls = `${itemCls}-action`;
|
||||
const listItemHeightSM = token.fontHeightSM;
|
||||
|
||||
return {
|
||||
[`${componentCls}-wrapper`]: {
|
||||
@ -25,6 +24,7 @@ const genListStyle: GenerateStyle<UploadToken> = (token) => {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
transition: `background-color ${token.motionDurationSlow}`,
|
||||
borderRadius: token.borderRadiusSM,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: token.controlItemBgHover,
|
||||
@ -56,12 +56,6 @@ const genListStyle: GenerateStyle<UploadToken> = (token) => {
|
||||
`]: {
|
||||
opacity: 1,
|
||||
},
|
||||
|
||||
[`${actionCls}${antCls}-btn`]: {
|
||||
height: listItemHeightSM,
|
||||
border: 0,
|
||||
lineHeight: 1,
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-icon ${iconCls}`]: {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "antd",
|
||||
"version": "5.22.1",
|
||||
"version": "5.22.2",
|
||||
"description": "An enterprise-class UI design language and React components implementation",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@ -126,7 +126,7 @@
|
||||
"rc-dialog": "~9.6.0",
|
||||
"rc-drawer": "~7.2.0",
|
||||
"rc-dropdown": "~4.2.0",
|
||||
"rc-field-form": "~2.5.0",
|
||||
"rc-field-form": "~2.5.1",
|
||||
"rc-image": "~7.11.0",
|
||||
"rc-input": "~1.6.3",
|
||||
"rc-input-number": "~9.3.0",
|
||||
@ -135,7 +135,7 @@
|
||||
"rc-motion": "^2.9.3",
|
||||
"rc-notification": "~5.6.2",
|
||||
"rc-pagination": "~4.3.0",
|
||||
"rc-picker": "~4.8.0",
|
||||
"rc-picker": "~4.8.1",
|
||||
"rc-progress": "~4.0.0",
|
||||
"rc-rate": "~2.13.0",
|
||||
"rc-resize-observer": "^1.4.0",
|
||||
|
@ -12,6 +12,7 @@ import pixelmatch from 'pixelmatch';
|
||||
import { PNG } from 'pngjs';
|
||||
import sharp from 'sharp';
|
||||
import simpleGit from 'simple-git';
|
||||
import filter from 'lodash/filter';
|
||||
|
||||
import markdown2Html from './convert';
|
||||
|
||||
@ -283,9 +284,17 @@ ${fullReport}
|
||||
|
||||
let diffCount = 0;
|
||||
|
||||
// Summary
|
||||
const badCount = badCases.length;
|
||||
const commentReportLimit = isLocalEnv ? badCount : 8;
|
||||
|
||||
const changedCount = filter(badCases, { type: 'changed' }).length;
|
||||
const removedCount = filter(badCases, { type: 'removed' }).length;
|
||||
const addedCount = filter(badCases, { type: 'added' }).length;
|
||||
|
||||
for (const badCase of badCases) {
|
||||
diffCount += 1;
|
||||
if (diffCount <= 10) {
|
||||
if (diffCount <= commentReportLimit) {
|
||||
// 将图片下方增加文件名
|
||||
reportMdStr += generateLineReport(badCase, publicPath, currentRef, true);
|
||||
}
|
||||
@ -293,18 +302,37 @@ ${fullReport}
|
||||
fullVersionMd += generateLineReport(badCase, publicPath, currentRef, false);
|
||||
}
|
||||
|
||||
reportMdStr += `\n\nCheck <a href="${htmlReportLink}" target="_blank">Full Report</a> for details`;
|
||||
const hasMore = badCount > commentReportLimit;
|
||||
|
||||
if (hasMore) {
|
||||
reportMdStr += [
|
||||
'\r',
|
||||
'> [!WARNING]',
|
||||
`> There are more diffs not shown in the table. Please check the <a href="${htmlReportLink}" target="_blank">Full Report</a> for details.`,
|
||||
'\r',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
// tips for comment `Pass Visual Diff` will pass the CI
|
||||
if (!passed) {
|
||||
reportMdStr += `
|
||||
const summaryLine = [
|
||||
changedCount > 0 && `🔄 **${changedCount}** changed`,
|
||||
removedCount > 0 && `🛑 **${removedCount}** removed`,
|
||||
addedCount > 0 && `🆕 **${addedCount}** added`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
|
||||
-----
|
||||
|
||||
If you think the visual diff is acceptable, please check:
|
||||
|
||||
- [ ] Visual diff is acceptable
|
||||
`;
|
||||
reportMdStr += [
|
||||
'\n---\n',
|
||||
'> [!IMPORTANT]',
|
||||
`> There are **${badCount}** diffs found in this PR: ${summaryLine}.`,
|
||||
'> **Please check all items:**',
|
||||
hasMore && '> - [ ] Checked all diffs in the full report',
|
||||
'> - [ ] Visual diff is acceptable',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
// convert fullVersionMd to html
|
||||
|
@ -29,6 +29,11 @@
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table > thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 8px;
|
||||
@ -61,13 +66,22 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
table > thead {
|
||||
position: unset;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding: 6px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
border-bottom: 3px dashed darkgray;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
background-color: transparent;
|
||||
|
Loading…
Reference in New Issue
Block a user