mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-09 02:49:18 +08:00
choer: merge feature into next
This commit is contained in:
@@ -64,7 +64,6 @@ const useThemeAnimation = () => {
|
||||
event: React.MouseEvent<HTMLElement, MouseEvent>,
|
||||
isDark: boolean,
|
||||
) => {
|
||||
// @ts-ignore
|
||||
if (!(event && typeof document.startViewTransition === 'function')) {
|
||||
return;
|
||||
}
|
||||
@@ -85,13 +84,10 @@ const useThemeAnimation = () => {
|
||||
'color-scheme',
|
||||
);
|
||||
document
|
||||
// @ts-ignore
|
||||
.startViewTransition(async () => {
|
||||
// wait for theme change end
|
||||
while (colorBgElevated === animateRef.current.colorBgElevated) {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000 / 60);
|
||||
});
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1000 / 60));
|
||||
}
|
||||
const root = document.documentElement;
|
||||
root.classList.remove(isDark ? 'dark' : 'light');
|
||||
@@ -111,7 +107,6 @@ const useThemeAnimation = () => {
|
||||
|
||||
// inject transition style
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
if (typeof document.startViewTransition === 'function') {
|
||||
updateCSS(viewTransitionStyle, 'view-transition-style');
|
||||
}
|
||||
|
||||
20
components/_util/convertToTooltipProps.ts
Normal file
20
components/_util/convertToTooltipProps.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { isValidElement } from 'react';
|
||||
import type { TooltipProps } from '../tooltip';
|
||||
|
||||
function convertToTooltipProps<P extends TooltipProps>(tooltip: P | ReactNode): P | null {
|
||||
// isNil
|
||||
if (tooltip === undefined || tooltip === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof tooltip === 'object' && !isValidElement(tooltip)) {
|
||||
return tooltip as P;
|
||||
}
|
||||
|
||||
return {
|
||||
title: tooltip,
|
||||
} as P;
|
||||
}
|
||||
|
||||
export default convertToTooltipProps;
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import omit from '@rc-component/util/lib/omit';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import convertToTooltipProps from '../_util/convertToTooltipProps';
|
||||
import { useZIndex } from '../_util/hooks/useZIndex';
|
||||
import { devUseWarning } from '../_util/warning';
|
||||
import Badge from '../badge';
|
||||
@@ -13,12 +14,7 @@ import type BackTop from './BackTop';
|
||||
import FloatButtonGroupContext from './context';
|
||||
import Content from './FloatButtonContent';
|
||||
import type FloatButtonGroup from './FloatButtonGroup';
|
||||
import type {
|
||||
FloatButtonContentProps,
|
||||
FloatButtonElement,
|
||||
FloatButtonProps,
|
||||
FloatButtonShape,
|
||||
} from './interface';
|
||||
import type { FloatButtonElement, FloatButtonProps, FloatButtonShape } from './interface';
|
||||
import type PurePanel from './PurePanel';
|
||||
import useStyle from './style';
|
||||
|
||||
@@ -39,8 +35,8 @@ const InternalFloatButton = React.forwardRef<FloatButtonElement, FloatButtonProp
|
||||
badge = {},
|
||||
...restProps
|
||||
} = props;
|
||||
const { getPrefixCls, direction } = useContext<ConfigConsumerProps>(ConfigContext);
|
||||
const groupShape = useContext<FloatButtonShape | undefined>(FloatButtonGroupContext);
|
||||
const { getPrefixCls, direction } = React.useContext<ConfigConsumerProps>(ConfigContext);
|
||||
const groupShape = React.useContext<FloatButtonShape | undefined>(FloatButtonGroupContext);
|
||||
const prefixCls = getPrefixCls(floatButtonPrefixCls, customizePrefixCls);
|
||||
const rootCls = useCSSVarCls(prefixCls);
|
||||
const [hashId, cssVarCls] = useStyle(prefixCls, rootCls);
|
||||
@@ -69,14 +65,9 @@ const InternalFloatButton = React.forwardRef<FloatButtonElement, FloatButtonProp
|
||||
// 虽然在 ts 中已经 omit 过了,但是为了防止多余的属性被透传进来,这里再 omit 一遍,以防万一
|
||||
const badgeProps = omit(badge, ['title', 'children', 'status', 'text'] as any[]);
|
||||
|
||||
const contentProps = useMemo<FloatButtonContentProps>(
|
||||
() => ({ prefixCls, description, icon, type }),
|
||||
[prefixCls, description, icon, type],
|
||||
);
|
||||
|
||||
let buttonNode = (
|
||||
<div className={`${prefixCls}-body`}>
|
||||
<Content {...contentProps} />
|
||||
<Content prefixCls={prefixCls} description={description} icon={icon} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -84,12 +75,10 @@ const InternalFloatButton = React.forwardRef<FloatButtonElement, FloatButtonProp
|
||||
buttonNode = <Badge {...badgeProps}>{buttonNode}</Badge>;
|
||||
}
|
||||
|
||||
if ('tooltip' in props) {
|
||||
buttonNode = (
|
||||
<Tooltip title={tooltip} placement={direction === 'rtl' ? 'right' : 'left'}>
|
||||
{buttonNode}
|
||||
</Tooltip>
|
||||
);
|
||||
// ============================ Tooltip ============================
|
||||
const tooltipProps = convertToTooltipProps(tooltip);
|
||||
if (tooltipProps) {
|
||||
buttonNode = <Tooltip {...tooltipProps}>{buttonNode}</Tooltip>;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
@@ -102,16 +91,14 @@ const InternalFloatButton = React.forwardRef<FloatButtonElement, FloatButtonProp
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
props.href ? (
|
||||
<a ref={ref} {...restProps} className={classString} style={mergedStyle}>
|
||||
{buttonNode}
|
||||
</a>
|
||||
) : (
|
||||
<button ref={ref} {...restProps} className={classString} style={mergedStyle} type={htmlType}>
|
||||
{buttonNode}
|
||||
</button>
|
||||
)
|
||||
return props.href ? (
|
||||
<a ref={ref} {...restProps} className={classString} style={mergedStyle}>
|
||||
{buttonNode}
|
||||
</a>
|
||||
) : (
|
||||
<button ref={ref} {...restProps} className={classString} style={mergedStyle} type={htmlType}>
|
||||
{buttonNode}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -5,20 +5,16 @@ import classNames from 'classnames';
|
||||
import type { FloatButtonContentProps } from './interface';
|
||||
|
||||
const FloatButtonContent: React.FC<FloatButtonContentProps> = (props) => {
|
||||
const { icon, description, prefixCls, className } = props;
|
||||
const { icon, description, prefixCls, className, ...rest } = props;
|
||||
|
||||
const defaultElement = (
|
||||
<div className={`${prefixCls}-icon`}>
|
||||
<FileTextOutlined />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={props.onClick}
|
||||
onFocus={props.onFocus}
|
||||
onMouseEnter={props.onMouseEnter}
|
||||
onMouseLeave={props.onMouseLeave}
|
||||
className={classNames(className, `${prefixCls}-content`)}
|
||||
>
|
||||
<div {...rest} className={classNames(className, `${prefixCls}-content`)}>
|
||||
{icon || description ? (
|
||||
<>
|
||||
{icon && <div className={`${prefixCls}-icon`}>{icon}</div>}
|
||||
|
||||
@@ -140,12 +140,12 @@ Array [
|
||||
</sup>
|
||||
</span>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast css-var-test-id ant-tooltip-placement-left"
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast css-var-test-id ant-tooltip-placement-top"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; top: 0px; right: 0px;"
|
||||
style="position: absolute; bottom: 0px; left: 0px;"
|
||||
/>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
@@ -2038,65 +2038,125 @@ Array [
|
||||
exports[`renders components/float-button/demo/shape.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/float-button/demo/tooltip.tsx extend context correctly 1`] = `
|
||||
<button
|
||||
class="css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
aria-describedby="test-id"
|
||||
class="ant-float-btn-body"
|
||||
Array [
|
||||
<button
|
||||
class="css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle"
|
||||
style="inset-block-end: 108px;"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-content"
|
||||
aria-describedby="test-id"
|
||||
class="ant-float-btn-body"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
class="ant-float-btn-content"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast css-var-test-id ant-tooltip-placement-left"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; top: 0px; right: 0px;"
|
||||
/>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
id="test-id"
|
||||
role="tooltip"
|
||||
>
|
||||
<div>
|
||||
Documents
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-tooltip-blue css-var-test-id ant-tooltip-placement-top"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; bottom: 0px; left: 0px;"
|
||||
/>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
id="test-id"
|
||||
role="tooltip"
|
||||
>
|
||||
Since 5.25.0+
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>,
|
||||
<button
|
||||
class="css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
aria-describedby="test-id"
|
||||
class="ant-float-btn-body"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-content"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast css-var-test-id ant-tooltip-placement-top"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; bottom: 0px; left: 0px;"
|
||||
/>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
id="test-id"
|
||||
role="tooltip"
|
||||
>
|
||||
<div>
|
||||
Documents
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/float-button/demo/tooltip.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
@@ -1974,43 +1974,83 @@ Array [
|
||||
`;
|
||||
|
||||
exports[`renders components/float-button/demo/tooltip.tsx correctly 1`] = `
|
||||
<button
|
||||
class="css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
aria-describedby="test-id"
|
||||
class="ant-float-btn-body"
|
||||
Array [
|
||||
<button
|
||||
class="css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle"
|
||||
style="inset-block-end:108px"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-content"
|
||||
aria-describedby="test-id"
|
||||
class="ant-float-btn-body"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
class="ant-float-btn-content"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</button>,
|
||||
<button
|
||||
class="css-var-test-id ant-float-btn-css-var ant-float-btn ant-float-btn-default ant-float-btn-circle"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
aria-describedby="test-id"
|
||||
class="ant-float-btn-body"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-content"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/float-button/demo/type.tsx correctly 1`] = `
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
||||
|
||||
describe('float-button image', () => {
|
||||
imageDemoTest('float-button', { onlyViewport: ['back-top.tsx'] });
|
||||
imageDemoTest('float-button', { onlyViewport: true });
|
||||
});
|
||||
|
||||
@@ -63,15 +63,27 @@ describe('FloatButton', () => {
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('tooltip should support number `0`', async () => {
|
||||
jest.useFakeTimers();
|
||||
const { container } = render(<FloatButton tooltip={0} />);
|
||||
fireEvent.mouseEnter(container.querySelector<HTMLDivElement>('.ant-float-btn-body')!);
|
||||
await waitFakeTimer();
|
||||
const element = container.querySelector('.ant-tooltip')?.querySelector('.ant-tooltip-inner');
|
||||
expect(element?.textContent).toBe('0');
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
describe('tooltip', () => {
|
||||
it('tooltip should support number `0`', async () => {
|
||||
jest.useFakeTimers();
|
||||
const { container } = render(<FloatButton tooltip={0} />);
|
||||
fireEvent.mouseEnter(container.querySelector<HTMLDivElement>('.ant-float-btn-body')!);
|
||||
await waitFakeTimer();
|
||||
const element = container.querySelector('.ant-tooltip')?.querySelector('.ant-tooltip-inner');
|
||||
expect(element?.textContent).toBe('0');
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
it('tooltip should support tooltipProps', async () => {
|
||||
jest.useFakeTimers();
|
||||
const { container } = render(<FloatButton tooltip={{ title: 'hi' }} />);
|
||||
fireEvent.mouseEnter(container.querySelector<HTMLDivElement>('.ant-float-btn-body')!);
|
||||
await waitFakeTimer();
|
||||
const element = container.querySelector('.ant-tooltip')?.querySelector('.ant-tooltip-inner');
|
||||
expect(element?.textContent).toBe('hi');
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
it('getOffset should return 0 when radius is 0', () => {
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
import React from 'react';
|
||||
import { FloatButton } from 'antd';
|
||||
|
||||
const App: React.FC = () => <FloatButton tooltip={<div>Documents</div>} />;
|
||||
const App: React.FC = () => (
|
||||
<>
|
||||
<FloatButton
|
||||
style={{ insetBlockEnd: 108 }}
|
||||
tooltip={{
|
||||
// tooltipProps is supported starting from version 5.25.0.
|
||||
title: 'Since 5.25.0+',
|
||||
color: 'blue',
|
||||
placement: 'top',
|
||||
}}
|
||||
/>
|
||||
<FloatButton tooltip={<div>Documents</div>} />
|
||||
</>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -44,7 +44,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| --- | --- | --- | --- | --- |
|
||||
| icon | Set the icon component of button | ReactNode | - | |
|
||||
| description | Text and other | ReactNode | - | |
|
||||
| tooltip | The text shown in the tooltip | ReactNode \| () => ReactNode | | |
|
||||
| tooltip | The text shown in the tooltip | ReactNode \| [TooltipProps](/components/tooltip#api) | - | TooltipProps: 5.25.0 |
|
||||
| type | Setting button type | `default` \| `primary` | `default` | |
|
||||
| shape | Setting button shape | `circle` \| `square` | `circle` | |
|
||||
| onClick | Set the handler to handle `click` event | (event) => void | - | |
|
||||
|
||||
@@ -45,7 +45,7 @@ tag: 5.0.0
|
||||
| --- | --- | --- | --- | --- |
|
||||
| icon | 自定义图标 | ReactNode | - | |
|
||||
| description | 文字及其它内容 | ReactNode | - | |
|
||||
| tooltip | 气泡卡片的内容 | ReactNode \| () => ReactNode | - | |
|
||||
| tooltip | 气泡卡片的内容 | ReactNode \| [TooltipProps](/components/tooltip-cn#api) | - | TooltipProps: 5.25.0 |
|
||||
| type | 设置按钮类型 | `default` \| `primary` | `default` | |
|
||||
| shape | 设置按钮形状 | `circle` \| `square` | `circle` | |
|
||||
| onClick | 点击按钮时的回调 | (event) => void | - | |
|
||||
|
||||
@@ -27,7 +27,7 @@ export interface FloatButtonProps extends React.DOMAttributes<FloatButtonElement
|
||||
description?: React.ReactNode;
|
||||
type?: FloatButtonType;
|
||||
shape?: FloatButtonShape;
|
||||
tooltip?: TooltipProps['title'];
|
||||
tooltip?: React.ReactNode | TooltipProps;
|
||||
href?: string;
|
||||
target?: React.HTMLAttributeAnchorTarget;
|
||||
badge?: FloatButtonBadgeProps;
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import QuestionCircleOutlined from '@ant-design/icons/QuestionCircleOutlined';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import convertToTooltipProps from '../_util/convertToTooltipProps';
|
||||
import type { ColProps } from '../grid/col';
|
||||
import Col from '../grid/col';
|
||||
import { useLocale } from '../locale';
|
||||
@@ -19,20 +20,6 @@ export type WrapperTooltipProps = TooltipProps & {
|
||||
|
||||
export type LabelTooltipType = WrapperTooltipProps | React.ReactNode;
|
||||
|
||||
function toTooltipProps(tooltip: LabelTooltipType): WrapperTooltipProps | null {
|
||||
if (!tooltip) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof tooltip === 'object' && !React.isValidElement(tooltip)) {
|
||||
return tooltip as WrapperTooltipProps;
|
||||
}
|
||||
|
||||
return {
|
||||
title: tooltip,
|
||||
};
|
||||
}
|
||||
|
||||
export interface FormItemLabelProps {
|
||||
colon?: boolean;
|
||||
htmlFor?: string;
|
||||
@@ -98,7 +85,7 @@ const FormItemLabel: React.FC<FormItemLabelProps & { required?: boolean; prefixC
|
||||
}
|
||||
|
||||
// Tooltip
|
||||
const tooltipProps = toTooltipProps(tooltip);
|
||||
const tooltipProps = convertToTooltipProps(tooltip);
|
||||
|
||||
if (tooltipProps) {
|
||||
const { icon = <QuestionCircleOutlined />, ...restTooltipProps } = tooltipProps;
|
||||
|
||||
@@ -20,6 +20,7 @@ import type { SizeType } from '../config-provider/SizeContext';
|
||||
import { FormItemInputContext } from '../form/context';
|
||||
import useVariant from '../form/hooks/useVariants';
|
||||
import { useCompactItemContext } from '../space/Compact';
|
||||
import useHandleResizeWrapper from './hooks/useHandleResizeWrapper';
|
||||
import type { InputFocusOptions } from './Input';
|
||||
import { triggerFocus } from './Input';
|
||||
import { useSharedStyle } from './style';
|
||||
@@ -58,6 +59,7 @@ const TextArea = forwardRef<TextAreaRef, TextAreaProps>((props, ref) => {
|
||||
style,
|
||||
styles,
|
||||
variant: customVariant,
|
||||
showCount,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@@ -117,6 +119,8 @@ const TextArea = forwardRef<TextAreaRef, TextAreaProps>((props, ref) => {
|
||||
|
||||
const mergedAllowClear = getAllowClear(allowClear ?? contextAllowClear);
|
||||
|
||||
const { handleResizeWrapper } = useHandleResizeWrapper();
|
||||
|
||||
return (
|
||||
<RcTextArea
|
||||
autoComplete={contextAutoComplete}
|
||||
@@ -165,6 +169,10 @@ const TextArea = forwardRef<TextAreaRef, TextAreaProps>((props, ref) => {
|
||||
prefixCls={prefixCls}
|
||||
suffix={hasFeedback && <span className={`${prefixCls}-textarea-suffix`}>{feedbackIcon}</span>}
|
||||
ref={innerRef}
|
||||
onResize={(size) => {
|
||||
rest.onResize?.(size);
|
||||
showCount && handleResizeWrapper(innerRef.current);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -9,11 +9,14 @@ import {
|
||||
fireEvent,
|
||||
pureRender,
|
||||
render,
|
||||
renderHook,
|
||||
triggerResize,
|
||||
waitFakeTimer,
|
||||
waitFakeTimer19,
|
||||
} from '../../../tests/utils';
|
||||
import type { TextAreaRef } from '../TextArea';
|
||||
import useHandleResizeWrapper from '../hooks/useHandleResizeWrapper';
|
||||
import type { TextAreaRef as RcTextAreaRef } from 'rc-textarea';
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
@@ -531,3 +534,112 @@ describe('TextArea allowClear', () => {
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('TextArea useHandleResizeWrapper', () => {
|
||||
let requestAnimationFrameSpy: jest.SpyInstance;
|
||||
|
||||
beforeAll(() => {
|
||||
// Use fake timers to control requestAnimationFrame.
|
||||
jest.useFakeTimers();
|
||||
// Override requestAnimationFrame to simulate a 16ms delay.
|
||||
requestAnimationFrameSpy = jest
|
||||
.spyOn(window, 'requestAnimationFrame')
|
||||
.mockImplementation((cb: FrameRequestCallback) => {
|
||||
return window.setTimeout(() => cb(performance.now()), 16);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
requestAnimationFrameSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('does nothing when rcTextArea is null', () => {
|
||||
const { result } = renderHook(() => useHandleResizeWrapper());
|
||||
// Calling with null should not throw or change anything.
|
||||
expect(() => result.current?.handleResizeWrapper(null)).not.toThrow();
|
||||
});
|
||||
|
||||
it('does nothing when style width does not include "px"', () => {
|
||||
const { result } = renderHook(() => useHandleResizeWrapper());
|
||||
|
||||
const fakeRcTextArea = {
|
||||
resizableTextArea: {
|
||||
textArea: {
|
||||
style: {
|
||||
width: '100', // missing 'px'
|
||||
},
|
||||
},
|
||||
},
|
||||
nativeElement: {
|
||||
offsetWidth: 110,
|
||||
style: {} as any,
|
||||
},
|
||||
} as unknown as RcTextAreaRef;
|
||||
|
||||
result.current?.handleResizeWrapper(fakeRcTextArea);
|
||||
|
||||
// Fast-forward time to see if any scheduled callback would execute.
|
||||
jest.advanceTimersByTime(16);
|
||||
// nativeElement.style.width remains unchanged.
|
||||
expect(fakeRcTextArea.nativeElement.style.width).toBeUndefined();
|
||||
});
|
||||
|
||||
it('adjusts width correctly when offsetWidth is slightly greater than the textArea width (increased scenario)', () => {
|
||||
const { result } = renderHook(() => useHandleResizeWrapper());
|
||||
|
||||
const fakeRcTextArea = {
|
||||
resizableTextArea: {
|
||||
textArea: {
|
||||
style: {
|
||||
width: '100px', // valid width with px
|
||||
},
|
||||
},
|
||||
},
|
||||
// offsetWidth is 101 so the difference is 1 (< ELEMENT_GAP of 2)
|
||||
nativeElement: {
|
||||
offsetWidth: 101,
|
||||
style: {} as any,
|
||||
},
|
||||
} as unknown as RcTextAreaRef;
|
||||
|
||||
// Immediately after calling handleResizeWrapper, the update is scheduled.
|
||||
expect(fakeRcTextArea.nativeElement.style.width).toBeUndefined();
|
||||
|
||||
result.current?.handleResizeWrapper(fakeRcTextArea);
|
||||
|
||||
// Fast-forward time to trigger the requestAnimationFrame callback.
|
||||
jest.advanceTimersByTime(16);
|
||||
// Expected new width: 100 + 2 = 102px.
|
||||
expect(fakeRcTextArea.nativeElement.style.width).toBe('102px');
|
||||
});
|
||||
|
||||
it('adjusts width correctly when offsetWidth is significantly greater than the textArea width (decreased scenario)', () => {
|
||||
const { result } = renderHook(() => useHandleResizeWrapper());
|
||||
|
||||
const fakeRcTextArea = {
|
||||
resizableTextArea: {
|
||||
textArea: {
|
||||
style: {
|
||||
width: '100px',
|
||||
},
|
||||
},
|
||||
},
|
||||
// offsetWidth is 105 so the difference is 5 (> ELEMENT_GAP of 2)
|
||||
nativeElement: {
|
||||
offsetWidth: 105,
|
||||
style: {} as any,
|
||||
},
|
||||
} as unknown as RcTextAreaRef;
|
||||
|
||||
// Immediately after calling handleResizeWrapper, the update is scheduled.
|
||||
expect(fakeRcTextArea.nativeElement.style.width).toBeUndefined();
|
||||
|
||||
result.current?.handleResizeWrapper(fakeRcTextArea);
|
||||
|
||||
// Fast-forward time to trigger the requestAnimationFrame callback.
|
||||
jest.advanceTimersByTime(16);
|
||||
// Expected new width remains: 100 + 2 = 102px.
|
||||
expect(fakeRcTextArea.nativeElement.style.width).toBe('102px');
|
||||
});
|
||||
});
|
||||
|
||||
40
components/input/hooks/useHandleResizeWrapper.ts
Normal file
40
components/input/hooks/useHandleResizeWrapper.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { TextAreaRef } from 'rc-textarea';
|
||||
import React from 'react';
|
||||
|
||||
type ResizeWrapperHandler = (rcTextArea: TextAreaRef | null) => void;
|
||||
|
||||
const ELEMENT_GAP = 2;
|
||||
|
||||
const adjustElementWidth = (width: number, wrapper: HTMLElement): void => {
|
||||
if (wrapper.offsetWidth - width < ELEMENT_GAP) {
|
||||
// The textarea's width is increased
|
||||
wrapper.style.width = `${width + ELEMENT_GAP}px`;
|
||||
} else if (wrapper.offsetWidth - width > ELEMENT_GAP) {
|
||||
// The textarea's width is decreased
|
||||
wrapper.style.width = `${width + ELEMENT_GAP}px`;
|
||||
}
|
||||
};
|
||||
|
||||
let isScheduled = false;
|
||||
const requestAnimationFrameDecorator = (callback: () => void) => {
|
||||
if (!isScheduled) {
|
||||
isScheduled = true;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
callback();
|
||||
isScheduled = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default function useHandleResizeWrapper(): { handleResizeWrapper: ResizeWrapperHandler } {
|
||||
const handleResizeWrapper: ResizeWrapperHandler = React.useCallback((rcTextArea) => {
|
||||
if (!rcTextArea) return;
|
||||
if (rcTextArea.resizableTextArea.textArea.style.width.includes('px')) {
|
||||
const width = parseInt(rcTextArea.resizableTextArea.textArea.style.width.replace('px', ''));
|
||||
requestAnimationFrameDecorator(() => adjustElementWidth(width, rcTextArea.nativeElement));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { handleResizeWrapper };
|
||||
}
|
||||
@@ -2,14 +2,12 @@ import type { UploadProps } from '../interface';
|
||||
|
||||
export const successRequest: UploadProps['customRequest'] = ({ onSuccess, file }) => {
|
||||
setTimeout(() => {
|
||||
// @ts-ignore
|
||||
onSuccess?.(null, file);
|
||||
});
|
||||
};
|
||||
|
||||
export const errorRequest: UploadProps['customRequest'] = ({ onError }) => {
|
||||
setTimeout(() => {
|
||||
// @ts-ignore
|
||||
onError?.();
|
||||
onError?.(new Error('test error'));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
"Artin",
|
||||
"Arvin Xu",
|
||||
"Ash Kumar",
|
||||
"Ashcon Partovi",
|
||||
"Ashot Mnatsakanyan",
|
||||
"Austaras",
|
||||
"Avan",
|
||||
@@ -198,6 +199,7 @@
|
||||
"Daewoong Moon",
|
||||
"Dalton Craven",
|
||||
"Damian Green",
|
||||
"Dan M.",
|
||||
"Dan Minshew",
|
||||
"Dana Janoskova",
|
||||
"Dane David",
|
||||
@@ -349,6 +351,7 @@
|
||||
"HJin.me",
|
||||
"Hai Phan Nguyen",
|
||||
"Haibin Yu",
|
||||
"Hakan Kosdag",
|
||||
"Hale Deng",
|
||||
"Han Han",
|
||||
"Hanai",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Humble",
|
||||
"Hyunseok.Kim",
|
||||
"ILdar Nogmanov",
|
||||
"Ideveloper (이승규)",
|
||||
"Igor",
|
||||
"Igor Andriushchenko",
|
||||
"Igor G",
|
||||
@@ -699,6 +703,7 @@
|
||||
"Muhammad Abuzar",
|
||||
"Muhammad Sameer",
|
||||
"Muhammad Sohaib Raza",
|
||||
"Muhammad Taif Khan",
|
||||
"MuxinFeng",
|
||||
"MyeongHyun Lew",
|
||||
"Mykyta Velykanov",
|
||||
@@ -987,11 +992,13 @@
|
||||
"Vu Hoang Minh",
|
||||
"Vyacheslav Kamenev",
|
||||
"Vyacheslav Sedykh",
|
||||
"Waiter",
|
||||
"Walter Barbagallo",
|
||||
"Wang Jun",
|
||||
"Wang Riwu",
|
||||
"Wang Zhengchen",
|
||||
"Wang yb",
|
||||
"Wangye",
|
||||
"Wanpan",
|
||||
"Warren Seymour",
|
||||
"WeLong",
|
||||
@@ -1121,6 +1128,7 @@
|
||||
"appleshell",
|
||||
"arange",
|
||||
"arifemrecelik",
|
||||
"arron.laihongwei",
|
||||
"arturpfb",
|
||||
"ascodelife",
|
||||
"ascoders",
|
||||
@@ -1398,6 +1406,7 @@
|
||||
"netcon",
|
||||
"neverland",
|
||||
"ngolin",
|
||||
"nicholas-codecov",
|
||||
"nick-ChenZe",
|
||||
"niko",
|
||||
"nitinknolder",
|
||||
@@ -1594,6 +1603,7 @@
|
||||
"zhoulixiang",
|
||||
"zhuguibiao",
|
||||
"zhujun24",
|
||||
"zhuzhu_coder",
|
||||
"zhyupe",
|
||||
"zilong",
|
||||
"zinkey",
|
||||
@@ -1607,12 +1617,12 @@
|
||||
"ztplz",
|
||||
"zty",
|
||||
"zuiidea",
|
||||
"zx6658",
|
||||
"zxyao",
|
||||
"zytjs",
|
||||
"zz",
|
||||
"°))))彡",
|
||||
"Ömer Faruk APLAK",
|
||||
"Đào Văn Hùng",
|
||||
"Ștefan Filip",
|
||||
"Зухриддин Камильжанов",
|
||||
"रोहन मल्होत्रा",
|
||||
@@ -1644,6 +1654,7 @@
|
||||
"只捱宅",
|
||||
"可乐",
|
||||
"叶枫",
|
||||
"合木",
|
||||
"吕立青",
|
||||
"吴泽康",
|
||||
"啸生",
|
||||
|
||||
Reference in New Issue
Block a user