mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-11-30 02:59:04 +08:00
Merge branch 'feature' into next-merge-feature
This commit is contained in:
commit
5c72d04b10
@ -7,7 +7,7 @@ version: 2.1
|
||||
jobs:
|
||||
test-argos-ci:
|
||||
docker:
|
||||
- image: circleci/node:16-browsers
|
||||
- image: cimg/node:lts-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@ -16,9 +16,6 @@ jobs:
|
||||
- run:
|
||||
name: Install argos cli
|
||||
command: npm i fast-glob lodash @argos-ci/core
|
||||
- run:
|
||||
name: Install puppeteer
|
||||
command: node node_modules/puppeteer/install.js
|
||||
- run:
|
||||
name: Build dist file
|
||||
command: npm run dist
|
||||
@ -28,7 +25,8 @@ jobs:
|
||||
- run:
|
||||
name: Upload screenshots to Argos CI
|
||||
command: npm run argos
|
||||
|
||||
# The resource_class feature allows configuring CPU and RAM resources for each job. Different resource classes are available for different executors. https://circleci.com/docs/2.0/configuration-reference/#resourceclass
|
||||
resource_class: large
|
||||
|
||||
# Invoke jobs via workflows
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
|
||||
|
@ -31,7 +31,7 @@ module.exports = {
|
||||
'@typescript-eslint/no-unused-vars': [2, { args: 'none' }],
|
||||
'no-unused-expressions': 'off',
|
||||
'@typescript-eslint/no-unused-expressions': 2,
|
||||
'@typescript-eslint/consistent-type-imports': 2,
|
||||
'@typescript-eslint/consistent-type-imports': [2, { disallowTypeAnnotations: false }],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -3,7 +3,6 @@ import { easeInOutCubic } from '../easings';
|
||||
describe('Test easings', () => {
|
||||
it('easeInOutCubic return value', () => {
|
||||
const nums: number[] = [];
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let index = 0; index < 5; index++) {
|
||||
nums.push(easeInOutCubic(index, 1, 5, 4));
|
||||
}
|
||||
|
@ -2,17 +2,14 @@ import { waitFakeTimer } from '../../../tests/utils';
|
||||
import scrollTo from '../scrollTo';
|
||||
|
||||
describe('Test ScrollTo function', () => {
|
||||
let dateNowMock: jest.SpyInstance;
|
||||
const dateNowMock = jest.spyOn(Date, 'now');
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
dateNowMock = jest
|
||||
.spyOn(Date, 'now')
|
||||
.mockImplementationOnce(() => 0)
|
||||
.mockImplementationOnce(() => 1000);
|
||||
dateNowMock.mockReturnValueOnce(0).mockReturnValueOnce(1000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@ -21,7 +18,7 @@ describe('Test ScrollTo function', () => {
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers();
|
||||
dateNowMock.mockRestore();
|
||||
dateNowMock.mockClear();
|
||||
});
|
||||
|
||||
it('test scrollTo', async () => {
|
||||
|
@ -4,7 +4,7 @@ import { render, fireEvent } from '../../../tests/utils';
|
||||
|
||||
describe('Table', () => {
|
||||
it('useSyncState', () => {
|
||||
const Test: React.FC = () => {
|
||||
const Test = () => {
|
||||
const [getVal, setVal] = useSyncState('light');
|
||||
return <span onClick={() => setVal('bamboo')}>{getVal()}</span>;
|
||||
};
|
||||
|
@ -294,15 +294,19 @@ describe('Wave component', () => {
|
||||
fakeDoc.appendChild(document.createElement('span'));
|
||||
expect(fakeDoc.childNodes).toHaveLength(2);
|
||||
|
||||
(container.querySelector('.bamboo') as any).getRootNode = () => fakeDoc;
|
||||
const elem = container.querySelector('.bamboo');
|
||||
|
||||
// Click should not throw
|
||||
fireEvent.click(container.querySelector('.bamboo')!);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
if (elem) {
|
||||
elem.getRootNode = () => fakeDoc;
|
||||
|
||||
expect(fakeDoc.querySelector('style')).toBeTruthy();
|
||||
// Click should not throw
|
||||
fireEvent.click(elem);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(fakeDoc.querySelector('style')).toBeTruthy();
|
||||
}
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
@ -29,17 +29,17 @@ const Content = () => {
|
||||
};
|
||||
|
||||
it('Delay loading timer in Button component', () => {
|
||||
const otherTimer: any = 9528;
|
||||
jest.spyOn(window, 'setTimeout').mockReturnValue(otherTimer);
|
||||
const otherTimer = 9528;
|
||||
jest.spyOn<Window, 'setTimeout'>(window, 'setTimeout').mockReturnValue(otherTimer);
|
||||
jest.restoreAllMocks();
|
||||
|
||||
const wrapper = render(<Content />);
|
||||
|
||||
const btnTimer: any = 9527;
|
||||
jest.spyOn(window, 'setTimeout').mockReturnValue(btnTimer);
|
||||
jest.spyOn(window, 'clearTimeout');
|
||||
const setTimeoutMock = window.setTimeout as any as jest.Mock;
|
||||
const clearTimeoutMock = window.clearTimeout as any as jest.Mock;
|
||||
const btnTimer = 9527;
|
||||
const setTimeoutMock = jest
|
||||
.spyOn<Window, 'setTimeout'>(window, 'setTimeout')
|
||||
.mockReturnValue(btnTimer);
|
||||
const clearTimeoutMock = jest.spyOn<Window, 'clearTimeout'>(window, 'clearTimeout');
|
||||
|
||||
// other component may call setTimeout or clearTimeout
|
||||
const setTimeoutCount = () => {
|
||||
@ -58,7 +58,11 @@ it('Delay loading timer in Button component', () => {
|
||||
|
||||
// trigger timer handler
|
||||
act(() => {
|
||||
setTimeoutMock.mock.calls[0][0]();
|
||||
const timerHandler = setTimeoutMock.mock.calls[0][0];
|
||||
|
||||
if (typeof timerHandler === 'function') {
|
||||
timerHandler();
|
||||
}
|
||||
});
|
||||
expect(setTimeoutCount()).toBe(1);
|
||||
expect(clearTimeoutCount()).toBe(0);
|
||||
|
@ -7,7 +7,6 @@ import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { fireEvent, render, sleep } from '../../../tests/utils';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
import type { SizeType } from '../../config-provider/SizeContext';
|
||||
|
||||
describe('Button', () => {
|
||||
mountTest(Button);
|
||||
@ -38,7 +37,8 @@ describe('Button', () => {
|
||||
it('warns if size is wrong', () => {
|
||||
resetWarned();
|
||||
const mockWarn = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const size = 'who am I' as any as SizeType;
|
||||
const size = 'who am I';
|
||||
// @ts-expect-error: Type '"who am I"' is not assignable to type 'SizeType'.ts(2322)
|
||||
render(<Button.Group size={size} />);
|
||||
expect(mockWarn).toHaveBeenCalledWith('Warning: [antd: Button.Group] Invalid prop `size`.');
|
||||
|
||||
|
@ -1,18 +1,19 @@
|
||||
import React from 'react';
|
||||
import Button from '..';
|
||||
import { fireEvent, render, sleep } from '../../../tests/utils';
|
||||
import { fireEvent, render, sleep, assertsExist } from '../../../tests/utils';
|
||||
|
||||
// Mock Wave ref
|
||||
let waveInstanceMock: any;
|
||||
let waveInstanceMock: InstanceType<typeof import('../../_util/wave').default> | null;
|
||||
jest.mock('../../_util/wave', () => {
|
||||
const Wave = jest.requireActual('../../_util/wave');
|
||||
const Wave: typeof import('../../_util/wave') = jest.requireActual('../../_util/wave');
|
||||
const WaveComponent = Wave.default;
|
||||
|
||||
return {
|
||||
...Wave,
|
||||
__esModule: true,
|
||||
default: (props: any) => (
|
||||
default: (props: import('../../_util/wave').WaveProps) => (
|
||||
<WaveComponent
|
||||
ref={(node: any) => {
|
||||
ref={node => {
|
||||
waveInstanceMock = node;
|
||||
}}
|
||||
{...props}
|
||||
@ -77,12 +78,14 @@ describe('click wave effect', () => {
|
||||
|
||||
it('should run resetEffect in transitionstart', async () => {
|
||||
const wrapper = render(<Button type="primary">button</Button>);
|
||||
assertsExist(waveInstanceMock);
|
||||
const resetEffect = jest.spyOn(waveInstanceMock, 'resetEffect');
|
||||
await clickButton(wrapper);
|
||||
expect(resetEffect).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(wrapper.container.querySelector('.ant-btn')!);
|
||||
await sleep(10);
|
||||
expect(resetEffect).toHaveBeenCalledTimes(2);
|
||||
// @ts-expect-error: Property 'animationStart' is private and only accessible within class 'Wave'.ts(2341)
|
||||
waveInstanceMock.animationStart = false;
|
||||
fireEvent(wrapper.container.querySelector('.ant-btn')!, new Event('transitionstart'));
|
||||
expect(resetEffect).toHaveBeenCalledTimes(3);
|
||||
@ -91,6 +94,7 @@ describe('click wave effect', () => {
|
||||
|
||||
it('should handle transitionend', async () => {
|
||||
const wrapper = render(<Button type="primary">button</Button>);
|
||||
assertsExist(waveInstanceMock);
|
||||
const resetEffect = jest.spyOn(waveInstanceMock, 'resetEffect');
|
||||
await clickButton(wrapper);
|
||||
expect(resetEffect).toHaveBeenCalledTimes(1);
|
||||
|
@ -7,6 +7,7 @@ import { ConfigContext } from '../config-provider';
|
||||
import DisabledContext from '../config-provider/DisabledContext';
|
||||
import type { SizeType } from '../config-provider/SizeContext';
|
||||
import SizeContext from '../config-provider/SizeContext';
|
||||
import { useCompactItemContext } from '../space/Compact';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import { tuple } from '../_util/type';
|
||||
import warning from '../_util/warning';
|
||||
@ -178,7 +179,6 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
|
||||
const [innerLoading, setLoading] = React.useState<Loading>(!!loading);
|
||||
const [hasTwoCNChar, setHasTwoCNChar] = React.useState(false);
|
||||
const buttonRef = (ref as any) || React.createRef<HTMLElement>();
|
||||
|
||||
const isNeedInserted = () =>
|
||||
React.Children.count(children) === 1 && !icon && !isUnBorderedButtonType(type);
|
||||
|
||||
@ -247,9 +247,10 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
|
||||
);
|
||||
|
||||
const autoInsertSpace = autoInsertSpaceInButton !== false;
|
||||
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
|
||||
|
||||
const sizeClassNameMap = { large: 'lg', small: 'sm', middle: undefined };
|
||||
const sizeFullname = groupSize || customizeSize || size;
|
||||
const sizeFullname = compactSize || groupSize || customizeSize || size;
|
||||
const sizeCls = sizeFullname ? sizeClassNameMap[sizeFullname] || '' : '';
|
||||
|
||||
const iconType = innerLoading ? 'loading' : icon;
|
||||
@ -272,6 +273,7 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
[`${prefixCls}-disabled`]: linkButtonRestProps.href !== undefined && mergedDisabled,
|
||||
},
|
||||
compactItemClassnames,
|
||||
className,
|
||||
);
|
||||
|
||||
|
88
components/button/style/space-compact.less
Normal file
88
components/button/style/space-compact.less
Normal file
@ -0,0 +1,88 @@
|
||||
@import '../../style/mixins/index';
|
||||
|
||||
@btn-prefix-cls: ~'@{ant-prefix}-btn';
|
||||
|
||||
// Button in Space.Compact
|
||||
.@{btn-prefix-cls} {
|
||||
.compact-item(@btn-prefix-cls);
|
||||
|
||||
// make `btn-icon-only` not too narrow
|
||||
&-icon-only&-compact-item {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
// Special styles for Primary Button
|
||||
&-compact-item.@{btn-prefix-cls}-primary {
|
||||
&:not([disabled]) + &:not([disabled]) {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: -@border-width-base;
|
||||
left: -@border-width-base;
|
||||
display: inline-block;
|
||||
width: @border-width-base;
|
||||
height: calc(100% + @border-width-base * 2);
|
||||
background-color: @btn-group-border;
|
||||
content: ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------RTL----------
|
||||
&-compact-item-rtl {
|
||||
&.@{btn-prefix-cls}-compact-first-item&:not(.@{btn-prefix-cls}-compact-last-item) {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
&.@{btn-prefix-cls}-compact-last-item&:not(.@{btn-prefix-cls}-compact-first-item) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
&.@{btn-prefix-cls}-sm {
|
||||
&.@{btn-prefix-cls}-compact-first-item&:not(.@{btn-prefix-cls}-compact-last-item) {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
&.@{btn-prefix-cls}-compact-last-item&:not(.@{btn-prefix-cls}-compact-first-item) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------RTL Special styles for Primary Button----------
|
||||
&.@{btn-prefix-cls}-primary {
|
||||
&:not([disabled]) + &:not([disabled]) {
|
||||
&::after {
|
||||
right: -@border-width-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Button in Space.Compact when direction=vertical
|
||||
.compact-item-vertical(@btn-prefix-cls);
|
||||
|
||||
// Special styles for Primary Button
|
||||
&-compact-vertical-item {
|
||||
&.@{btn-prefix-cls}-primary {
|
||||
&:not([disabled]) + &:not([disabled]) {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: -@border-width-base;
|
||||
left: -@border-width-base;
|
||||
display: inline-block;
|
||||
width: calc(100% + @border-width-base * 2);
|
||||
height: @border-width-base;
|
||||
background-color: @btn-group-border;
|
||||
content: ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,19 +14,16 @@ import Button from '../../radio/radioButton';
|
||||
import Select from '../../select';
|
||||
import Header, { type CalendarHeaderProps } from '../Header';
|
||||
|
||||
function calendarProps(): PickerPanelProps<any> {
|
||||
return (global as any).calendarProps;
|
||||
}
|
||||
|
||||
function calendarHeaderProps(): CalendarHeaderProps<any> {
|
||||
return (global as any).calendarHeaderProps;
|
||||
}
|
||||
const ref: {
|
||||
calendarProps?: PickerPanelProps<unknown>;
|
||||
calendarHeaderProps?: CalendarHeaderProps<unknown>;
|
||||
} = {};
|
||||
|
||||
jest.mock('../Header', () => {
|
||||
const HeaderModule = jest.requireActual('../Header');
|
||||
const HeaderComponent = HeaderModule.default;
|
||||
return (props: CalendarHeaderProps<any>) => {
|
||||
(global as any).calendarHeaderProps = props;
|
||||
ref.calendarHeaderProps = props;
|
||||
return <HeaderComponent {...props} />;
|
||||
};
|
||||
});
|
||||
@ -36,8 +33,8 @@ jest.mock('rc-picker', () => {
|
||||
const PickerPanelComponent = RcPicker.PickerPanel;
|
||||
return {
|
||||
...RcPicker,
|
||||
PickerPanel: (props: PickerPanelProps<any>) => {
|
||||
(global as any).calendarProps = props;
|
||||
PickerPanel: (props: PickerPanelProps<unknown>) => {
|
||||
ref.calendarProps = props;
|
||||
return <PickerPanelComponent {...props} />;
|
||||
},
|
||||
};
|
||||
@ -151,8 +148,8 @@ describe('Calendar', () => {
|
||||
it('getDateRange should returns a disabledDate function', () => {
|
||||
const validRange: [Dayjs.Dayjs, Dayjs.Dayjs] = [Dayjs('2018-02-02'), Dayjs('2018-05-18')];
|
||||
render(<Calendar validRange={validRange} defaultValue={Dayjs('2018-02-02')} />);
|
||||
expect(calendarProps().disabledDate?.(Dayjs('2018-06-02'))).toBe(true);
|
||||
expect(calendarProps().disabledDate?.(Dayjs('2018-04-02'))).toBe(false);
|
||||
expect(ref.calendarProps?.disabledDate?.(Dayjs('2018-06-02'))).toBe(true);
|
||||
expect(ref.calendarProps?.disabledDate?.(Dayjs('2018-04-02'))).toBe(false);
|
||||
});
|
||||
|
||||
it('validRange should work with disabledDate function', () => {
|
||||
@ -161,11 +158,11 @@ describe('Calendar', () => {
|
||||
<Calendar validRange={validRange} disabledDate={data => data.isSame(Dayjs('2018-02-03'))} />,
|
||||
);
|
||||
|
||||
expect(calendarProps().disabledDate?.(Dayjs('2018-02-01'))).toBe(true);
|
||||
expect(calendarProps().disabledDate?.(Dayjs('2018-02-02'))).toBe(false);
|
||||
expect(calendarProps().disabledDate?.(Dayjs('2018-02-03'))).toBe(true);
|
||||
expect(calendarProps().disabledDate?.(Dayjs('2018-02-04'))).toBe(false);
|
||||
expect(calendarProps().disabledDate?.(Dayjs('2018-06-01'))).toBe(true);
|
||||
expect(ref.calendarProps?.disabledDate?.(Dayjs('2018-02-01'))).toBe(true);
|
||||
expect(ref.calendarProps?.disabledDate?.(Dayjs('2018-02-02'))).toBe(false);
|
||||
expect(ref.calendarProps?.disabledDate?.(Dayjs('2018-02-03'))).toBe(true);
|
||||
expect(ref.calendarProps?.disabledDate?.(Dayjs('2018-02-04'))).toBe(false);
|
||||
expect(ref.calendarProps?.disabledDate?.(Dayjs('2018-06-01'))).toBe(true);
|
||||
});
|
||||
|
||||
it('Calendar MonthSelect should display correct label', () => {
|
||||
@ -178,9 +175,9 @@ describe('Calendar', () => {
|
||||
const monthMode = 'month';
|
||||
const yearMode = 'year';
|
||||
const wrapper = render(<Calendar />);
|
||||
expect(calendarHeaderProps().mode).toEqual(monthMode);
|
||||
expect(ref.calendarHeaderProps?.mode).toEqual(monthMode);
|
||||
wrapper.rerender(<Calendar mode={yearMode} />);
|
||||
expect(calendarHeaderProps().mode).toEqual(yearMode);
|
||||
expect(ref.calendarHeaderProps?.mode).toEqual(yearMode);
|
||||
});
|
||||
|
||||
it('Calendar should switch mode', () => {
|
||||
@ -188,9 +185,9 @@ describe('Calendar', () => {
|
||||
const yearMode = 'year';
|
||||
const onPanelChangeStub = jest.fn();
|
||||
const wrapper = render(<Calendar mode={yearMode} onPanelChange={onPanelChangeStub} />);
|
||||
expect(calendarHeaderProps().mode).toEqual(yearMode);
|
||||
expect(ref.calendarHeaderProps?.mode).toEqual(yearMode);
|
||||
wrapper.rerender(<Calendar mode={monthMode} onPanelChange={onPanelChangeStub} />);
|
||||
expect(calendarHeaderProps().mode).toEqual(monthMode);
|
||||
expect(ref.calendarHeaderProps?.mode).toEqual(monthMode);
|
||||
expect(onPanelChangeStub).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
@ -231,7 +228,7 @@ describe('Calendar', () => {
|
||||
const date = Dayjs(new Date(Date.UTC(2017, 7, 9, 8)));
|
||||
const wrapper = render(<Calendar onPanelChange={onPanelChange} value={date} />);
|
||||
|
||||
expect(calendarHeaderProps().mode).toBe('month');
|
||||
expect(ref.calendarHeaderProps?.mode).toBe('month');
|
||||
expect(wrapper.container.querySelectorAll('.ant-picker-date-panel').length).toBe(1);
|
||||
expect(wrapper.container.querySelectorAll('.ant-picker-month-panel').length).toBe(0);
|
||||
fireEvent.click(wrapper.container.querySelector('.ant-radio-button-input[value="year"]')!);
|
||||
|
@ -262,7 +262,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
4
|
||||
</h3>
|
||||
@ -282,7 +282,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
1
|
||||
</h3>
|
||||
@ -302,7 +302,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
2
|
||||
</h3>
|
||||
@ -322,7 +322,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
3
|
||||
</h3>
|
||||
@ -342,7 +342,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
4
|
||||
</h3>
|
||||
@ -362,7 +362,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
1
|
||||
</h3>
|
||||
@ -382,7 +382,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
2
|
||||
</h3>
|
||||
@ -402,7 +402,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
3
|
||||
</h3>
|
||||
@ -422,7 +422,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
4
|
||||
</h3>
|
||||
|
@ -262,7 +262,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
4
|
||||
</h3>
|
||||
@ -282,7 +282,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
1
|
||||
</h3>
|
||||
@ -302,7 +302,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
2
|
||||
</h3>
|
||||
@ -322,7 +322,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
3
|
||||
</h3>
|
||||
@ -342,7 +342,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
4
|
||||
</h3>
|
||||
@ -362,7 +362,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
1
|
||||
</h3>
|
||||
@ -382,7 +382,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
2
|
||||
</h3>
|
||||
@ -402,7 +402,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
3
|
||||
</h3>
|
||||
@ -422,7 +422,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
4
|
||||
</h3>
|
||||
|
@ -18,6 +18,7 @@ import { Carousel } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const contentStyle: React.CSSProperties = {
|
||||
margin: 0,
|
||||
height: '160px',
|
||||
color: '#fff',
|
||||
lineHeight: '160px',
|
||||
|
@ -19,6 +19,8 @@ import defaultRenderEmpty from '../config-provider/defaultRenderEmpty';
|
||||
import DisabledContext from '../config-provider/DisabledContext';
|
||||
import type { SizeType } from '../config-provider/SizeContext';
|
||||
import SizeContext from '../config-provider/SizeContext';
|
||||
import { useCompactItemContext } from '../space/Compact';
|
||||
|
||||
import { FormItemInputContext } from '../form/context';
|
||||
import getIcons from '../select/utils/iconUtil';
|
||||
import type { SelectCommonPlacement } from '../_util/motion';
|
||||
@ -196,6 +198,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
|
||||
const [wrapSelectSSR, hashId] = useSelectStyle(prefixCls);
|
||||
const [wrapCascaderSSR] = useStyle(cascaderPrefixCls);
|
||||
|
||||
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
|
||||
// =================== Dropdown ====================
|
||||
const mergedDropdownClassName = classNames(
|
||||
popupClassName || dropdownClassName,
|
||||
@ -228,7 +231,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
|
||||
|
||||
// ===================== Size ======================
|
||||
const size = React.useContext(SizeContext);
|
||||
const mergedSize = customizeSize || size;
|
||||
const mergedSize = compactSize || customizeSize || size;
|
||||
|
||||
// ===================== Disabled =====================
|
||||
const disabled = React.useContext(DisabledContext);
|
||||
@ -287,6 +290,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
|
||||
[`${prefixCls}-in-form-item`]: isFormItemInput,
|
||||
},
|
||||
getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
|
||||
compactItemClassnames,
|
||||
className,
|
||||
hashId,
|
||||
)}
|
||||
|
@ -242,10 +242,10 @@ describe('CheckboxGroup', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const Demo: React.FC = () => {
|
||||
const [v, setV] = useState<string>('');
|
||||
const [v, setV] = useState('');
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeout(setV('1') as unknown as TimerHandler, 1000);
|
||||
setV('1');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
@ -13,6 +13,7 @@ import { ConfigContext } from '../../config-provider';
|
||||
import DisabledContext from '../../config-provider/DisabledContext';
|
||||
import SizeContext from '../../config-provider/SizeContext';
|
||||
import { FormItemInputContext } from '../../form/context';
|
||||
import { useCompactItemContext } from '../../space/Compact';
|
||||
import LocaleReceiver from '../../locale-provider/LocaleReceiver';
|
||||
import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
|
||||
import enUS from '../locale/en_US';
|
||||
@ -53,6 +54,7 @@ export default function generateRangePicker<DateType>(
|
||||
const innerRef = React.useRef<RCRangePicker<DateType>>(null);
|
||||
const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext);
|
||||
const prefixCls = getPrefixCls('picker', customizePrefixCls);
|
||||
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
|
||||
const { format, showTime, picker } = props as any;
|
||||
const rootPrefixCls = getPrefixCls();
|
||||
|
||||
@ -76,7 +78,7 @@ export default function generateRangePicker<DateType>(
|
||||
|
||||
// ===================== Size =====================
|
||||
const size = React.useContext(SizeContext);
|
||||
const mergedSize = customizeSize || size;
|
||||
const mergedSize = compactSize || customizeSize || size;
|
||||
|
||||
// ===================== Disabled =====================
|
||||
const disabled = React.useContext(DisabledContext);
|
||||
@ -135,6 +137,7 @@ export default function generateRangePicker<DateType>(
|
||||
hasFeedback,
|
||||
),
|
||||
hashId,
|
||||
compactItemClassnames,
|
||||
className,
|
||||
)}
|
||||
locale={locale!.lang}
|
||||
|
@ -7,6 +7,7 @@ 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 { PickerDateProps, PickerProps, PickerTimeProps } from '.';
|
||||
import { Components, getTimeProps } from '.';
|
||||
import { ConfigContext } from '../../config-provider';
|
||||
@ -54,6 +55,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
|
||||
|
||||
const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext);
|
||||
const prefixCls = getPrefixCls('picker', customizePrefixCls);
|
||||
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
|
||||
const innerRef = React.useRef<RCPicker<DateType>>(null);
|
||||
const { format, showTime } = props as any;
|
||||
|
||||
@ -100,7 +102,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
|
||||
|
||||
// ===================== Size =====================
|
||||
const size = React.useContext(SizeContext);
|
||||
const mergedSize = customizeSize || size;
|
||||
const mergedSize = compactSize || customizeSize || size;
|
||||
|
||||
// ===================== Disabled =====================
|
||||
const disabled = React.useContext(DisabledContext);
|
||||
@ -150,6 +152,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
|
||||
hasFeedback,
|
||||
),
|
||||
hashId,
|
||||
compactItemClassnames,
|
||||
className,
|
||||
)}
|
||||
prefixCls={prefixCls}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import classNames from 'classnames';
|
||||
import type { ColProps } from 'antd/es/grid';
|
||||
import type { FormInstance } from '..';
|
||||
@ -20,15 +19,7 @@ import Switch from '../../switch';
|
||||
import TreeSelect from '../../tree-select';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
sleep,
|
||||
act,
|
||||
screen,
|
||||
pureRender,
|
||||
waitFakeTimer,
|
||||
} from '../../../tests/utils';
|
||||
import { fireEvent, render, screen, pureRender, waitFakeTimer } from '../../../tests/utils';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
import Drawer from '../../drawer';
|
||||
import zhCN from '../../locale/zh_CN';
|
||||
@ -51,27 +42,52 @@ describe('Form', () => {
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
const change = async (
|
||||
container: ReturnType<typeof render>['container'],
|
||||
index: number,
|
||||
value: string,
|
||||
executeMockTimer: boolean,
|
||||
) => {
|
||||
fireEvent.change(container.querySelectorAll('input')?.[index], { target: { value } });
|
||||
await sleep(200);
|
||||
// const change = async (
|
||||
// container: ReturnType<typeof render>['container'],
|
||||
// index: number,
|
||||
// value: string,
|
||||
// executeMockTimer: boolean,
|
||||
// ) => {
|
||||
// fireEvent.change(container.querySelectorAll('input')?.[index], { target: { value } });
|
||||
// await sleep(200);
|
||||
|
||||
if (executeMockTimer) {
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
}
|
||||
await sleep(1);
|
||||
// if (executeMockTimer) {
|
||||
// for (let i = 0; i < 10; i += 1) {
|
||||
// act(() => {
|
||||
// jest.runAllTimers();
|
||||
// });
|
||||
// }
|
||||
// await sleep(1);
|
||||
// }
|
||||
// };
|
||||
|
||||
const changeValue = async (
|
||||
input: HTMLElement | null | number,
|
||||
value: string,
|
||||
advTimer = 1000,
|
||||
) => {
|
||||
let element: HTMLElement;
|
||||
|
||||
if (typeof input === 'number') {
|
||||
element = document.querySelectorAll('input')[input];
|
||||
}
|
||||
|
||||
expect(element!).toBeTruthy();
|
||||
|
||||
fireEvent.change(element!, {
|
||||
target: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
if (advTimer) {
|
||||
await waitFakeTimer(advTimer / 20, 20);
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useRealTimers();
|
||||
document.body.innerHTML = '';
|
||||
jest.useFakeTimers();
|
||||
(scrollIntoView as any).mockReset();
|
||||
});
|
||||
|
||||
@ -80,6 +96,8 @@ describe('Form', () => {
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
errorSpy.mockRestore();
|
||||
warnSpy.mockRestore();
|
||||
(scrollIntoView as any).mockRestore();
|
||||
@ -100,34 +118,38 @@ describe('Form', () => {
|
||||
);
|
||||
|
||||
// user type something and clear
|
||||
await userEvent.type(screen.getByLabelText('test'), 'test');
|
||||
await userEvent.clear(screen.getByLabelText('test'));
|
||||
await changeValue(0, 'test');
|
||||
await changeValue(0, '');
|
||||
|
||||
// should show alert with correct message and show correct styles
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent("'test' is required");
|
||||
expect(screen.getByLabelText('test')).toHaveClass('ant-input-status-error');
|
||||
expect(container.querySelectorAll('.ant-form-item-has-error').length).toBeTruthy();
|
||||
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
|
||||
"'test' is required",
|
||||
);
|
||||
expect(container.querySelector('.ant-input-status-error')).toBeTruthy();
|
||||
expect(container.querySelector('.ant-form-item-has-error')).toBeTruthy();
|
||||
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should clean up', async () => {
|
||||
jest.useFakeTimers();
|
||||
const Demo: React.FC = () => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const onChange = async () => {
|
||||
// Wait a while and then some logic to validate
|
||||
await waitFakeTimer();
|
||||
|
||||
try {
|
||||
await form.validateFields();
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form form={form} initialValues={{ aaa: '2' }}>
|
||||
<Form.Item name="aaa">
|
||||
<Input
|
||||
onChange={async () => {
|
||||
await sleep(0);
|
||||
try {
|
||||
await form.validateFields();
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Input onChange={onChange} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
@ -155,14 +177,18 @@ describe('Form', () => {
|
||||
};
|
||||
|
||||
const { container } = render(<Demo />);
|
||||
await change(container, 0, '1', true);
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('aaa');
|
||||
await change(container, 0, '2', true);
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('ccc');
|
||||
await change(container, 0, '1', true);
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('aaa');
|
||||
|
||||
jest.useRealTimers();
|
||||
await changeValue(0, '1');
|
||||
await waitFakeTimer();
|
||||
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent('aaa');
|
||||
|
||||
await changeValue(0, '2');
|
||||
await waitFakeTimer();
|
||||
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent('ccc');
|
||||
|
||||
await changeValue(0, '1');
|
||||
await waitFakeTimer();
|
||||
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent('aaa');
|
||||
});
|
||||
});
|
||||
|
||||
@ -176,6 +202,7 @@ describe('Form', () => {
|
||||
'Warning: [antd: Form.Item] `children` of render props only work with `shouldUpdate` or `dependencies`.',
|
||||
);
|
||||
});
|
||||
|
||||
it("`shouldUpdate` shouldn't work with `dependencies`", () => {
|
||||
render(
|
||||
<Form>
|
||||
@ -255,7 +282,6 @@ describe('Form', () => {
|
||||
});
|
||||
|
||||
it('input element should have the prop aria-describedby pointing to the help id when there are errors', async () => {
|
||||
jest.useFakeTimers();
|
||||
const { container } = pureRender(
|
||||
<Form>
|
||||
<Form.Item name="test" rules={[{ len: 3 }, { type: 'number' }]}>
|
||||
@ -263,15 +289,11 @@ describe('Form', () => {
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
fireEvent.change(container.querySelector('input')!, { target: { value: 'Invalid number' } });
|
||||
|
||||
await waitFakeTimer();
|
||||
await changeValue(0, 'Invalid number');
|
||||
|
||||
expect(container.querySelector('input')?.getAttribute('aria-describedby')).toBe('test_help');
|
||||
expect(container.querySelector('.ant-form-item-explain')?.id).toBe('test_help');
|
||||
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('input element should have the prop aria-invalid when there are errors', async () => {
|
||||
@ -283,8 +305,7 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
fireEvent.change(container.querySelector('input')!, { target: { value: 'Invalid number' } });
|
||||
await sleep(800);
|
||||
await changeValue(0, 'Invalid number');
|
||||
expect(container.querySelector('input')?.getAttribute('aria-invalid')).toBe('true');
|
||||
});
|
||||
|
||||
@ -407,7 +428,7 @@ describe('Form', () => {
|
||||
it('scrollToFirstError', async () => {
|
||||
const onFinishFailed = jest.fn();
|
||||
|
||||
render(
|
||||
const { container } = render(
|
||||
<Form scrollToFirstError={{ block: 'center' }} onFinishFailed={onFinishFailed}>
|
||||
<Form.Item name="test" rules={[{ required: true }]}>
|
||||
<input />
|
||||
@ -419,7 +440,9 @@ describe('Form', () => {
|
||||
);
|
||||
|
||||
expect(scrollIntoView).not.toHaveBeenCalled();
|
||||
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
|
||||
fireEvent.submit(container.querySelector('form')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
const inputNode = document.getElementById('test');
|
||||
expect(scrollIntoView).toHaveBeenCalledWith(inputNode, {
|
||||
block: 'center',
|
||||
@ -452,7 +475,7 @@ describe('Form', () => {
|
||||
});
|
||||
|
||||
it('dynamic change required', async () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
<Form>
|
||||
<Form.Item label="light" name="light" valuePropName="checked">
|
||||
<input type="checkbox" />
|
||||
@ -469,45 +492,24 @@ describe('Form', () => {
|
||||
);
|
||||
|
||||
// should not show alert by default
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
expect(container.querySelector('.ant-form-item-explain')).toBeFalsy();
|
||||
|
||||
// click to change the light field value to true
|
||||
await userEvent.click(screen.getByLabelText('light'));
|
||||
fireEvent.click(container.querySelector('input')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
// user input something and clear
|
||||
await userEvent.type(screen.getByLabelText('bamboo'), '1');
|
||||
await userEvent.clear(screen.getByLabelText('bamboo'));
|
||||
await changeValue(1, '1');
|
||||
await changeValue(1, '');
|
||||
|
||||
// should show alert says that the field is required
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent("'bamboo' is required");
|
||||
});
|
||||
|
||||
it('should show alert with string when help is non-empty string', async () => {
|
||||
render(
|
||||
<Form>
|
||||
<Form.Item help="good">
|
||||
<input />
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
|
||||
"'bamboo' is required",
|
||||
);
|
||||
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('good');
|
||||
});
|
||||
|
||||
it('should show alert with empty string when help is empty string', async () => {
|
||||
render(
|
||||
<Form>
|
||||
<Form.Item help="">
|
||||
<input />
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('');
|
||||
});
|
||||
|
||||
describe('should show related className when customize help', () => {
|
||||
it('normal', () => {
|
||||
it('normal', async () => {
|
||||
const { container } = render(
|
||||
<Form>
|
||||
<Form.Item help="good">
|
||||
@ -515,10 +517,14 @@ describe('Form', () => {
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(container.querySelector('.ant-form-item-explain')).toHaveTextContent('good');
|
||||
expect(container.querySelector('.ant-form-item-with-help')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('empty string', () => {
|
||||
it('empty string', async () => {
|
||||
const { container } = render(
|
||||
<Form>
|
||||
<Form.Item help="">
|
||||
@ -526,6 +532,10 @@ describe('Form', () => {
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(container.querySelector('.ant-form-item-explain')).toHaveTextContent('');
|
||||
expect(container.querySelector('.ant-form-item-with-help')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -539,8 +549,6 @@ describe('Form', () => {
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/20706
|
||||
it('Error change should work', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const { container } = render(
|
||||
<Form>
|
||||
<Form.Item
|
||||
@ -565,17 +573,16 @@ describe('Form', () => {
|
||||
|
||||
/* eslint-disable no-await-in-loop */
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
await change(container, 0, 'bamboo', true);
|
||||
await change(container, 0, '', true);
|
||||
await changeValue(0, 'bamboo');
|
||||
await changeValue(0, '');
|
||||
expect(container.querySelector('.ant-form-item-explain')?.textContent).toEqual(
|
||||
"'name' is required",
|
||||
);
|
||||
await change(container, 0, 'p', true);
|
||||
await sleep(100);
|
||||
|
||||
await changeValue(0, 'p');
|
||||
expect(container.querySelector('.ant-form-item-explain')?.textContent).toEqual('not a p');
|
||||
}
|
||||
/* eslint-enable */
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/20813
|
||||
@ -592,15 +599,17 @@ describe('Form', () => {
|
||||
);
|
||||
};
|
||||
|
||||
render(<App />);
|
||||
const { container } = render(<App />);
|
||||
|
||||
// should show initial text
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('');
|
||||
await waitFakeTimer();
|
||||
expect(container.querySelector('.ant-form-item-explain')).toHaveTextContent('');
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
|
||||
// should show bamboo alert without opacity and hide first alert with opacity: 0
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('bamboo');
|
||||
await waitFakeTimer();
|
||||
expect(container.querySelector('.ant-form-item-explain')).toHaveTextContent('bamboo');
|
||||
});
|
||||
|
||||
it('warning when use `dependencies` but `name` is empty & children is not a render props', () => {
|
||||
@ -616,8 +625,6 @@ describe('Form', () => {
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/20948
|
||||
it('not repeat render when Form.Item is not a real Field', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const shouldNotRender = jest.fn();
|
||||
const StaticInput: React.FC<React.InputHTMLAttributes<HTMLInputElement>> = ({
|
||||
id,
|
||||
@ -663,9 +670,6 @@ describe('Form', () => {
|
||||
expect(container.querySelector<HTMLInputElement>('#changed')!.value).toEqual('bamboo');
|
||||
expect(shouldNotRender).toHaveBeenCalledTimes(1);
|
||||
expect(shouldRender).toHaveBeenCalledTimes(2);
|
||||
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('empty help should also render', () => {
|
||||
@ -692,14 +696,12 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await change(container, 0, '', true);
|
||||
await changeValue(0, '');
|
||||
expect(container.querySelector('.ant-form-item')).toHaveClass('ant-form-item-has-error');
|
||||
expect(container.querySelector('.ant-form-item-explain')!.textContent).toEqual('help');
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('clear validation message when', async () => {
|
||||
jest.useFakeTimers();
|
||||
const { container } = render(
|
||||
<Form>
|
||||
<Form.Item name="test" label="test" rules={[{ required: true, message: 'message' }]}>
|
||||
@ -708,27 +710,26 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await change(container, 0, '1', true);
|
||||
await changeValue(0, '1');
|
||||
expect(container.querySelectorAll('.ant-form-item-explain').length).toBeFalsy();
|
||||
|
||||
await change(container, 0, '', true);
|
||||
await changeValue(0, '');
|
||||
expect(container.querySelectorAll('.ant-form-item-explain').length).toBeTruthy();
|
||||
|
||||
await change(container, 0, '123', true);
|
||||
await sleep(800);
|
||||
await changeValue(0, '123');
|
||||
expect(container.querySelectorAll('.ant-form-item-explain').length).toBeFalsy();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/21167
|
||||
it('`require` without `name`', () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
<Form.Item label="test" name="test" required>
|
||||
<input />
|
||||
</Form.Item>,
|
||||
);
|
||||
|
||||
expect(screen.getByTitle('test')).toHaveClass('ant-form-item-required');
|
||||
// expect(screen.getByTitle('test')).toHaveClass('ant-form-item-required');
|
||||
expect(container.querySelector('.ant-form-item-required')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('0 is a validate Field', () => {
|
||||
@ -757,7 +758,7 @@ describe('Form', () => {
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/21415
|
||||
it('should not throw error when Component.props.onChange is null', () => {
|
||||
it('should not throw error when Component.props.onChange is null', async () => {
|
||||
const CustomComponent: React.FC = () => (
|
||||
<input onChange={null as unknown as ChangeEventHandler<HTMLInputElement>} />
|
||||
);
|
||||
@ -768,10 +769,8 @@ describe('Form', () => {
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
const handle = async () => {
|
||||
await userEvent.type(screen.getByRole('textbox'), 'aaa');
|
||||
};
|
||||
expect(handle).not.toThrow();
|
||||
|
||||
await changeValue(0, 'aaa');
|
||||
});
|
||||
|
||||
it('change `help` should not warning', async () => {
|
||||
@ -797,91 +796,91 @@ describe('Form', () => {
|
||||
);
|
||||
};
|
||||
|
||||
render(<Demo />);
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
const { container } = render(<Demo />);
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('`label` support template', async () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
<Form validateMessages={{ required: '${label} is good!' }}>
|
||||
<Form.Item name="test" label="Bamboo" rules={[{ required: true }]}>
|
||||
<input />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button htmlType="submit">Submit</Button>
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
fireEvent.submit(container.querySelector('form')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('Bamboo is good!');
|
||||
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
|
||||
'Bamboo is good!',
|
||||
);
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/33691
|
||||
it('should keep upper locale in nested ConfigProvider', async () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
<ConfigProvider locale={zhCN}>
|
||||
<ConfigProvider>
|
||||
<Form>
|
||||
<Form.Item name="test" label="Bamboo" rules={[{ required: true }]}>
|
||||
<input />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button htmlType="submit">Submit</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</ConfigProvider>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
fireEvent.submit(container.querySelector('form')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('请输入Bamboo');
|
||||
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
|
||||
'请输入Bamboo',
|
||||
);
|
||||
});
|
||||
|
||||
it('`name` support template when label is not provided', async () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
<Form validateMessages={{ required: '${label} is good!' }}>
|
||||
<Form.Item name="Bamboo" rules={[{ required: true }]}>
|
||||
<input />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button htmlType="submit">Submit</Button>
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
fireEvent.submit(container.querySelector('form')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('Bamboo is good!');
|
||||
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
|
||||
'Bamboo is good!',
|
||||
);
|
||||
});
|
||||
|
||||
it('`messageVariables` support validate', async () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
<Form validateMessages={{ required: '${label} is good!' }}>
|
||||
<Form.Item name="test" messageVariables={{ label: 'Bamboo' }} rules={[{ required: true }]}>
|
||||
<input />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button htmlType="submit">Submit</Button>
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
fireEvent.submit(container.querySelector('form')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('Bamboo is good!');
|
||||
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
|
||||
'Bamboo is good!',
|
||||
);
|
||||
});
|
||||
|
||||
it('validation message should has alert role', async () => {
|
||||
// https://github.com/ant-design/ant-design/issues/25711
|
||||
render(
|
||||
const { container } = render(
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
<Form validateMessages={{ required: 'name is good!' }}>
|
||||
<Form.Item name="test" rules={[{ required: true }]}>
|
||||
@ -893,9 +892,12 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
fireEvent.submit(container.querySelector('form')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('name is good!');
|
||||
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
|
||||
'name is good!',
|
||||
);
|
||||
});
|
||||
|
||||
it('return same form instance', async () => {
|
||||
@ -917,11 +919,12 @@ describe('Form', () => {
|
||||
);
|
||||
};
|
||||
|
||||
pureRender(<App />);
|
||||
const { container } = pureRender(<App />);
|
||||
|
||||
for (let i = 0; i < 5; i += 1) {
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
await waitFakeTimer();
|
||||
}
|
||||
|
||||
expect(instances.size).toBe(1);
|
||||
@ -942,12 +945,13 @@ describe('Form', () => {
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
pureRender(<Demo />);
|
||||
const { container } = pureRender(<Demo />);
|
||||
renderTimes = 0;
|
||||
jest.clearAllMocks();
|
||||
fireEvent.change(screen.getByLabelText('username'), { target: { value: 'a' } });
|
||||
|
||||
await changeValue(0, 'a');
|
||||
|
||||
expect(renderTimes).toEqual(1);
|
||||
expect(screen.getByLabelText('username')).toHaveValue('a');
|
||||
expect(container.querySelector('input')).toHaveValue('a');
|
||||
});
|
||||
|
||||
it('should warning with `defaultValue`', () => {
|
||||
@ -979,13 +983,15 @@ describe('Form', () => {
|
||||
</Form>
|
||||
);
|
||||
|
||||
const { rerender } = render(<Demo showA />);
|
||||
const { container, rerender } = render(<Demo showA />);
|
||||
|
||||
await expect(screen.findByRole('alert')).resolves.toBeInTheDocument();
|
||||
await waitFakeTimer();
|
||||
expect(container.querySelector('.ant-form-item-explain')).toBeTruthy();
|
||||
|
||||
rerender(<Demo showA={false} />);
|
||||
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
await waitFakeTimer();
|
||||
expect(container.querySelector('.ant-form-item-explain')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('no warning of initialValue & getValueProps & preserve', () => {
|
||||
@ -1000,7 +1006,7 @@ describe('Form', () => {
|
||||
});
|
||||
|
||||
it('should customize id when pass with id', () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
<Form>
|
||||
<Form.Item name="light">
|
||||
<Input id="bamboo" />
|
||||
@ -1008,11 +1014,11 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole('textbox')).toHaveAttribute('id', 'bamboo');
|
||||
expect(container.querySelector('input')!.id).toEqual('bamboo');
|
||||
});
|
||||
|
||||
it('should trigger validate when onBlur when pass validateTrigger onBlur', async () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
<Form validateTrigger="onBlur">
|
||||
<Form.Item name="light" label="light" rules={[{ len: 3 }]}>
|
||||
<Input />
|
||||
@ -1021,14 +1027,14 @@ describe('Form', () => {
|
||||
);
|
||||
|
||||
// type a invalidate value, not trigger validation
|
||||
await userEvent.type(screen.getByRole('textbox'), '7777');
|
||||
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
await changeValue(0, '7777');
|
||||
expect(container.querySelector('.ant-form-item-explain')).toBeFalsy();
|
||||
|
||||
// tab(onBlur) the input field, trigger and see the alert
|
||||
fireEvent.blur(screen.getByRole('textbox'));
|
||||
fireEvent.blur(container.querySelector('input')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
await expect(screen.findByRole('alert')).resolves.toBeInTheDocument();
|
||||
expect(container.querySelector('.ant-form-item-explain')).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('Form item hidden', () => {
|
||||
@ -1056,7 +1062,7 @@ describe('Form', () => {
|
||||
});
|
||||
|
||||
it('legacy hideRequiredMark', () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
<Form hideRequiredMark role="form">
|
||||
<Form.Item name="light" label="light" required>
|
||||
<Input />
|
||||
@ -1064,7 +1070,7 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole('form')).toHaveClass('ant-form-hide-required-mark');
|
||||
expect(container.querySelector('form')!).toHaveClass('ant-form-hide-required-mark');
|
||||
});
|
||||
|
||||
it('form should support disabled', () => {
|
||||
@ -1137,7 +1143,7 @@ describe('Form', () => {
|
||||
});
|
||||
|
||||
it('_internalItemRender api test', () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
<Form>
|
||||
<Form.Item
|
||||
name="light"
|
||||
@ -1159,7 +1165,7 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole('heading')).toHaveTextContent(/warning title/i);
|
||||
expect(container.querySelector('h1')!).toHaveTextContent(/warning title/i);
|
||||
});
|
||||
|
||||
it('Form Item element id will auto add form_item prefix if form name is empty and item name is in the black list', async () => {
|
||||
@ -1198,16 +1204,17 @@ describe('Form', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const { rerender } = render(<Demo />);
|
||||
const { container, rerender } = render(<Demo />);
|
||||
expect(mockFn).toHaveBeenCalled();
|
||||
expect((Util.getFieldId as () => string)()).toBe(itemName);
|
||||
|
||||
// make sure input id is parentNode
|
||||
expect(screen.getByLabelText(itemName)).toHaveAccessibleName(itemName);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(screen.getByRole('button')).toHaveTextContent('show');
|
||||
expect(container.querySelector('button')!).toHaveTextContent('show');
|
||||
|
||||
mockFn.mockRestore();
|
||||
|
||||
@ -1217,7 +1224,7 @@ describe('Form', () => {
|
||||
|
||||
describe('tooltip', () => {
|
||||
it('ReactNode', async () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
<Form>
|
||||
<Form.Item label="light" tooltip={<span>Bamboo</span>}>
|
||||
<Input />
|
||||
@ -1225,21 +1232,14 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await userEvent.hover(screen.getByRole('img', { name: 'question-circle' }));
|
||||
await expect(screen.findByRole('tooltip')).resolves.toMatchInlineSnapshot(`
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
<span>
|
||||
Bamboo
|
||||
</span>
|
||||
</div>
|
||||
`);
|
||||
fireEvent.mouseEnter(container.querySelector('.anticon-question-circle')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(container.querySelector('.ant-tooltip-inner')).toHaveTextContent('Bamboo');
|
||||
});
|
||||
|
||||
it('config tooltip should show when hover on icon', async () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
<Form>
|
||||
<Form.Item label="light" tooltip={{ title: 'Bamboo' }}>
|
||||
<Input />
|
||||
@ -1247,9 +1247,10 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await userEvent.hover(screen.getByRole('img', { name: 'question-circle' }));
|
||||
fireEvent.mouseEnter(container.querySelector('.anticon-question-circle')!);
|
||||
await waitFakeTimer();
|
||||
|
||||
await expect(screen.findByRole('tooltip')).resolves.toHaveTextContent('Bamboo');
|
||||
expect(container.querySelector('.ant-tooltip-inner')).toHaveTextContent('Bamboo');
|
||||
});
|
||||
});
|
||||
|
||||
@ -1269,20 +1270,17 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await userEvent.type(screen.getByLabelText('test'), 'test');
|
||||
await userEvent.clear(screen.getByLabelText('test'));
|
||||
await changeValue(0, 'test');
|
||||
await changeValue(0, '');
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
expect(container.querySelectorAll('.ant-form-item-with-help').length).toBeTruthy();
|
||||
expect(container.querySelectorAll('.ant-form-item-has-warning').length).toBeTruthy();
|
||||
expect(container.querySelector('.ant-form-item-with-help')).toBeTruthy();
|
||||
expect(container.querySelector('.ant-form-item-has-warning')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('not warning when remove on validate', async () => {
|
||||
jest.useFakeTimers();
|
||||
let rejectFn: (reason?: any) => void = jest.fn();
|
||||
|
||||
const { container, unmount } = render(
|
||||
const { unmount } = render(
|
||||
<Form>
|
||||
<Form.Item>
|
||||
<Form.Item
|
||||
@ -1304,7 +1302,7 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await change(container, 0, '', true);
|
||||
await changeValue(0, '');
|
||||
|
||||
unmount();
|
||||
|
||||
@ -1312,8 +1310,6 @@ describe('Form', () => {
|
||||
rejectFn(new Error('delay failed'));
|
||||
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe('form colon', () => {
|
||||
@ -1437,8 +1433,6 @@ describe('Form', () => {
|
||||
});
|
||||
|
||||
it('Form.Item.useStatus should work', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const {
|
||||
Item: { useStatus },
|
||||
} = Form;
|
||||
@ -1495,9 +1489,6 @@ describe('Form', () => {
|
||||
expect(container.querySelector('.custom-input-required')?.classList).toContain(
|
||||
'custom-input-status-error',
|
||||
);
|
||||
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('item customize margin', async () => {
|
||||
@ -1513,9 +1504,8 @@ describe('Form', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
fireEvent.change(container.querySelector('input')!, { target: { value: '' } });
|
||||
await changeValue(0, '');
|
||||
|
||||
await sleep(0);
|
||||
computeSpy.mockRestore();
|
||||
|
||||
expect(container.querySelector('.ant-form-item-margin-offset')).toHaveStyle({
|
||||
|
@ -143,4 +143,44 @@ describe('Grid', () => {
|
||||
xxl: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should align by responsive align prop', () => {
|
||||
const matchMediaSpy = jest.spyOn(window, 'matchMedia');
|
||||
matchMediaSpy.mockImplementation(
|
||||
query =>
|
||||
({
|
||||
addListener: (cb: (e: { matches: boolean }) => void) => {
|
||||
cb({ matches: query === '(max-width: 575px)' });
|
||||
},
|
||||
removeListener: jest.fn(),
|
||||
matches: query === '(max-width: 575px)',
|
||||
} as any),
|
||||
);
|
||||
const { container } = render(<Row align="middle" />);
|
||||
expect(container.innerHTML).toContain('ant-row-middle');
|
||||
const { container: container2 } = render(<Row align={{ xs: 'middle' }} />);
|
||||
expect(container2.innerHTML).toContain('ant-row-middle');
|
||||
const { container: container3 } = render(<Row align={{ lg: 'middle' }} />);
|
||||
expect(container3.innerHTML).not.toContain('ant-row-middle');
|
||||
});
|
||||
|
||||
it('should justify by responsive justify prop', () => {
|
||||
const matchMediaSpy = jest.spyOn(window, 'matchMedia');
|
||||
matchMediaSpy.mockImplementation(
|
||||
query =>
|
||||
({
|
||||
addListener: (cb: (e: { matches: boolean }) => void) => {
|
||||
cb({ matches: query === '(max-width: 575px)' });
|
||||
},
|
||||
removeListener: jest.fn(),
|
||||
matches: query === '(max-width: 575px)',
|
||||
} as any),
|
||||
);
|
||||
const { container } = render(<Row justify="center" />);
|
||||
expect(container.innerHTML).toContain('ant-row-center');
|
||||
const { container: container2 } = render(<Row justify={{ xs: 'center' }} />);
|
||||
expect(container2.innerHTML).toContain('ant-row-center');
|
||||
const { container: container3 } = render(<Row justify={{ lg: 'center' }} />);
|
||||
expect(container3.innerHTML).not.toContain('ant-row-center');
|
||||
});
|
||||
});
|
||||
|
@ -44,9 +44,9 @@ If the Ant Design grid layout component does not meet your needs, you can use th
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| align | Vertical alignment | `top` \| `middle` \| `bottom` | `top` | |
|
||||
| align | Vertical alignment | `top` \| `middle` \| `bottom` \| `stretch` \| `{[key in 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'xxl']: 'top' \| 'middle' \| 'bottom' \| 'stretch'}` | `top` | object: 4.24.0 |
|
||||
| gutter | Spacing between grids, could be a number or a object like { xs: 8, sm: 16, md: 24}. Or you can use array to make horizontal and vertical spacing work at the same time `[horizontal, vertical]` | number \| object \| array | 0 | |
|
||||
| justify | Horizontal arrangement | `start` \| `end` \| `center` \| `space-around` \| `space-between` \| `space-evenly` | `start` | |
|
||||
| justify | Horizontal arrangement | `start` \| `end` \| `center` \| `space-around` \| `space-between` \| `space-evenly` \| `{[key in 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'xxl']: 'start' \| 'end' \| 'center' \| 'space-around' \| 'space-between' \| 'space-evenly'}` | `start` | object: 4.24.0 |
|
||||
| wrap | Auto wrap line | boolean | true | 4.8.0 |
|
||||
|
||||
### Col
|
||||
|
@ -43,9 +43,9 @@ Ant Design 的布局组件若不能满足你的需求,你也可以直接使用
|
||||
|
||||
| 成员 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| align | 垂直对齐方式 | `top` \| `middle` \| `bottom` | `top` | |
|
||||
| align | 垂直对齐方式 | `top` \| `middle` \| `bottom` \| `stretch` \| `{[key in 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'xxl']: 'top' \| 'middle' \| 'bottom' \| 'stretch'}` | `top` | object: 4.24.0 |
|
||||
| gutter | 栅格间隔,可以写成像素值或支持响应式的对象写法来设置水平间隔 { xs: 8, sm: 16, md: 24}。或者使用数组形式同时设置 `[水平间距, 垂直间距]` | number \| object \| array | 0 | |
|
||||
| justify | 水平排列方式 | `start` \| `end` \| `center` \| `space-around` \| `space-between` \| `space-evenly` | `start` | |
|
||||
| justify | 水平排列方式 | `start` \| `end` \| `center` \| `space-around` \| `space-between` \| `space-evenly` \| `{[key in 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'xxl']: 'start' \| 'end' \| 'center' \| 'space-around' \| 'space-between' \| 'space-evenly'}` | `start` | object: 4.24.0 |
|
||||
| wrap | 是否自动换行 | boolean | true | 4.8.0 |
|
||||
|
||||
### Col
|
||||
|
@ -11,16 +11,50 @@ import { useRowStyle } from './style';
|
||||
const RowAligns = tuple('top', 'middle', 'bottom', 'stretch');
|
||||
const RowJustify = tuple('start', 'end', 'center', 'space-around', 'space-between', 'space-evenly');
|
||||
|
||||
type Responsive = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';
|
||||
type ResponsiveLike<T> = {
|
||||
[key in Responsive]?: T;
|
||||
};
|
||||
|
||||
type Gap = number | undefined;
|
||||
export type Gutter = number | undefined | Partial<Record<Breakpoint, number>>;
|
||||
|
||||
type ResponsiveAligns = ResponsiveLike<typeof RowAligns[number]>;
|
||||
type ResponsiveJustify = ResponsiveLike<typeof RowJustify[number]>;
|
||||
export interface RowProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
gutter?: Gutter | [Gutter, Gutter];
|
||||
align?: typeof RowAligns[number];
|
||||
justify?: typeof RowJustify[number];
|
||||
align?: typeof RowAligns[number] | ResponsiveAligns;
|
||||
justify?: typeof RowJustify[number] | ResponsiveJustify;
|
||||
prefixCls?: string;
|
||||
wrap?: boolean;
|
||||
}
|
||||
|
||||
function useMergePropByScreen(oriProp: RowProps['align'] | RowProps['justify'], screen: ScreenMap) {
|
||||
const [prop, setProp] = React.useState(typeof oriProp === 'string' ? oriProp : '');
|
||||
|
||||
const clacMergeAlignOrJustify = () => {
|
||||
if (typeof oriProp !== 'object') {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < responsiveArray.length; i++) {
|
||||
const breakpoint: Breakpoint = responsiveArray[i];
|
||||
// if do not match, do nothing
|
||||
if (!screen[breakpoint]) continue;
|
||||
const curVal = oriProp[breakpoint];
|
||||
if (curVal !== undefined) {
|
||||
setProp(curVal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
clacMergeAlignOrJustify();
|
||||
}, [JSON.stringify(oriProp), screen]);
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
@ -44,6 +78,20 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
|
||||
xl: true,
|
||||
xxl: true,
|
||||
});
|
||||
// to save screens info when responsiveObserve callback had been call
|
||||
const [curScreens, setCurScreens] = React.useState<ScreenMap>({
|
||||
xs: false,
|
||||
sm: false,
|
||||
md: false,
|
||||
lg: false,
|
||||
xl: false,
|
||||
xxl: false,
|
||||
});
|
||||
|
||||
// ================================== calc reponsive data ==================================
|
||||
const mergeAlign = useMergePropByScreen(align, curScreens);
|
||||
|
||||
const mergeJustify = useMergePropByScreen(justify, curScreens);
|
||||
|
||||
const supportFlexGap = useFlexGapSupport();
|
||||
|
||||
@ -52,6 +100,7 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
|
||||
// ================================== Effect ==================================
|
||||
React.useEffect(() => {
|
||||
const token = ResponsiveObserve.subscribe(screen => {
|
||||
setCurScreens(screen);
|
||||
const currentGutter = gutterRef.current || 0;
|
||||
if (
|
||||
(!Array.isArray(currentGutter) && typeof currentGutter === 'object') ||
|
||||
@ -91,8 +140,8 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}-no-wrap`]: wrap === false,
|
||||
[`${prefixCls}-${justify}`]: justify,
|
||||
[`${prefixCls}-${align}`]: align,
|
||||
[`${prefixCls}-${mergeJustify}`]: mergeJustify,
|
||||
[`${prefixCls}-${mergeAlign}`]: mergeAlign,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
},
|
||||
className,
|
||||
|
@ -11,6 +11,7 @@ import DisabledContext from '../config-provider/DisabledContext';
|
||||
import type { SizeType } from '../config-provider/SizeContext';
|
||||
import SizeContext from '../config-provider/SizeContext';
|
||||
import { FormItemInputContext, NoFormStyle } from '../form/context';
|
||||
import { useCompactItemContext } from '../space/Compact';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import type { InputStatus } from '../_util/statusUtils';
|
||||
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
||||
@ -57,6 +58,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
|
||||
// Style
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
|
||||
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
|
||||
let upIcon = <UpOutlined className={`${prefixCls}-handler-up-inner`} />;
|
||||
let downIcon = <DownOutlined className={`${prefixCls}-handler-down-inner`} />;
|
||||
const controlsTemp = typeof controls === 'boolean' ? controls : undefined;
|
||||
@ -84,7 +86,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
|
||||
} = useContext(FormItemInputContext);
|
||||
const mergedStatus = getMergedStatus(contextStatus, customStatus);
|
||||
|
||||
const mergeSize = customizeSize || size;
|
||||
const mergeSize = compactSize || customizeSize || size;
|
||||
// ===================== Disabled =====================
|
||||
const disabled = React.useContext(DisabledContext);
|
||||
const mergedDisabled = customDisabled ?? disabled;
|
||||
@ -98,6 +100,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
|
||||
[`${prefixCls}-in-form-item`]: isFormItemInput,
|
||||
},
|
||||
getStatusClassNames(prefixCls, mergedStatus),
|
||||
compactItemClassnames,
|
||||
hashId,
|
||||
className,
|
||||
);
|
||||
|
@ -10,6 +10,7 @@ import DisabledContext from '../config-provider/DisabledContext';
|
||||
import type { SizeType } from '../config-provider/SizeContext';
|
||||
import SizeContext from '../config-provider/SizeContext';
|
||||
import { FormItemInputContext, NoFormStyle } from '../form/context';
|
||||
import { NoCompactStyle, useCompactItemContext } from '../space/Compact';
|
||||
import type { InputStatus } from '../_util/statusUtils';
|
||||
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
||||
import warning from '../_util/warning';
|
||||
@ -140,6 +141,7 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
|
||||
allowClear,
|
||||
addonAfter,
|
||||
addonBefore,
|
||||
className,
|
||||
onChange,
|
||||
...rest
|
||||
} = props;
|
||||
@ -151,9 +153,12 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
|
||||
// Style
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
|
||||
// ===================== Compact Item =====================
|
||||
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
|
||||
|
||||
// ===================== Size =====================
|
||||
const size = React.useContext(SizeContext);
|
||||
const mergedSize = customSize || size;
|
||||
const mergedSize = compactSize || customSize || size;
|
||||
|
||||
// ===================== Disabled =====================
|
||||
const disabled = React.useContext(DisabledContext);
|
||||
@ -221,19 +226,24 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
|
||||
onFocus={handleFocus}
|
||||
suffix={suffixNode}
|
||||
allowClear={mergedAllowClear}
|
||||
className={classNames(className, compactItemClassnames)}
|
||||
onChange={handleChange}
|
||||
addonAfter={
|
||||
addonAfter && (
|
||||
<NoFormStyle override status>
|
||||
{addonAfter}
|
||||
</NoFormStyle>
|
||||
<NoCompactStyle>
|
||||
<NoFormStyle override status>
|
||||
{addonAfter}
|
||||
</NoFormStyle>
|
||||
</NoCompactStyle>
|
||||
)
|
||||
}
|
||||
addonBefore={
|
||||
addonBefore && (
|
||||
<NoFormStyle override status>
|
||||
{addonBefore}
|
||||
</NoFormStyle>
|
||||
<NoCompactStyle>
|
||||
<NoFormStyle override status>
|
||||
{addonBefore}
|
||||
</NoFormStyle>
|
||||
</NoCompactStyle>
|
||||
)
|
||||
}
|
||||
inputClassName={classNames(
|
||||
|
@ -5,6 +5,7 @@ import * as React from 'react';
|
||||
import Button from '../button';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import SizeContext from '../config-provider/SizeContext';
|
||||
import { useCompactItemContext } from '../space/Compact';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import type { InputProps, InputRef } from './Input';
|
||||
import Input from './Input';
|
||||
@ -44,7 +45,11 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
|
||||
const contextSize = React.useContext(SizeContext);
|
||||
const composedRef = React.useRef<boolean>(false);
|
||||
|
||||
const size = customizeSize || contextSize;
|
||||
const prefixCls = getPrefixCls('input-search', customizePrefixCls);
|
||||
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
|
||||
const { compactSize } = useCompactItemContext(prefixCls, direction);
|
||||
|
||||
const size = compactSize || customizeSize || contextSize;
|
||||
|
||||
const inputRef = React.useRef<InputRef>(null);
|
||||
|
||||
@ -76,9 +81,6 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
|
||||
onSearch(e);
|
||||
};
|
||||
|
||||
const prefixCls = getPrefixCls('input-search', customizePrefixCls);
|
||||
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
|
||||
|
||||
const searchIcon = typeof enterButton === 'boolean' ? <SearchOutlined /> : null;
|
||||
const btnClassName = `${prefixCls}-button`;
|
||||
|
||||
|
@ -5081,6 +5081,170 @@ exports[`renders ./components/input/demo/borderless-debug.md extend context corr
|
||||
RMB
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/input/demo/debug-addon.md extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
Input addon Button:
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
<span
|
||||
class="ant-input-group-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-input-wrapper ant-input-group"
|
||||
>
|
||||
<input
|
||||
class="ant-input"
|
||||
type="text"
|
||||
value="mysite"
|
||||
/>
|
||||
<span
|
||||
class="ant-input-group-addon"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Submit
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
<span
|
||||
class="ant-input-group-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-input-wrapper ant-input-group"
|
||||
>
|
||||
<input
|
||||
class="ant-input"
|
||||
type="text"
|
||||
value="mysite"
|
||||
/>
|
||||
<span
|
||||
class="ant-input-group-addon"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Submit
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
<br />
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
<br />
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
Input addon Button icon:
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span
|
||||
class="ant-input-group-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-input-wrapper ant-input-group"
|
||||
>
|
||||
<input
|
||||
class="ant-input"
|
||||
type="text"
|
||||
value="mysite"
|
||||
/>
|
||||
<span
|
||||
class="ant-input-group-addon"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="setting"
|
||||
class="anticon anticon-setting"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="setting"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -1300,6 +1300,170 @@ exports[`renders ./components/input/demo/borderless-debug.md correctly 1`] = `
|
||||
RMB
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/input/demo/debug-addon.md correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
Input addon Button:
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
<span
|
||||
class="ant-input-group-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-input-wrapper ant-input-group"
|
||||
>
|
||||
<input
|
||||
class="ant-input"
|
||||
type="text"
|
||||
value="mysite"
|
||||
/>
|
||||
<span
|
||||
class="ant-input-group-addon"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Submit
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
<span
|
||||
class="ant-input-group-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-input-wrapper ant-input-group"
|
||||
>
|
||||
<input
|
||||
class="ant-input"
|
||||
type="text"
|
||||
value="mysite"
|
||||
/>
|
||||
<span
|
||||
class="ant-input-group-addon"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Submit
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
<br />
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
<br />
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-bottom:8px"
|
||||
>
|
||||
Input addon Button icon:
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<span
|
||||
class="ant-input-group-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-input-wrapper ant-input-group"
|
||||
>
|
||||
<input
|
||||
class="ant-input"
|
||||
type="text"
|
||||
value="mysite"
|
||||
/>
|
||||
<span
|
||||
class="ant-input-group-addon"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="setting"
|
||||
class="anticon anticon-setting"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="setting"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
---
|
||||
order: 98
|
||||
title:
|
||||
zh-CN: Borderless Debug
|
||||
en-US: Borderless Debug
|
||||
zh-CN: Style Debug
|
||||
en-US: Style Debug
|
||||
debug: true
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
Buggy!
|
||||
Buggy! 测试一些踩过的样式坑。
|
||||
|
||||
## en-US
|
||||
|
||||
@ -29,6 +29,7 @@ const App: React.FC = () => (
|
||||
<Input placeholder="Unbordered" bordered={false} allowClear />
|
||||
<Input prefix="¥" suffix="RMB" bordered={false} />
|
||||
<Input prefix="¥" suffix="RMB" disabled bordered={false} />
|
||||
<TextArea allowClear style={{ border: '2px solid #000' }} />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
42
components/input/demo/debug-addon.md
Normal file
42
components/input/demo/debug-addon.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
order: 100
|
||||
title:
|
||||
zh-CN: debug 前置/后置标签
|
||||
en-US: debug Pre / Post tab
|
||||
debug: true
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
一些特殊的前置后置标签。
|
||||
|
||||
## en-US
|
||||
|
||||
Some special pre & post tabs example.
|
||||
|
||||
```tsx
|
||||
import { SettingOutlined } from '@ant-design/icons';
|
||||
import { Input, Space, Button } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space direction="vertical">
|
||||
Input addon Button:
|
||||
<Input addonAfter={<Button type="primary">Submit</Button>} defaultValue="mysite" />
|
||||
<Input addonAfter={<Button>Submit</Button>} defaultValue="mysite" />
|
||||
<br />
|
||||
<br />
|
||||
Input addon Button icon:
|
||||
<Input
|
||||
addonAfter={
|
||||
<Button>
|
||||
<SettingOutlined />
|
||||
</Button>
|
||||
}
|
||||
defaultValue="mysite"
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
||||
```
|
@ -1,9 +1,12 @@
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
import Pagination from 'rc-pagination/lib/locale/it_IT';
|
||||
import Calendar from '../calendar/locale/it_IT';
|
||||
import DatePicker from '../date-picker/locale/it_IT';
|
||||
import type { Locale } from '../locale-provider';
|
||||
import TimePicker from '../time-picker/locale/it_IT';
|
||||
|
||||
const typeTemplate = ' ${label} non è un ${type} valido';
|
||||
|
||||
const localeValues: Locale = {
|
||||
locale: 'it',
|
||||
Pagination,
|
||||
@ -17,11 +20,17 @@ const localeValues: Locale = {
|
||||
filterTitle: 'Menù Filtro',
|
||||
filterConfirm: 'OK',
|
||||
filterReset: 'Reset',
|
||||
selectNone: 'Deseleziona tutto',
|
||||
selectionAll: 'Seleziona tutto',
|
||||
filterEmptyText: 'Senza filtri',
|
||||
filterCheckall: 'Seleziona tutti',
|
||||
filterSearchPlaceholder: 'Cerca nei filtri',
|
||||
emptyText: 'Senza dati',
|
||||
selectAll: 'Seleziona pagina corrente',
|
||||
selectInvert: 'Inverti selezione nella pagina corrente',
|
||||
selectNone: 'Deseleziona tutto',
|
||||
selectionAll: 'Seleziona tutto',
|
||||
sortTitle: 'Ordina',
|
||||
expand: 'Espandi riga',
|
||||
collapse: 'Comprimi riga ',
|
||||
triggerDesc: 'Clicca per ordinare in modo discendente',
|
||||
triggerAsc: 'Clicca per ordinare in modo ascendente',
|
||||
cancelSort: "Clicca per eliminare l'ordinamento",
|
||||
@ -36,16 +45,23 @@ const localeValues: Locale = {
|
||||
cancelText: 'Annulla',
|
||||
},
|
||||
Transfer: {
|
||||
titles: ['', ''],
|
||||
searchPlaceholder: 'Cerca qui',
|
||||
itemUnit: 'elemento',
|
||||
itemsUnit: 'elementi',
|
||||
remove: 'Elimina',
|
||||
selectCurrent: 'Seleziona la pagina corrente',
|
||||
removeCurrent: 'Rimuovi la pagina corrente',
|
||||
selectAll: 'Seleziona tutti i dati',
|
||||
removeAll: 'Rimuovi tutti i dati',
|
||||
selectInvert: 'Inverti la pagina corrente',
|
||||
},
|
||||
Upload: {
|
||||
uploading: 'Caricamento...',
|
||||
removeFile: 'Rimuovi il file',
|
||||
uploadError: 'Errore di caricamento',
|
||||
previewFile: 'Anteprima file',
|
||||
downloadFile: 'Download file',
|
||||
downloadFile: 'Scarica file',
|
||||
},
|
||||
Empty: {
|
||||
description: 'Nessun dato',
|
||||
@ -59,6 +75,62 @@ const localeValues: Locale = {
|
||||
copied: 'copia effettuata',
|
||||
expand: 'espandi',
|
||||
},
|
||||
PageHeader: {
|
||||
back: 'Torna',
|
||||
},
|
||||
Form: {
|
||||
optional: '(opzionale)',
|
||||
defaultValidateMessages: {
|
||||
default: 'Errore di convalida del campo ${label}',
|
||||
required: 'Si prega di inserire ${label}',
|
||||
enum: '${label} deve essere uno di [${enum}]',
|
||||
whitespace: '${label} non può essere un carattere vuoto',
|
||||
date: {
|
||||
format: 'Il formato della data ${label} non è valido',
|
||||
parse: '${label} non può essere convertito in una data',
|
||||
invalid: '${label} non è una data valida',
|
||||
},
|
||||
types: {
|
||||
string: typeTemplate,
|
||||
method: typeTemplate,
|
||||
array: typeTemplate,
|
||||
object: typeTemplate,
|
||||
number: typeTemplate,
|
||||
date: typeTemplate,
|
||||
boolean: typeTemplate,
|
||||
integer: typeTemplate,
|
||||
float: typeTemplate,
|
||||
regexp: typeTemplate,
|
||||
email: typeTemplate,
|
||||
url: typeTemplate,
|
||||
hex: typeTemplate,
|
||||
},
|
||||
string: {
|
||||
len: '${label} deve avere ${len} caratteri',
|
||||
min: '${label} deve contenere almeno ${min} caratteri',
|
||||
max: '${label} deve contenere fino a ${max} caratteri',
|
||||
range: '${label} deve contenere tra ${min}-${max} caratteri',
|
||||
},
|
||||
number: {
|
||||
len: '${label} deve essere uguale a ${len}',
|
||||
min: '${label} valore minimo è ${min}',
|
||||
max: '${label} valor e massimo è ${max}',
|
||||
range: '${label} deve essere compreso tra ${min}-${max}',
|
||||
},
|
||||
array: {
|
||||
len: 'Deve essere ${len} ${label}',
|
||||
min: 'Almeno ${min} ${label}',
|
||||
max: 'Massimo ${max} ${label}',
|
||||
range: 'Il totale di ${label} deve essere compreso tra ${min}-${max}',
|
||||
},
|
||||
pattern: {
|
||||
mismatch: '${label} non corrisponde al modello ${pattern}',
|
||||
},
|
||||
},
|
||||
},
|
||||
Image: {
|
||||
preview: 'Anteprima',
|
||||
},
|
||||
};
|
||||
|
||||
export default localeValues;
|
||||
|
@ -1,6 +1,9 @@
|
||||
import classNames from 'classnames';
|
||||
import RcMentions from 'rc-mentions';
|
||||
import type { MentionsProps as RcMentionsProps } from 'rc-mentions/lib/Mentions';
|
||||
import type {
|
||||
MentionsProps as RcMentionsProps,
|
||||
MentionsRef as RcMentionsRef,
|
||||
} from 'rc-mentions/lib/Mentions';
|
||||
import { composeRef } from 'rc-util/lib/ref';
|
||||
// eslint-disable-next-line import/no-named-as-default
|
||||
import * as React from 'react';
|
||||
@ -34,6 +37,8 @@ export interface MentionProps extends RcMentionsProps {
|
||||
popupClassName?: string;
|
||||
}
|
||||
|
||||
export interface MentionsRef extends RcMentionsRef {}
|
||||
|
||||
export interface MentionState {
|
||||
focused: boolean;
|
||||
}
|
||||
@ -49,13 +54,13 @@ interface MentionsEntity {
|
||||
}
|
||||
|
||||
interface CompoundedComponent
|
||||
extends React.ForwardRefExoticComponent<MentionProps & React.RefAttributes<HTMLElement>> {
|
||||
extends React.ForwardRefExoticComponent<MentionProps & React.RefAttributes<MentionsRef>> {
|
||||
Option: typeof Option;
|
||||
_InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel;
|
||||
getMentions: (value: string, config?: MentionsConfig) => MentionsEntity[];
|
||||
}
|
||||
|
||||
const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> = (
|
||||
const InternalMentions: React.ForwardRefRenderFunction<MentionsRef, MentionProps> = (
|
||||
{
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
@ -71,7 +76,7 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
|
||||
ref,
|
||||
) => {
|
||||
const [focused, setFocused] = React.useState(false);
|
||||
const innerRef = React.useRef<HTMLElement>();
|
||||
const innerRef = React.useRef<MentionsRef>();
|
||||
const mergedRef = composeRef(ref, innerRef);
|
||||
const { getPrefixCls, renderEmpty, direction } = React.useContext(ConfigContext);
|
||||
const {
|
||||
@ -176,7 +181,9 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
|
||||
return wrapSSR(mentions);
|
||||
};
|
||||
|
||||
const Mentions = React.forwardRef<unknown, MentionProps>(InternalMentions) as CompoundedComponent;
|
||||
const Mentions = React.forwardRef<MentionsRef, MentionProps>(
|
||||
InternalMentions,
|
||||
) as CompoundedComponent;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Mentions.displayName = 'Mentions';
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import getIcons from './utils/iconUtil';
|
||||
import useStyle from './style';
|
||||
import genPurePanel from '../_util/PurePanel';
|
||||
import warning from '../_util/warning';
|
||||
import { useCompactItemContext } from '../space/Compact';
|
||||
|
||||
type RawValue = string | number;
|
||||
|
||||
@ -96,6 +97,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
|
||||
|
||||
const prefixCls = getPrefixCls('select', customizePrefixCls);
|
||||
const rootPrefixCls = getPrefixCls();
|
||||
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
|
||||
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
|
||||
@ -156,7 +158,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
|
||||
hashId,
|
||||
);
|
||||
|
||||
const mergedSize = customizeSize || size;
|
||||
const mergedSize = compactSize || customizeSize || size;
|
||||
|
||||
// ===================== Disabled =====================
|
||||
const disabled = React.useContext(DisabledContext);
|
||||
@ -171,6 +173,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
|
||||
[`${prefixCls}-in-form-item`]: isFormItemInput,
|
||||
},
|
||||
getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
|
||||
compactItemClassnames,
|
||||
className,
|
||||
hashId,
|
||||
);
|
||||
|
123
components/space/Compact.tsx
Normal file
123
components/space/Compact.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import classNames from 'classnames';
|
||||
import toArray from 'rc-util/lib/Children/toArray';
|
||||
import * as React from 'react';
|
||||
|
||||
import type { DirectionType } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import type { SizeType } from '../config-provider/SizeContext';
|
||||
|
||||
export interface SpaceCompactItemContextType {
|
||||
compactSize?: SizeType;
|
||||
compactDirection?: 'horizontal' | 'vertical';
|
||||
isFirstItem?: boolean;
|
||||
isLastItem?: boolean;
|
||||
}
|
||||
|
||||
export const SpaceCompactItemContext = React.createContext<SpaceCompactItemContextType | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
export const useCompactItemContext = (prefixCls: string, direction: DirectionType) => {
|
||||
const compactItemContext = React.useContext(SpaceCompactItemContext);
|
||||
|
||||
const compactItemClassnames = React.useMemo(() => {
|
||||
if (!compactItemContext) return '';
|
||||
|
||||
const { compactDirection, isFirstItem, isLastItem } = compactItemContext;
|
||||
const separator = compactDirection === 'vertical' ? '-vertical-' : '-';
|
||||
|
||||
return classNames({
|
||||
[`${prefixCls}-compact${separator}item`]: true,
|
||||
[`${prefixCls}-compact${separator}first-item`]: isFirstItem,
|
||||
[`${prefixCls}-compact${separator}last-item`]: isLastItem,
|
||||
[`${prefixCls}-compact${separator}item-rtl`]: direction === 'rtl',
|
||||
});
|
||||
}, [prefixCls, direction, compactItemContext]);
|
||||
|
||||
return {
|
||||
compactSize: compactItemContext?.compactSize,
|
||||
compactDirection: compactItemContext?.compactDirection,
|
||||
compactItemClassnames,
|
||||
};
|
||||
};
|
||||
|
||||
export const NoCompactStyle: React.FC<React.PropsWithChildren<{}>> = ({ children }) => (
|
||||
<SpaceCompactItemContext.Provider value={null}>{children}</SpaceCompactItemContext.Provider>
|
||||
);
|
||||
|
||||
export interface SpaceCompactProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
prefixCls?: string;
|
||||
size?: SizeType;
|
||||
direction?: 'horizontal' | 'vertical';
|
||||
block?: boolean;
|
||||
}
|
||||
|
||||
const CompactItem: React.FC<React.PropsWithChildren<SpaceCompactItemContextType>> = ({
|
||||
children,
|
||||
...otherProps
|
||||
}) => (
|
||||
<SpaceCompactItemContext.Provider value={otherProps}>{children}</SpaceCompactItemContext.Provider>
|
||||
);
|
||||
|
||||
const Compact: React.FC<SpaceCompactProps> = props => {
|
||||
const { getPrefixCls, direction: directionConfig } = React.useContext(ConfigContext);
|
||||
|
||||
const {
|
||||
size = 'middle',
|
||||
direction,
|
||||
block,
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
children,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const prefixCls = getPrefixCls('space-compact', customizePrefixCls);
|
||||
const clx = classNames(
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}-rtl`]: directionConfig === 'rtl',
|
||||
[`${prefixCls}-block`]: block,
|
||||
[`${prefixCls}-vertical`]: direction === 'vertical',
|
||||
},
|
||||
className,
|
||||
);
|
||||
|
||||
const compactItemContext = React.useContext(SpaceCompactItemContext);
|
||||
|
||||
const childNodes = toArray(children);
|
||||
const nodes = React.useMemo(
|
||||
() =>
|
||||
childNodes.map((child, i) => {
|
||||
const key = (child && child.key) || `${prefixCls}-item-${i}`;
|
||||
|
||||
return (
|
||||
<CompactItem
|
||||
key={key}
|
||||
compactSize={size}
|
||||
compactDirection={direction}
|
||||
isFirstItem={i === 0 && (!compactItemContext || compactItemContext?.isFirstItem)}
|
||||
isLastItem={
|
||||
i === childNodes.length - 1 && (!compactItemContext || compactItemContext?.isLastItem)
|
||||
}
|
||||
>
|
||||
{child}
|
||||
</CompactItem>
|
||||
);
|
||||
}),
|
||||
[size, childNodes, compactItemContext],
|
||||
);
|
||||
|
||||
// =========================== Render ===========================
|
||||
if (childNodes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clx} {...restProps}>
|
||||
{nodes}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Compact;
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,18 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Space.Compact rtl render component should be rendered correctly in RTL direction 1`] = `null`;
|
||||
|
||||
exports[`Space.Compact rtl render component should be rendered correctly in RTL direction 2`] = `
|
||||
<div
|
||||
class="ant-space-compact ant-space-compact-rtl"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-rtl ant-btn-compact-item ant-btn-compact-first-item ant-btn-compact-last-item ant-btn-compact-item-rtl"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Submit
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
185
components/space/__tests__/space-compact.test.tsx
Normal file
185
components/space/__tests__/space-compact.test.tsx
Normal file
@ -0,0 +1,185 @@
|
||||
/* eslint-disable no-console */
|
||||
import React from 'react';
|
||||
import Space from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { render } from '../../../tests/utils';
|
||||
import Input from '../../input';
|
||||
import Button from '../../button';
|
||||
import AutoComplete from '../../auto-complete';
|
||||
import Cascader from '../../cascader';
|
||||
import DatePicker from '../../date-picker';
|
||||
import Select from '../../select';
|
||||
import TimePicker from '../../time-picker';
|
||||
import TreeSelect from '../../tree-select';
|
||||
|
||||
describe('Space.Compact', () => {
|
||||
mountTest(Space.Compact);
|
||||
mountTest(() => (
|
||||
<Space.Compact>
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>
|
||||
));
|
||||
|
||||
rtlTest(Space.Compact);
|
||||
rtlTest(() => (
|
||||
<Space.Compact>
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>
|
||||
));
|
||||
|
||||
it('should render width empty children', () => {
|
||||
const { container } = render(<Space.Compact />);
|
||||
|
||||
expect(container.children.length).toBe(0);
|
||||
});
|
||||
|
||||
it('block className', () => {
|
||||
const { container } = render(
|
||||
<Space.Compact block>
|
||||
<Input defaultValue="https://ant.design" />
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>,
|
||||
);
|
||||
expect(
|
||||
container.querySelector('.ant-space-compact')?.classList.contains('ant-space-compact-block'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('compact-item className', () => {
|
||||
const { container } = render(
|
||||
<Space.Compact>
|
||||
<Input defaultValue="https://ant.design" />
|
||||
<Input.Search />
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>,
|
||||
);
|
||||
expect(
|
||||
container.querySelector('.ant-input')?.classList.contains('ant-input-compact-item'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
container.querySelector('.ant-input-search')?.classList.contains('ant-input-compact-item'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
container.querySelector('.ant-input')?.classList.contains('ant-input-compact-first-item'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
container
|
||||
.querySelector('.ant-btn-compact-item')
|
||||
?.classList.contains('ant-btn-compact-last-item'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
name: 'Button',
|
||||
component: Button,
|
||||
targetCls: 'ant-btn',
|
||||
expectClsPrefix: 'ant-btn',
|
||||
},
|
||||
{
|
||||
name: 'AutoComplete',
|
||||
component: AutoComplete,
|
||||
targetCls: 'ant-select',
|
||||
expectClsPrefix: 'ant-select',
|
||||
},
|
||||
{
|
||||
name: 'Cascader',
|
||||
component: Cascader,
|
||||
targetCls: 'ant-cascader',
|
||||
expectClsPrefix: 'ant-select',
|
||||
},
|
||||
{
|
||||
name: 'DatePicker',
|
||||
component: DatePicker,
|
||||
targetCls: 'ant-picker',
|
||||
expectClsPrefix: 'ant-picker',
|
||||
},
|
||||
{
|
||||
name: 'Input',
|
||||
component: Input,
|
||||
targetCls: 'ant-input',
|
||||
expectClsPrefix: 'ant-input',
|
||||
},
|
||||
{
|
||||
name: 'Input.Search',
|
||||
component: Input.Search,
|
||||
targetCls: 'ant-input-search',
|
||||
expectClsPrefix: 'ant-input',
|
||||
},
|
||||
{
|
||||
name: 'Select',
|
||||
component: Select,
|
||||
targetCls: 'ant-select',
|
||||
expectClsPrefix: 'ant-select',
|
||||
},
|
||||
{
|
||||
name: 'TimePicker',
|
||||
component: TimePicker,
|
||||
targetCls: 'ant-picker',
|
||||
expectClsPrefix: 'ant-picker',
|
||||
},
|
||||
{
|
||||
name: 'TreeSelect',
|
||||
component: TreeSelect,
|
||||
targetCls: 'ant-select',
|
||||
expectClsPrefix: 'ant-select',
|
||||
},
|
||||
].forEach(({ component, name, targetCls, expectClsPrefix }) => {
|
||||
it(`compact-item for ${name}`, () => {
|
||||
const { container } = render(
|
||||
<Space.Compact>{React.createElement(component as any)}</Space.Compact>,
|
||||
);
|
||||
expect(container.querySelectorAll(`.${targetCls}`).length).toBe(1);
|
||||
['compact-item', 'compact-first-item', 'compact-last-item'].forEach(suffix => {
|
||||
expect(
|
||||
container
|
||||
.querySelector(`.${targetCls}`)
|
||||
?.classList.contains([expectClsPrefix, suffix].join('-')),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('size', () => {
|
||||
const { container } = render(
|
||||
<Space.Compact size="small">
|
||||
<Input defaultValue="https://ant.design" />
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>,
|
||||
);
|
||||
expect(container.querySelector('.ant-input')?.classList.contains('ant-input-sm')).toBe(true);
|
||||
expect(container.querySelector('.ant-btn')?.classList.contains('ant-btn-sm')).toBe(true);
|
||||
});
|
||||
|
||||
it('direction=vertical', () => {
|
||||
const { container } = render(
|
||||
<Space.Compact size="small" direction="vertical">
|
||||
<Button type="primary">Button 1</Button>
|
||||
<Button type="primary">Button 2</Button>
|
||||
<Button type="primary">Button 3</Button>
|
||||
<Button type="primary">Button 4</Button>
|
||||
</Space.Compact>,
|
||||
);
|
||||
expect(
|
||||
container
|
||||
.querySelector('.ant-space-compact')
|
||||
?.classList.contains('ant-space-compact-vertical'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
container.querySelector('.ant-btn')?.classList.contains('ant-btn-compact-vertical-item'),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
container
|
||||
.querySelectorAll('.ant-btn')[0]
|
||||
?.classList.contains('ant-btn-compact-vertical-first-item'),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
container
|
||||
.querySelectorAll('.ant-btn')[3]
|
||||
?.classList.contains('ant-btn-compact-vertical-last-item'),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
42
components/space/demo/compact-button-vertical.md
Normal file
42
components/space/demo/compact-button-vertical.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
order: 10
|
||||
version: 4.24.0
|
||||
title:
|
||||
zh-CN: 垂直方向紧凑布局
|
||||
en-US: Vertical Compact Mode
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
垂直方向的紧凑布局,目前仅支持 Button 组合。
|
||||
|
||||
## en-US
|
||||
|
||||
Vertical Mode for Space.Compact, support Button only.
|
||||
|
||||
```tsx
|
||||
import { Button, Space } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space>
|
||||
<Space.Compact direction="vertical">
|
||||
<Button>Button 1</Button>
|
||||
<Button>Button 2</Button>
|
||||
<Button>Button 3</Button>
|
||||
</Space.Compact>
|
||||
<Space.Compact direction="vertical">
|
||||
<Button type="dashed">Button 1</Button>
|
||||
<Button type="dashed">Button 2</Button>
|
||||
<Button type="dashed">Button 3</Button>
|
||||
</Space.Compact>
|
||||
<Space.Compact direction="vertical">
|
||||
<Button type="primary">Button 1</Button>
|
||||
<Button type="primary">Button 2</Button>
|
||||
<Button type="primary">Button 3</Button>
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
||||
```
|
136
components/space/demo/compact-buttons.md
Normal file
136
components/space/demo/compact-buttons.md
Normal file
@ -0,0 +1,136 @@
|
||||
---
|
||||
order: 9
|
||||
version: 4.24.0
|
||||
title:
|
||||
zh-CN: Button 紧凑布局
|
||||
en-US: Button Compact Mode
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
Button 组件紧凑排列的示例。
|
||||
|
||||
## en-US
|
||||
|
||||
Button component compact example.
|
||||
|
||||
```tsx
|
||||
import {
|
||||
DownloadOutlined,
|
||||
EllipsisOutlined,
|
||||
HeartOutlined,
|
||||
LikeOutlined,
|
||||
CommentOutlined,
|
||||
StarOutlined,
|
||||
ShareAltOutlined,
|
||||
WarningOutlined,
|
||||
MailOutlined,
|
||||
MobileOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Menu, Dropdown, Space, Tooltip } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<div>
|
||||
<Space.Compact block>
|
||||
<Tooltip title="Like">
|
||||
<Button icon={<LikeOutlined />} />
|
||||
</Tooltip>
|
||||
<Tooltip title="Comment">
|
||||
<Button icon={<CommentOutlined />} />
|
||||
</Tooltip>
|
||||
<Tooltip title="Star">
|
||||
<Button icon={<StarOutlined />} />
|
||||
</Tooltip>
|
||||
<Tooltip title="Heart">
|
||||
<Button icon={<HeartOutlined />} />
|
||||
</Tooltip>
|
||||
<Tooltip title="Share">
|
||||
<Button icon={<ShareAltOutlined />} />
|
||||
</Tooltip>
|
||||
<Tooltip title="Download">
|
||||
<Button icon={<DownloadOutlined />} />
|
||||
</Tooltip>
|
||||
<Dropdown
|
||||
placement="bottomRight"
|
||||
overlay={
|
||||
<Menu
|
||||
items={[
|
||||
{
|
||||
key: '1',
|
||||
label: 'Report',
|
||||
icon: <WarningOutlined />,
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: 'Mail',
|
||||
icon: <MailOutlined />,
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: 'Mobile',
|
||||
icon: <MobileOutlined />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
trigger={['click']}
|
||||
>
|
||||
<Button icon={<EllipsisOutlined />} />
|
||||
</Dropdown>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Button type="primary">Button 1</Button>
|
||||
<Button type="primary">Button 2</Button>
|
||||
<Button type="primary">Button 3</Button>
|
||||
<Button type="primary">Button 4</Button>
|
||||
<Tooltip title="Tooltip">
|
||||
<Button type="primary" icon={<DownloadOutlined />} disabled />
|
||||
</Tooltip>
|
||||
<Tooltip title="Tooltip">
|
||||
<Button type="primary" icon={<DownloadOutlined />} />
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Button>Button 1</Button>
|
||||
<Button>Button 2</Button>
|
||||
<Button>Button 3</Button>
|
||||
<Tooltip title="Tooltip">
|
||||
<Button icon={<DownloadOutlined />} disabled />
|
||||
</Tooltip>
|
||||
<Tooltip title="Tooltip">
|
||||
<Button icon={<DownloadOutlined />} />
|
||||
</Tooltip>
|
||||
<Button type="primary">Button 4</Button>
|
||||
<Dropdown
|
||||
placement="bottomRight"
|
||||
overlay={
|
||||
<Menu
|
||||
items={[
|
||||
{
|
||||
key: '1',
|
||||
label: '1st item',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: '2nd item',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: '3rd item',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
trigger={['click']}
|
||||
>
|
||||
<Button type="primary" icon={<EllipsisOutlined />} />
|
||||
</Dropdown>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default App;
|
||||
```
|
107
components/space/demo/compact-debug.md
Normal file
107
components/space/demo/compact-debug.md
Normal file
@ -0,0 +1,107 @@
|
||||
---
|
||||
order: 99
|
||||
version: 4.24.0
|
||||
title:
|
||||
zh-CN: 调试 Input 前置/后置标签
|
||||
en-US: Input addon debug
|
||||
debug: true
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
调试 Input 前置/后置标签。
|
||||
|
||||
## en-US
|
||||
|
||||
Input addon debug.
|
||||
|
||||
```tsx
|
||||
import { SettingOutlined, CopyOutlined, DownloadOutlined } from '@ant-design/icons';
|
||||
import { Cascader, Input, Select, Space, Button, Tooltip } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const selectBefore = (
|
||||
<Select defaultValue="http://" className="select-before">
|
||||
<Option value="http://">http://</Option>
|
||||
<Option value="https://">https://</Option>
|
||||
</Select>
|
||||
);
|
||||
const selectAfter = (
|
||||
<Select defaultValue=".com" className="select-after">
|
||||
<Option value=".com">.com</Option>
|
||||
<Option value=".jp">.jp</Option>
|
||||
<Option value=".cn">.cn</Option>
|
||||
<Option value=".org">.org</Option>
|
||||
</Select>
|
||||
);
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space direction="vertical">
|
||||
<Space.Compact block>
|
||||
<Button>default Button</Button>
|
||||
<Button danger>danger Button</Button>
|
||||
<Button type="dashed">dashed Button</Button>
|
||||
<Button type="text">text Button</Button>
|
||||
<Button type="link">Link Button</Button>
|
||||
<Tooltip title="Tooltip">
|
||||
<Button icon={<DownloadOutlined />} disabled />
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact>
|
||||
<Button>Prefix</Button>
|
||||
<Input addonBefore="http://" addonAfter=".com" defaultValue="mysite" />
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>
|
||||
<Space.Compact>
|
||||
<Input placeholder="prefix" />
|
||||
<Input addonBefore={selectBefore} addonAfter={selectAfter} defaultValue="mysite" />
|
||||
<Button icon={<CopyOutlined />} />
|
||||
</Space.Compact>
|
||||
<Space.Compact>
|
||||
<Input.Search />
|
||||
<Input.Search />
|
||||
<Button icon={<CopyOutlined />} />
|
||||
</Space.Compact>
|
||||
<Space.Compact>
|
||||
<Input addonAfter={<SettingOutlined />} defaultValue="mysite" />
|
||||
<Button type="primary">Submit</Button>
|
||||
<Input placeholder="suffix" addonAfter={<SettingOutlined />} />
|
||||
</Space.Compact>
|
||||
<Space.Compact>
|
||||
<Input addonBefore="http://" suffix=".com" defaultValue="mysite" />
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>
|
||||
<Space.Compact>
|
||||
<Button>Prefix</Button>
|
||||
<Input
|
||||
addonBefore={<Cascader placeholder="cascader" style={{ width: 150 }} />}
|
||||
defaultValue="mysite"
|
||||
/>
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
```css
|
||||
.select-before {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.select-after {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
[data-theme='compact'] .select-before {
|
||||
width: 71px;
|
||||
}
|
||||
|
||||
[data-theme='compact'] .select-after {
|
||||
width: 65px;
|
||||
}
|
||||
```
|
116
components/space/demo/compact-nested.md
Normal file
116
components/space/demo/compact-nested.md
Normal file
@ -0,0 +1,116 @@
|
||||
---
|
||||
order: 99
|
||||
version: 4.24.0
|
||||
title:
|
||||
zh-CN: 紧凑布局嵌套
|
||||
en-US: Nested Space Compact
|
||||
debug: true
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
嵌套使用的紧凑布局
|
||||
|
||||
## en-US
|
||||
|
||||
Nested `Space.Compact`
|
||||
|
||||
```tsx
|
||||
import { CopyOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import { Button, Cascader, Input, InputNumber, Space, Select, TimePicker } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const App: React.FC = () => (
|
||||
<>
|
||||
<Space.Compact block>
|
||||
<Space.Compact>
|
||||
<Space.Compact>
|
||||
<Input style={{ width: 90 }} placeholder="Typing..." />
|
||||
<Button icon={<SearchOutlined />} />
|
||||
</Space.Compact>
|
||||
<Space.Compact>
|
||||
<InputNumber defaultValue={12} />
|
||||
<Select defaultValue="Option1">
|
||||
<Option value="Option1">Opt1</Option>
|
||||
<Option value="Option2">Opt2</Option>
|
||||
</Select>
|
||||
</Space.Compact>
|
||||
</Space.Compact>
|
||||
<Button type="primary">Separator</Button>
|
||||
<Space.Compact>
|
||||
<Space.Compact>
|
||||
<Input.Search style={{ width: 110 }} placeholder="Search" />
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>
|
||||
<Space.Compact>
|
||||
<Input defaultValue="mysite" />
|
||||
<Button icon={<CopyOutlined />} />
|
||||
</Space.Compact>
|
||||
</Space.Compact>
|
||||
</Space.Compact>
|
||||
<>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Space.Compact>
|
||||
<TimePicker />
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>
|
||||
<Space.Compact>
|
||||
<Cascader
|
||||
options={[
|
||||
{
|
||||
value: 'zhejiang',
|
||||
label: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
value: 'hangzhou',
|
||||
label: 'Hangzhou',
|
||||
children: [
|
||||
{
|
||||
value: 'xihu',
|
||||
label: 'West Lake',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'jiangsu',
|
||||
label: 'Jiangsu',
|
||||
children: [
|
||||
{
|
||||
value: 'nanjing',
|
||||
label: 'Nanjing',
|
||||
children: [
|
||||
{
|
||||
value: 'zhonghuamen',
|
||||
label: 'Zhong Hua Men',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
placeholder="Select Address"
|
||||
/>
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>
|
||||
</Space.Compact>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
```css
|
||||
[data-theme='compact'] .select-before {
|
||||
width: 71px;
|
||||
}
|
||||
|
||||
[data-theme='compact'] .select-after {
|
||||
width: 65px;
|
||||
}
|
||||
```
|
256
components/space/demo/compact.md
Normal file
256
components/space/demo/compact.md
Normal file
@ -0,0 +1,256 @@
|
||||
---
|
||||
order: 8
|
||||
version: 4.24.0
|
||||
title:
|
||||
zh-CN: 紧凑布局组合
|
||||
en-US: Compact Mode for form component
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
使用 Space.Compact 让表单组件之间紧凑连接且合并边框。
|
||||
|
||||
## en-US
|
||||
|
||||
Compact Mode for form component.
|
||||
|
||||
```tsx
|
||||
import { CopyOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Cascader,
|
||||
DatePicker,
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
Space,
|
||||
TimePicker,
|
||||
Tooltip,
|
||||
TreeSelect,
|
||||
} from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const { Option } = Select;
|
||||
const { TreeNode } = TreeSelect;
|
||||
|
||||
const App: React.FC = () => (
|
||||
<div className="site-space-compact-wrapper">
|
||||
<Space.Compact block>
|
||||
<Input style={{ width: '20%' }} defaultValue="0571" />
|
||||
<Input style={{ width: '30%' }} defaultValue="26888888" />
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block size="small">
|
||||
<Input style={{ width: 'calc(100% - 200px)' }} defaultValue="https://ant.design" />
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Input style={{ width: 'calc(100% - 200px)' }} defaultValue="https://ant.design" />
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Input
|
||||
style={{ width: 'calc(100% - 200px)' }}
|
||||
defaultValue="git@github.com:ant-design/ant-design.git"
|
||||
/>
|
||||
<Tooltip title="copy git url">
|
||||
<Button icon={<CopyOutlined />} />
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Select defaultValue="Zhejiang">
|
||||
<Option value="Zhejiang">Zhejiang</Option>
|
||||
<Option value="Jiangsu">Jiangsu</Option>
|
||||
</Select>
|
||||
<Input style={{ width: '50%' }} defaultValue="Xihu District, Hangzhou" />
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Select mode="multiple" defaultValue="Zhejianggggg" style={{ width: '50%' }}>
|
||||
<Option value="Zhejianggggg">Zhejianggggg</Option>
|
||||
<Option value="Jiangsu">Jiangsu</Option>
|
||||
</Select>
|
||||
<Input style={{ width: '50%' }} defaultValue="Xihu District, Hangzhou" />
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Input.Search style={{ width: '30%' }} defaultValue="0571" />
|
||||
<Input.Search allowClear style={{ width: '50%' }} defaultValue="26888888" />
|
||||
<Input.Search style={{ width: '20%' }} defaultValue="+1" />
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Select defaultValue="Option1">
|
||||
<Option value="Option1">Option1</Option>
|
||||
<Option value="Option2">Option2</Option>
|
||||
</Select>
|
||||
<Input style={{ width: '50%' }} defaultValue="input content" />
|
||||
<InputNumber defaultValue={12} />
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Input style={{ width: '50%' }} defaultValue="input content" />
|
||||
<DatePicker style={{ width: '50%' }} />
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<DatePicker.RangePicker style={{ width: '70%' }} />
|
||||
<Input style={{ width: '30%' }} defaultValue="input content" />
|
||||
<Button type="primary">查询</Button>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Input style={{ width: '30%' }} defaultValue="input content" />
|
||||
<DatePicker.RangePicker style={{ width: '70%' }} />
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Select defaultValue="Option1-1">
|
||||
<Option value="Option1-1">Option1-1</Option>
|
||||
<Option value="Option1-2">Option1-2</Option>
|
||||
</Select>
|
||||
<Select defaultValue="Option2-2">
|
||||
<Option value="Option2-1">Option2-1</Option>
|
||||
<Option value="Option2-2">Option2-2</Option>
|
||||
</Select>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Select defaultValue="1">
|
||||
<Option value="1">Between</Option>
|
||||
<Option value="2">Except</Option>
|
||||
</Select>
|
||||
<Input style={{ width: 100, textAlign: 'center' }} placeholder="Minimum" />
|
||||
<Input
|
||||
className="site-input-split"
|
||||
style={{
|
||||
width: 30,
|
||||
borderLeft: 0,
|
||||
borderRight: 0,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
placeholder="~"
|
||||
disabled
|
||||
/>
|
||||
<Input
|
||||
className="site-input-right"
|
||||
style={{
|
||||
width: 100,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
placeholder="Maximum"
|
||||
/>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<Select defaultValue="Sign Up" style={{ width: '30%' }}>
|
||||
<Option value="Sign Up">Sign Up</Option>
|
||||
<Option value="Sign In">Sign In</Option>
|
||||
</Select>
|
||||
<AutoComplete
|
||||
style={{ width: '70%' }}
|
||||
placeholder="Email"
|
||||
options={[{ value: 'text 1' }, { value: 'text 2' }]}
|
||||
/>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<TimePicker style={{ width: '70%' }} />
|
||||
<Cascader
|
||||
style={{ width: '70%' }}
|
||||
options={[
|
||||
{
|
||||
value: 'zhejiang',
|
||||
label: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
value: 'hangzhou',
|
||||
label: 'Hangzhou',
|
||||
children: [
|
||||
{
|
||||
value: 'xihu',
|
||||
label: 'West Lake',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'jiangsu',
|
||||
label: 'Jiangsu',
|
||||
children: [
|
||||
{
|
||||
value: 'nanjing',
|
||||
label: 'Nanjing',
|
||||
children: [
|
||||
{
|
||||
value: 'zhonghuamen',
|
||||
label: 'Zhong Hua Men',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
placeholder="Select Address"
|
||||
/>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
<Space.Compact block>
|
||||
<TimePicker.RangePicker />
|
||||
<TreeSelect
|
||||
showSearch
|
||||
style={{ width: '60%' }}
|
||||
value="leaf1"
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
placeholder="Please select"
|
||||
allowClear
|
||||
treeDefaultExpandAll
|
||||
onChange={() => {}}
|
||||
>
|
||||
<TreeNode value="parent 1" title="parent 1">
|
||||
<TreeNode value="parent 1-0" title="parent 1-0">
|
||||
<TreeNode value="leaf1" title="leaf1" />
|
||||
<TreeNode value="leaf2" title="leaf2" />
|
||||
</TreeNode>
|
||||
<TreeNode value="parent 1-1" title="parent 1-1">
|
||||
<TreeNode value="leaf3" title={<b style={{ color: '#08c' }}>leaf3</b>} />
|
||||
</TreeNode>
|
||||
</TreeNode>
|
||||
</TreeSelect>
|
||||
<Button type="primary">Submit</Button>
|
||||
</Space.Compact>
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
```css
|
||||
.site-space-compact-wrapper .site-input-split {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.site-space-compact-wrapper .site-input-right:not(.ant-input-rtl) {
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.site-space-compact-wrapper .site-input-right:not(.ant-input-rtl):hover,
|
||||
.site-space-compact-wrapper .site-input-right:not(.ant-input-rtl):focus {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
.site-space-compact-wrapper .site-input-right.ant-input-rtl {
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
.site-space-compact-wrapper .site-input-right.ant-input-rtl:hover,
|
||||
.site-space-compact-wrapper .site-input-right.ant-input-rtl:focus {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
```
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 3
|
||||
order: 5
|
||||
title:
|
||||
zh-CN: 自定义尺寸
|
||||
en-US: Customize Size
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 99
|
||||
order: 7
|
||||
title:
|
||||
zh-CN: 分隔符
|
||||
en-US: Split
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
order: 98
|
||||
order: 6
|
||||
title:
|
||||
zh-CN: 自动换行
|
||||
en-US: Wrap
|
||||
|
@ -10,7 +10,8 @@ Set components spacing.
|
||||
|
||||
## When To Use
|
||||
|
||||
Avoid components clinging together and set a unified space.
|
||||
- Avoid components clinging together and set a unified space.
|
||||
- Use Space.Compact when child form components are compactly connected and the border is collapsed.
|
||||
|
||||
## API
|
||||
|
||||
@ -25,3 +26,22 @@ Avoid components clinging together and set a unified space.
|
||||
### Size
|
||||
|
||||
`'small' | 'middle' | 'large' | number`
|
||||
|
||||
### Space.Compact
|
||||
|
||||
Use Space.Compact when child form components are compactly connected and the border is collapsed. The supported components are:
|
||||
|
||||
- Button
|
||||
- AutoComplete
|
||||
- Cascader
|
||||
- DatePicker
|
||||
- Input/Input.Search
|
||||
- Select
|
||||
- TimePicker
|
||||
- TreeSelect
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| block | Option to fit width to its parent\'s width | boolean | false | 4.24.0 |
|
||||
| direction | Set direction of layout | `vertical` \| `horizontal` | `horizontal` | 4.24.0 |
|
||||
| size | Set child component size | `large` \| `middle` \| `small` | `middle` | 4.24.0 |
|
||||
|
@ -5,6 +5,7 @@ import { ConfigContext } from '../config-provider';
|
||||
import type { SizeType } from '../config-provider/SizeContext';
|
||||
import useFlexGapSupport from '../_util/hooks/useFlexGapSupport';
|
||||
import Item from './Item';
|
||||
import Compact from './Compact';
|
||||
|
||||
import useStyle from './style';
|
||||
|
||||
@ -150,4 +151,11 @@ const Space: React.FC<SpaceProps> = props => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Space;
|
||||
interface CompoundedComponent extends React.FC<SpaceProps> {
|
||||
Compact: typeof Compact;
|
||||
}
|
||||
|
||||
const CompoundedSpace = Space as CompoundedComponent;
|
||||
CompoundedSpace.Compact = Compact;
|
||||
|
||||
export default CompoundedSpace;
|
||||
|
@ -15,9 +15,12 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/wc6%263gJ0Y8/Space.svg
|
||||
|
||||
- 适合行内元素的水平间距。
|
||||
- 可以设置各种水平对齐方式。
|
||||
- 需要表单组件之间紧凑连接且合并边框时,使用 Space.Compact(自 antd@4.24.0 版本开始提供该组件。)。
|
||||
|
||||
## API
|
||||
|
||||
### Space
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| align | 对齐方式 | `start` \| `end` \|`center` \|`baseline` | - | 4.2.0 |
|
||||
@ -29,3 +32,24 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/wc6%263gJ0Y8/Space.svg
|
||||
### Size
|
||||
|
||||
`'small' | 'middle' | 'large' | number`
|
||||
|
||||
### Space.Compact
|
||||
|
||||
> 自 antd@4.24.0 版本开始提供该组件。
|
||||
|
||||
需要表单组件之间紧凑连接且合并边框时,使用 Space.Compact。支持的组件有:
|
||||
|
||||
- Button
|
||||
- AutoComplete
|
||||
- Cascader
|
||||
- DatePicker
|
||||
- Input/Input.Search
|
||||
- Select
|
||||
- TimePicker
|
||||
- TreeSelect
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| block | 将宽度调整为父元素宽度的选项 | boolean | false | 4.24.0 |
|
||||
| direction | 指定排列方向 | `vertical` \| `horizontal` | `horizontal` | 4.24.0 |
|
||||
| size | 子组件大小 | `large` \| `middle` \| `small` | `middle` | 4.24.0 |
|
||||
|
17
components/space/style/compact.less
Normal file
17
components/space/style/compact.less
Normal file
@ -0,0 +1,17 @@
|
||||
@import '../../style/themes/index';
|
||||
@import '../../style/mixins/index';
|
||||
|
||||
@space-compact-prefix-cls: ~'@{ant-prefix}-space-compact';
|
||||
|
||||
.@{space-compact-prefix-cls} {
|
||||
display: inline-flex;
|
||||
|
||||
&-block {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
51
components/style/mixins/compact-item-vertical.less
Normal file
51
components/style/mixins/compact-item-vertical.less
Normal file
@ -0,0 +1,51 @@
|
||||
.compact-item-vertical-border-radius(@prefix-cls) {
|
||||
&-item:not(&-first-item):not(&-last-item) {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&-item&-first-item {
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
.@{prefix-cls}-sm & {
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-item&-last-item {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
|
||||
.@{prefix-cls}-sm & {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compact-item-vertical-border(@prefix-cls) {
|
||||
// border collapse
|
||||
&-item:not(&-last-item) {
|
||||
margin-bottom: -@border-width-base;
|
||||
}
|
||||
|
||||
&-item {
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compact-item-vertical(@prefix-cls) {
|
||||
&-compact-vertical {
|
||||
.compact-item-vertical-border(@prefix-cls);
|
||||
.compact-item-vertical-border-radius(@prefix-cls);
|
||||
}
|
||||
}
|
170
components/style/mixins/compact-item.less
Normal file
170
components/style/mixins/compact-item.less
Normal file
@ -0,0 +1,170 @@
|
||||
.compact-item-border-radius(@prefix-cls, @bordered-item-cls: null) {
|
||||
& when (@bordered-item-cls = null) {
|
||||
// border-radius
|
||||
&-item:not(&-first-item):not(&-last-item).@{prefix-cls} {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&-item.@{prefix-cls}&-first-item:not(&-item-rtl) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
.@{prefix-cls}-sm & {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-item.@{prefix-cls}&-last-item:not(&-item-rtl) {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
.@{prefix-cls}-sm & {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------rtl for first item----------
|
||||
&-item.@{prefix-cls}&-item-rtl&-first-item {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
.@{prefix-cls}-sm & {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------rtl for last item----------
|
||||
&-item.@{prefix-cls}&-item-rtl&-last-item {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
.@{prefix-cls}-sm & {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& when (not (@bordered-item-cls = null)) {
|
||||
// border-radius
|
||||
&-item:not(&-first-item):not(&-last-item).@{prefix-cls} > .@{bordered-item-cls} {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&-item&-first-item.@{prefix-cls}:not(&-item-rtl) > .@{bordered-item-cls} {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
.@{prefix-cls}-sm & {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-item&-last-item.@{prefix-cls}:not(&-item-rtl) > .@{bordered-item-cls} {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
.@{prefix-cls}-sm & {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------rtl for first item----------
|
||||
&-item.@{prefix-cls}&-first-item&-item-rtl > .@{bordered-item-cls} {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
.@{prefix-cls}-sm & {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------rtl for last item----------
|
||||
&-item.@{prefix-cls}&-last-item&-item-rtl > .@{bordered-item-cls} {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
.@{prefix-cls}-sm & {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compact-item-border(@prefix-cls, @bordered-item-cls: null, @special-open-cls) {
|
||||
& when (@bordered-item-cls = null) {
|
||||
// border collapse
|
||||
&-item:not(&-last-item):not(&-item-rtl) {
|
||||
margin-right: -@border-width-base;
|
||||
}
|
||||
|
||||
// rtl border collapse
|
||||
&-item:not(&-last-item)&-item-rtl {
|
||||
margin-left: -@border-width-base;
|
||||
}
|
||||
|
||||
&-item {
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
// Select has an extra focus className
|
||||
& when (not (@special-item-cls = null)) {
|
||||
&.@{special-item-cls} {
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& when (not (@bordered-item-cls = null)) {
|
||||
// border collapse
|
||||
&-item:not(&-last-item) {
|
||||
margin-right: -@border-width-base;
|
||||
|
||||
&.@{prefix-cls}-compact-item-rtl {
|
||||
margin-right: 0;
|
||||
margin-left: -@border-width-base;
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
> * {
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Select has an special focus-item
|
||||
& when (not (@special-item-cls = null)) {
|
||||
&.@{special-item-cls} > * {
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&[disabled] > * {
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compact-item(@prefix-cls, @bordered-item-cls: null, @special-item-cls: null) {
|
||||
&-compact {
|
||||
.compact-item-border(@prefix-cls, @bordered-item-cls, @special-item-cls);
|
||||
|
||||
.compact-item-border-radius(@prefix-cls, @bordered-item-cls);
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import type { SelectCommonPlacement } from '../_util/motion';
|
||||
import { getTransitionDirection, getTransitionName } from '../_util/motion';
|
||||
import type { InputStatus } from '../_util/statusUtils';
|
||||
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
||||
import { useCompactItemContext } from '../space/Compact';
|
||||
import warning from '../_util/warning';
|
||||
|
||||
import useStyle from './style';
|
||||
@ -119,6 +120,7 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
|
||||
const prefixCls = getPrefixCls('select', customizePrefixCls);
|
||||
const treePrefixCls = getPrefixCls('select-tree', customizePrefixCls);
|
||||
const treeSelectPrefixCls = getPrefixCls('tree-select', customizePrefixCls);
|
||||
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
|
||||
|
||||
const [wrapSelectSSR, hashId] = useSelectStyle(prefixCls);
|
||||
const [wrapTreeSelectSSR] = useStyle(treeSelectPrefixCls, treePrefixCls);
|
||||
@ -181,7 +183,7 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
|
||||
: ('bottomLeft' as SelectCommonPlacement);
|
||||
};
|
||||
|
||||
const mergedSize = customizeSize || size;
|
||||
const mergedSize = compactSize || customizeSize || size;
|
||||
// ===================== Disabled =====================
|
||||
const disabled = React.useContext(DisabledContext);
|
||||
const mergedDisabled = customDisabled ?? disabled;
|
||||
@ -196,6 +198,7 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
|
||||
[`${prefixCls}-in-form-item`]: isFormItemInput,
|
||||
},
|
||||
getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
|
||||
compactItemClassnames,
|
||||
className,
|
||||
hashId,
|
||||
);
|
||||
|
@ -36,6 +36,7 @@ interface CopyConfig {
|
||||
}
|
||||
|
||||
interface EditConfig {
|
||||
text?: string;
|
||||
editing?: boolean;
|
||||
icon?: React.ReactNode;
|
||||
tooltip?: boolean | React.ReactNode;
|
||||
@ -339,11 +340,11 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
// ========================== Tooltip ===========================
|
||||
let tooltipProps: TooltipProps = {};
|
||||
if (ellipsisConfig.tooltip === true) {
|
||||
tooltipProps = { title: children };
|
||||
tooltipProps = { title: editConfig.text ?? children };
|
||||
} else if (React.isValidElement(ellipsisConfig.tooltip)) {
|
||||
tooltipProps = { title: ellipsisConfig.tooltip };
|
||||
} else if (typeof ellipsisConfig.tooltip === 'object') {
|
||||
tooltipProps = { title: children, ...ellipsisConfig.tooltip };
|
||||
tooltipProps = { title: editConfig.text ?? children, ...ellipsisConfig.tooltip };
|
||||
} else {
|
||||
tooltipProps = { title: ellipsisConfig.tooltip };
|
||||
}
|
||||
@ -354,6 +355,10 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (isValid(editConfig.text)) {
|
||||
return editConfig.text;
|
||||
}
|
||||
|
||||
if (isValid(children)) {
|
||||
return children;
|
||||
}
|
||||
@ -374,7 +379,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
if (editing) {
|
||||
return (
|
||||
<Editable
|
||||
value={typeof children === 'string' ? children : ''}
|
||||
value={editConfig.text ?? (typeof children === 'string' ? children : '')}
|
||||
onSave={onEditChange}
|
||||
onCancel={onEditCancel}
|
||||
onEnd={editConfig.onEnd}
|
||||
|
@ -1175,6 +1175,133 @@ Array [
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
aria-label="This is a loooooooooooooooooooooooooooooooong editable text with suffix."
|
||||
class="ant-typography ant-typography-ellipsis ant-typography-single-line"
|
||||
>
|
||||
This is a loooooooooooooooooooooooooooooooong editable text
|
||||
<!-- -->
|
||||
with suffix.
|
||||
<div
|
||||
aria-label="Edit"
|
||||
class="ant-typography-edit"
|
||||
role="button"
|
||||
style="border:0;background:transparent;padding:0;line-height:inherit;display:inline-block"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-label="edit"
|
||||
class="anticon anticon-edit"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="edit"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-tooltip"
|
||||
style="opacity:0"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
>
|
||||
<span
|
||||
class="ant-tooltip-arrow-content"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
Edit
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
style="position:fixed;display:block;left:0;top:0;z-index:-9999;visibility:hidden;pointer-events:none;font-size:0;word-break:keep-all;white-space:nowrap"
|
||||
>
|
||||
lg
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
style="position:fixed;display:block;left:0;top:0;z-index:-9999;visibility:hidden;pointer-events:none;font-size:0;width:0;white-space:normal;margin:0;padding:0"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
...
|
||||
</span>
|
||||
with suffix.
|
||||
<div
|
||||
aria-label="Edit"
|
||||
class="ant-typography-edit"
|
||||
role="button"
|
||||
style="border:0;background:transparent;padding:0;line-height:inherit;display:inline-block"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-label="edit"
|
||||
class="anticon anticon-edit"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="edit"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-tooltip"
|
||||
style="opacity:0"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
>
|
||||
<span
|
||||
class="ant-tooltip-arrow-content"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
Edit
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-typography"
|
||||
>
|
||||
@ -1231,9 +1358,7 @@ Array [
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
Trigger edit with:,
|
||||
<!-- -->,
|
||||
,
|
||||
Trigger edit with: ,
|
||||
<div
|
||||
class="ant-radio-group ant-radio-group-outline"
|
||||
>
|
||||
|
@ -887,6 +887,85 @@ Array [
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
aria-label="This is a loooooooooooooooooooooooooooooooong editable text with suffix."
|
||||
class="ant-typography ant-typography-ellipsis ant-typography-single-line"
|
||||
>
|
||||
This is a loooooooooooooooooooooooooooooooong editable text
|
||||
<!-- -->
|
||||
with suffix.
|
||||
<div
|
||||
aria-label="Edit"
|
||||
class="ant-typography-edit"
|
||||
role="button"
|
||||
style="border:0;background:transparent;padding:0;line-height:inherit;display:inline-block"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-label="edit"
|
||||
class="anticon anticon-edit"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="edit"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
style="position:fixed;display:block;left:0;top:0;z-index:-9999;visibility:hidden;pointer-events:none;font-size:0;word-break:keep-all;white-space:nowrap"
|
||||
>
|
||||
lg
|
||||
</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
style="position:fixed;display:block;left:0;top:0;z-index:-9999;visibility:hidden;pointer-events:none;font-size:0;width:0;white-space:normal;margin:0;padding:0"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
...
|
||||
</span>
|
||||
with suffix.
|
||||
<div
|
||||
aria-label="Edit"
|
||||
class="ant-typography-edit"
|
||||
role="button"
|
||||
style="border:0;background:transparent;padding:0;line-height:inherit;display:inline-block"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-label="edit"
|
||||
class="anticon anticon-edit"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="edit"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-typography"
|
||||
>
|
||||
@ -919,9 +998,7 @@ Array [
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
Trigger edit with:,
|
||||
<!-- -->,
|
||||
,
|
||||
Trigger edit with: ,
|
||||
<div
|
||||
class="ant-radio-group ant-radio-group-outline"
|
||||
>
|
||||
|
81
components/typography/__tests__/editable.test.tsx
Normal file
81
components/typography/__tests__/editable.test.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
|
||||
import React from 'react';
|
||||
import { fireEvent, render } from '../../../tests/utils';
|
||||
import Base from '../Base';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
jest.mock('copy-to-clipboard');
|
||||
|
||||
jest.mock('../../_util/styleChecker', () => ({
|
||||
isStyleSupport: () => true,
|
||||
}));
|
||||
|
||||
describe('Typography.Editable', () => {
|
||||
const LINE_STR_COUNT = 20;
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
let mockRectSpy: ReturnType<typeof spyElementPrototypes>;
|
||||
|
||||
beforeAll(() => {
|
||||
mockRectSpy = spyElementPrototypes(HTMLElement, {
|
||||
offsetHeight: {
|
||||
get() {
|
||||
let html = this.innerHTML;
|
||||
html = html.replace(/<[^>]*>/g, '');
|
||||
const lines = Math.ceil(html.length / LINE_STR_COUNT);
|
||||
return lines * 16;
|
||||
},
|
||||
},
|
||||
getBoundingClientRect() {
|
||||
let html = this.innerHTML;
|
||||
html = html.replace(/<[^>]*>/g, '');
|
||||
const lines = Math.ceil(html.length / LINE_STR_COUNT);
|
||||
return { height: lines * 16 };
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
errorSpy.mockRestore();
|
||||
mockRectSpy.mockRestore();
|
||||
});
|
||||
|
||||
const fullStr =
|
||||
'Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light';
|
||||
|
||||
it('should use editConfig.text over children in editing mode ', async () => {
|
||||
const suffix = '--The information is very important';
|
||||
const ref = React.createRef<any>();
|
||||
const { container: wrapper, unmount } = render(
|
||||
<Base
|
||||
ellipsis={{ rows: 1, suffix }}
|
||||
component="p"
|
||||
editable={{ text: fullStr + suffix }}
|
||||
ref={ref}
|
||||
>
|
||||
{fullStr}
|
||||
</Base>,
|
||||
);
|
||||
|
||||
fireEvent.click(wrapper.querySelector('.ant-typography-edit')!);
|
||||
|
||||
expect(wrapper.querySelector('textarea')?.textContent).toEqual(fullStr + suffix);
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should use children as the fallback of editConfig.text in editing mode', async () => {
|
||||
const suffix = '--The information is very important';
|
||||
const ref = React.createRef<any>();
|
||||
const { container: wrapper, unmount } = render(
|
||||
<Base ellipsis={{ rows: 1, suffix }} component="p" ref={ref} editable>
|
||||
{fullStr}
|
||||
</Base>,
|
||||
);
|
||||
|
||||
fireEvent.click(wrapper.querySelector('.ant-typography-edit')!);
|
||||
|
||||
expect(wrapper.querySelector('textarea')?.textContent).toEqual(fullStr);
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
@ -396,6 +396,15 @@ describe('Typography', () => {
|
||||
expect(onEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should trigger onStart when type Start', () => {
|
||||
const onStart = jest.fn();
|
||||
const { container: wrapper } = render(<Paragraph editable={{ onStart }}>Bamboo</Paragraph>);
|
||||
fireEvent.click(wrapper.querySelectorAll('.ant-typography-edit')[0]);
|
||||
fireEvent.keyDown(wrapper.querySelector('textarea')!, { keyCode: KeyCode.A });
|
||||
fireEvent.keyUp(wrapper.querySelector('textarea')!, { keyCode: KeyCode.A });
|
||||
expect(onStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should trigger onCancel when type ESC', () => {
|
||||
const onCancel = jest.fn();
|
||||
const { container: wrapper } = render(
|
||||
|
@ -16,12 +16,19 @@ Provide additional interactive capacity of editable and copyable.
|
||||
```tsx
|
||||
import { CheckOutlined, HighlightOutlined, SmileFilled, SmileOutlined } from '@ant-design/icons';
|
||||
import { Divider, Radio, Typography } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [editableStr, setEditableStr] = useState('This is an editable text.');
|
||||
const [editableStrWithSuffix, setEditableStrWithSuffix] = useState(
|
||||
'This is a loooooooooooooooooooooooooooooooong editable text with suffix.',
|
||||
);
|
||||
const [editableStrWithSuffixStartPart, editableStrWithSuffixSuffixPart] = useMemo(
|
||||
() => [editableStrWithSuffix.slice(0, -12), editableStrWithSuffix.slice(-12)],
|
||||
[editableStrWithSuffix],
|
||||
);
|
||||
const [customIconStr, setCustomIconStr] = useState('Custom Edit icon and replace tooltip text.');
|
||||
const [clickTriggerStr, setClickTriggerStr] = useState(
|
||||
'Text or icon as trigger - click to start editing.',
|
||||
@ -60,6 +67,17 @@ const App: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Paragraph editable={{ onChange: setEditableStr }}>{editableStr}</Paragraph>
|
||||
<Paragraph
|
||||
editable={{
|
||||
onChange: setEditableStrWithSuffix,
|
||||
text: editableStrWithSuffix,
|
||||
}}
|
||||
ellipsis={{
|
||||
suffix: editableStrWithSuffixSuffixPart,
|
||||
}}
|
||||
>
|
||||
{editableStrWithSuffixStartPart}
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
editable={{
|
||||
icon: <HighlightOutlined />,
|
||||
@ -69,8 +87,7 @@ const App: React.FC = () => {
|
||||
>
|
||||
{customIconStr}
|
||||
</Paragraph>
|
||||
Trigger edit with:{' '}
|
||||
<Radio.Group
|
||||
Trigger edit with: <Radio.Group
|
||||
onChange={e => setChooseTrigger(radioToState(e.target.value))}
|
||||
value={stateToRadio()}
|
||||
>
|
||||
|
@ -93,9 +93,10 @@ Basic text writing, including headings, body text, lists, and more.
|
||||
editing: boolean,
|
||||
maxLength: number,
|
||||
autoSize: boolean | { minRows: number, maxRows: number },
|
||||
onStart: function,
|
||||
text: string,
|
||||
onChange: function(string),
|
||||
onCancel: function,
|
||||
onStart: function,
|
||||
onEnd: function,
|
||||
triggerType: ('icon' | 'text')[],
|
||||
enterIcon: ReactNode,
|
||||
@ -108,9 +109,10 @@ Basic text writing, including headings, body text, lists, and more.
|
||||
| icon | Custom editable icon | ReactNode | <EditOutlined /> | 4.6.0 |
|
||||
| maxLength | `maxLength` attribute of textarea | number | - | 4.4.0 |
|
||||
| tooltip | Custom tooltip text, hide when it is false | boolean \| ReactNode | `Edit` | 4.6.0 |
|
||||
| onStart | Called when enter editable state | function | - | |
|
||||
| text | Edit text, specify the editing content instead of using the children implicitly | string | - | 4.24.0 |
|
||||
| onChange | Called when input at textarea | function(value: string) | - | |
|
||||
| onCancel | Called when type ESC to exit editable state | function | - | |
|
||||
| onStart | Called when enter editable state | function | - | |
|
||||
| onEnd | Called when type ENTER to exit editable state | function | - | 4.14.0 |
|
||||
| triggerType | Edit mode trigger - icon, text or both (not specifying icon as trigger hides it) | Array<`icon`\|`text`> | \[`icon`] | |
|
||||
| enterIcon | Custom "enter" icon in the edit field (passing `null` removes the icon) | ReactNode | `<EnterOutlined />` | 4.17.0 |
|
||||
|
@ -94,9 +94,10 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
|
||||
editing: boolean,
|
||||
maxLength: number,
|
||||
autoSize: boolean | { minRows: number, maxRows: number },
|
||||
onStart: function,
|
||||
text: string,
|
||||
onChange: function(string),
|
||||
onCancel: function,
|
||||
onStart: function,
|
||||
onEnd: function,
|
||||
triggerType: ('icon' | 'text')[],
|
||||
enterIcon: ReactNode,
|
||||
@ -109,10 +110,11 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
|
||||
| icon | 自定义编辑图标 | ReactNode | <EditOutlined /> | 4.6.0 |
|
||||
| maxLength | 编辑中文本域最大长度 | number | - | 4.4.0 |
|
||||
| tooltip | 自定义提示文本,为 false 时关闭 | boolean \| ReactNode | `编辑` | 4.6.0 |
|
||||
| onCancel | 按 ESC 退出编辑状态时触发 | function | - | |
|
||||
| text | 显式地指定编辑文案,为空时将隐式地使用 children | string | - | 4.24.0 |
|
||||
| onChange | 文本域编辑时触发 | function(value: string) | - | |
|
||||
| onEnd | 按 ENTER 结束编辑状态时触发 | function | - | 4.14.0 |
|
||||
| onCancel | 按 ESC 退出编辑状态时触发 | function | - | |
|
||||
| onStart | 进入编辑中状态时触发 | function | - | |
|
||||
| onEnd | 按 ENTER 结束编辑状态时触发 | function | - | 4.14.0 |
|
||||
| triggerType | 编辑模式触发器类型,图标、文本或者两者都设置(不设置图标作为触发器时它会隐藏) | Array<`icon`\|`text`> | \[`icon`] | |
|
||||
| enterIcon | 在编辑段中自定义“enter”图标(传递“null”将删除图标) | ReactNode | `<EnterOutlined />` | 4.17.0 |
|
||||
|
||||
|
@ -91,7 +91,7 @@
|
||||
"test-node": "npm run version && jest --config .jest.node.js --cache=false",
|
||||
"tsc": "tsc --noEmit",
|
||||
"site:test": "jest --config .jest.site.js --cache=false --force-exit",
|
||||
"test-image": "npm run dist && jest --config .jest.image.js --no-cache -i -u",
|
||||
"test-image": "npm run dist && jest --config .jest.image.js -i -u",
|
||||
"argos": "node ./scripts/argos-upload.js",
|
||||
"version": "node ./scripts/generate-version",
|
||||
"install-react-16": "npm i --no-save --legacy-peer-deps react@16 react-dom@16",
|
||||
|
@ -6,6 +6,11 @@ import { render, act } from '@testing-library/react';
|
||||
import { _rs as onLibResize } from 'rc-resize-observer/lib/utils/observerUtil';
|
||||
import { _rs as onEsResize } from 'rc-resize-observer/es/utils/observerUtil';
|
||||
|
||||
export function assertsExist<T>(item: T | null | undefined): asserts item is T {
|
||||
expect(item).not.toBeUndefined();
|
||||
expect(item).not.toBeNull();
|
||||
}
|
||||
|
||||
export function setMockDate(dateString = '2017-09-18T03:30:07.795') {
|
||||
MockDate.set(dateString);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user