chore: auto merge branches (#40574)

chore: merge feature into master
This commit is contained in:
github-actions[bot] 2023-02-06 14:39:23 +00:00 committed by GitHub
commit b9de155fdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
260 changed files with 7988 additions and 3023 deletions

View File

@ -27,7 +27,7 @@ const transformIgnorePatterns = [
function getTestRegex(libDir) {
if (['dist', 'lib', 'es'].includes(libDir)) {
return 'demo\\.test\\.(j|t)s$';
return 'demo\\.test\\.(j|t)sx?$';
}
return '.*\\.test\\.(j|t)sx?$';
}

View File

@ -10,6 +10,8 @@ jest.mock('../../tests/shared/demoTest', () => {
(global as any).testConfig[name] = option;
}
fakeDemoTest.rootPropsTest = () => {};
return fakeDemoTest;
});

View File

@ -19,11 +19,12 @@ export interface AdjustOverflow {
}
export interface PlacementsConfig {
arrowWidth?: number;
arrowWidth: number;
horizontalArrowShift?: number;
verticalArrowShift?: number;
arrowPointAtCenter?: boolean;
autoAdjustOverflow?: boolean | AdjustOverflow;
offset: number;
}
export function getOverflowOptions(autoAdjustOverflow?: boolean | AdjustOverflow) {
@ -36,73 +37,114 @@ export function getOverflowOptions(autoAdjustOverflow?: boolean | AdjustOverflow
};
}
type PlacementType = keyof BuildInPlacements;
function getArrowOffset(type: PlacementType, arrowWidth: number, offset: number): number[] {
switch (type) {
case 'top':
case 'topLeft':
case 'topRight':
return [0, -(arrowWidth / 2 + offset)];
case 'bottom':
case 'bottomLeft':
case 'bottomRight':
return [0, arrowWidth / 2 + offset];
case 'left':
case 'leftTop':
case 'leftBottom':
return [-(arrowWidth / 2 + offset), 0];
case 'right':
case 'rightTop':
case 'rightBottom':
return [arrowWidth / 2 + offset, 0];
/* istanbul ignore next */
default:
return [0, 0];
}
}
function vertexCalc(point1: number[], point2: number[]): number[] {
return [point1[0] + point2[0], point1[1] + point2[1]];
}
export default function getPlacements(config: PlacementsConfig) {
const {
arrowWidth = 4,
arrowWidth,
horizontalArrowShift = 16,
verticalArrowShift = 8,
autoAdjustOverflow,
arrowPointAtCenter,
offset,
} = config;
const halfArrowWidth = arrowWidth / 2;
const placementMap: BuildInPlacements = {
left: {
points: ['cr', 'cl'],
offset: [-4, 0],
offset: [-offset, 0],
},
right: {
points: ['cl', 'cr'],
offset: [4, 0],
offset: [offset, 0],
},
top: {
points: ['bc', 'tc'],
offset: [0, -4],
offset: [0, -offset],
},
bottom: {
points: ['tc', 'bc'],
offset: [0, 4],
offset: [0, offset],
},
topLeft: {
points: ['bl', 'tc'],
offset: [-(horizontalArrowShift + arrowWidth), -4],
offset: [-(horizontalArrowShift + halfArrowWidth), -offset],
},
leftTop: {
points: ['tr', 'cl'],
offset: [-4, -(verticalArrowShift + arrowWidth)],
offset: [-offset, -(verticalArrowShift + halfArrowWidth)],
},
topRight: {
points: ['br', 'tc'],
offset: [horizontalArrowShift + arrowWidth, -4],
offset: [horizontalArrowShift + halfArrowWidth, -offset],
},
rightTop: {
points: ['tl', 'cr'],
offset: [4, -(verticalArrowShift + arrowWidth)],
offset: [offset, -(verticalArrowShift + halfArrowWidth)],
},
bottomRight: {
points: ['tr', 'bc'],
offset: [horizontalArrowShift + arrowWidth, 4],
offset: [horizontalArrowShift + halfArrowWidth, offset],
},
rightBottom: {
points: ['bl', 'cr'],
offset: [4, verticalArrowShift + arrowWidth],
offset: [offset, verticalArrowShift + halfArrowWidth],
},
bottomLeft: {
points: ['tl', 'bc'],
offset: [-(horizontalArrowShift + arrowWidth), 4],
offset: [-(horizontalArrowShift + halfArrowWidth), offset],
},
leftBottom: {
points: ['br', 'cl'],
offset: [-4, verticalArrowShift + arrowWidth],
offset: [-offset, verticalArrowShift + halfArrowWidth],
},
};
Object.keys(placementMap).forEach((key) => {
placementMap[key] = arrowPointAtCenter
? {
...placementMap[key],
offset: vertexCalc(
placementMap[key].offset!,
getArrowOffset(key as PlacementType, arrowWidth, offset),
),
overflow: getOverflowOptions(autoAdjustOverflow),
targetOffset,
}
: {
...placements[key],
offset: vertexCalc(
placements[key].offset!,
getArrowOffset(key as PlacementType, arrowWidth, offset),
),
overflow: getOverflowOptions(autoAdjustOverflow),
};

View File

@ -1,3 +0,0 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('affix');

View File

@ -0,0 +1,33 @@
import { spyElementPrototype } from 'rc-util/lib/test/domHook';
import * as React from 'react';
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
demoTest('affix', {
testRootProps: false,
});
rootPropsTest(
'affix',
(Affix, props) => (
<Affix {...props} className="fixed" target={() => document.querySelector('#holder')}>
Bamboo
</Affix>
),
{
beforeRender: () => {
spyElementPrototype(HTMLElement, 'getBoundingClientRect', function getBoundingClientRect() {
if (this.id === 'holder') {
return { top: 0, bottom: 100 };
}
if (this.className === 'fixed') {
return { top: -100, bottom: -100 };
}
return { top: 0, bottom: 0 };
});
},
findRootElements: () => document.querySelectorAll('.ant-affix'),
expectCount: 1,
},
);

View File

@ -32,12 +32,12 @@ export interface AffixProps {
target?: () => Window | HTMLElement | null;
prefixCls?: string;
className?: string;
rootClassName?: string;
children: React.ReactNode;
}
interface InternalAffixProps extends AffixProps {
affixPrefixCls: string;
rootClassName: string;
}
enum AffixStatus {
@ -262,8 +262,7 @@ class Affix extends React.Component<InternalAffixProps, AffixState> {
render() {
const { affixStyle, placeholderStyle } = this.state;
const { affixPrefixCls, rootClassName, children } = this.props;
const className = classNames({
[rootClassName]: !!affixStyle,
const className = classNames(affixStyle && rootClassName, {
[affixPrefixCls]: !!affixStyle,
});
@ -297,7 +296,7 @@ class Affix extends React.Component<InternalAffixProps, AffixState> {
export type InternalAffixClass = Affix;
const AffixFC = forwardRef<Affix, AffixProps>((props, ref) => {
const { prefixCls: customizePrefixCls } = props;
const { prefixCls: customizePrefixCls, rootClassName } = props;
const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext);
const affixPrefixCls = getPrefixCls('affix', customizePrefixCls);
@ -306,7 +305,7 @@ const AffixFC = forwardRef<Affix, AffixProps>((props, ref) => {
const AffixProps: InternalAffixProps = {
...props,
affixPrefixCls,
rootClassName: hashId,
rootClassName: classNames(rootClassName, hashId),
};
return wrapSSR(<Affix {...AffixProps} ref={ref} />);

View File

@ -37,6 +37,7 @@ export interface AlertProps {
style?: React.CSSProperties;
prefixCls?: string;
className?: string;
rootClassName?: string;
banner?: boolean;
icon?: React.ReactNode;
/** Custom closeIcon */
@ -100,7 +101,8 @@ const Alert: CompoundedComponent = ({
prefixCls: customizePrefixCls,
message,
banner,
className = '',
className,
rootClassName,
style,
onMouseEnter,
onMouseLeave,
@ -151,6 +153,7 @@ const Alert: CompoundedComponent = ({
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
);

View File

@ -1,14 +1,16 @@
import classNames from 'classnames';
import * as React from 'react';
import scrollIntoView from 'scroll-into-view-if-needed';
import Affix from '../affix';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import getScroll from '../_util/getScroll';
import scrollTo from '../_util/scrollTo';
import warning from '../_util/warning';
import AnchorContext from './context';
import type { AnchorLinkBaseProps } from './AnchorLink';
import AnchorLink from './AnchorLink';
import AnchorContext from './context';
import useStyle from './style';
@ -51,6 +53,7 @@ interface Section {
export interface AnchorProps {
prefixCls?: string;
className?: string;
rootClassName?: string;
style?: React.CSSProperties;
/**
* @deprecated Please use `items` instead.
@ -72,6 +75,7 @@ export interface AnchorProps {
/** Listening event when scrolling change active link */
onChange?: (currentActiveLink: string) => void;
items?: AnchorLinkItemProps[];
direction?: AnchorDirection;
}
interface InternalAnchorProps extends AnchorProps {
@ -90,6 +94,8 @@ export interface AnchorDefaultProps extends AnchorProps {
getContainer: () => AnchorContainer;
}
export type AnchorDirection = 'vertical' | 'horizontal';
export interface AntAnchor {
registerLink: (link: string) => void;
unregisterLink: (link: string) => void;
@ -99,6 +105,7 @@ export interface AntAnchor {
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
link: { title: React.ReactNode; href: string },
) => void;
direction: AnchorDirection;
}
const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
@ -112,6 +119,7 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
showInkInFixed = false,
children,
items,
direction: anchorDirection = 'vertical',
bounds,
targetOffset,
onClick,
@ -125,6 +133,14 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
warning(!children, 'Anchor', '`Anchor children` is deprecated. Please use `items` instead.');
}
if (process.env.NODE_ENV !== 'production') {
warning(
!(anchorDirection === 'horizontal' && items?.some((n) => 'children' in n)),
'Anchor',
'`Anchor items#children` is not supported when `Anchor` direction is horizontal.',
);
}
const [links, setLinks] = React.useState<string[]>([]);
const [activeLink, setActiveLink] = React.useState<string | null>(null);
const activeLinkRef = React.useRef<string | null>(activeLink);
@ -162,8 +178,17 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
`.${prefixCls}-link-title-active`,
);
if (linkNode && spanLinkNode.current) {
spanLinkNode.current.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2}px`;
spanLinkNode.current.style.height = `${linkNode.clientHeight}px`;
if (anchorDirection !== 'horizontal') {
spanLinkNode.current.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2}px`;
spanLinkNode.current.style.height = `${linkNode.clientHeight}px`;
} else {
spanLinkNode.current.style.left = `${linkNode.offsetLeft}px`;
spanLinkNode.current.style.width = `${linkNode.clientWidth}px`;
scrollIntoView(linkNode, {
scrollMode: 'if-needed',
block: 'nearest',
});
}
}
};
@ -249,17 +274,11 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
[targetOffset, offsetTop],
);
const inkClass = classNames(
{
[`${prefixCls}-ink-ball-visible`]: activeLink,
},
`${prefixCls}-ink-ball`,
);
const wrapperClass = classNames(
rootClassName,
`${prefixCls}-wrapper`,
{
[`${prefixCls}-wrapper-horizontal`]: anchorDirection === 'horizontal',
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
@ -269,6 +288,10 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
[`${prefixCls}-fixed`]: !affix && !showInkInFixed,
});
const inkClass = classNames(`${prefixCls}-ink`, {
[`${prefixCls}-ink-visible`]: activeLink,
});
const wrapperStyle: React.CSSProperties = {
maxHeight: offsetTop ? `calc(100vh - ${offsetTop}px)` : '100vh',
...style,
@ -278,7 +301,7 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
Array.isArray(options)
? options.map((item) => (
<AnchorLink {...item} key={item.key}>
{createNestedLink(item.children)}
{anchorDirection === 'vertical' && createNestedLink(item.children)}
</AnchorLink>
))
: null;
@ -286,9 +309,7 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
const anchorContent = (
<div ref={wrapperRef} className={wrapperClass} style={wrapperStyle}>
<div className={anchorClass}>
<div className={`${prefixCls}-ink`}>
<span className={inkClass} ref={spanLinkNode} />
</div>
<span className={inkClass} ref={spanLinkNode} />
{'items' in props ? createNestedLink(items) : children}
</div>
</div>
@ -311,7 +332,7 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
React.useEffect(() => {
updateInk();
}, [getCurrentAnchor, dependencyListItem, activeLink]);
}, [anchorDirection, getCurrentAnchor, dependencyListItem, activeLink]);
const memoizedContextValue = React.useMemo<AntAnchor>(
() => ({
@ -320,8 +341,9 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
scrollTo: handleScrollTo,
activeLink,
onClick,
direction: anchorDirection,
}),
[activeLink, onClick, handleScrollTo],
[activeLink, onClick, handleScrollTo, anchorDirection],
);
return (
@ -338,14 +360,18 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
};
const Anchor: React.FC<AnchorProps> = (props) => {
const { prefixCls: customizePrefixCls } = props;
const { prefixCls: customizePrefixCls, rootClassName } = props;
const { getPrefixCls } = React.useContext<ConfigConsumerProps>(ConfigContext);
const anchorPrefixCls = getPrefixCls('anchor', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(anchorPrefixCls);
return wrapSSR(
<AnchorContent {...props} rootClassName={hashId} anchorPrefixCls={anchorPrefixCls} />,
<AnchorContent
{...props}
rootClassName={classNames(hashId, rootClassName)}
anchorPrefixCls={anchorPrefixCls}
/>,
);
};

View File

@ -1,7 +1,8 @@
import classNames from 'classnames';
import * as React from 'react';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import { ConfigConsumer } from '../config-provider';
import warning from '../_util/warning';
import type { AntAnchor } from './Anchor';
import AnchorContext from './context';
@ -22,7 +23,7 @@ const AnchorLink: React.FC<AnchorLinkProps> = (props) => {
const context = React.useContext<AntAnchor | undefined>(AnchorContext);
const { registerLink, unregisterLink, scrollTo, onClick, activeLink } = context || {};
const { registerLink, unregisterLink, scrollTo, onClick, activeLink, direction } = context || {};
React.useEffect(() => {
registerLink?.(href);
@ -36,31 +37,42 @@ const AnchorLink: React.FC<AnchorLinkProps> = (props) => {
scrollTo?.(href);
};
const { getPrefixCls } = React.useContext<ConfigConsumerProps>(ConfigContext);
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
const wrapperClassName = classNames(`${prefixCls}-link`, className, {
[`${prefixCls}-link-active`]: activeLink === href,
});
const titleClassName = classNames(`${prefixCls}-link-title`, {
[`${prefixCls}-link-title-active`]: activeLink === href,
});
// =================== Warning =====================
if (process.env.NODE_ENV !== 'production') {
warning(
!children || direction !== 'horizontal',
'Anchor.Link',
'`Anchor.Link children` is not supported when `Anchor` direction is horizontal',
);
}
return (
<div className={wrapperClassName}>
<a
className={titleClassName}
href={href}
title={typeof title === 'string' ? title : ''}
target={target}
onClick={handleClick}
>
{title}
</a>
{children}
</div>
<ConfigConsumer>
{({ getPrefixCls }: ConfigConsumerProps) => {
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
const active = activeLink === href;
const wrapperClassName = classNames(`${prefixCls}-link`, className, {
[`${prefixCls}-link-active`]: active,
});
const titleClassName = classNames(`${prefixCls}-link-title`, {
[`${prefixCls}-link-title-active`]: active,
});
return (
<div className={wrapperClassName}>
<a
className={titleClassName}
href={href}
title={typeof title === 'string' ? title : ''}
target={target}
onClick={handleClick}
>
{title}
</a>
{direction !== 'horizontal' ? children : null}
</div>
);
}}
</ConfigConsumer>
);
};

View File

@ -1,4 +1,7 @@
import React from 'react';
import { resetWarned } from 'rc-util/lib/warning';
import scrollIntoView from 'scroll-into-view-if-needed';
import Anchor from '..';
import { fireEvent, render, waitFakeTimer } from '../../../tests/utils';
@ -13,12 +16,15 @@ function createDiv() {
let idCounter = 0;
const getHashUrl = () => `Anchor-API-${idCounter++}`;
jest.mock('scroll-into-view-if-needed', () => jest.fn());
describe('Anchor Render', () => {
const getBoundingClientRectMock = jest.spyOn(
HTMLHeadingElement.prototype,
'getBoundingClientRect',
);
const getClientRectsMock = jest.spyOn(HTMLHeadingElement.prototype, 'getClientRects');
const scrollIntoViewMock = jest.createMockFromModule<any>('scroll-into-view-if-needed');
beforeAll(() => {
jest.useFakeTimers();
@ -27,11 +33,12 @@ describe('Anchor Render', () => {
height: 100,
top: 1000,
} as DOMRect);
getClientRectsMock.mockReturnValue({ length: 1 } as DOMRectList);
getClientRectsMock.mockReturnValue([1] as unknown as DOMRectList);
});
beforeEach(() => {
jest.useFakeTimers();
scrollIntoViewMock.mockReset();
});
afterEach(() => {
@ -46,22 +53,133 @@ describe('Anchor Render', () => {
getClientRectsMock.mockRestore();
});
it('renders correctly', () => {
const hash = getHashUrl();
const { container } = render(
<Anchor>
<Link href={`#${hash}`} title={hash} />
it('renders items correctly', () => {
const { container, asFragment } = render(
<Anchor
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Item Basic Demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
children: [
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
],
},
]}
/>,
);
expect(container.querySelectorAll('.ant-anchor .ant-anchor-link').length).toBe(5);
const linkTitles = Array.from(container.querySelector('.ant-anchor')?.childNodes!).map((n) =>
(n as HTMLElement).querySelector('.ant-anchor-link-title'),
);
expect((linkTitles[1] as HTMLAnchorElement).href).toContain('#components-anchor-demo-basic');
expect((linkTitles[2] as HTMLAnchorElement).href).toContain('#components-anchor-demo-static');
expect((linkTitles[3] as HTMLAnchorElement).href).toContain('#api');
expect(
(
container.querySelector(
'.ant-anchor .ant-anchor-link .ant-anchor-link .ant-anchor-link-title',
) as HTMLAnchorElement
)?.href,
).toContain('#anchor-props');
expect(
(
container.querySelector(
'.ant-anchor .ant-anchor-link .ant-anchor-link .ant-anchor-link .ant-anchor-link-title',
) as HTMLAnchorElement
)?.href,
).toContain('#link-props');
expect(asFragment().firstChild).toMatchSnapshot();
});
it('renders items correctly#horizontal', () => {
const { container, asFragment } = render(
<Anchor
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Item Basic Demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
},
]}
/>,
);
expect(container.querySelectorAll('.ant-anchor .ant-anchor-link').length).toBe(3);
const linkTitles = Array.from(container.querySelector('.ant-anchor')?.childNodes!).map((n) =>
(n as HTMLElement).querySelector('.ant-anchor-link-title'),
);
expect((linkTitles[1] as HTMLAnchorElement).href).toContain('#components-anchor-demo-basic');
expect((linkTitles[2] as HTMLAnchorElement).href).toContain('#components-anchor-demo-static');
expect((linkTitles[3] as HTMLAnchorElement).href).toContain('#api');
expect(asFragment().firstChild).toMatchSnapshot();
});
it('render items and ignore jsx children', () => {
const { container, asFragment } = render(
<Anchor
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Item Basic Demo',
},
]}
>
<Link href="#api" title="API" />
</Anchor>,
);
expect(container.querySelector(`a[href="#${hash}"]`)).not.toBe(null);
expect(container.querySelectorAll('.ant-anchor .ant-anchor-link').length).toBe(1);
expect(
(container.querySelector('.ant-anchor .ant-anchor-link-title') as HTMLAnchorElement).href,
).toContain('#components-anchor-demo-basic');
expect(asFragment().firstChild).toMatchSnapshot();
});
it('actives the target when clicking a link', async () => {
const hash = getHashUrl();
const { container } = render(
<Anchor prefixCls="ant-anchor">
<Link href={`http://www.example.com/#${hash}`} title={hash} />
</Anchor>,
<Anchor
prefixCls="ant-anchor"
direction="horizontal"
items={[
{
key: hash,
title: hash,
href: `http://www.example.com/#${hash}`,
},
]}
/>,
);
const link = container.querySelector(`a[href="http://www.example.com/#${hash}"]`)!;
fireEvent.click(link);
@ -74,9 +192,7 @@ describe('Anchor Render', () => {
const scrollToSpy = jest.spyOn(window, 'scrollTo');
render(<div id="/faq?locale=en#Q1">Q1</div>, { container: root });
const { container } = render(
<Anchor>
<Link href="/#/faq?locale=en#Q1" title="Q1" />
</Anchor>,
<Anchor items={[{ key: 'Q1', title: 'Q1', href: '/#/faq?locale=en#Q1' }]} />,
);
const link = container.querySelector(`a[href="/#/faq?locale=en#Q1"]`)!;
fireEvent.click(link);
@ -97,10 +213,13 @@ describe('Anchor Render', () => {
{ container: root },
);
const { container } = render(
<Anchor onChange={onChange}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
<Anchor
onChange={onChange}
items={[
{ key: hash1, href: `#${hash1}`, title: hash1 },
{ key: hash2, href: `#${hash2}`, title: hash2 },
]}
/>,
);
onChange.mockClear();
@ -119,9 +238,7 @@ describe('Anchor Render', () => {
it('should update DOM when children are unmounted', () => {
const hash = getHashUrl();
const { container, rerender } = render(
<Anchor>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
<Anchor items={[{ key: hash, href: `#${hash}`, title: hash }]} />,
);
expect(container.querySelectorAll('.ant-anchor-link-title')).toHaveLength(1);
@ -134,11 +251,7 @@ describe('Anchor Render', () => {
it('should update DOM when link href is changed', async () => {
const hash = getHashUrl();
function AnchorUpdate({ href }: { href: string }) {
return (
<Anchor>
<Link href={href} title={hash} />
</Anchor>
);
return <Anchor items={[{ key: hash, href, title: hash }]} />;
}
const { container, rerender } = render(<AnchorUpdate href={`#${hash}`} />);
@ -154,17 +267,11 @@ describe('Anchor Render', () => {
const root = createDiv();
render(<h1 id={hash}>Hello</h1>, { container: root });
const { container, rerender } = render(
<Anchor>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
<Anchor items={[{ key: hash, href: `#${hash}`, title: hash }]} />,
);
const setProps = (props: Record<string, any>) =>
rerender(
<Anchor {...props}>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
rerender(<Anchor {...props} items={[{ key: hash, href: `#${hash}`, title: hash }]} />);
fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!);
await waitFakeTimer();
@ -191,17 +298,11 @@ describe('Anchor Render', () => {
const root = createDiv();
render(<h1 id={hash}>Hello</h1>, { container: root });
const { container, rerender } = render(
<Anchor>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
<Anchor items={[{ key: hash, href: `#${hash}`, title: hash }]} />,
);
const setProps = (props: Record<string, any>) =>
rerender(
<Anchor {...props}>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
rerender(<Anchor {...props} items={[{ key: hash, href: `#${hash}`, title: hash }]} />);
fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!);
await waitFakeTimer();
@ -233,9 +334,7 @@ describe('Anchor Render', () => {
const href = `#${hash}`;
const title = hash;
const { container } = render(
<Anchor onClick={handleClick}>
<Link href={href} title={title} />
</Anchor>,
<Anchor onClick={handleClick} items={[{ key: hash, href, title }]} />,
);
fireEvent.click(container.querySelector(`a[href="${href}"]`)!);
@ -248,10 +347,21 @@ describe('Anchor Render', () => {
const hash2 = getHashUrl();
const onChange = jest.fn();
const { container } = render(
<Anchor onChange={onChange}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
<Anchor
onChange={onChange}
items={[
{
key: hash1,
href: `#${hash1}`,
title: hash1,
},
{
key: hash2,
href: `#${hash2}`,
title: hash2,
},
]}
/>,
// https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0
{ legacyRoot: true },
);
@ -264,9 +374,7 @@ describe('Anchor Render', () => {
it('handles invalid hash correctly', () => {
const { container } = render(
<Anchor>
<Link href="notexsited" title="title" />
</Anchor>,
<Anchor items={[{ key: 'title', href: 'notexsited', title: 'title' }]} />,
);
const link = container.querySelector(`a[href="notexsited"]`)!;
@ -356,10 +464,13 @@ describe('Anchor Render', () => {
const hash2 = getHashUrl();
const getCurrentAnchor = () => `#${hash2}`;
const { container } = render(
<Anchor getCurrentAnchor={getCurrentAnchor}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
<Anchor
getCurrentAnchor={getCurrentAnchor}
items={[
{ key: hash1, href: `#${hash1}`, title: hash1 },
{ key: hash2, href: `#${hash2}`, title: hash2 },
]}
/>,
);
expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe(hash2);
@ -371,10 +482,14 @@ describe('Anchor Render', () => {
const hash2 = getHashUrl();
const onChange = jest.fn();
const { container } = render(
<Anchor onChange={onChange} getCurrentAnchor={() => hash1}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
<Anchor
onChange={onChange}
getCurrentAnchor={() => hash1}
items={[
{ key: hash1, href: `#${hash1}`, title: hash1 },
{ key: hash2, href: `#${hash2}`, title: hash2 },
]}
/>,
// https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0
{ legacyRoot: true },
);
@ -391,10 +506,13 @@ describe('Anchor Render', () => {
const hash2 = getHashUrl();
const getCurrentAnchor = jest.fn();
const { container } = render(
<Anchor getCurrentAnchor={getCurrentAnchor}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
<Anchor
getCurrentAnchor={getCurrentAnchor}
items={[
{ key: hash1, href: `#${hash1}`, title: hash1 },
{ key: hash2, href: `#${hash2}`, title: hash2 },
]}
/>,
);
fireEvent.click(container.querySelector(`a[href="#${hash1}"]`)!);
@ -408,10 +526,13 @@ describe('Anchor Render', () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const Demo: React.FC<{ current: string }> = ({ current }) => (
<Anchor getCurrentAnchor={() => `#${current}`}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>
<Anchor
getCurrentAnchor={() => `#${current}`}
items={[
{ key: hash1, href: `#${hash1}`, title: hash1 },
{ key: hash2, href: `#${hash2}`, title: hash2 },
]}
/>
);
const { container, rerender } = render(<Demo current={hash1} />);
expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe(hash1);
@ -422,72 +543,348 @@ describe('Anchor Render', () => {
it('should render correctly when href is null', () => {
expect(() => {
render(
<Anchor>
<Link href={null as unknown as string} title="test" />
</Anchor>,
<Anchor items={[{ key: 'test', href: null as unknown as string, title: 'test' }]} />,
);
fireEvent.scroll(window || document);
}).not.toThrow();
});
});
it('renders items correctly', () => {
const { container, asFragment } = render(
<Anchor
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Item Basic Demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
describe('horizontal anchor', () => {
describe('scroll x', () => {
it('targetOffset horizontal', async () => {
const hash = getHashUrl();
const scrollToSpy = jest.spyOn(window, 'scrollTo');
const root = createDiv();
render(<h1 id={hash}>Hello</h1>, { container: root });
const { container, rerender } = render(
<Anchor
direction="horizontal"
items={[
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
children: [
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
key: hash,
href: `#${hash}`,
title: hash,
},
],
},
]}
/>,
);
expect(container.querySelectorAll('.ant-anchor .ant-anchor-link').length).toBe(5);
const linkTitles = Array.from(container.querySelector('.ant-anchor')?.childNodes!)
.slice(1)
.map((n) => (n as HTMLElement).querySelector('.ant-anchor-link-title'));
expect((linkTitles[0] as HTMLAnchorElement).href).toContain('#components-anchor-demo-basic');
expect((linkTitles[1] as HTMLAnchorElement).href).toContain('#components-anchor-demo-static');
expect((linkTitles[2] as HTMLAnchorElement).href).toContain('#api');
expect(asFragment().firstChild).toMatchSnapshot();
expect(
(
container.querySelector(
'.ant-anchor .ant-anchor-link .ant-anchor-link .ant-anchor-link-title',
) as HTMLAnchorElement
)?.href,
).toContain('#anchor-props');
expect(
(
container.querySelector(
'.ant-anchor .ant-anchor-link .ant-anchor-link .ant-anchor-link .ant-anchor-link-title',
) as HTMLAnchorElement
)?.href,
).toContain('#link-props');
]}
/>,
);
const setProps = (props: Record<string, any>) =>
rerender(
<Anchor
{...props}
direction="horizontal"
items={[
{
key: hash,
href: `#${hash}`,
title: hash,
},
]}
/>,
);
fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!);
await waitFakeTimer();
expect(scrollIntoView).toHaveBeenCalled();
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000);
setProps({ offsetTop: 100 });
fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!);
await waitFakeTimer();
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900);
setProps({ targetOffset: 200 });
fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!);
await waitFakeTimer();
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
});
});
it('test direction prop', () => {
const { container } = render(
<Anchor
direction="horizontal"
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Item Basic Demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
},
]}
/>,
);
expect(container.querySelectorAll('.ant-anchor-ink').length).toBe(1);
expect(
container
.querySelector('.ant-anchor-wrapper')
?.classList.contains('ant-anchor-wrapper-horizontal'),
).toBeTruthy();
});
it('nested children via items should be filtered out when direction is horizontal', () => {
const { container } = render(
<Anchor
direction="horizontal"
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Item Basic Demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
]}
/>,
);
expect(container.querySelectorAll('.ant-anchor-link').length).toBe(3);
});
it('nested children via jsx should be filtered out when direction is horizontal', () => {
const { container } = render(
<Anchor direction="horizontal">
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
<Link href="#api" title="API">
<Link href="#anchor-props" title="Anchor Props" />
<Link href="#link-props" title="Link Props" />
</Link>
</Anchor>,
);
expect(container.querySelectorAll('.ant-anchor-link').length).toBe(3);
});
});
describe('deprecated/legacy jsx syntax', () => {
it('renders jsx correctly', () => {
const hash = getHashUrl();
const { container } = render(
<Anchor>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
expect(container.querySelector(`a[href="#${hash}"]`)).not.toBe(null);
});
it('actives the target when clicking a link', async () => {
const hash = getHashUrl();
const { container } = render(
<Anchor prefixCls="ant-anchor">
<Link href={`http://www.example.com/#${hash}`} title={hash} />
</Anchor>,
);
const link = container.querySelector(`a[href="http://www.example.com/#${hash}"]`)!;
fireEvent.click(link);
await waitFakeTimer();
expect(link.classList).toContain('ant-anchor-link-title-active');
});
it('scrolls the page when clicking a link', async () => {
const root = createDiv();
const scrollToSpy = jest.spyOn(window, 'scrollTo');
render(<div id="/faq?locale=en#Q1">Q1</div>, { container: root });
const { container } = render(
<Anchor>
<Link href="/#/faq?locale=en#Q1" title="Q1" />
</Anchor>,
);
const link = container.querySelector(`a[href="/#/faq?locale=en#Q1"]`)!;
fireEvent.click(link);
await waitFakeTimer();
expect(scrollToSpy).toHaveBeenCalled();
});
it('handleScroll should not be triggered when scrolling caused by clicking a link', async () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const root = createDiv();
const onChange = jest.fn();
render(
<div>
<div id={hash1}>Hello</div>
<div id={hash2}>World</div>
</div>,
{ container: root },
);
const { container } = render(
<Anchor onChange={onChange}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
);
onChange.mockClear();
const link = container.querySelector(`a[href="#${hash2}"]`)!;
// this will trigger 1 onChange
fireEvent.click(link);
// smooth scroll caused by clicking needs time to finish.
// we scroll the window before it finish, the scroll listener should not be triggered,
fireEvent.scroll(window);
await waitFakeTimer();
// if the scroll listener is triggered, we will get 2 onChange, now we expect only 1.
expect(onChange).toHaveBeenCalledTimes(1);
});
it('should update DOM when children are unmounted', () => {
const hash = getHashUrl();
const { container, rerender } = render(
<Anchor>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
expect(container.querySelectorAll('.ant-anchor-link-title')).toHaveLength(1);
expect(container.querySelector('.ant-anchor-link-title')).toHaveAttribute('href', `#${hash}`);
rerender(<Anchor />);
expect(container.querySelector('.ant-anchor-link-title')).toBeFalsy();
});
it('should update DOM when link href is changed', async () => {
const hash = getHashUrl();
function AnchorUpdate({ href }: { href: string }) {
return (
<Anchor>
<Link href={href} title={hash} />
</Anchor>
);
}
const { container, rerender } = render(<AnchorUpdate href={`#${hash}`} />);
expect(container.querySelector(`a[href="#${hash}"]`)).toBeTruthy();
rerender(<AnchorUpdate href={`#${hash}_1`} />);
expect(container.querySelector(`a[href="#${hash}_1"]`)).toBeTruthy();
});
it('handles invalid hash correctly', () => {
const { container } = render(
<Anchor>
<Link href="notexsited" title="title" />
</Anchor>,
);
const link = container.querySelector(`a[href="notexsited"]`)!;
fireEvent.click(link);
expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe('title');
});
});
describe('warning', () => {
let errSpy: jest.SpyInstance;
beforeEach(() => {
resetWarned();
errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
errSpy.mockRestore();
});
it('warning nested children when direction is horizontal ', () => {
render(
<Anchor
direction="horizontal"
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Item Basic Demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
],
},
]}
/>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Anchor] `Anchor items#children` is not supported when `Anchor` direction is horizontal.',
);
});
it('deprecated jsx style', () => {
render(
<Anchor direction="horizontal">
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
</Anchor>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Anchor] `Anchor children` is deprecated. Please use `items` instead.',
);
});
it('deprecated jsx style for direction#vertical', () => {
render(
<Anchor>
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
</Anchor>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Anchor] `Anchor children` is deprecated. Please use `items` instead.',
);
});
it('deprecated jsx style for direction#vertical 1: with nested children', () => {
render(
<Anchor direction="horizontal">
<Link href="#api" title="API">
<Link href="#anchor-props" title="Anchor Props" />
</Link>
</Anchor>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Anchor] `Anchor children` is deprecated. Please use `items` instead.',
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Anchor.Link] `Anchor.Link children` is not supported when `Anchor` direction is horizontal',
);
});
});
});

View File

@ -1,5 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Anchor Render render items and ignore jsx children 1`] = `
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Item Basic Demo"
>
Item Basic Demo
</a>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Anchor Render renders items correctly 1`] = `
<div>
<div
@ -12,13 +44,9 @@ exports[`Anchor Render renders items correctly 1`] = `
<div
class="ant-anchor"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -79,3 +107,57 @@ exports[`Anchor Render renders items correctly 1`] = `
</div>
</div>
`;
exports[`Anchor Render renders items correctly#horizontal 1`] = `
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Item Basic Demo"
>
Item Basic Demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -34,13 +34,9 @@ exports[`renders ./components/anchor/demo/basic.tsx extend context correctly 1`]
<div
class="ant-anchor"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -90,13 +86,9 @@ exports[`renders ./components/anchor/demo/customizeHighlight.tsx extend context
<div
class="ant-anchor ant-anchor-fixed"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -156,82 +148,191 @@ exports[`renders ./components/anchor/demo/customizeHighlight.tsx extend context
</div>
`;
exports[`renders ./components/anchor/demo/legacy-anchor.tsx extend context correctly 1`] = `
<div>
exports[`renders ./components/anchor/demo/horizontal.tsx extend context correctly 1`] = `
Array [
<div
class=""
style="padding:20px"
>
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div>
<div
class="ant-anchor"
class=""
>
<div
class="ant-anchor-ink"
class="ant-anchor-wrapper ant-anchor-wrapper-horizontal"
style="max-height:100vh"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
class="ant-anchor"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
<a
class="ant-anchor-link-title"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
Link Props
</a>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-4"
title="Part 4"
>
Part 4
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-5"
title="Part 5"
>
Part 5
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-6"
title="Part 6"
>
Part 6
</a>
</div>
</div>
</div>
</div>
</div>
</div>,
<div>
<div
id="part-1"
style="width:100vw;height:100vh;text-align:center;background:rgba(0,255,0,0.02)"
/>
<div
id="part-2"
style="width:100vw;height:100vh;text-align:center;background:rgba(0,0,255,0.02)"
/>
<div
id="part-3"
style="width:100vw;height:100vh;text-align:center;background:#FFFBE9"
/>
<div
id="part-4"
style="width:100vw;height:100vh;text-align:center;background:#F4EAD5"
/>
<div
id="part-5"
style="width:100vw;height:100vh;text-align:center;background:#DAE2B6"
/>
<div
id="part-6"
style="width:100vw;height:100vh;text-align:center;background:#CCD6A6"
/>
</div>,
]
`;
exports[`renders ./components/anchor/demo/legacy-anchor.tsx extend context correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
@ -244,13 +345,9 @@ exports[`renders ./components/anchor/demo/onChange.tsx extend context correctly
<div
class="ant-anchor ant-anchor-fixed"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -318,13 +415,9 @@ exports[`renders ./components/anchor/demo/onClick.tsx extend context correctly 1
<div
class="ant-anchor ant-anchor-fixed"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -392,13 +485,9 @@ exports[`renders ./components/anchor/demo/static.tsx extend context correctly 1`
<div
class="ant-anchor ant-anchor-fixed"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -499,13 +588,9 @@ exports[`renders ./components/anchor/demo/targetOffset.tsx extend context correc
<div
class="ant-anchor"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>

View File

@ -34,13 +34,9 @@ exports[`renders ./components/anchor/demo/basic.tsx correctly 1`] = `
<div
class="ant-anchor"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -90,13 +86,9 @@ exports[`renders ./components/anchor/demo/customizeHighlight.tsx correctly 1`] =
<div
class="ant-anchor ant-anchor-fixed"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -156,82 +148,191 @@ exports[`renders ./components/anchor/demo/customizeHighlight.tsx correctly 1`] =
</div>
`;
exports[`renders ./components/anchor/demo/legacy-anchor.tsx correctly 1`] = `
<div>
exports[`renders ./components/anchor/demo/horizontal.tsx correctly 1`] = `
Array [
<div
class=""
style="padding:20px"
>
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div>
<div
class="ant-anchor"
class=""
>
<div
class="ant-anchor-ink"
class="ant-anchor-wrapper ant-anchor-wrapper-horizontal"
style="max-height:100vh"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
class="ant-anchor"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
<a
class="ant-anchor-link-title"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
Link Props
</a>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-4"
title="Part 4"
>
Part 4
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-5"
title="Part 5"
>
Part 5
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-6"
title="Part 6"
>
Part 6
</a>
</div>
</div>
</div>
</div>
</div>
</div>,
<div>
<div
id="part-1"
style="width:100vw;height:100vh;text-align:center;background:rgba(0,255,0,0.02)"
/>
<div
id="part-2"
style="width:100vw;height:100vh;text-align:center;background:rgba(0,0,255,0.02)"
/>
<div
id="part-3"
style="width:100vw;height:100vh;text-align:center;background:#FFFBE9"
/>
<div
id="part-4"
style="width:100vw;height:100vh;text-align:center;background:#F4EAD5"
/>
<div
id="part-5"
style="width:100vw;height:100vh;text-align:center;background:#DAE2B6"
/>
<div
id="part-6"
style="width:100vw;height:100vh;text-align:center;background:#CCD6A6"
/>
</div>,
]
`;
exports[`renders ./components/anchor/demo/legacy-anchor.tsx correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
@ -244,13 +345,9 @@ exports[`renders ./components/anchor/demo/onChange.tsx correctly 1`] = `
<div
class="ant-anchor ant-anchor-fixed"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -318,13 +415,9 @@ exports[`renders ./components/anchor/demo/onClick.tsx correctly 1`] = `
<div
class="ant-anchor ant-anchor-fixed"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -392,13 +485,9 @@ exports[`renders ./components/anchor/demo/static.tsx correctly 1`] = `
<div
class="ant-anchor ant-anchor-fixed"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -499,13 +588,9 @@ exports[`renders ./components/anchor/demo/targetOffset.tsx correctly 1`] = `
<div
class="ant-anchor"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>

View File

@ -1,3 +0,0 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('anchor');

View File

@ -0,0 +1,25 @@
import * as React from 'react';
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
demoTest('anchor', {
testRootProps: false,
});
rootPropsTest(
'anchor',
(Anchor, props) => (
<Anchor
{...props}
items={[
{
key: 'part-1',
href: '#part-1',
title: 'Part 1',
},
]}
/>
),
{
findRootElements: () => document.querySelector('.ant-anchor-wrapper')!,
},
);

View File

@ -0,0 +1,7 @@
## zh-CN
横向 Anchor。
## en-US
Horizontally aligned anchors

View File

@ -0,0 +1,82 @@
import React from 'react';
import { Anchor } from 'antd';
const App: React.FC = () => (
<>
<div style={{ padding: '20px' }}>
<Anchor
direction="horizontal"
items={[
{
key: 'part-1',
href: '#part-1',
title: 'Part 1',
},
{
key: 'part-2',
href: '#part-2',
title: 'Part 2',
},
{
key: 'part-3',
href: '#part-3',
title: 'Part 3',
},
{
key: 'part-4',
href: '#part-4',
title: 'Part 4',
},
{
key: 'part-5',
href: '#part-5',
title: 'Part 5',
},
{
key: 'part-6',
href: '#part-6',
title: 'Part 6',
},
]}
/>
</div>
<div>
<div
id="part-1"
style={{
width: '100vw',
height: '100vh',
textAlign: 'center',
background: 'rgba(0,255,0,0.02)',
}}
/>
<div
id="part-2"
style={{
width: '100vw',
height: '100vh',
textAlign: 'center',
background: 'rgba(0,0,255,0.02)',
}}
/>
<div
id="part-3"
style={{ width: '100vw', height: '100vh', textAlign: 'center', background: '#FFFBE9' }}
/>
<div
id="part-4"
style={{ width: '100vw', height: '100vh', textAlign: 'center', background: '#F4EAD5' }}
/>
<div
id="part-5"
style={{ width: '100vw', height: '100vh', textAlign: 'center', background: '#DAE2B6' }}
/>
<div
id="part-6"
style={{ width: '100vw', height: '100vh', textAlign: 'center', background: '#CCD6A6' }}
/>
</div>
</>
);
export default App;

View File

@ -4,7 +4,7 @@ import { Anchor } from 'antd';
const { Link } = Anchor;
const App: React.FC = () => (
<Anchor>
<Anchor affix={false}>
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
<Link href="#api" title="API">

View File

@ -3,7 +3,6 @@ category: Components
title: Anchor
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*TBTSR4PyVmkAAAAAAAAAAAAADrJ8AQ/original
demo:
cols: 2
group:
title: Navigation
order: 3
@ -23,7 +22,8 @@ For displaying anchor hyperlinks on page and jumping between them.
<!-- prettier-ignore -->
<code src="./demo/basic.tsx" iframe="200">Basic</code>
<code src="./demo/static.tsx">Static Anchor</code>
<code src="./demo/horizontal.tsx" iframe="200">Horizontal Anchor</code>
<code src="./demo/static.tsx" >Static Anchor</code>
<code src="./demo/onClick.tsx">Customize the onClick event</code>
<code src="./demo/customizeHighlight.tsx">Customize the anchor highlight</code>
<code src="./demo/targetOffset.tsx" iframe="200">Set Anchor scroll offset</code>
@ -45,10 +45,23 @@ For displaying anchor hyperlinks on page and jumping between them.
| targetOffset | Anchor scroll offset, default as `offsetTop`, [example](#components-anchor-demo-targetoffset) | number | - | |
| onChange | Listening for anchor link change | (currentActiveLink: string) => void | | |
| onClick | Set the handler to handle `click` event | (e: MouseEvent, link: object) => void | - | |
| items | Data configuration option content, support nesting through children | { href, title, target, children }\[] | - | |
| items | Data configuration option content, support nesting through children | { key, href, title, target, children }\[] [see](#anchoritem) | - | 5.1.0 |
| direction | Set Anchor direction | `vertical` \| `horizontal` | `vertical` | 5.2.0 |
### AnchorItem
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| key | The unique identifier of the Anchor Link | string \| number | - | |
| href | The target of hyperlink | string | | |
| target | Specifies where to display the linked URL | string | | |
| title | The content of hyperlink | ReactNode | | |
| children | Nested Anchor Link, `Attention: This attribute does not support horizontal orientation` | [AnchorItem](#anchoritem)\[] | - | |
### Link Props
We recommend using the items form instead.
| Property | Description | Type | Default | Version |
| -------- | ----------------------------------------- | --------- | ------- | ------- |
| href | The target of hyperlink | string | | |

View File

@ -4,7 +4,6 @@ title: Anchor
subtitle: 锚点
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*TBTSR4PyVmkAAAAAAAAAAAAADrJ8AQ/original
demo:
cols: 2
group:
title: 导航
order: 3
@ -24,6 +23,7 @@ group:
<!-- prettier-ignore -->
<code src="./demo/basic.tsx" iframe="200">基本</code>
<code src="./demo/horizontal.tsx" iframe="200">横向 Anchor</code>
<code src="./demo/static.tsx">静态位置</code>
<code src="./demo/onClick.tsx">自定义 onClick 事件</code>
<code src="./demo/customizeHighlight.tsx">自定义锚点高亮</code>
@ -46,12 +46,25 @@ group:
| targetOffset | 锚点滚动偏移量,默认与 offsetTop 相同,[例子](#components-anchor-demo-targetoffset) | number | - | |
| onChange | 监听锚点链接改变 | (currentActiveLink: string) => void | - | |
| onClick | `click` 事件的 handler | (e: MouseEvent, link: object) => void | - | |
| items | 数据化配置选项内容,支持通过 children 嵌套 | { href, title, target, children }\[] | - | |
| items | 数据化配置选项内容,支持通过 children 嵌套 | { key, href, title, target, children }\[] [具体见](#anchoritem) | - | 5.1.0 |
| direction | 设置导航方向 | `vertical` \| `horizontal` | `vertical` | 5.2.0 |
### AnchorItem
| 成员 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| key | 唯一标志 | string \| number | - | |
| href | 锚点链接 | string | - | |
| target | 该属性指定在何处显示链接的资源 | string | - | |
| title | 文字内容 | ReactNode | - | |
| children | 嵌套的 Anchor Link`注意:水平方向该属性不支持` | [AnchorItem](#anchoritem)\[] | - | |
### Link Props
| 成员 | 说明 | 类型 | 默认值 | 版本 |
| ------ | -------------------------------- | --------- | ------ | ---- |
| href | 锚点链接 | string | - | |
| target | 该属性指定在何处显示链接的资源。 | string | - | |
| title | 文字内容 | ReactNode | - | |
建议使用 items 形式。
| 成员 | 说明 | 类型 | 默认值 | 版本 |
| ------ | ------------------------------ | --------- | ------ | ---- |
| href | 锚点链接 | string | - | |
| target | 该属性指定在何处显示链接的资源 | string | - | |
| title | 文字内容 | ReactNode | - | |

View File

@ -16,8 +16,15 @@ interface AnchorToken extends FullToken<'Anchor'> {
// ============================== Shared ==============================
const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
const { componentCls, holderOffsetBlock, motionDurationSlow, lineWidthBold, colorPrimary } =
token;
const {
componentCls,
holderOffsetBlock,
motionDurationSlow,
lineWidthBold,
colorPrimary,
lineType,
colorSplit,
} = token;
return {
[`${componentCls}-wrapper`]: {
@ -34,40 +41,6 @@ const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
position: 'relative',
paddingInlineStart: lineWidthBold,
[`${componentCls}-ink`]: {
position: 'absolute',
insetBlockStart: 0,
insetInlineStart: 0,
height: '100%',
'&::before': {
position: 'relative',
display: 'block',
width: lineWidthBold,
height: '100%',
margin: '0 auto',
backgroundColor: token.colorSplit,
content: '" "',
},
},
[`${componentCls}-ink-ball`]: {
position: 'absolute',
left: {
_skip_check_: true,
value: 0,
},
display: 'none',
transform: 'translateY(-50%)',
transition: `top ${motionDurationSlow} ease-in-out`,
width: lineWidthBold,
backgroundColor: colorPrimary,
[`&${componentCls}-ink-ball-visible`]: {
display: 'inline-block',
},
},
[`${componentCls}-link`]: {
paddingBlock: token.anchorPaddingBlock,
paddingInline: `${token.anchorPaddingInline}px 0`,
@ -96,13 +69,94 @@ const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
},
},
[`${componentCls}-fixed ${componentCls}-ink ${componentCls}-ink-ball`]: {
[`&:not(${componentCls}-horizontal)`]: {
[componentCls]: {
'&::before': {
position: 'absolute',
left: {
_skip_check_: true,
value: 0,
},
top: 0,
height: '100%',
borderInlineStart: `${lineWidthBold}px ${lineType} ${colorSplit}`,
content: '" "',
},
[`${componentCls}-ink`]: {
position: 'absolute',
left: {
_skip_check_: true,
value: 0,
},
display: 'none',
transform: 'translateY(-50%)',
transition: `top ${motionDurationSlow} ease-in-out`,
width: lineWidthBold,
backgroundColor: colorPrimary,
[`&${componentCls}-ink-visible`]: {
display: 'inline-block',
},
},
},
},
[`${componentCls}-fixed ${componentCls}-ink ${componentCls}-ink`]: {
display: 'none',
},
},
};
};
const genSharedAnchorHorizontalStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
const { componentCls, motionDurationSlow, lineWidthBold, colorPrimary } = token;
return {
[`${componentCls}-wrapper-horizontal`]: {
position: 'relative',
'&::before': {
position: 'absolute',
left: {
_skip_check_: true,
value: 0,
},
right: {
_skip_check_: true,
value: 0,
},
bottom: 0,
borderBottom: `1px ${token.lineType} ${token.colorSplit}`,
content: '" "',
},
[componentCls]: {
overflowX: 'scroll',
position: 'relative',
display: 'flex',
scrollbarWidth: 'none' /* Firefox */,
'&::-webkit-scrollbar': {
display: 'none' /* Safari and Chrome */,
},
[`${componentCls}-link:first-of-type`]: {
paddingInline: 0,
},
[`${componentCls}-ink`]: {
position: 'absolute',
bottom: 0,
transition: `left ${motionDurationSlow} ease-in-out, width ${motionDurationSlow} ease-in-out`,
height: lineWidthBold,
backgroundColor: colorPrimary,
},
},
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook('Anchor', (token) => {
const { fontSize, fontSizeLG, padding, paddingXXS } = token;
@ -115,5 +169,5 @@ export default genComponentStyleHook('Anchor', (token) => {
anchorTitleBlock: (fontSize / 14) * 3,
anchorBallSize: fontSizeLG / 2,
});
return [genSharedAnchorStyle(anchorToken)];
return [genSharedAnchorStyle(anchorToken), genSharedAnchorHorizontalStyle(anchorToken)];
});

View File

@ -1,17 +1,18 @@
import React, { useContext } from 'react';
import type { ReactNode } from 'react';
import classNames from 'classnames';
import type { ReactNode } from 'react';
import React, { useContext } from 'react';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import useStyle from './style';
import useMessage from '../message/useMessage';
import useNotification from '../notification/useNotification';
import useModal from '../modal/useModal';
import AppContext from './context';
import useNotification from '../notification/useNotification';
import type { useAppProps } from './context';
import AppContext from './context';
import useStyle from './style';
export type AppProps = {
className?: string;
rootClassName?: string;
prefixCls?: string;
children?: ReactNode;
};
@ -19,11 +20,11 @@ export type AppProps = {
const useApp = () => React.useContext<useAppProps>(AppContext);
const App: React.FC<AppProps> & { useApp: () => useAppProps } = (props) => {
const { prefixCls: customizePrefixCls, children, className } = props;
const { prefixCls: customizePrefixCls, children, className, rootClassName } = props;
const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext);
const prefixCls = getPrefixCls('app', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls);
const customClassName = classNames(hashId, prefixCls, className);
const customClassName = classNames(hashId, prefixCls, className, rootClassName);
const [messageApi, messageContextHolder] = useMessage();
const [notificationApi, notificationContextHolder] = useNotification();

View File

@ -27,8 +27,8 @@ demo:
App 组件通过 `Context` 提供上下文方法调用,因而 useApp 需要作为子组件才能使用,我们推荐在应用中顶层包裹 App。
```tsx
import React from 'react';
import { App } from 'antd';
import React from 'react';
const MyPage: React.FC = () => {
const { message, notification, modal } = App.useApp();
@ -78,11 +78,10 @@ App 组件只能在 `ConfigProvider` 之下才能使用 Design Token 如果
```tsx
// Entry component
import React, { useEffect } from 'react';
import { App } from 'antd';
import type { MessageInstance } from 'antd/es/message/interface';
import type { NotificationInstance } from 'antd/es/notification/interface';
import type { ModalStaticFunctions } from 'antd/es/modal/confirm';
import type { NotificationInstance } from 'antd/es/notification/interface';
let message: MessageInstance;
let notification: NotificationInstance;
@ -101,9 +100,9 @@ export { message, notification, modal };
```tsx
// sub page
import React from 'react';
import { Button, Space } from 'antd';
import { message, modal, notification } from './store';
import React from 'react';
import { message } from './store';
export default () => {
const showMessage = () => {

View File

@ -1,3 +0,0 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('avatar');

View File

@ -0,0 +1,17 @@
import * as React from 'react';
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
demoTest('avatar');
rootPropsTest(
'avatar',
(Avatar, props) => (
<Avatar.Group {...props} maxCount={1}>
<Avatar>Bamboo</Avatar>
<Avatar>Light</Avatar>
</Avatar.Group>
),
{
name: 'Avatar.Group',
},
);

View File

@ -30,6 +30,7 @@ export interface AvatarProps {
style?: React.CSSProperties;
prefixCls?: string;
className?: string;
rootClassName?: string;
children?: React.ReactNode;
alt?: string;
crossOrigin?: '' | 'anonymous' | 'use-credentials';
@ -99,6 +100,7 @@ const InternalAvatar: React.ForwardRefRenderFunction<HTMLSpanElement, AvatarProp
srcSet,
icon,
className,
rootClassName,
alt,
draggable,
children,
@ -155,6 +157,7 @@ const InternalAvatar: React.ForwardRefRenderFunction<HTMLSpanElement, AvatarProp
[`${prefixCls}-icon`]: !!icon,
},
className,
rootClassName,
hashId,
);

View File

@ -11,6 +11,7 @@ import useStyle from './style';
export interface GroupProps {
className?: string;
rootClassName?: string;
children?: React.ReactNode;
style?: React.CSSProperties;
prefixCls?: string;
@ -27,7 +28,14 @@ export interface GroupProps {
const Group: React.FC<GroupProps> = (props) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const { prefixCls: customizePrefixCls, className = '', maxCount, maxStyle, size } = props;
const {
prefixCls: customizePrefixCls,
className,
rootClassName,
maxCount,
maxStyle,
size,
} = props;
const prefixCls = getPrefixCls('avatar', customizePrefixCls);
const groupPrefixCls = `${prefixCls}-group`;
@ -39,6 +47,7 @@ const Group: React.FC<GroupProps> = (props) => {
[`${groupPrefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
);

View File

@ -19,6 +19,7 @@ export interface BackTopProps {
prefixCls?: string;
children?: React.ReactNode;
className?: string;
rootClassName?: string;
style?: React.CSSProperties;
duration?: number;
}
@ -26,7 +27,8 @@ export interface BackTopProps {
const BackTop: React.FC<BackTopProps> = (props) => {
const {
prefixCls: customizePrefixCls,
className = '',
className,
rootClassName,
visibilityHeight = 400,
target,
onClick,
@ -79,12 +81,14 @@ const BackTop: React.FC<BackTopProps> = (props) => {
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
);
// fix https://fb.me/react-unknown-prop
const divProps = omit(props, [
'prefixCls',
'className',
'rootClassName',
'children',
'visibilityHeight',
'target',

View File

@ -3,14 +3,14 @@ import CSSMotion from 'rc-motion';
import * as React from 'react';
import { useMemo, useRef } from 'react';
import { ConfigContext } from '../config-provider';
import type { PresetColorKey } from '../theme/internal';
import type { PresetStatusColorType } from '../_util/colors';
import { isPresetColor } from '../_util/colors';
import { cloneElement } from '../_util/reactNode';
import type { LiteralUnion } from '../_util/type';
import Ribbon from './Ribbon';
import ScrollNumber from './ScrollNumber';
import useStyle from './style';
import { isPresetColor } from '../_util/colors';
import type { PresetColorKey } from '../theme/internal';
export type { ScrollNumberProps } from './ScrollNumber';
@ -30,6 +30,7 @@ export interface BadgeProps {
prefixCls?: string;
scrollNumberPrefixCls?: string;
className?: string;
rootClassName?: string;
status?: PresetStatusColorType;
color?: LiteralUnion<PresetColorKey>;
text?: React.ReactNode;
@ -54,6 +55,7 @@ const Badge: CompoundedComponent = ({
offset,
style,
className,
rootClassName,
showZero = false,
...restProps
}) => {
@ -169,13 +171,15 @@ const Badge: CompoundedComponent = ({
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
);
// <Badge status="success" />
if (!children && hasStatus) {
const statusTextColor = mergedStyle.color;
return wrapSSR(
<span {...restProps} className={classNames(badgeClassName, hashId)} style={mergedStyle}>
<span {...restProps} className={badgeClassName} style={mergedStyle}>
<span className={statusCls} style={statusStyle} />
{text && (
<span style={{ color: statusTextColor }} className={`${prefixCls}-status-text`}>
@ -186,9 +190,8 @@ const Badge: CompoundedComponent = ({
);
}
// <Badge status="success" count={<Icon type="xxx" />}></Badge>
return wrapSSR(
<span {...restProps} className={classNames(badgeClassName, hashId)}>
<span {...restProps} className={badgeClassName}>
{children}
<CSSMotion
visible={!isHidden}

View File

@ -31,6 +31,7 @@ export interface BreadcrumbProps {
) => React.ReactNode;
style?: React.CSSProperties;
className?: string;
rootClassName?: string;
children?: React.ReactNode;
}
@ -79,6 +80,7 @@ const Breadcrumb: CompoundedComponent = ({
separator = '/',
style,
className,
rootClassName,
routes,
children,
itemRender = defaultItemRender,
@ -152,6 +154,7 @@ const Breadcrumb: CompoundedComponent = ({
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
);

View File

@ -34,6 +34,7 @@ export interface BaseButtonProps {
loading?: boolean | { delay?: number };
prefixCls?: string;
className?: string;
rootClassName?: string;
ghost?: boolean;
danger?: boolean;
block?: boolean;
@ -78,6 +79,7 @@ const InternalButton: React.ForwardRefRenderFunction<
size: customizeSize,
disabled: customDisabled,
className,
rootClassName,
children,
icon,
ghost = false,
@ -198,6 +200,7 @@ const InternalButton: React.ForwardRefRenderFunction<
},
compactItemClassnames,
className,
rootClassName,
);
const iconNode =

View File

@ -1,3 +1,8 @@
import dayjs from 'dayjs';
import demoTest from '../../../tests/shared/demoTest';
demoTest('calendar');
demoTest('calendar', {
testRootProps: {
value: dayjs(),
},
});

View File

@ -45,6 +45,7 @@ export type HeaderRender<DateType> = (config: {
export interface CalendarProps<DateType> {
prefixCls?: string;
className?: string;
rootClassName?: string;
style?: React.CSSProperties;
locale?: typeof enUS;
validRange?: [DateType, DateType];
@ -84,6 +85,7 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
const {
prefixCls: customizePrefixCls,
className,
rootClassName,
style,
dateFullCellRender,
dateCellRender,
@ -246,6 +248,7 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
[`${calendarPrefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
)}
style={style}

View File

@ -32,6 +32,7 @@ export interface CardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 't
children?: React.ReactNode;
id?: string;
className?: string;
rootClassName?: string;
size?: CardSize;
type?: CardType;
cover?: React.ReactNode;
@ -75,6 +76,7 @@ const Card = React.forwardRef((props: CardProps, ref: React.Ref<HTMLDivElement>)
const {
prefixCls: customizePrefixCls,
className,
rootClassName,
extra,
headStyle = {},
bodyStyle = {},
@ -164,6 +166,7 @@ const Card = React.forwardRef((props: CardProps, ref: React.Ref<HTMLDivElement>)
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
);

View File

@ -228,7 +228,7 @@ const genCardStyle: GenerateStyle<CardToken> = (token): CSSObject => {
cardShadow,
cardHeadPadding,
colorBorderSecondary,
boxShadow,
boxShadowTertiary,
cardPaddingBase,
} = token;
@ -241,7 +241,7 @@ const genCardStyle: GenerateStyle<CardToken> = (token): CSSObject => {
borderRadius: token.borderRadiusLG,
[`&:not(${componentCls}-bordered)`]: {
boxShadow,
boxShadow: boxShadowTertiary,
},
[`${componentCls}-head`]: genCardHeadStyle(token),

View File

@ -13,6 +13,7 @@ export interface CarouselProps extends Omit<Settings, 'dots' | 'dotsClass'> {
effect?: CarouselEffect;
style?: React.CSSProperties;
prefixCls?: string;
rootClassName?: string;
slickGoTo?: number;
dotPosition?: DotPosition;
children?: React.ReactNode;
@ -35,6 +36,7 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>(
draggable = false,
dotPosition = 'bottom',
vertical = dotPosition === 'left' || dotPosition === 'right',
rootClassName,
...props
},
ref,
@ -95,6 +97,7 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>(
[`${prefixCls}-vertical`]: newProps.vertical,
},
hashId,
rootClassName,
);
return wrapSSR(

View File

@ -1,3 +0,0 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('cascader');

View File

@ -0,0 +1,11 @@
import * as React from 'react';
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
demoTest('cascader', {
testRootProps: false,
});
rootPropsTest('cascader', (Cascader, props) => <Cascader {...props} />, {
findRootElements: () => document.querySelectorAll('.ant-cascader, .ant-cascader-dropdown'),
expectCount: 2,
});

View File

@ -114,6 +114,7 @@ export type CascaderProps<DataNodeType> = UnionCascaderProps & {
options?: DataNodeType[];
status?: InputStatus;
rootClassName?: string;
popupClassName?: string;
/** @deprecated Please use `popupClassName` instead */
dropdownClassName?: string;
@ -130,6 +131,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
size: customizeSize,
disabled: customDisabled,
className,
rootClassName,
multiple,
bordered = true,
transitionName,
@ -207,6 +209,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
{
[`${cascaderPrefixCls}-dropdown-rtl`]: mergedDirection === 'rtl',
},
rootClassName,
hashId,
);
@ -291,6 +294,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
compactItemClassnames,
className,
rootClassName,
hashId,
)}
disabled={mergedDisabled}

View File

@ -12,6 +12,7 @@ import useStyle from './style';
export interface AbstractCheckboxProps<T> {
prefixCls?: string;
className?: string;
rootClassName?: string;
defaultChecked?: boolean;
checked?: boolean;
style?: React.CSSProperties;
@ -51,6 +52,7 @@ const InternalCheckbox: React.ForwardRefRenderFunction<HTMLInputElement, Checkbo
{
prefixCls: customizePrefixCls,
className,
rootClassName,
children,
indeterminate = false,
style,
@ -116,6 +118,7 @@ const InternalCheckbox: React.ForwardRefRenderFunction<HTMLInputElement, Checkbo
[`${prefixCls}-wrapper-in-form-item`]: isFormItemInput,
},
className,
rootClassName,
hashId,
);
const checkboxClass = classNames(

View File

@ -20,6 +20,7 @@ export interface CheckboxOptionType {
export interface AbstractCheckboxGroupProps {
prefixCls?: string;
className?: string;
rootClassName?: string;
options?: Array<CheckboxOptionType | string | number>;
disabled?: boolean;
style?: React.CSSProperties;
@ -51,6 +52,7 @@ const InternalCheckboxGroup: React.ForwardRefRenderFunction<HTMLDivElement, Chec
options = [],
prefixCls: customizePrefixCls,
className,
rootClassName,
style,
onChange,
...restProps
@ -152,6 +154,7 @@ const InternalCheckboxGroup: React.ForwardRefRenderFunction<HTMLDivElement, Chec
[`${groupPrefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
);
return wrapSSR(

View File

@ -1,3 +0,0 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('checkbox');

View File

@ -0,0 +1,12 @@
import * as React from 'react';
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
demoTest('checkbox');
rootPropsTest(
'checkbox',
(Checkbox, props) => <Checkbox.Group {...props} value={[]} options={['Bamboo']} />,
{
name: 'Checkbox.Group',
},
);

View File

@ -11,6 +11,8 @@ import initCollapseMotion from '../_util/motion';
import { cloneElement } from '../_util/reactNode';
import warning from '../_util/warning';
import type { CollapsibleType } from './CollapsePanel';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
import CollapsePanel from './CollapsePanel';
import useStyle from './style';
@ -28,11 +30,13 @@ export interface CollapseProps {
onChange?: (key: string | string[]) => void;
style?: React.CSSProperties;
className?: string;
rootClassName?: string;
bordered?: boolean;
prefixCls?: string;
expandIcon?: (panelProps: PanelProps) => React.ReactNode;
expandIconPosition?: ExpandIconPosition;
ghost?: boolean;
size?: SizeType;
collapsible?: CollapsibleType;
children?: React.ReactNode;
}
@ -52,13 +56,19 @@ interface PanelProps {
const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const size = React.useContext(SizeContext);
const {
prefixCls: customizePrefixCls,
className = '',
className,
rootClassName,
bordered = true,
ghost,
size: customizeSize,
expandIconPosition = 'start',
} = props;
const mergedSize = customizeSize || size || 'middle';
const prefixCls = getPrefixCls('collapse', customizePrefixCls);
const rootPrefixCls = getPrefixCls();
const [wrapSSR, hashId] = useStyle(prefixCls);
@ -99,8 +109,10 @@ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) =>
[`${prefixCls}-borderless`]: !bordered,
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-ghost`]: !!ghost,
[`${prefixCls}-${mergedSize}`]: mergedSize !== 'middle',
},
className,
rootClassName,
hashId,
);
const openMotion: CSSMotionProps = {
@ -130,7 +142,7 @@ const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>((props, ref) =>
<RcCollapse
ref={ref}
openMotion={openMotion}
{...props}
{...omit(props, ['rootClassName'])}
expandIcon={renderExpandIcon}
prefixCls={prefixCls}
className={collapseClassName}

View File

@ -1438,3 +1438,170 @@ exports[`renders ./components/collapse/demo/noarrow.tsx extend context correctly
</div>
</div>
`;
exports[`renders ./components/collapse/demo/size.tsx extend context correctly 1`] = `
Array [
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left"
role="separator"
>
<span
class="ant-divider-inner-text"
>
Default Size
</span>
</div>,
<div
class="ant-collapse ant-collapse-icon-position-start"
>
<div
class="ant-collapse-item"
>
<div
aria-disabled="false"
aria-expanded="false"
class="ant-collapse-header"
role="button"
tabindex="0"
>
<div
class="ant-collapse-expand-icon"
>
<span
aria-label="right"
class="anticon anticon-right ant-collapse-arrow"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
<span
class="ant-collapse-header-text"
>
This is default size panel header
</span>
</div>
</div>
</div>,
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left"
role="separator"
>
<span
class="ant-divider-inner-text"
>
Small Size
</span>
</div>,
<div
class="ant-collapse ant-collapse-icon-position-start ant-collapse-small"
>
<div
class="ant-collapse-item"
>
<div
aria-disabled="false"
aria-expanded="false"
class="ant-collapse-header"
role="button"
tabindex="0"
>
<div
class="ant-collapse-expand-icon"
>
<span
aria-label="right"
class="anticon anticon-right ant-collapse-arrow"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
<span
class="ant-collapse-header-text"
>
This is small size panel header
</span>
</div>
</div>
</div>,
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left"
role="separator"
>
<span
class="ant-divider-inner-text"
>
Large Size
</span>
</div>,
<div
class="ant-collapse ant-collapse-icon-position-start ant-collapse-large"
>
<div
class="ant-collapse-item"
>
<div
aria-disabled="false"
aria-expanded="false"
class="ant-collapse-header"
role="button"
tabindex="0"
>
<div
class="ant-collapse-expand-icon"
>
<span
aria-label="right"
class="anticon anticon-right ant-collapse-arrow"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
<span
class="ant-collapse-header-text"
>
This is large size panel header
</span>
</div>
</div>
</div>,
]
`;

View File

@ -1356,3 +1356,170 @@ exports[`renders ./components/collapse/demo/noarrow.tsx correctly 1`] = `
</div>
</div>
`;
exports[`renders ./components/collapse/demo/size.tsx correctly 1`] = `
Array [
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left"
role="separator"
>
<span
class="ant-divider-inner-text"
>
Default Size
</span>
</div>,
<div
class="ant-collapse ant-collapse-icon-position-start"
>
<div
class="ant-collapse-item"
>
<div
aria-disabled="false"
aria-expanded="false"
class="ant-collapse-header"
role="button"
tabindex="0"
>
<div
class="ant-collapse-expand-icon"
>
<span
aria-label="right"
class="anticon anticon-right ant-collapse-arrow"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
<span
class="ant-collapse-header-text"
>
This is default size panel header
</span>
</div>
</div>
</div>,
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left"
role="separator"
>
<span
class="ant-divider-inner-text"
>
Small Size
</span>
</div>,
<div
class="ant-collapse ant-collapse-icon-position-start ant-collapse-small"
>
<div
class="ant-collapse-item"
>
<div
aria-disabled="false"
aria-expanded="false"
class="ant-collapse-header"
role="button"
tabindex="0"
>
<div
class="ant-collapse-expand-icon"
>
<span
aria-label="right"
class="anticon anticon-right ant-collapse-arrow"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
<span
class="ant-collapse-header-text"
>
This is small size panel header
</span>
</div>
</div>
</div>,
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left"
role="separator"
>
<span
class="ant-divider-inner-text"
>
Large Size
</span>
</div>,
<div
class="ant-collapse ant-collapse-icon-position-start ant-collapse-large"
>
<div
class="ant-collapse-item"
>
<div
aria-disabled="false"
aria-expanded="false"
class="ant-collapse-header"
role="button"
tabindex="0"
>
<div
class="ant-collapse-expand-icon"
>
<span
aria-label="right"
class="anticon anticon-right ant-collapse-arrow"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
<span
class="ant-collapse-header-text"
>
This is large size panel header
</span>
</div>
</div>
</div>,
]
`;

View File

@ -39,6 +39,14 @@ describe('Collapse', () => {
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should be able to config size', () => {
const { container: small } = render(<Collapse size="small" />);
const { container: large } = render(<Collapse size="large" />);
expect(small.querySelector('.ant-collapse')).toHaveClass('ant-collapse-small');
expect(large.querySelector('.ant-collapse')).toHaveClass('ant-collapse-large');
});
it('should keep the className of the expandIcon', () => {
const { container } = render(
<Collapse

View File

@ -0,0 +1,11 @@
## zh-CN
折叠面板有大、中、小三种尺寸。
通过设置 `size``large` `small` 分别把折叠面板设为大、小尺寸。若不设置 `size`,则尺寸为中。
## en-US
Ant Design supports a default collapse size as well as a large and small size.
If a large or small collapse is desired, set the `size` property to either `large` or `small` respectively. Omit the `size` property for a collapse with the default size.

View File

@ -0,0 +1,35 @@
import React from 'react';
import { Collapse, Divider } from 'antd';
const { Panel } = Collapse;
const text = `
A dog is a type of domesticated animal.
Known for its loyalty and faithfulness,
it can be found as a welcome guest in many households across the world.
`;
const App: React.FC = () => (
<>
<Divider orientation="left">Default Size</Divider>
<Collapse>
<Panel header="This is default size panel header" key="1">
<p>{text}</p>
</Panel>
</Collapse>
<Divider orientation="left">Small Size</Divider>
<Collapse size="small">
<Panel header="This is small size panel header" key="1">
<p>{text}</p>
</Panel>
</Collapse>
<Divider orientation="left">Large Size</Divider>
<Collapse size="large">
<Panel header="This is large size panel header" key="1">
<p>{text}</p>
</Panel>
</Collapse>
</>
);
export default App;

View File

@ -16,6 +16,7 @@ A content area which can be collapsed and expanded.
<!-- prettier-ignore -->
<code src="./demo/basic.tsx">Collapse</code>
<code src="./demo/size.tsx">Size</code>
<code src="./demo/accordion.tsx">Accordion</code>
<code src="./demo/mix.tsx">Nested panel</code>
<code src="./demo/borderless.tsx">Borderless</code>
@ -40,6 +41,7 @@ A content area which can be collapsed and expanded.
| expandIcon | Allow to customize collapse icon | (panelProps) => ReactNode | - | |
| expandIconPosition | Set expand icon position | `start` \| `end` | - | 4.21.0 |
| ghost | Make the collapse borderless and its background transparent | boolean | false | 4.4.0 |
| size | Set the size of collapse | `large` \| `middle` \| `small` | `middle` | 5.2.0 |
| onChange | Callback function executed when active panel is changed | function | - | |
### Collapse.Panel

View File

@ -17,6 +17,7 @@ cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*B7HKR5OBe8gAAAAAAA
<!-- prettier-ignore -->
<code src="./demo/basic.tsx">折叠面板</code>
<code src="./demo/size.tsx">面板尺寸</code>
<code src="./demo/accordion.tsx">手风琴</code>
<code src="./demo/mix.tsx">面板嵌套</code>
<code src="./demo/borderless.tsx">简洁风格</code>
@ -41,6 +42,7 @@ cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*B7HKR5OBe8gAAAAAAA
| expandIcon | 自定义切换图标 | (panelProps) => ReactNode | - | |
| expandIconPosition | 设置图标位置 | `start` \| `end` | - | 4.21.0 |
| ghost | 使折叠面板透明且无边框 | boolean | false | 4.4.0 |
| size | 设置折叠面板大小 | `large` \| `middle` \| `small` | `middle` | 5.2.0 |
| onChange | 切换面板的回调 | function | - | |
### Collapse.Panel

View File

@ -9,6 +9,8 @@ type CollapseToken = FullToken<'Collapse'> & {
collapseContentBg: string;
collapseHeaderBg: string;
collapseHeaderPadding: string;
collapseHeaderPaddingSM: string;
collapseHeaderPaddingLG: string;
collapsePanelBorderRadius: number;
collapseContentPaddingHorizontal: number;
};
@ -21,6 +23,8 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = (token) => {
collapseContentPaddingHorizontal,
collapseHeaderBg,
collapseHeaderPadding,
collapseHeaderPaddingSM,
collapseHeaderPaddingLG,
collapsePanelBorderRadius,
lineWidth,
@ -30,9 +34,11 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = (token) => {
colorTextHeading,
colorTextDisabled,
fontSize,
fontSizeLG,
lineHeight,
marginSM,
paddingSM,
paddingLG,
motionDurationSlow,
fontSizeIcon,
} = token;
@ -141,6 +147,34 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = (token) => {
},
},
[`&-small`]: {
[`> ${componentCls}-item`]: {
[`> ${componentCls}-header`]: {
padding: collapseHeaderPaddingSM,
},
[`> ${componentCls}-content > ${componentCls}-content-box`]: {
padding: paddingSM,
},
},
},
[`&-large`]: {
[`> ${componentCls}-item`]: {
fontSize: fontSizeLG,
[`> ${componentCls}-header`]: {
padding: collapseHeaderPaddingLG,
[`> ${componentCls}-expand-icon`]: {
height: fontSizeLG * lineHeight,
},
},
[`> ${componentCls}-content > ${componentCls}-content-box`]: {
padding: paddingLG,
},
},
},
[`${componentCls}-item:last-child`]: {
[`> ${componentCls}-content`]: {
borderRadius: `0 0 ${collapsePanelBorderRadius}px ${collapsePanelBorderRadius}px`,
@ -254,6 +288,8 @@ export default genComponentStyleHook('Collapse', (token) => {
collapseContentBg: token.colorBgContainer,
collapseHeaderBg: token.colorFillAlter,
collapseHeaderPadding: `${token.paddingSM}px ${token.padding}px`,
collapseHeaderPaddingSM: `${token.paddingXS}px ${token.paddingSM}px`,
collapseHeaderPaddingLG: `${token.padding}px ${token.paddingLG}px`,
collapsePanelBorderRadius: token.borderRadiusLG,
collapseContentPaddingHorizontal: 16, // Fixed value
});

View File

@ -138,13 +138,9 @@ exports[`ConfigProvider components Anchor configProvider 1`] = `
<div
class="config-anchor"
>
<div
<span
class="config-anchor-ink"
>
<span
class="config-anchor-ink-ball"
/>
</div>
/>
<div
class="config-anchor-link"
>
@ -174,13 +170,9 @@ exports[`ConfigProvider components Anchor configProvider componentDisabled 1`] =
<div
class="config-anchor"
>
<div
<span
class="config-anchor-ink"
>
<span
class="config-anchor-ink-ball"
/>
</div>
/>
<div
class="config-anchor-link"
>
@ -210,13 +202,9 @@ exports[`ConfigProvider components Anchor configProvider componentSize large 1`]
<div
class="config-anchor"
>
<div
<span
class="config-anchor-ink"
>
<span
class="config-anchor-ink-ball"
/>
</div>
/>
<div
class="config-anchor-link"
>
@ -246,13 +234,9 @@ exports[`ConfigProvider components Anchor configProvider componentSize middle 1`
<div
class="config-anchor"
>
<div
<span
class="config-anchor-ink"
>
<span
class="config-anchor-ink-ball"
/>
</div>
/>
<div
class="config-anchor-link"
>
@ -282,13 +266,9 @@ exports[`ConfigProvider components Anchor configProvider virtual and dropdownMat
<div
class="ant-anchor"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -318,13 +298,9 @@ exports[`ConfigProvider components Anchor normal 1`] = `
<div
class="ant-anchor"
>
<div
<span
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
/>
<div
class="ant-anchor-link"
>
@ -354,13 +330,9 @@ exports[`ConfigProvider components Anchor prefixCls 1`] = `
<div
class="prefix-Anchor"
>
<div
<span
class="prefix-Anchor-ink"
>
<span
class="prefix-Anchor-ink-ball"
/>
</div>
/>
<div
class="prefix-Anchor-link"
>
@ -12357,7 +12329,7 @@ exports[`ConfigProvider components Collapse configProvider componentDisabled 1`]
exports[`ConfigProvider components Collapse configProvider componentSize large 1`] = `
<div
class="config-collapse config-collapse-icon-position-start"
class="config-collapse config-collapse-icon-position-start config-collapse-large"
>
<div
class="config-collapse-item"

View File

@ -1,19 +1,30 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import scrollIntoView from 'scroll-into-view-if-needed';
import Button from '../../button';
import ConfigProvider from '..';
import { render } from '../../../tests/utils';
import { fireEvent, render, waitFakeTimer } from '../../../tests/utils';
import type { FormInstance } from '../../form';
import Form from '../../form';
import Input from '../../input';
import zhCN from '../../locale/zh_CN';
jest.mock('scroll-into-view-if-needed');
describe('ConfigProvider.Form', () => {
(scrollIntoView as any).mockImplementation(() => {});
beforeEach(() => {
(scrollIntoView as any).mockReset();
});
beforeAll(() => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
(scrollIntoView as any).mockRestore();
});
describe('form validateMessages', () => {
@ -146,4 +157,89 @@ describe('ConfigProvider.Form', () => {
expect(container.querySelector('#input[disabled]')).toBeTruthy();
});
});
describe('form scrollToFirstError', () => {
it('set object, form not set', async () => {
(scrollIntoView as any).mockImplementation(() => {});
const onFinishFailed = jest.fn();
const { container } = render(
<ConfigProvider form={{ scrollToFirstError: { block: 'center' } }}>
<Form onFinishFailed={onFinishFailed}>
<Form.Item name="test" rules={[{ required: true }]}>
<input />
</Form.Item>
<Form.Item>
<Button htmlType="submit">Submit</Button>
</Form.Item>
</Form>
</ConfigProvider>,
);
expect(scrollIntoView).not.toHaveBeenCalled();
fireEvent.submit(container.querySelector('form')!);
await waitFakeTimer();
const inputNode = document.getElementById('test');
expect(scrollIntoView).toHaveBeenCalledWith(inputNode, {
block: 'center',
scrollMode: 'if-needed',
});
expect(onFinishFailed).toHaveBeenCalled();
});
it('not set, form set object', async () => {
(scrollIntoView as any).mockImplementation(() => {});
const onFinishFailed = jest.fn();
const { container } = render(
<ConfigProvider>
<Form scrollToFirstError={{ block: 'center' }} onFinishFailed={onFinishFailed}>
<Form.Item name="test" rules={[{ required: true }]}>
<input />
</Form.Item>
<Form.Item>
<Button htmlType="submit">Submit</Button>
</Form.Item>
</Form>
</ConfigProvider>,
);
expect(scrollIntoView).not.toHaveBeenCalled();
fireEvent.submit(container.querySelector('form')!);
await waitFakeTimer();
const inputNode = document.getElementById('test');
expect(scrollIntoView).toHaveBeenCalledWith(inputNode, {
block: 'center',
scrollMode: 'if-needed',
});
expect(onFinishFailed).toHaveBeenCalled();
});
it('set object, form set false', async () => {
(scrollIntoView as any).mockImplementation(() => {});
const onFinishFailed = jest.fn();
const { container } = render(
<ConfigProvider form={{ scrollToFirstError: { block: 'center' } }}>
<Form scrollToFirstError={false} onFinishFailed={onFinishFailed}>
<Form.Item name="test" rules={[{ required: true }]}>
<input />
</Form.Item>
<Form.Item>
<Button htmlType="submit">Submit</Button>
</Form.Item>
</Form>
</ConfigProvider>,
);
expect(scrollIntoView).not.toHaveBeenCalled();
fireEvent.submit(container.querySelector('form')!);
await waitFakeTimer();
expect(scrollIntoView).not.toHaveBeenCalled();
expect(onFinishFailed).toHaveBeenCalled();
});
});
});

View File

@ -1,5 +1,6 @@
import * as React from 'react';
import type { DerivativeFunc } from '@ant-design/cssinjs';
import type { Options } from 'scroll-into-view-if-needed';
import type { RequiredMark } from '../form/Form';
import type { Locale } from '../locale';
import type { AliasToken, MapToken, OverrideToken, SeedToken } from '../theme/interface';
@ -61,6 +62,7 @@ export interface ConfigConsumerProps {
form?: {
requiredMark?: RequiredMark;
colon?: boolean;
scrollToFirstError?: Options | boolean;
};
theme?: ThemeConfig;
select?: {

View File

@ -54,7 +54,7 @@ Some components use dynamic style to support wave effect. You can config `csp` p
| csp | Set [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) config | { nonce: string } | - | |
| direction | Set direction of layout. See [demo](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | - | 4.3.0 |
| form | Set Form common props | { validateMessages?: [ValidateMessages](/components/form/#validatemessages), requiredMark?: boolean \| `optional` } | - | requiredMark: 4.8.0 |
| form | Set Form common props | { validateMessages?: [ValidateMessages](/components/form/#validatemessages), requiredMark?: boolean \| `optional`, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options) } | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0 |
| getPopupContainer | To set the container of the popup element. The default is to create a `div` element in `body` | function(triggerNode) | () => document.body | |
| getTargetContainer | Config Affix, Anchor scroll target container | () => HTMLElement | () => window | 4.2.0 |
| iconPrefixCls | Set icon prefix className | string | `anticon` | 4.11.0 |

View File

@ -5,6 +5,7 @@ import type { ValidateMessages } from 'rc-field-form/lib/interface';
import useMemo from 'rc-util/lib/hooks/useMemo';
import * as React from 'react';
import type { ReactElement } from 'react';
import type { Options } from 'scroll-into-view-if-needed';
import type { RequiredMark } from '../form/Form';
import type { Locale } from '../locale';
import LocaleProvider, { ANT_MARK } from '../locale';
@ -70,6 +71,7 @@ export interface ConfigProviderProps {
validateMessages?: ValidateMessages;
requiredMark?: RequiredMark;
colon?: boolean;
scrollToFirstError?: Options | boolean;
};
input?: {
autoComplete?: string;

View File

@ -55,7 +55,7 @@ export default Demo;
| csp | 设置 [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 配置 | { nonce: string } | - | |
| direction | 设置文本展示方向。 [示例](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。`false` 时会关闭虚拟滚动 | boolean \| number | - | 4.3.0 |
| form | 设置 Form 组件的通用属性 | { validateMessages?: [ValidateMessages](/components/form-cn#validatemessages), requiredMark?: boolean \| `optional`, colon?: boolean} | - | requiredMark: 4.8.0; colon: 4.18.0 |
| form | 设置 Form 组件的通用属性 | { validateMessages?: [ValidateMessages](/components/form-cn#validatemessages), requiredMark?: boolean \| `optional`, colon?: boolean, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options)} | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0 |
| getPopupContainer | 弹出框Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | function(triggerNode) | () => document.body | |
| getTargetContainer | 配置 Affix、Anchor 滚动监听容器。 | () => HTMLElement | () => window | 4.2.0 |
| iconPrefixCls | 设置图标统一样式前缀 | string | `anticon` | 4.11.0 |

View File

@ -1,3 +0,0 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('date-picker', { skip: ['locale.tsx'] });

View File

@ -0,0 +1,10 @@
import dayjs from 'dayjs';
import * as React from 'react';
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
demoTest('date-picker', { skip: ['locale.tsx'], testRootProps: false });
rootPropsTest('time-picker', (DatePicker, props) => <DatePicker {...props} value={dayjs()} />, {
findRootElements: () => document.querySelectorAll('.ant-picker, .ant-picker-dropdown'),
expectCount: 2,
});

View File

@ -7,7 +7,6 @@ import type { GenerateConfig } from 'rc-picker/lib/generate/index';
import type { PickerMode } from 'rc-picker/lib/interface';
import * as React from 'react';
import { forwardRef, useContext, useImperativeHandle } from 'react';
import { useCompactItemContext } from '../../space/Compact';
import type { PickerProps, PickerTimeProps } from '.';
import { Components, getTimeProps } from '.';
import { ConfigContext } from '../../config-provider';
@ -15,6 +14,7 @@ import DisabledContext from '../../config-provider/DisabledContext';
import SizeContext from '../../config-provider/SizeContext';
import { FormItemInputContext } from '../../form/context';
import LocaleReceiver from '../../locale/LocaleReceiver';
import { useCompactItemContext } from '../../space/Compact';
import type { InputStatus } from '../../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
import warning from '../../_util/warning';
@ -29,6 +29,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
status?: InputStatus;
hashId?: string;
popupClassName?: string;
rootClassName?: string;
};
type DatePickerProps = PickerProps<DateType> & CustomPickerProps;
type TimePickerProps = PickerTimeProps<DateType> & CustomPickerProps;
@ -43,6 +44,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
prefixCls: customizePrefixCls,
getPopupContainer: customizeGetPopupContainer,
className,
rootClassName,
size: customizeSize,
bordered = true,
placement,
@ -155,6 +157,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
hashId,
compactItemClassnames,
className,
rootClassName,
)}
prefixCls={prefixCls}
getPopupContainer={customizeGetPopupContainer || getPopupContainer}
@ -162,7 +165,11 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
components={Components}
direction={direction}
disabled={mergedDisabled}
dropdownClassName={classNames(hashId, popupClassName || dropdownClassName)}
dropdownClassName={classNames(
hashId,
rootClassName,
popupClassName || dropdownClassName,
)}
/>
);
}}

View File

@ -101,6 +101,7 @@ function getRows(children: React.ReactNode, column: number) {
export interface DescriptionsProps {
prefixCls?: string;
className?: string;
rootClassName?: string;
style?: React.CSSProperties;
bordered?: boolean;
size?: 'middle' | 'small' | 'default';
@ -124,6 +125,7 @@ function Descriptions({
layout,
children,
className,
rootClassName,
style,
size,
labelStyle,
@ -169,6 +171,7 @@ function Descriptions({
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
)}
style={style}

View File

@ -40,6 +40,7 @@ const genBorderedStyle = (token: DescriptionsToken): CSSObject => {
},
},
[`${componentCls}-item-label`]: {
color: token.colorTextSecondary,
backgroundColor: descriptionsBg,
'&::after': {
display: 'none',
@ -116,7 +117,7 @@ const genDescriptionStyles: GenerateStyle<DescriptionsToken> = (token: Descripti
},
},
[`${componentCls}-item-label`]: {
color: token.colorText,
color: token.colorTextTertiary,
fontWeight: 'normal',
fontSize: token.fontSize,
lineHeight: token.lineHeight,

View File

@ -11,6 +11,7 @@ export interface DividerProps {
orientation?: 'left' | 'right' | 'center';
orientationMargin?: string | number;
className?: string;
rootClassName?: string;
children?: React.ReactNode;
dashed?: boolean;
style?: React.CSSProperties;
@ -26,6 +27,7 @@ const Divider: React.FC<DividerProps> = (props) => {
orientation = 'center',
orientationMargin,
className,
rootClassName,
children,
dashed,
plain,
@ -52,6 +54,7 @@ const Divider: React.FC<DividerProps> = (props) => {
[`${prefixCls}-no-default-orientation-margin-right`]: hasCustomMarginRight,
},
className,
rootClassName,
);
const innerStyle = {

View File

@ -1,3 +0,0 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('dropdown');

View File

@ -0,0 +1,35 @@
import * as React from 'react';
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
demoTest('dropdown', {
testRootProps: false,
});
rootPropsTest(
'dropdown',
(Dropdown, props) => (
<Dropdown
{...props}
menu={{
openKeys: ['1'],
items: [
{
key: '1',
label: 'parent',
children: [
{
key: '2',
label: 'child',
},
],
},
],
}}
>
<a />
</Dropdown>
),
{
findRootElements: () => document.querySelector('.ant-dropdown')!,
},
);

View File

@ -3,18 +3,21 @@ import classNames from 'classnames';
import RcDropdown from 'rc-dropdown';
import useEvent from 'rc-util/lib/hooks/useEvent';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import omit from 'rc-util/lib/omit';
import * as React from 'react';
import Menu from '../menu';
import type { MenuProps } from '../menu';
import { ConfigContext } from '../config-provider';
import type { MenuProps } from '../menu';
import Menu from '../menu';
import { OverrideProvider } from '../menu/OverrideContext';
import genPurePanel from '../_util/PurePanel';
import { NoCompactStyle } from '../space/Compact';
import type { AdjustOverflow } from '../_util/placements';
import getPlacements from '../_util/placements';
import genPurePanel from '../_util/PurePanel';
import { cloneElement } from '../_util/reactNode';
import warning from '../_util/warning';
import { NoCompactStyle } from '../space/Compact';
import DropdownButton from './dropdown-button';
import useStyle from './style';
import theme from '../theme';
const Placements = [
'topLeft',
@ -62,6 +65,7 @@ export interface DropdownProps {
getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
prefixCls?: string;
className?: string;
rootClassName?: string;
transitionName?: string;
placement?: Placement;
overlayClassName?: string;
@ -71,6 +75,7 @@ export interface DropdownProps {
mouseLeaveDelay?: number;
openClassName?: string;
children?: React.ReactNode;
autoAdjustOverflow?: boolean | AdjustOverflow;
// Deprecated
/** @deprecated Please use `menu` instead */
@ -154,6 +159,7 @@ const Dropdown: CompoundedComponent = (props) => {
dropdownRender,
getPopupContainer,
overlayClassName,
rootClassName,
open,
onOpenChange,
@ -162,6 +168,7 @@ const Dropdown: CompoundedComponent = (props) => {
onVisibleChange,
mouseEnterDelay = 0.15,
mouseLeaveDelay = 0.1,
autoAdjustOverflow = true,
} = props;
if (process.env.NODE_ENV !== 'production') {
@ -180,6 +187,8 @@ const Dropdown: CompoundedComponent = (props) => {
const prefixCls = getPrefixCls('dropdown', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls);
const { token } = theme.useToken();
const child = React.Children.only(children) as React.ReactElement<any>;
const dropdownTrigger = cloneElement(child, {
@ -211,13 +220,15 @@ const Dropdown: CompoundedComponent = (props) => {
});
// =========================== Overlay ============================
const overlayClassNameCustomized = classNames(overlayClassName, hashId, {
const overlayClassNameCustomized = classNames(overlayClassName, rootClassName, hashId, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
const builtinPlacements = getPlacements({
arrowPointAtCenter: typeof arrow === 'object' && arrow.pointAtCenter,
autoAdjustOverflow: true,
autoAdjustOverflow,
offset: token.marginXXS,
arrowWidth: arrow ? token.sizePopupArrow : 0,
});
const onMenuClick = React.useCallback(() => {
@ -273,7 +284,7 @@ const Dropdown: CompoundedComponent = (props) => {
return wrapSSR(
<RcDropdown
alignPoint={alignPoint!}
{...props}
{...omit(props, ['rootClassName'])}
mouseEnterDelay={mouseEnterDelay}
mouseLeaveDelay={mouseLeaveDelay}
visible={mergedOpen}

View File

@ -40,6 +40,7 @@ When there are more than a few options to choose from, you can wrap them in a `D
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| arrow | Whether the dropdown arrow should be visible | boolean \| { pointAtCenter: boolean } | false | |
| autoAdjustOverflow | Whether to adjust dropdown placement automatically when dropdown is off screen | boolean | true | 5.2.0 |
| autoFocus | Focus element in `overlay` when opened | boolean | false | 4.21.0 |
| disabled | Whether the dropdown menu is disabled | boolean | - | |
| destroyPopupOnHide | Whether destroy dropdown when hidden | boolean | false | |

View File

@ -44,6 +44,7 @@ demo:
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| arrow | 下拉框箭头是否显示 | boolean \| { pointAtCenter: boolean } | false | |
| autoAdjustOverflow | 下拉框被遮挡时自动调整位置 | boolean | true | 5.2.0 |
| autoFocus | 打开后自动聚焦下拉框 | boolean | false | 4.21.0 |
| disabled | 菜单是否禁用 | boolean | - | |
| destroyPopupOnHide | 关闭后是否销毁 Dropdown | boolean | false | |

View File

@ -1,4 +1,4 @@
import { getArrowOffset } from '../../style/placementArrow';
import { genFocusStyle, resetComponent } from '../../style';
import {
initMoveMotion,
initSlideMotion,
@ -8,11 +8,11 @@ import {
slideUpIn,
slideUpOut,
} from '../../style/motion';
import getArrowStyle, { getArrowOffset } from '../../style/placementArrow';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import genButtonStyle from './button';
import genStatusStyle from './status';
import { genFocusStyle, resetComponent, roundedArrow } from '../../style';
export interface ComponentToken {
zIndexPopup: number;
@ -34,7 +34,6 @@ const genBaseStyle: GenerateStyle<DropdownToken> = (token) => {
menuCls,
zIndexPopup,
dropdownArrowDistance,
dropdownArrowOffset,
sizePopupArrow,
antCls,
iconCls,
@ -46,7 +45,6 @@ const genBaseStyle: GenerateStyle<DropdownToken> = (token) => {
fontSizeIcon,
controlPaddingHorizontal,
colorBgElevated,
boxShadowPopoverArrow,
} = token;
return [
@ -99,103 +97,6 @@ const genBaseStyle: GenerateStyle<DropdownToken> = (token) => {
display: 'none',
},
// =============================================================
// == Arrow ==
// =============================================================
// Offset the popover to account for the dropdown arrow
[`
&-show-arrow${componentCls}-placement-topLeft,
&-show-arrow${componentCls}-placement-top,
&-show-arrow${componentCls}-placement-topRight
`]: {
paddingBottom: dropdownArrowDistance,
},
[`
&-show-arrow${componentCls}-placement-bottomLeft,
&-show-arrow${componentCls}-placement-bottom,
&-show-arrow${componentCls}-placement-bottomRight
`]: {
paddingTop: dropdownArrowDistance,
},
// Note: .popover-arrow is outer, .popover-arrow:after is inner
[`${componentCls}-arrow`]: {
position: 'absolute',
zIndex: 1, // lift it up so the menu wouldn't cask shadow on it
display: 'block',
...roundedArrow(
sizePopupArrow,
token.borderRadiusXS,
token.borderRadiusOuter,
colorBgElevated,
boxShadowPopoverArrow,
),
},
[`
&-placement-top > ${componentCls}-arrow,
&-placement-topLeft > ${componentCls}-arrow,
&-placement-topRight > ${componentCls}-arrow
`]: {
bottom: dropdownArrowDistance,
transform: 'translateY(100%) rotate(180deg)',
},
[`&-placement-top > ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: '50%',
},
transform: 'translateX(-50%) translateY(100%) rotate(180deg)',
},
[`&-placement-topLeft > ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
[`&-placement-topRight > ${componentCls}-arrow`]: {
right: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
[`
&-placement-bottom > ${componentCls}-arrow,
&-placement-bottomLeft > ${componentCls}-arrow,
&-placement-bottomRight > ${componentCls}-arrow
`]: {
top: dropdownArrowDistance,
transform: `translateY(-100%)`,
},
[`&-placement-bottom > ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: '50%',
},
transform: `translateY(-100%) translateX(-50%)`,
},
[`&-placement-bottomLeft > ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
[`&-placement-bottomRight > ${componentCls}-arrow`]: {
right: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
// =============================================================
// == Motion ==
// =============================================================
@ -237,6 +138,15 @@ const genBaseStyle: GenerateStyle<DropdownToken> = (token) => {
},
},
// =============================================================
// == Arrow style ==
// =============================================================
getArrowStyle<DropdownToken>(token, {
colorBg: colorBgElevated,
limitVerticalRadius: true,
arrowPlacement: { top: true, bottom: true },
}),
{
// =============================================================
// == Menu ==

View File

@ -17,6 +17,7 @@ export interface TransferLocale {
export interface EmptyProps {
prefixCls?: string;
className?: string;
rootClassName?: string;
style?: React.CSSProperties;
/** @since 3.16.0 */
imageStyle?: React.CSSProperties;
@ -32,6 +33,7 @@ type CompoundedComponent = React.FC<EmptyProps> & {
const Empty: CompoundedComponent = ({
className,
rootClassName,
prefixCls: customizePrefixCls,
image = defaultEmptyImg,
description,
@ -68,6 +70,7 @@ const Empty: CompoundedComponent = ({
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
)}
{...restProps}
>

View File

@ -15,7 +15,7 @@ import useStyle from './style';
const BackTop: React.FC<BackTopProps> = (props) => {
const {
prefixCls: customizePrefixCls,
className = '',
className,
type = 'default',
shape = 'circle',
visibilityHeight = 400,

View File

@ -23,6 +23,7 @@ const FloatButton: React.ForwardRefRenderFunction<
const {
prefixCls: customizePrefixCls,
className,
rootClassName,
type = 'default',
shape = 'circle',
icon,
@ -41,6 +42,7 @@ const FloatButton: React.ForwardRefRenderFunction<
hashId,
prefixCls,
className,
rootClassName,
`${prefixCls}-${type}`,
`${prefixCls}-${mergeShape}`,
{

View File

@ -13,6 +13,7 @@ export type FloatButtonGroupTrigger = 'click' | 'hover';
export interface FloatButtonProps {
prefixCls?: string;
className?: string;
rootClassName?: string;
style?: React.CSSProperties;
icon?: React.ReactNode;
description?: React.ReactNode;
@ -51,6 +52,7 @@ export interface BackTopProps extends Omit<FloatButtonProps, 'target'> {
prefixCls?: string;
children?: React.ReactNode;
className?: string;
rootClassName?: string;
style?: React.CSSProperties;
duration?: number;
}

View File

@ -1,7 +1,7 @@
import classNames from 'classnames';
import FieldForm, { List, useWatch } from 'rc-field-form';
import type { FormProps as RcFormProps } from 'rc-field-form/lib/Form';
import type { ValidateErrorEntity } from 'rc-field-form/lib/interface';
import type { InternalNamePath, ValidateErrorEntity } from 'rc-field-form/lib/interface';
import * as React from 'react';
import { useMemo } from 'react';
import type { Options } from 'scroll-into-view-if-needed';
@ -36,6 +36,7 @@ export interface FormProps<Values = any> extends Omit<RcFormProps<Values>, 'form
requiredMark?: RequiredMark;
/** @deprecated Will warning in future branch. Pls use `requiredMark` instead. */
hideRequiredMark?: boolean;
rootClassName?: string;
}
const InternalForm: React.ForwardRefRenderFunction<FormInstance, FormProps> = (props, ref) => {
@ -45,7 +46,8 @@ const InternalForm: React.ForwardRefRenderFunction<FormInstance, FormProps> = (p
const {
prefixCls: customizePrefixCls,
className = '',
className,
rootClassName,
size = contextSize,
disabled = contextDisabled,
form,
@ -96,6 +98,7 @@ const InternalForm: React.ForwardRefRenderFunction<FormInstance, FormProps> = (p
},
hashId,
className,
rootClassName,
);
const [wrapForm] = useForm(form);
@ -120,16 +123,28 @@ const InternalForm: React.ForwardRefRenderFunction<FormInstance, FormProps> = (p
React.useImperativeHandle(ref, () => wrapForm);
const scrollToField = (options: boolean | Options, fieldName: InternalNamePath) => {
if (options) {
let defaultScrollToFirstError: Options = { block: 'nearest' };
if (typeof options === 'object') {
defaultScrollToFirstError = options;
}
wrapForm.scrollToField(fieldName, defaultScrollToFirstError);
}
};
const onInternalFinishFailed = (errorInfo: ValidateErrorEntity) => {
onFinishFailed?.(errorInfo);
let defaultScrollToFirstError: Options = { block: 'nearest' };
if (scrollToFirstError && errorInfo.errorFields.length) {
if (typeof scrollToFirstError === 'object') {
defaultScrollToFirstError = scrollToFirstError;
if (errorInfo.errorFields.length) {
const fieldName = errorInfo.errorFields[0].name;
if (scrollToFirstError !== undefined) {
scrollToField(scrollToFirstError, fieldName);
return;
}
if (contextForm && contextForm.scrollToFirstError !== undefined) {
scrollToField(contextForm.scrollToFirstError, fieldName);
}
wrapForm.scrollToField(errorInfo.errorFields[0].name, defaultScrollToFirstError);
}
};

View File

@ -2,17 +2,17 @@ import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled';
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled';
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
import classNames from 'classnames';
import * as React from 'react';
import omit from 'rc-util/lib/omit';
import type { Meta } from 'rc-field-form/lib/interface';
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
import omit from 'rc-util/lib/omit';
import * as React from 'react';
import type { FormItemProps, ValidateStatus } from '.';
import { Row } from '../../grid';
import FormItemLabel from '../FormItemLabel';
import FormItemInput from '../FormItemInput';
import type { FormItemStatusContextProps, ReportMetaChange } from '../context';
import { FormContext, FormItemInputContext, NoStyleItemContext } from '../context';
import type { FormItemProps, ValidateStatus } from '.';
import FormItemInput from '../FormItemInput';
import FormItemLabel from '../FormItemLabel';
import useDebounce from '../hooks/useDebounce';
const iconMap = {
@ -25,6 +25,7 @@ const iconMap = {
export interface ItemHolderProps extends FormItemProps {
prefixCls: string;
className?: string;
rootClassName?: string;
style?: React.CSSProperties;
errors: React.ReactNode[];
warnings: React.ReactNode[];
@ -39,6 +40,7 @@ export default function ItemHolder(props: ItemHolderProps) {
const {
prefixCls,
className,
rootClassName,
style,
help,
errors,
@ -117,10 +119,8 @@ export default function ItemHolder(props: ItemHolderProps) {
}, [mergedValidateStatus, hasFeedback]);
// ======================== Render ========================
const itemClassName = {
[itemPrefixCls]: true,
const itemClassName = classNames(itemPrefixCls, className, rootClassName, {
[`${itemPrefixCls}-with-help`]: hasHelp || debounceErrors.length || debounceWarnings.length,
[`${className}`]: !!className,
// Status
[`${itemPrefixCls}-has-feedback`]: mergedValidateStatus && hasFeedback,
@ -129,10 +129,10 @@ export default function ItemHolder(props: ItemHolderProps) {
[`${itemPrefixCls}-has-error`]: mergedValidateStatus === 'error',
[`${itemPrefixCls}-is-validating`]: mergedValidateStatus === 'validating',
[`${itemPrefixCls}-hidden`]: hidden,
};
});
return (
<div className={classNames(itemClassName)} style={style} ref={itemRef}>
<div className={itemClassName} style={style} ref={itemRef}>
<Row
className={`${itemPrefixCls}-row`}
{...omit(restProps, [

View File

@ -58,6 +58,7 @@ export interface FormItemProps<Values = any>
noStyle?: boolean;
style?: React.CSSProperties;
className?: string;
rootClassName?: string;
children?: ChildrenType<Values>;
id?: string;
hasFeedback?: boolean;

View File

@ -9023,7 +9023,7 @@ exports[`renders ./components/form/demo/register.tsx extend context correctly 1`
class="ant-form-item-control-input-content"
>
<div
class="ant-input-textarea ant-input-textarea-show-count"
class="ant-input-show-count ant-input-textarea ant-input-textarea-show-count"
data-count="0 / 100"
>
<textarea
@ -9031,6 +9031,11 @@ exports[`renders ./components/form/demo/register.tsx extend context correctly 1`
class="ant-input"
id="register_intro"
/>
<span
class="ant-input-data-count"
>
0 / 100
</span>
</div>
</div>
</div>
@ -26455,8 +26460,8 @@ exports[`renders ./components/form/demo/validate-static.tsx extend context corre
<div
class="ant-form-item-control-input-content"
>
<div
class="ant-mentions-affix-wrapper ant-mentions-affix-wrapper-status-error ant-mentions-affix-wrapper-has-feedback"
<span
class="ant-mentions-affix-wrapper"
>
<div
class="ant-mentions ant-mentions-status-error"
@ -26493,7 +26498,7 @@ exports[`renders ./components/form/demo/validate-static.tsx extend context corre
</span>
</span>
</span>
</div>
</span>
</div>
</div>
</div>
@ -26525,62 +26530,76 @@ exports[`renders ./components/form/demo/validate-static.tsx extend context corre
class="ant-form-item-control-input-content"
>
<div
class="ant-input-textarea ant-input-textarea-show-count ant-input-textarea-status-error ant-input-textarea-has-feedback"
class="ant-input-show-count ant-input-textarea ant-input-textarea-show-count"
data-count="0"
>
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn ant-input-affix-wrapper-status-error ant-input-affix-wrapper-has-feedback"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper ant-input-affix-wrapper-status-error"
>
<textarea
class="ant-input ant-input-status-error"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden ant-input-clear-icon-has-suffix"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
<span
class="ant-input-textarea-suffix"
>
<span
class="ant-form-item-feedback-icon ant-form-item-feedback-icon-error"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
</span>
<span
class="ant-input-textarea-suffix"
class="ant-input-data-count"
>
<span
class="ant-form-item-feedback-icon ant-form-item-feedback-icon-error"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
0
</span>
</div>
</div>

View File

@ -6122,7 +6122,7 @@ exports[`renders ./components/form/demo/register.tsx correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
class="ant-input-textarea ant-input-textarea-show-count"
class="ant-input-show-count ant-input-textarea ant-input-textarea-show-count"
data-count="0 / 100"
>
<textarea
@ -6130,6 +6130,11 @@ exports[`renders ./components/form/demo/register.tsx correctly 1`] = `
class="ant-input"
id="register_intro"
/>
<span
class="ant-input-data-count"
>
0 / 100
</span>
</div>
</div>
</div>
@ -11050,8 +11055,8 @@ exports[`renders ./components/form/demo/validate-static.tsx correctly 1`] = `
<div
class="ant-form-item-control-input-content"
>
<div
class="ant-mentions-affix-wrapper ant-mentions-affix-wrapper-status-error ant-mentions-affix-wrapper-has-feedback"
<span
class="ant-mentions-affix-wrapper"
>
<div
class="ant-mentions ant-mentions-status-error"
@ -11088,7 +11093,7 @@ exports[`renders ./components/form/demo/validate-static.tsx correctly 1`] = `
</span>
</span>
</span>
</div>
</span>
</div>
</div>
</div>
@ -11120,62 +11125,76 @@ exports[`renders ./components/form/demo/validate-static.tsx correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
class="ant-input-textarea ant-input-textarea-show-count ant-input-textarea-status-error ant-input-textarea-has-feedback"
class="ant-input-show-count ant-input-textarea ant-input-textarea-show-count"
data-count="0"
>
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn ant-input-affix-wrapper-status-error ant-input-affix-wrapper-has-feedback"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper ant-input-affix-wrapper-status-error"
>
<textarea
class="ant-input ant-input-status-error"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden ant-input-clear-icon-has-suffix"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
<span
class="ant-input-textarea-suffix"
>
<span
class="ant-form-item-feedback-icon ant-form-item-feedback-icon-error"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
</span>
<span
class="ant-input-textarea-suffix"
class="ant-input-data-count"
>
<span
class="ant-form-item-feedback-icon ant-form-item-feedback-icon-error"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
0
</span>
</div>
</div>

View File

@ -1,3 +0,0 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('form', { skip: ['complex-form-control.tsx', 'dep-debug.tsx'] });

View File

@ -0,0 +1,8 @@
import * as React from 'react';
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
demoTest('form', { skip: ['complex-form-control.tsx', 'dep-debug.tsx'] });
rootPropsTest('form', (Form, props) => <Form.Item {...props} />, {
name: 'Form.Item',
});

View File

@ -1,3 +1,5 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('grid');
demoTest('grid', {
testRootProps: false,
});

View File

@ -1,3 +1,5 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('icon');
demoTest('icon', {
testRootProps: false,
});

View File

@ -1,3 +0,0 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('input-number');

View File

@ -0,0 +1,15 @@
import * as React from 'react';
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
demoTest('input-number');
rootPropsTest(
'input-number',
(InputNumber, props) => <InputNumber {...props} addonBefore="Bamboo" />,
{
name: 'input-number.addonBefore',
},
);
rootPropsTest('input-number', (InputNumber, props) => <InputNumber {...props} prefix="Bamboo" />, {
name: 'input-number.prefix',
});

View File

@ -1,9 +1,9 @@
import DownOutlined from '@ant-design/icons/DownOutlined';
import UpOutlined from '@ant-design/icons/UpOutlined';
import type { ValueType } from '@rc-component/mini-decimal';
import classNames from 'classnames';
import type { InputNumberProps as RcInputNumberProps } from 'rc-input-number';
import RcInputNumber from 'rc-input-number';
import type { ValueType } from '@rc-component/mini-decimal';
import * as React from 'react';
import ConfigProvider, { ConfigContext } from '../config-provider';
import DisabledContext from '../config-provider/DisabledContext';
@ -19,6 +19,7 @@ import useStyle from './style';
export interface InputNumberProps<T extends ValueType = ValueType>
extends Omit<RcInputNumberProps<T>, 'prefix' | 'size' | 'controls'> {
prefixCls?: string;
rootClassName?: string;
addonBefore?: React.ReactNode;
addonAfter?: React.ReactNode;
prefix?: React.ReactNode;
@ -39,6 +40,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
const {
className,
rootClassName,
size: customizeSize,
disabled: customDisabled,
prefixCls: customizePrefixCls,
@ -86,6 +88,10 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
const mergedStatus = getMergedStatus(contextStatus, customStatus);
const mergeSize = compactSize || customizeSize || size;
const hasPrefix = prefix != null || hasFeedback;
const hasAddon = !!(addonBefore || addonAfter);
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled ?? disabled;
@ -102,6 +108,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
compactItemClassnames,
hashId,
className,
!hasPrefix && !hasAddon && rootClassName,
);
let element = (
@ -118,7 +125,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
/>
);
if (prefix != null || hasFeedback) {
if (hasPrefix) {
const affixWrapperCls = classNames(
`${prefixCls}-affix-wrapper`,
getStatusClassNames(`${prefixCls}-affix-wrapper`, mergedStatus, hasFeedback),
@ -130,9 +137,11 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
[`${prefixCls}-affix-wrapper-readonly`]: readOnly,
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
// className will go to addon wrapper
[`${className}`]: !(addonBefore || addonAfter) && className,
},
// className will go to addon wrapper
!hasAddon && className,
!hasAddon && rootClassName,
hashId,
);
@ -160,7 +169,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
);
}
if (addonBefore != null || addonAfter != null) {
if (hasAddon) {
const wrapperClassName = `${prefixCls}-group`;
const addonClassName = `${wrapperClassName}-addon`;
const addonBeforeNode = addonBefore ? (
@ -182,6 +191,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
getStatusClassNames(`${prefixCls}-group-wrapper`, mergedStatus, hasFeedback),
hashId,
className,
rootClassName,
);
element = (
<div className={mergedGroupClassName} style={props.style}>

View File

@ -1,137 +0,0 @@
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import classNames from 'classnames';
import * as React from 'react';
import type { DirectionType } from '../config-provider';
import type { SizeType } from '../config-provider/SizeContext';
import type { FormItemStatusContextProps } from '../form/context';
import { FormItemInputContext } from '../form/context';
import { cloneElement } from '../_util/reactNode';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import type { InputProps } from './Input';
const ClearableInputType = ['text', 'input'] as const;
function hasAddon(props: InputProps | ClearableInputProps) {
return !!(props.addonBefore || props.addonAfter);
}
/** This basic props required for input and textarea. */
interface BasicProps {
prefixCls: string;
inputType: typeof ClearableInputType[number];
value?: any;
allowClear?: boolean;
element: React.ReactElement;
handleReset: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
className?: string;
style?: React.CSSProperties;
disabled?: boolean;
direction?: DirectionType;
focused?: boolean;
readOnly?: boolean;
bordered: boolean;
hidden?: boolean;
}
/** This props only for input. */
export interface ClearableInputProps extends BasicProps {
size?: SizeType;
suffix?: React.ReactNode;
prefix?: React.ReactNode;
addonBefore?: React.ReactNode;
addonAfter?: React.ReactNode;
triggerFocus?: () => void;
status?: InputStatus;
hashId?: string;
}
class ClearableLabeledInput extends React.Component<ClearableInputProps> {
renderClearIcon(prefixCls: string) {
const { value, disabled, readOnly, handleReset, suffix } = this.props;
const needClear = !disabled && !readOnly && value;
const className = `${prefixCls}-clear-icon`;
return (
<CloseCircleFilled
onClick={handleReset}
// Do not trigger onBlur when clear input
// https://github.com/ant-design/ant-design/issues/31200
onMouseDown={(e) => e.preventDefault()}
className={classNames(
{
[`${className}-hidden`]: !needClear,
[`${className}-has-suffix`]: !!suffix,
},
className,
)}
role="button"
/>
);
}
renderTextAreaWithClearIcon(
prefixCls: string,
element: React.ReactElement,
statusContext: FormItemStatusContextProps,
) {
const {
value,
allowClear,
className,
style,
direction,
bordered,
hidden,
status: customStatus,
hashId,
} = this.props;
const { status: contextStatus, hasFeedback } = statusContext;
if (!allowClear) {
return cloneElement(element, {
value,
});
}
const affixWrapperCls = classNames(
`${prefixCls}-affix-wrapper`,
`${prefixCls}-affix-wrapper-textarea-with-clear-btn`,
getStatusClassNames(
`${prefixCls}-affix-wrapper`,
getMergedStatus(contextStatus, customStatus),
hasFeedback,
),
{
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
// className will go to addon wrapper
[`${className}`]: !hasAddon(this.props) && className,
},
hashId,
);
return (
<span className={affixWrapperCls} style={style} hidden={hidden}>
{cloneElement(element, {
style: null,
value,
})}
{this.renderClearIcon(prefixCls)}
</span>
);
}
render() {
return (
<FormItemInputContext.Consumer>
{(statusContext) => {
const { prefixCls, inputType, element } = this.props;
if (inputType === ClearableInputType[0]) {
return this.renderTextAreaWithClearIcon(prefixCls, element, statusContext);
}
}}
</FormItemInputContext.Consumer>
);
}
}
export default ClearableLabeledInput;

View File

@ -26,67 +26,6 @@ export interface InputFocusOptions extends FocusOptions {
export type { InputRef };
export function fixControlledValue<T>(value: T) {
if (typeof value === 'undefined' || value === null) {
return '';
}
return String(value);
}
export function resolveOnChange<E extends HTMLInputElement | HTMLTextAreaElement>(
target: E,
e:
| React.ChangeEvent<E>
| React.MouseEvent<HTMLElement, MouseEvent>
| React.CompositionEvent<HTMLElement>,
onChange?: (event: React.ChangeEvent<E>) => void,
targetValue?: string,
) {
if (!onChange) {
return;
}
let event = e as React.ChangeEvent<E>;
if (e.type === 'click') {
// Clone a new target for event.
// Avoid the following usage, the setQuery method gets the original value.
//
// const [query, setQuery] = React.useState('');
// <Input
// allowClear
// value={query}
// onChange={(e)=> {
// setQuery((prevStatus) => e.target.value);
// }}
// />
const currentTarget = target.cloneNode(true) as E;
// click clear icon
event = Object.create(e, {
target: { value: currentTarget },
currentTarget: { value: currentTarget },
});
currentTarget.value = '';
onChange(event);
return;
}
// Trigger by composition event, this means we need force change the input value
if (targetValue !== undefined) {
event = Object.create(e, {
target: { value: target },
currentTarget: { value: target },
});
target.value = targetValue;
onChange(event);
return;
}
onChange(event);
}
export function triggerFocus(
element?: HTMLInputElement | HTMLTextAreaElement,
option?: InputFocusOptions,
@ -121,6 +60,7 @@ export interface InputProps
RcInputProps,
'wrapperClassName' | 'groupClassName' | 'inputClassName' | 'affixWrapperClassName'
> {
rootClassName?: string;
size?: SizeType;
disabled?: boolean;
status?: InputStatus;
@ -142,6 +82,7 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
addonAfter,
addonBefore,
className,
rootClassName,
onChange,
...rest
} = props;
@ -226,7 +167,7 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
onFocus={handleFocus}
suffix={suffixNode}
allowClear={mergedAllowClear}
className={classNames(className, compactItemClassnames)}
className={classNames(className, rootClassName, compactItemClassnames)}
onChange={handleChange}
addonAfter={
addonAfter && (
@ -246,41 +187,43 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
</NoCompactStyle>
)
}
inputClassName={classNames(
{
[`${prefixCls}-sm`]: mergedSize === 'small',
[`${prefixCls}-lg`]: mergedSize === 'large',
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-borderless`]: !bordered,
},
!inputHasPrefixSuffix && getStatusClassNames(prefixCls, mergedStatus),
hashId,
)}
affixWrapperClassName={classNames(
{
[`${prefixCls}-affix-wrapper-sm`]: mergedSize === 'small',
[`${prefixCls}-affix-wrapper-lg`]: mergedSize === 'large',
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
},
getStatusClassNames(`${prefixCls}-affix-wrapper`, mergedStatus, hasFeedback),
hashId,
)}
wrapperClassName={classNames(
{
[`${prefixCls}-group-rtl`]: direction === 'rtl',
},
hashId,
)}
groupClassName={classNames(
{
[`${prefixCls}-group-wrapper-sm`]: mergedSize === 'small',
[`${prefixCls}-group-wrapper-lg`]: mergedSize === 'large',
[`${prefixCls}-group-wrapper-rtl`]: direction === 'rtl',
},
getStatusClassNames(`${prefixCls}-group-wrapper`, mergedStatus, hasFeedback),
hashId,
)}
classes={{
input: classNames(
{
[`${prefixCls}-sm`]: mergedSize === 'small',
[`${prefixCls}-lg`]: mergedSize === 'large',
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-borderless`]: !bordered,
},
!inputHasPrefixSuffix && getStatusClassNames(prefixCls, mergedStatus),
hashId,
),
affixWrapper: classNames(
{
[`${prefixCls}-affix-wrapper-sm`]: mergedSize === 'small',
[`${prefixCls}-affix-wrapper-lg`]: mergedSize === 'large',
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
},
getStatusClassNames(`${prefixCls}-affix-wrapper`, mergedStatus, hasFeedback),
hashId,
),
wrapper: classNames(
{
[`${prefixCls}-group-rtl`]: direction === 'rtl',
},
hashId,
),
group: classNames(
{
[`${prefixCls}-group-wrapper-sm`]: mergedSize === 'small',
[`${prefixCls}-group-wrapper-lg`]: mergedSize === 'large',
[`${prefixCls}-group-wrapper-rtl`]: direction === 'rtl',
},
getStatusClassNames(`${prefixCls}-group-wrapper`, mergedStatus, hasFeedback),
hashId,
),
}}
/>,
);
});

View File

@ -1,91 +1,58 @@
import classNames from 'classnames';
import type { TextAreaProps as RcTextAreaProps } from 'rc-textarea';
import RcTextArea from 'rc-textarea';
import type { ResizableTextAreaRef } from 'rc-textarea/lib/ResizableTextArea';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import omit from 'rc-util/lib/omit';
import type { TextAreaProps as RcTextAreaProps } from 'rc-textarea/lib/interface';
import type { TextAreaRef as RcTextAreaRef } from 'rc-textarea';
import { forwardRef } from 'react';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import DisabledContext from '../config-provider/DisabledContext';
import RcTextArea from 'rc-textarea';
import classNames from 'classnames';
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import type { BaseInputProps } from 'rc-input/lib/interface';
import { FormItemInputContext } from '../form/context';
import useStyle from './style';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
import { FormItemInputContext } from '../form/context';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import ClearableLabeledInput from './ClearableLabeledInput';
import type { InputFocusOptions } from './Input';
import { fixControlledValue, resolveOnChange, triggerFocus } from './Input';
import useStyle from './style';
import { triggerFocus } from './Input';
import DisabledContext from '../config-provider/DisabledContext';
import { ConfigContext } from '../config-provider';
interface ShowCountProps {
formatter: (args: { value: string; count: number; maxLength?: number }) => string;
}
function fixEmojiLength(value: string, maxLength: number) {
return [...(value || '')].slice(0, maxLength).join('');
}
function setTriggerValue(
isCursorInEnd: boolean,
preValue: string,
triggerValue: string,
maxLength: number,
) {
let newTriggerValue = triggerValue;
if (isCursorInEnd) {
// 光标在尾部,直接截断
newTriggerValue = fixEmojiLength(triggerValue, maxLength!);
} else if (
[...(preValue || '')].length < triggerValue.length &&
[...(triggerValue || '')].length > maxLength!
) {
// 光标在中间,如果最后的值超过最大值,则采用原先的值
newTriggerValue = preValue;
}
return newTriggerValue;
}
export interface TextAreaProps extends RcTextAreaProps {
allowClear?: boolean;
export interface TextAreaProps extends Omit<RcTextAreaProps, 'suffix'> {
bordered?: boolean;
showCount?: boolean | ShowCountProps;
size?: SizeType;
disabled?: boolean;
status?: InputStatus;
}
export interface TextAreaRef {
focus: (options?: InputFocusOptions) => void;
blur: () => void;
resizableTextArea?: ResizableTextAreaRef;
resizableTextArea?: RcTextAreaRef['resizableTextArea'];
}
const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
const TextArea = forwardRef<TextAreaRef, TextAreaProps>(
(
{
prefixCls: customizePrefixCls,
bordered = true,
showCount = false,
maxLength,
className,
style,
size: customizeSize,
disabled: customDisabled,
onCompositionStart,
onCompositionEnd,
onChange,
status: customStatus,
...props
allowClear,
...rest
},
ref,
) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
// ===================== Size =====================
const size = React.useContext(SizeContext);
const mergedSize = customizeSize || size;
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled ?? disabled;
// ===================== Status =====================
const {
status: contextStatus,
hasFeedback,
@ -93,87 +60,8 @@ const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
} = React.useContext(FormItemInputContext);
const mergedStatus = getMergedStatus(contextStatus, customStatus);
const innerRef = React.useRef<RcTextArea>(null);
const clearableInputRef = React.useRef<ClearableLabeledInput>(null);
const [compositing, setCompositing] = React.useState(false);
const oldCompositionValueRef = React.useRef<string>();
const oldSelectionStartRef = React.useRef<number>(0);
const [value, setValue] = useMergedState(props.defaultValue, {
value: props.value,
});
const { hidden } = props;
const handleSetValue = (val: string, callback?: () => void) => {
if (props.value === undefined) {
setValue(val);
callback?.();
}
};
// =========================== Value Update ===========================
// Max length value
const hasMaxLength = Number(maxLength) > 0;
const onInternalCompositionStart: React.CompositionEventHandler<HTMLTextAreaElement> = (e) => {
setCompositing(true);
// 拼音输入前保存一份旧值
oldCompositionValueRef.current = value as string;
// 保存旧的光标位置
oldSelectionStartRef.current = e.currentTarget.selectionStart;
onCompositionStart?.(e);
};
const onInternalCompositionEnd: React.CompositionEventHandler<HTMLTextAreaElement> = (e) => {
setCompositing(false);
let triggerValue = e.currentTarget.value;
if (hasMaxLength) {
const isCursorInEnd =
oldSelectionStartRef.current >= maxLength! + 1 ||
oldSelectionStartRef.current === oldCompositionValueRef.current?.length;
triggerValue = setTriggerValue(
isCursorInEnd,
oldCompositionValueRef.current as string,
triggerValue,
maxLength!,
);
}
// Patch composition onChange when value changed
if (triggerValue !== value) {
handleSetValue(triggerValue);
resolveOnChange(e.currentTarget, e, onChange, triggerValue);
}
onCompositionEnd?.(e);
};
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
let triggerValue = e.target.value;
if (!compositing && hasMaxLength) {
// 1. 复制粘贴超过maxlength的情况 2.未超过maxlength的情况
const isCursorInEnd =
e.target.selectionStart >= maxLength! + 1 ||
e.target.selectionStart === triggerValue.length ||
!e.target.selectionStart;
triggerValue = setTriggerValue(isCursorInEnd, value as string, triggerValue, maxLength!);
}
handleSetValue(triggerValue);
resolveOnChange(e.currentTarget, e, onChange, triggerValue);
};
// ============================== Reset ===============================
const handleReset = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
handleSetValue('');
innerRef.current?.focus();
resolveOnChange(innerRef.current?.resizableTextArea?.textArea!, e, onChange);
};
const prefixCls = getPrefixCls('input', customizePrefixCls);
// Style
const [wrapSSR, hashId] = useStyle(prefixCls);
// ===================== Ref =====================
const innerRef = React.useRef<RcTextAreaRef>(null);
React.useImperativeHandle(ref, () => ({
resizableTextArea: innerRef.current?.resizableTextArea,
@ -183,89 +71,58 @@ const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
blur: () => innerRef.current?.blur(),
}));
const textArea = (
const prefixCls = getPrefixCls('input', customizePrefixCls);
// Allow clear
let mergedAllowClear: BaseInputProps['allowClear'];
if (typeof allowClear === 'object' && allowClear?.clearIcon) {
mergedAllowClear = allowClear;
} else if (allowClear) {
mergedAllowClear = { clearIcon: <CloseCircleFilled /> };
}
// ===================== Style =====================
const [wrapSSR, hashId] = useStyle(prefixCls);
return wrapSSR(
<RcTextArea
{...omit(props, ['allowClear'])}
{...rest}
disabled={mergedDisabled}
className={classNames(
{
[`${prefixCls}-borderless`]: !bordered,
[className!]: className && !showCount,
[`${prefixCls}-sm`]: size === 'small' || customizeSize === 'small',
[`${prefixCls}-lg`]: size === 'large' || customizeSize === 'large',
},
getStatusClassNames(prefixCls, mergedStatus),
hashId,
)}
style={showCount ? { resize: style?.resize } : style}
prefixCls={prefixCls}
onCompositionStart={onInternalCompositionStart}
onChange={handleChange}
onCompositionEnd={onInternalCompositionEnd}
ref={innerRef}
/>
);
let val = fixControlledValue(value) as string;
if (!compositing && hasMaxLength && (props.value === null || props.value === undefined)) {
// fix #27612 将value转为数组进行截取解决 '😂'.length === 2 等emoji表情导致的截取乱码的问题
val = fixEmojiLength(val, maxLength!);
}
// TextArea
const textareaNode = (
<ClearableLabeledInput
disabled={mergedDisabled}
{...props}
prefixCls={prefixCls}
direction={direction}
inputType="text"
value={val}
element={textArea}
handleReset={handleReset}
ref={clearableInputRef}
bordered={bordered}
status={customStatus}
style={showCount ? undefined : style}
hashId={hashId}
/>
);
// Only show text area wrapper when needed
if (showCount || hasFeedback) {
const valueLength = [...val].length;
let dataCount = '';
if (typeof showCount === 'object') {
dataCount = showCount.formatter({ value: val, count: valueLength, maxLength });
} else {
dataCount = `${valueLength}${hasMaxLength ? ` / ${maxLength}` : ''}`;
}
return (
<div
hidden={hidden}
className={classNames(
`${prefixCls}-textarea`,
allowClear={mergedAllowClear}
classes={{
affixWrapper: classNames(
`${prefixCls}-textarea-affix-wrapper`,
{
[`${prefixCls}-textarea-rtl`]: direction === 'rtl',
[`${prefixCls}-textarea-show-count`]: showCount,
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
[`${prefixCls}-affix-wrapper-sm`]: mergedSize === 'small',
[`${prefixCls}-affix-wrapper-lg`]: mergedSize === 'large',
},
getStatusClassNames(`${prefixCls}-textarea`, mergedStatus, hasFeedback),
className,
getStatusClassNames(`${prefixCls}-affix-wrapper`, mergedStatus),
hashId,
)}
style={style}
data-count={dataCount}
>
{textareaNode}
{hasFeedback && <span className={`${prefixCls}-textarea-suffix`}>{feedbackIcon}</span>}
</div>
);
}
return wrapSSR(textareaNode);
),
countWrapper: classNames(
`${prefixCls}-textarea`,
`${prefixCls}-textarea-show-count`,
hashId,
),
textarea: classNames(
{
[`${prefixCls}-borderless`]: !bordered,
[`${prefixCls}-sm`]: mergedSize === 'small',
[`${prefixCls}-lg`]: mergedSize === 'large',
},
getStatusClassNames(prefixCls, mergedStatus),
hashId,
),
}}
prefixCls={prefixCls}
suffix={
hasFeedback && <span className={`${prefixCls}-textarea-suffix`}>{feedbackIcon}</span>
}
ref={innerRef}
/>,
);
},
);

View File

@ -4902,31 +4902,40 @@ Array [
<br />,
<br />,
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
>
<textarea
class="ant-input"
placeholder="textarea with clear icon"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>,
]
@ -4994,31 +5003,40 @@ exports[`renders ./components/input/demo/borderless-debug.tsx extend context cor
placeholder="Unbordered"
/>
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn ant-input-affix-wrapper-borderless"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper ant-input-affix-wrapper-borderless"
>
<textarea
class="ant-input ant-input-borderless"
placeholder="Unbordered"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
<span
@ -5100,31 +5118,40 @@ exports[`renders ./components/input/demo/borderless-debug.tsx extend context cor
</span>
</span>
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
style="border:2px solid #000"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
</div>
@ -9707,12 +9734,17 @@ Array [
<br />,
<br />,
<div
class="ant-input-textarea ant-input-textarea-show-count"
class="ant-input-show-count ant-input-textarea ant-input-textarea-show-count"
data-count="0 / 100"
>
<textarea
class="ant-input"
/>
<span
class="ant-input-data-count"
>
0 / 100
</span>
</div>,
]
`;
@ -9970,31 +10002,40 @@ Array [
The autoSize property applies to textarea nodes, and only the height changes automatically. In addition, autoSize can be set to an object, specifying the minimum number of rows and the maximum number of rows. The autoSize property applies to textarea nodes, and only the height changes automatically. In addition, autoSize can be set to an object, specifying the minimum number of rows and the maximum number of rows.
</textarea>,
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
style="width:93px"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>,
]
@ -10003,7 +10044,7 @@ Array [
exports[`renders ./components/input/demo/textarea-show-count.tsx extend context correctly 1`] = `
Array [
<div
class="ant-input-textarea ant-input-textarea-show-count"
class="ant-input-show-count ant-input-textarea ant-input-textarea-show-count"
data-count="0 / 100"
style="height:120px;margin-bottom:24px"
>
@ -10011,9 +10052,14 @@ Array [
class="ant-input"
placeholder="can resize"
/>
<span
class="ant-input-data-count"
>
0 / 100
</span>
</div>,
<div
class="ant-input-textarea ant-input-textarea-show-count"
class="ant-input-show-count ant-input-textarea ant-input-textarea-show-count"
data-count="0 / 100"
style="height:120px;resize:none"
>
@ -10022,6 +10068,11 @@ Array [
placeholder="disable resize"
style="resize:none"
/>
<span
class="ant-input-data-count"
>
0 / 100
</span>
</div>,
]
`;

View File

@ -1103,31 +1103,40 @@ Array [
<br />,
<br />,
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
>
<textarea
class="ant-input"
placeholder="textarea with clear icon"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>,
]
@ -1195,31 +1204,40 @@ exports[`renders ./components/input/demo/borderless-debug.tsx correctly 1`] = `
placeholder="Unbordered"
/>
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn ant-input-affix-wrapper-borderless"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper ant-input-affix-wrapper-borderless"
>
<textarea
class="ant-input ant-input-borderless"
placeholder="Unbordered"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
<span
@ -1301,31 +1319,40 @@ exports[`renders ./components/input/demo/borderless-debug.tsx correctly 1`] = `
</span>
</span>
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
style="border:2px solid #000"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
</div>
@ -3455,12 +3482,17 @@ Array [
<br />,
<br />,
<div
class="ant-input-textarea ant-input-textarea-show-count"
class="ant-input-show-count ant-input-textarea ant-input-textarea-show-count"
data-count="0 / 100"
>
<textarea
class="ant-input"
/>
<span
class="ant-input-data-count"
>
0 / 100
</span>
</div>,
]
`;
@ -3718,31 +3750,40 @@ Array [
The autoSize property applies to textarea nodes, and only the height changes automatically. In addition, autoSize can be set to an object, specifying the minimum number of rows and the maximum number of rows. The autoSize property applies to textarea nodes, and only the height changes automatically. In addition, autoSize can be set to an object, specifying the minimum number of rows and the maximum number of rows.
</textarea>,
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
style="width:93px"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>,
]
@ -3751,7 +3792,7 @@ Array [
exports[`renders ./components/input/demo/textarea-show-count.tsx correctly 1`] = `
Array [
<div
class="ant-input-textarea ant-input-textarea-show-count"
class="ant-input-show-count ant-input-textarea ant-input-textarea-show-count"
data-count="0 / 100"
style="height:120px;margin-bottom:24px"
>
@ -3759,9 +3800,14 @@ Array [
class="ant-input"
placeholder="can resize"
/>
<span
class="ant-input-data-count"
>
0 / 100
</span>
</div>,
<div
class="ant-input-textarea ant-input-textarea-show-count"
class="ant-input-show-count ant-input-textarea ant-input-textarea-show-count"
data-count="0 / 100"
style="height:120px;resize:none"
>
@ -3770,6 +3816,11 @@ Array [
placeholder="disable resize"
style="resize:none"
/>
<span
class="ant-input-data-count"
>
0 / 100
</span>
</div>,
]
`;

View File

@ -2,7 +2,7 @@
exports[`TextArea allowClear should change type when click 1`] = `
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
>
<textarea
class="ant-input"
@ -10,234 +10,306 @@ exports[`TextArea allowClear should change type when click 1`] = `
111
</textarea>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
`;
exports[`TextArea allowClear should change type when click 2`] = `
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
`;
exports[`TextArea allowClear should not show icon if defaultValue is undefined, null or empty string 1`] = `
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
`;
exports[`TextArea allowClear should not show icon if defaultValue is undefined, null or empty string 2`] = `
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
`;
exports[`TextArea allowClear should not show icon if defaultValue is undefined, null or empty string 3`] = `
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
`;
exports[`TextArea allowClear should not show icon if value is undefined, null or empty string 1`] = `
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
`;
exports[`TextArea allowClear should not show icon if value is undefined, null or empty string 2`] = `
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
`;
exports[`TextArea allowClear should not show icon if value is undefined, null or empty string 3`] = `
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
class="ant-input-suffix"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-input-clear-icon ant-input-clear-icon-hidden"
role="button"
tabindex="-1"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</span>
`;

View File

@ -1,3 +0,0 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('input');

Some files were not shown because too many files have changed in this diff Show More