mirror of
https://github.com/ant-design/ant-design.git
synced 2024-12-01 23:29:30 +08:00
commit
c9ea74299a
23
.github/workflows/disscustion-open-check.yml
vendored
Normal file
23
.github/workflows/disscustion-open-check.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
name: Discussions
|
||||||
|
|
||||||
|
on:
|
||||||
|
discussion:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
discussion-create:
|
||||||
|
permissions:
|
||||||
|
contents: read # for visiky/dingtalk-release-notify to get latest release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: send to dingtalk
|
||||||
|
uses: visiky/dingtalk-release-notify@main
|
||||||
|
with:
|
||||||
|
DING_TALK_TOKEN: ${{ secrets.DINGDING_BOT_TOKEN }}
|
||||||
|
notify_title: '🔥 @${{ github.event.discussion.user.login }} 创建了讨论:${{ github.event.discussion.title }}'
|
||||||
|
notify_body: '### 🔥 @${{ github.event.discussion.user.login }} 创建了讨论:[${{ github.event.discussion.title }}](${{ github.event.discussion.html_url }}) <hr /> ![](https://gw.alipayobjects.com/zos/antfincdn/5Cl2G7JjF/jieping2022-03-20%252520xiawu11.06.04.png)'
|
||||||
|
notify_footer: '> 💬 欢迎前往 GitHub 进行讨论,社区可能需要你的帮助。'
|
||||||
|
at_all: false # whether to ding everybody
|
@ -89,4 +89,18 @@ describe('AutoComplete', () => {
|
|||||||
);
|
);
|
||||||
expect(container.querySelector('input').classList.contains('custom')).toBeTruthy();
|
expect(container.querySelector('input').classList.contains('custom')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show warning when use dropdownClassName', () => {
|
||||||
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
render(
|
||||||
|
<AutoComplete dropdownClassName="myCustomClassName">
|
||||||
|
<AutoComplete.Option value="111">111</AutoComplete.Option>
|
||||||
|
<AutoComplete.Option value="222">222</AutoComplete.Option>
|
||||||
|
</AutoComplete>,
|
||||||
|
);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
|
'Warning: [antd: AutoComplete] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
errorSpy.mockRestore();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -66,7 +66,7 @@ const options = [
|
|||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
dropdownClassName="certain-category-search-dropdown"
|
popupClassName="certain-category-search-dropdown"
|
||||||
dropdownMatchSelectWidth={500}
|
dropdownMatchSelectWidth={500}
|
||||||
style={{ width: 250 }}
|
style={{ width: 250 }}
|
||||||
options={options}
|
options={options}
|
||||||
|
@ -31,7 +31,7 @@ The differences with Select are:
|
|||||||
| defaultOpen | Initial open state of dropdown | boolean | - | |
|
| defaultOpen | Initial open state of dropdown | boolean | - | |
|
||||||
| defaultValue | Initial selected option | string | - | |
|
| defaultValue | Initial selected option | string | - | |
|
||||||
| disabled | Whether disabled select | boolean | false | |
|
| disabled | Whether disabled select | boolean | false | |
|
||||||
| dropdownClassName | The className of dropdown menu | string | - | |
|
| popupClassName | The className of dropdown menu | string | - | 4.23.0 |
|
||||||
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | true | |
|
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | true | |
|
||||||
| filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns true, the option will be included in the filtered set; Otherwise, it will be excluded | boolean \| function(inputValue, option) | true | |
|
| filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns true, the option will be included in the filtered set; Otherwise, it will be excluded | boolean \| function(inputValue, option) | true | |
|
||||||
| notFoundContent | Specify content to show when no result matches | string | `Not Found` | |
|
| notFoundContent | Specify content to show when no result matches | string | `Not Found` | |
|
||||||
|
@ -41,6 +41,12 @@ export interface AutoCompleteProps<
|
|||||||
> {
|
> {
|
||||||
dataSource?: DataSourceItemType[];
|
dataSource?: DataSourceItemType[];
|
||||||
status?: InputStatus;
|
status?: InputStatus;
|
||||||
|
/**
|
||||||
|
* @deprecated `dropdownClassName` is deprecated which will be removed in next major version.
|
||||||
|
* Please use `popupClassName` instead.
|
||||||
|
*/
|
||||||
|
dropdownClassName?: string;
|
||||||
|
popupClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSelectOptionOrSelectOptGroup(child: any): Boolean {
|
function isSelectOptionOrSelectOptGroup(child: any): Boolean {
|
||||||
@ -51,7 +57,14 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
|
|||||||
props,
|
props,
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const { prefixCls: customizePrefixCls, className, children, dataSource } = props;
|
const {
|
||||||
|
prefixCls: customizePrefixCls,
|
||||||
|
className,
|
||||||
|
popupClassName,
|
||||||
|
dropdownClassName,
|
||||||
|
children,
|
||||||
|
dataSource,
|
||||||
|
} = props;
|
||||||
const childNodes: React.ReactElement[] = toArray(children);
|
const childNodes: React.ReactElement[] = toArray(children);
|
||||||
|
|
||||||
// ============================= Input =============================
|
// ============================= Input =============================
|
||||||
@ -112,6 +125,12 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
|
|||||||
'`dataSource` is deprecated, please use `options` instead.',
|
'`dataSource` is deprecated, please use `options` instead.',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
warning(
|
||||||
|
!dropdownClassName,
|
||||||
|
'AutoComplete',
|
||||||
|
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
|
||||||
warning(
|
warning(
|
||||||
!customizeInput || !('size' in props),
|
!customizeInput || !('size' in props),
|
||||||
'AutoComplete',
|
'AutoComplete',
|
||||||
@ -128,6 +147,7 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
{...omit(props, ['dataSource'])}
|
{...omit(props, ['dataSource'])}
|
||||||
prefixCls={prefixCls}
|
prefixCls={prefixCls}
|
||||||
|
dropdownClassName={popupClassName || dropdownClassName}
|
||||||
className={classNames(`${prefixCls}-auto-complete`, className)}
|
className={classNames(`${prefixCls}-auto-complete`, className)}
|
||||||
mode={Select.SECRET_COMBOBOX_MODE_DO_NOT_USE as any}
|
mode={Select.SECRET_COMBOBOX_MODE_DO_NOT_USE as any}
|
||||||
{...{
|
{...{
|
||||||
|
@ -32,7 +32,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/qtJm4yt45/AutoComplete.svg
|
|||||||
| defaultOpen | 是否默认展开下拉菜单 | boolean | - | |
|
| defaultOpen | 是否默认展开下拉菜单 | boolean | - | |
|
||||||
| defaultValue | 指定默认选中的条目 | string | - | |
|
| defaultValue | 指定默认选中的条目 | string | - | |
|
||||||
| disabled | 是否禁用 | boolean | false | |
|
| disabled | 是否禁用 | boolean | false | |
|
||||||
| dropdownClassName | 下拉菜单的 className 属性 | string | - | |
|
| popupClassName | 下拉菜单的 className 属性 | string | - | 4.23.0 |
|
||||||
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。false 时会关闭虚拟滚动 | boolean \| number | true | |
|
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。false 时会关闭虚拟滚动 | boolean \| number | true | |
|
||||||
| filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 true,反之则返回 false | boolean \| function(inputValue, option) | true | |
|
| filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 true,反之则返回 false | boolean \| function(inputValue, option) | true | |
|
||||||
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |
|
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |
|
||||||
|
@ -540,7 +540,7 @@ describe('Cascader', () => {
|
|||||||
it('popupClassName', () => {
|
it('popupClassName', () => {
|
||||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<Cascader open popupPlacement="bottomLeft" popupClassName="mock-cls" />,
|
<Cascader open popupPlacement="bottomLeft" dropdownClassName="mock-cls" />,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(container.querySelector('.mock-cls')).toBeTruthy();
|
expect(container.querySelector('.mock-cls')).toBeTruthy();
|
||||||
@ -549,7 +549,7 @@ describe('Cascader', () => {
|
|||||||
expect(global.triggerProps.popupPlacement).toEqual('bottomLeft');
|
expect(global.triggerProps.popupPlacement).toEqual('bottomLeft');
|
||||||
|
|
||||||
expect(errorSpy).toHaveBeenCalledWith(
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
'Warning: [antd: Cascader] `popupClassName` is deprecated. Please use `dropdownClassName` instead.',
|
'Warning: [antd: Cascader] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
);
|
);
|
||||||
|
|
||||||
errorSpy.mockRestore();
|
errorSpy.mockRestore();
|
||||||
|
@ -30,7 +30,7 @@ Cascade selection box.
|
|||||||
| defaultValue | Initial selected value | string\[] \| number\[] | \[] | |
|
| defaultValue | Initial selected value | string\[] \| number\[] | \[] | |
|
||||||
| disabled | Whether disabled select | boolean | false | |
|
| disabled | Whether disabled select | boolean | false | |
|
||||||
| displayRender | The render function of displaying selected options | (label, selectedOptions) => ReactNode | label => label.join(`/`) | `multiple`: 4.18.0 |
|
| displayRender | The render function of displaying selected options | (label, selectedOptions) => ReactNode | label => label.join(`/`) | `multiple`: 4.18.0 |
|
||||||
| dropdownClassName | The additional className of popup overlay | string | - | 4.17.0 |
|
| popupClassName | The additional className of popup overlay | string | - | 4.23.0 |
|
||||||
| dropdownRender | Customize dropdown content | (menus: ReactNode) => ReactNode | - | 4.4.0 |
|
| dropdownRender | Customize dropdown content | (menus: ReactNode) => ReactNode | - | 4.4.0 |
|
||||||
| expandIcon | Customize the current item expand icon | ReactNode | - | 4.4.0 |
|
| expandIcon | Customize the current item expand icon | ReactNode | - | 4.4.0 |
|
||||||
| expandTrigger | expand current item when click or hover, one of `click` `hover` | string | `click` | |
|
| expandTrigger | expand current item when click or hover, one of `click` `hover` | string | `click` | |
|
||||||
|
@ -112,6 +112,11 @@ export type CascaderProps<DataNodeType> = UnionCascaderProps & {
|
|||||||
suffixIcon?: React.ReactNode;
|
suffixIcon?: React.ReactNode;
|
||||||
options?: DataNodeType[];
|
options?: DataNodeType[];
|
||||||
status?: InputStatus;
|
status?: InputStatus;
|
||||||
|
/**
|
||||||
|
* @deprecated `dropdownClassName` is deprecated which will be removed in next major
|
||||||
|
* version.Please use `popupClassName` instead.
|
||||||
|
*/
|
||||||
|
dropdownClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface CascaderRef {
|
export interface CascaderRef {
|
||||||
@ -168,9 +173,9 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
|
|||||||
|
|
||||||
// =================== Warning =====================
|
// =================== Warning =====================
|
||||||
warning(
|
warning(
|
||||||
popupClassName === undefined,
|
!dropdownClassName,
|
||||||
'Cascader',
|
'Cascader',
|
||||||
'`popupClassName` is deprecated. Please use `dropdownClassName` instead.',
|
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
);
|
);
|
||||||
|
|
||||||
warning(
|
warning(
|
||||||
@ -192,7 +197,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
|
|||||||
|
|
||||||
// =================== Dropdown ====================
|
// =================== Dropdown ====================
|
||||||
const mergedDropdownClassName = classNames(
|
const mergedDropdownClassName = classNames(
|
||||||
dropdownClassName || popupClassName,
|
popupClassName || dropdownClassName,
|
||||||
`${cascaderPrefixCls}-dropdown`,
|
`${cascaderPrefixCls}-dropdown`,
|
||||||
{
|
{
|
||||||
[`${cascaderPrefixCls}-dropdown-rtl`]: mergedDirection === 'rtl',
|
[`${cascaderPrefixCls}-dropdown-rtl`]: mergedDirection === 'rtl',
|
||||||
|
@ -31,7 +31,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
|
|||||||
| defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | |
|
| defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | |
|
||||||
| disabled | 禁用 | boolean | false | |
|
| disabled | 禁用 | boolean | false | |
|
||||||
| displayRender | 选择后展示的渲染函数 | (label, selectedOptions) => ReactNode | label => label.join(`/`) | `multiple`: 4.18.0 |
|
| displayRender | 选择后展示的渲染函数 | (label, selectedOptions) => ReactNode | label => label.join(`/`) | `multiple`: 4.18.0 |
|
||||||
| dropdownClassName | 自定义浮层类名 | string | - | 4.17.0 |
|
| popupClassName | 自定义浮层类名 | string | - | 4.23.0 |
|
||||||
| dropdownRender | 自定义下拉框内容 | (menus: ReactNode) => ReactNode | - | 4.4.0 |
|
| dropdownRender | 自定义下拉框内容 | (menus: ReactNode) => ReactNode | - | 4.4.0 |
|
||||||
| expandIcon | 自定义次级菜单展开图标 | ReactNode | - | 4.4.0 |
|
| expandIcon | 自定义次级菜单展开图标 | ReactNode | - | 4.4.0 |
|
||||||
| expandTrigger | 次级菜单的展开方式,可选 'click' 和 'hover' | string | `click` | |
|
| expandTrigger | 次级菜单的展开方式,可选 'click' 和 'hover' | string | `click` | |
|
||||||
|
@ -188,6 +188,20 @@ describe('DatePicker', () => {
|
|||||||
).toBe(60);
|
).toBe(60);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('DatePicker should show warning when use dropdownClassName', () => {
|
||||||
|
mount(<DatePicker dropdownClassName="myCustomClassName" />);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
|
'Warning: [antd: DatePicker] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('RangePicker should show warning when use dropdownClassName', () => {
|
||||||
|
mount(<DatePicker.RangePicker dropdownClassName="myCustomClassName" />);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
|
'Warning: [antd: RangePicker] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('DatePicker.RangePicker with defaultPickerValue and showTime', () => {
|
it('DatePicker.RangePicker with defaultPickerValue and showTime', () => {
|
||||||
const startDate = dayjs('1982-02-12');
|
const startDate = dayjs('1982-02-12');
|
||||||
const endDate = dayjs('1982-02-22');
|
const endDate = dayjs('1982-02-22');
|
||||||
|
@ -18,6 +18,7 @@ import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
|
|||||||
import enUS from '../locale/en_US';
|
import enUS from '../locale/en_US';
|
||||||
import { getRangePlaceholder, transPlacement2DropdownAlign } from '../util';
|
import { getRangePlaceholder, transPlacement2DropdownAlign } from '../util';
|
||||||
import type { CommonPickerMethods, PickerComponentClass } from './interface';
|
import type { CommonPickerMethods, PickerComponentClass } from './interface';
|
||||||
|
import warning from '../../_util/warning';
|
||||||
|
|
||||||
import useStyle from '../style';
|
import useStyle from '../style';
|
||||||
|
|
||||||
@ -28,7 +29,14 @@ export default function generateRangePicker<DateType>(
|
|||||||
|
|
||||||
const RangePicker = forwardRef<
|
const RangePicker = forwardRef<
|
||||||
InternalRangePickerProps | CommonPickerMethods,
|
InternalRangePickerProps | CommonPickerMethods,
|
||||||
RangePickerProps<DateType>
|
RangePickerProps<DateType> & {
|
||||||
|
/**
|
||||||
|
* @deprecated `dropdownClassName` is deprecated which will be removed in next major
|
||||||
|
* version.Please use `popupClassName` instead.
|
||||||
|
*/
|
||||||
|
dropdownClassName: string;
|
||||||
|
popupClassName?: string;
|
||||||
|
}
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const {
|
const {
|
||||||
prefixCls: customizePrefixCls,
|
prefixCls: customizePrefixCls,
|
||||||
@ -39,8 +47,9 @@ export default function generateRangePicker<DateType>(
|
|||||||
disabled: customDisabled,
|
disabled: customDisabled,
|
||||||
bordered = true,
|
bordered = true,
|
||||||
placeholder,
|
placeholder,
|
||||||
status: customStatus,
|
popupClassName,
|
||||||
dropdownClassName,
|
dropdownClassName,
|
||||||
|
status: customStatus,
|
||||||
...restProps
|
...restProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@ -59,6 +68,12 @@ export default function generateRangePicker<DateType>(
|
|||||||
...(picker === 'time' ? getTimeProps({ format, ...props, picker }) : {}),
|
...(picker === 'time' ? getTimeProps({ format, ...props, picker }) : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
warning(
|
||||||
|
!dropdownClassName,
|
||||||
|
'RangePicker',
|
||||||
|
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
|
||||||
// ===================== Size =====================
|
// ===================== Size =====================
|
||||||
const size = React.useContext(SizeContext);
|
const size = React.useContext(SizeContext);
|
||||||
const mergedSize = customizeSize || size;
|
const mergedSize = customizeSize || size;
|
||||||
@ -128,7 +143,7 @@ export default function generateRangePicker<DateType>(
|
|||||||
generateConfig={generateConfig}
|
generateConfig={generateConfig}
|
||||||
components={Components}
|
components={Components}
|
||||||
direction={direction}
|
direction={direction}
|
||||||
dropdownClassName={classNames(hashId, dropdownClassName)}
|
dropdownClassName={classNames(hashId, popupClassName || dropdownClassName)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -27,8 +27,13 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
|
|||||||
type DatePickerProps = PickerProps<DateType> & {
|
type DatePickerProps = PickerProps<DateType> & {
|
||||||
status?: InputStatus;
|
status?: InputStatus;
|
||||||
hashId?: string;
|
hashId?: string;
|
||||||
|
/**
|
||||||
|
* @deprecated `dropdownClassName` is deprecated which will be removed in next major
|
||||||
|
* version.Please use `popupClassName` instead.
|
||||||
|
*/
|
||||||
|
dropdownClassName?: string;
|
||||||
|
popupClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getPicker<InnerPickerProps extends DatePickerProps>(
|
function getPicker<InnerPickerProps extends DatePickerProps>(
|
||||||
picker?: PickerMode,
|
picker?: PickerMode,
|
||||||
displayName?: string,
|
displayName?: string,
|
||||||
@ -43,18 +48,13 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
|
|||||||
bordered = true,
|
bordered = true,
|
||||||
placement,
|
placement,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
popupClassName,
|
||||||
|
dropdownClassName,
|
||||||
disabled: customDisabled,
|
disabled: customDisabled,
|
||||||
status: customStatus,
|
status: customStatus,
|
||||||
dropdownClassName,
|
|
||||||
...restProps
|
...restProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
warning(
|
|
||||||
picker !== 'quarter',
|
|
||||||
displayName!,
|
|
||||||
`DatePicker.${displayName} is legacy usage. Please use DatePicker[picker='${picker}'] directly.`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext);
|
const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext);
|
||||||
const prefixCls = getPrefixCls('picker', customizePrefixCls);
|
const prefixCls = getPrefixCls('picker', customizePrefixCls);
|
||||||
const innerRef = React.useRef<RCPicker<DateType>>(null);
|
const innerRef = React.useRef<RCPicker<DateType>>(null);
|
||||||
@ -86,6 +86,18 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
|
|||||||
};
|
};
|
||||||
const rootPrefixCls = getPrefixCls();
|
const rootPrefixCls = getPrefixCls();
|
||||||
|
|
||||||
|
// =================== Warning =====================
|
||||||
|
warning(
|
||||||
|
picker !== 'quarter',
|
||||||
|
displayName!,
|
||||||
|
`DatePicker.${displayName} is legacy usage. Please use DatePicker[picker='${picker}'] directly.`,
|
||||||
|
);
|
||||||
|
|
||||||
|
warning(
|
||||||
|
!dropdownClassName,
|
||||||
|
'DatePicker',
|
||||||
|
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
// ===================== Size =====================
|
// ===================== Size =====================
|
||||||
const size = React.useContext(SizeContext);
|
const size = React.useContext(SizeContext);
|
||||||
const mergedSize = customizeSize || size;
|
const mergedSize = customizeSize || size;
|
||||||
@ -146,7 +158,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
|
|||||||
components={Components}
|
components={Components}
|
||||||
direction={direction}
|
direction={direction}
|
||||||
disabled={mergedDisabled}
|
disabled={mergedDisabled}
|
||||||
dropdownClassName={classNames(hashId, dropdownClassName)}
|
dropdownClassName={classNames(hashId, popupClassName || dropdownClassName)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -59,7 +59,7 @@ The following APIs are shared by DatePicker, RangePicker.
|
|||||||
| dateRender | Custom rendering function for date cells | function(currentDate: dayjs, today: dayjs) => React.ReactNode | - | |
|
| dateRender | Custom rendering function for date cells | function(currentDate: dayjs, today: dayjs) => React.ReactNode | - | |
|
||||||
| disabled | Determine whether the DatePicker is disabled | boolean | false | |
|
| disabled | Determine whether the DatePicker is disabled | boolean | false | |
|
||||||
| disabledDate | Specify the date that cannot be selected | (currentDate: dayjs) => boolean | - | |
|
| disabledDate | Specify the date that cannot be selected | (currentDate: dayjs) => boolean | - | |
|
||||||
| dropdownClassName | To customize the className of the popup calendar | string | - | |
|
| popupClassName | To customize the className of the popup calendar | string | - | 4.23.0 |
|
||||||
| getPopupContainer | To set the container of the floating layer, while the default is to create a `div` element in `body` | function(trigger) | - | |
|
| getPopupContainer | To set the container of the floating layer, while the default is to create a `div` element in `body` | function(trigger) | - | |
|
||||||
| inputReadOnly | Set the `readonly` attribute of the input tag (avoids virtual keyboard on touch devices) | boolean | false | |
|
| inputReadOnly | Set the `readonly` attribute of the input tag (avoids virtual keyboard on touch devices) | boolean | false | |
|
||||||
| locale | Localization configuration | object | [default](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | |
|
| locale | Localization configuration | object | [default](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | |
|
||||||
|
@ -60,7 +60,7 @@ import locale from 'antd/es/locale/zh_CN';
|
|||||||
| dateRender | 自定义日期单元格的内容 | function(currentDate: dayjs, today: dayjs) => React.ReactNode | - | |
|
| dateRender | 自定义日期单元格的内容 | function(currentDate: dayjs, today: dayjs) => React.ReactNode | - | |
|
||||||
| disabled | 禁用 | boolean | false | |
|
| disabled | 禁用 | boolean | false | |
|
||||||
| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | - | |
|
| disabledDate | 不可选择的日期 | (currentDate: dayjs) => boolean | - | |
|
||||||
| dropdownClassName | 额外的弹出日历 className | string | - | |
|
| popupClassName | 额外的弹出日历 className | string | - | 4.23.0 |
|
||||||
| getPopupContainer | 定义浮层的容器,默认为 body 上新建 div | function(trigger) | - | |
|
| getPopupContainer | 定义浮层的容器,默认为 body 上新建 div | function(trigger) | - | |
|
||||||
| inputReadOnly | 设置输入框为只读(避免在移动设备上打开虚拟键盘) | boolean | false | |
|
| inputReadOnly | 设置输入框为只读(避免在移动设备上打开虚拟键盘) | boolean | false | |
|
||||||
| locale | 国际化配置 | object | [默认配置](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | |
|
| locale | 国际化配置 | object | [默认配置](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | |
|
||||||
|
@ -69,7 +69,7 @@ const genDrawerStyle: GenerateStyle<DrawerToken> = (token: DrawerToken) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Placement
|
// Placement
|
||||||
[`&-left ${wrapperCls}`]: {
|
[`&-left > ${wrapperCls}`]: {
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: {
|
left: {
|
||||||
@ -78,7 +78,7 @@ const genDrawerStyle: GenerateStyle<DrawerToken> = (token: DrawerToken) => {
|
|||||||
},
|
},
|
||||||
boxShadow: token.boxShadowDrawerRight,
|
boxShadow: token.boxShadowDrawerRight,
|
||||||
},
|
},
|
||||||
[`&-right ${wrapperCls}`]: {
|
[`&-right > ${wrapperCls}`]: {
|
||||||
top: 0,
|
top: 0,
|
||||||
right: {
|
right: {
|
||||||
_skip_check_: true,
|
_skip_check_: true,
|
||||||
@ -87,12 +87,12 @@ const genDrawerStyle: GenerateStyle<DrawerToken> = (token: DrawerToken) => {
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
boxShadow: token.boxShadowDrawerLeft,
|
boxShadow: token.boxShadowDrawerLeft,
|
||||||
},
|
},
|
||||||
[`&-top ${wrapperCls}`]: {
|
[`&-top > ${wrapperCls}`]: {
|
||||||
top: 0,
|
top: 0,
|
||||||
insetInline: 0,
|
insetInline: 0,
|
||||||
boxShadow: token.boxShadowDrawerDown,
|
boxShadow: token.boxShadowDrawerDown,
|
||||||
},
|
},
|
||||||
[`&-bottom ${wrapperCls}`]: {
|
[`&-bottom > ${wrapperCls}`]: {
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
insetInline: 0,
|
insetInline: 0,
|
||||||
boxShadow: token.boxShadowDrawerUp,
|
boxShadow: token.boxShadowDrawerUp,
|
||||||
|
@ -4246,7 +4246,7 @@ exports[`renders ./components/dropdown/demo/dropdown-button.md extend context co
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-space-item"
|
class="ant-space-item"
|
||||||
style="padding-bottom:8px"
|
style="margin-right:8px;padding-bottom:8px"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||||
@ -4528,6 +4528,288 @@ exports[`renders ./components/dropdown/demo/dropdown-button.md extend context co
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-space-item"
|
||||||
|
style="padding-bottom:8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-btn-group ant-dropdown-button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-default ant-btn-dangerous"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Danger
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-default ant-btn-icon-only ant-btn-dangerous ant-dropdown-trigger"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="ellipsis"
|
||||||
|
class="anticon anticon-ellipsis"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="ellipsis"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ant-dropdown"
|
||||||
|
style="opacity:0"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical"
|
||||||
|
data-menu-list="true"
|
||||||
|
role="menu"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="ant-dropdown-menu-item"
|
||||||
|
role="menuitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="user"
|
||||||
|
class="anticon anticon-user ant-dropdown-menu-item-icon"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="user"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M858.5 763.6a374 374 0 00-80.6-119.5 375.63 375.63 0 00-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 00-80.6 119.5A371.7 371.7 0 00136 901.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 008-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-dropdown-menu-title-content"
|
||||||
|
>
|
||||||
|
1st menu item
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip ant-dropdown-menu-inline-collapsed-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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<li
|
||||||
|
class="ant-dropdown-menu-item"
|
||||||
|
role="menuitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="user"
|
||||||
|
class="anticon anticon-user ant-dropdown-menu-item-icon"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="user"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M858.5 763.6a374 374 0 00-80.6-119.5 375.63 375.63 0 00-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 00-80.6 119.5A371.7 371.7 0 00136 901.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 008-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-dropdown-menu-title-content"
|
||||||
|
>
|
||||||
|
2nd menu item
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip ant-dropdown-menu-inline-collapsed-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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<li
|
||||||
|
class="ant-dropdown-menu-item"
|
||||||
|
role="menuitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="user"
|
||||||
|
class="anticon anticon-user ant-dropdown-menu-item-icon"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="user"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M858.5 763.6a374 374 0 00-80.6-119.5 375.63 375.63 0 00-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 00-80.6 119.5A371.7 371.7 0 00136 901.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 008-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-dropdown-menu-title-content"
|
||||||
|
>
|
||||||
|
3rd menu item
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip ant-dropdown-menu-inline-collapsed-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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
style="display:none"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip ant-dropdown-menu-inline-collapsed-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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip ant-dropdown-menu-inline-collapsed-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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip ant-dropdown-menu-inline-collapsed-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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -334,7 +334,7 @@ exports[`renders ./components/dropdown/demo/dropdown-button.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ant-space-item"
|
class="ant-space-item"
|
||||||
style="padding-bottom:8px"
|
style="margin-right:8px;padding-bottom:8px"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
class="ant-btn ant-btn-default ant-dropdown-trigger"
|
||||||
@ -375,6 +375,47 @@ exports[`renders ./components/dropdown/demo/dropdown-button.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-space-item"
|
||||||
|
style="padding-bottom:8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-btn-group ant-dropdown-button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-default ant-btn-dangerous"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Danger
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-default ant-btn-icon-only ant-btn-dangerous ant-dropdown-trigger"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="ellipsis"
|
||||||
|
class="anticon anticon-ellipsis"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="ellipsis"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -82,6 +82,9 @@ const App: React.FC = () => (
|
|||||||
</Space>
|
</Space>
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
<Dropdown.Button danger onClick={handleButtonClick} overlay={menu}>
|
||||||
|
Danger
|
||||||
|
</Dropdown.Button>
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ export type DropdownButtonType = 'default' | 'primary' | 'ghost' | 'dashed' | 'l
|
|||||||
export interface DropdownButtonProps extends ButtonGroupProps, DropdownProps {
|
export interface DropdownButtonProps extends ButtonGroupProps, DropdownProps {
|
||||||
type?: DropdownButtonType;
|
type?: DropdownButtonType;
|
||||||
htmlType?: ButtonHTMLType;
|
htmlType?: ButtonHTMLType;
|
||||||
|
danger?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
loading?: ButtonProps['loading'];
|
loading?: ButtonProps['loading'];
|
||||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
@ -41,6 +42,7 @@ const DropdownButton: DropdownButtonInterface = props => {
|
|||||||
const {
|
const {
|
||||||
prefixCls: customizePrefixCls,
|
prefixCls: customizePrefixCls,
|
||||||
type = 'default',
|
type = 'default',
|
||||||
|
danger,
|
||||||
disabled,
|
disabled,
|
||||||
loading,
|
loading,
|
||||||
onClick,
|
onClick,
|
||||||
@ -97,6 +99,7 @@ const DropdownButton: DropdownButtonInterface = props => {
|
|||||||
const leftButton = (
|
const leftButton = (
|
||||||
<Button
|
<Button
|
||||||
type={type}
|
type={type}
|
||||||
|
danger={danger}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
@ -108,7 +111,7 @@ const DropdownButton: DropdownButtonInterface = props => {
|
|||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
const rightButton = <Button type={type} icon={icon} />;
|
const rightButton = <Button type={type} danger={danger} icon={icon} />;
|
||||||
|
|
||||||
const [leftButtonToRender, rightButtonToRender] = buttonsRender!([leftButton, rightButton]);
|
const [leftButtonToRender, rightButtonToRender] = buttonsRender!([leftButton, rightButton]);
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ You should use [Menu](/components/menu/) as `overlay`. The menu items and divide
|
|||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| buttonsRender | Custom buttons inside Dropdown.Button | (buttons: ReactNode\[]) => ReactNode\[] | - | |
|
| buttonsRender | Custom buttons inside Dropdown.Button | (buttons: ReactNode\[]) => ReactNode\[] | - | |
|
||||||
| loading | Set the loading status of button | boolean \| { delay: number } | false | |
|
| loading | Set the loading status of button | boolean \| { delay: number } | false | |
|
||||||
|
| danger | Set the danger status of button | boolean | - | 4.23.0 |
|
||||||
| disabled | Whether the dropdown menu is disabled | boolean | - | |
|
| disabled | Whether the dropdown menu is disabled | boolean | - | |
|
||||||
| icon | Icon (appears on the right) | ReactNode | - | |
|
| icon | Icon (appears on the right) | ReactNode | - | |
|
||||||
| overlay | The dropdown menu | [Menu](/components/menu) | - | |
|
| overlay | The dropdown menu | [Menu](/components/menu) | - | |
|
||||||
|
@ -46,6 +46,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
|
|||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| buttonsRender | 自定义左右两个按钮 | (buttons: ReactNode\[]) => ReactNode\[] | - | |
|
| buttonsRender | 自定义左右两个按钮 | (buttons: ReactNode\[]) => ReactNode\[] | - | |
|
||||||
| loading | 设置按钮载入状态 | boolean \| { delay: number } | false | |
|
| loading | 设置按钮载入状态 | boolean \| { delay: number } | false | |
|
||||||
|
| danger | 设置危险按钮 | boolean | - | 4.23.0 |
|
||||||
| disabled | 菜单是否禁用 | boolean | - | |
|
| disabled | 菜单是否禁用 | boolean | - | |
|
||||||
| icon | 右侧的 icon | ReactNode | - | |
|
| icon | 右侧的 icon | ReactNode | - | |
|
||||||
| overlay | 菜单 | [Menu](/components/menu/) | - | |
|
| overlay | 菜单 | [Menu](/components/menu/) | - | |
|
||||||
|
@ -121,7 +121,7 @@ export interface InputProps
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
status?: InputStatus;
|
status?: InputStatus;
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
[key: `data-${string}`]: string;
|
[key: `data-${string}`]: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Input = forwardRef<InputRef, InputProps>((props, ref) => {
|
const Input = forwardRef<InputRef, InputProps>((props, ref) => {
|
||||||
|
@ -18,7 +18,7 @@ import { fixControlledValue, resolveOnChange, triggerFocus } from './Input';
|
|||||||
import useStyle from './style';
|
import useStyle from './style';
|
||||||
|
|
||||||
interface ShowCountProps {
|
interface ShowCountProps {
|
||||||
formatter: (args: { count: number; maxLength?: number }) => string;
|
formatter: (args: { value: string; count: number; maxLength?: number }) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixEmojiLength(value: string, maxLength: number) {
|
function fixEmojiLength(value: string, maxLength: number) {
|
||||||
@ -238,7 +238,7 @@ const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
|
|||||||
|
|
||||||
let dataCount = '';
|
let dataCount = '';
|
||||||
if (typeof showCount === 'object') {
|
if (typeof showCount === 'object') {
|
||||||
dataCount = showCount.formatter({ count: valueLength, maxLength });
|
dataCount = showCount.formatter({ value: val, count: valueLength, maxLength });
|
||||||
} else {
|
} else {
|
||||||
dataCount = `${valueLength}${hasMaxLength ? ` / ${maxLength}` : ''}`;
|
dataCount = `${valueLength}${hasMaxLength ? ` / ${maxLength}` : ''}`;
|
||||||
}
|
}
|
||||||
|
@ -272,12 +272,14 @@ describe('should support showCount', () => {
|
|||||||
const { container } = render(
|
const { container } = render(
|
||||||
<Input
|
<Input
|
||||||
maxLength={5}
|
maxLength={5}
|
||||||
showCount={{ formatter: ({ count, maxLength }) => `${count}, ${maxLength}` }}
|
showCount={{
|
||||||
|
formatter: ({ value, count, maxLength }) => `${value}, ${count}, ${maxLength}`,
|
||||||
|
}}
|
||||||
value="12345"
|
value="12345"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(container.querySelector('input')?.getAttribute('value')).toBe('12345');
|
expect(container.querySelector('input')?.getAttribute('value')).toBe('12345');
|
||||||
expect(container.querySelector('.ant-input-show-count-suffix')?.innerHTML).toBe('5, 5');
|
expect(container.querySelector('.ant-input-show-count-suffix')?.innerHTML).toBe('12345, 5, 5');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -304,13 +304,15 @@ describe('TextArea', () => {
|
|||||||
const { container } = render(
|
const { container } = render(
|
||||||
<TextArea
|
<TextArea
|
||||||
maxLength={5}
|
maxLength={5}
|
||||||
showCount={{ formatter: ({ count, maxLength }) => `${count}, ${maxLength}` }}
|
showCount={{
|
||||||
|
formatter: ({ value, count, maxLength }) => `${value}, ${count}, ${maxLength}`,
|
||||||
|
}}
|
||||||
value="12345"
|
value="12345"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(container.querySelector('textarea').value).toBe('12345');
|
expect(container.querySelector('textarea').value).toBe('12345');
|
||||||
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe(
|
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe(
|
||||||
'5, 5',
|
'12345, 5, 5',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,7 @@ A basic widget for getting the user input is a text field. Keyboard and mouse ca
|
|||||||
| disabled | Whether the input is disabled | boolean | false | |
|
| disabled | Whether the input is disabled | boolean | false | |
|
||||||
| id | The ID for input | string | - | |
|
| id | The ID for input | string | - | |
|
||||||
| maxLength | The max length | number | - | |
|
| maxLength | The max length | number | - | |
|
||||||
| showCount | Whether show text count | boolean \| { formatter: ({ count: number, maxLength?: number }) => ReactNode } | false | 4.18.0 |
|
| showCount | Whether show text count | boolean \| { formatter: (info: { value: string, count: number, maxLength?: number }) => ReactNode } | false | 4.18.0 info.value: 4.23.0 |
|
||||||
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
|
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
|
||||||
| prefix | The prefix icon for the Input | ReactNode | - | |
|
| prefix | The prefix icon for the Input | ReactNode | - | |
|
||||||
| size | The size of the input box. Note: in the context of a form, the `middle` size is used | `large` \| `middle` \| `small` | - | |
|
| size | The size of the input box. Note: in the context of a form, the `middle` size is used | `large` \| `middle` \| `small` | - | |
|
||||||
@ -49,7 +49,7 @@ The rest of the props of Input are exactly the same as the original [input](http
|
|||||||
| bordered | Whether has border style | boolean | true | 4.5.0 |
|
| bordered | Whether has border style | boolean | true | 4.5.0 |
|
||||||
| defaultValue | The initial input content | string | - | |
|
| defaultValue | The initial input content | string | - | |
|
||||||
| maxLength | The max length | number | - | 4.7.0 |
|
| maxLength | The max length | number | - | 4.7.0 |
|
||||||
| showCount | Whether show text count | boolean \| { formatter: ({ count: number, maxLength?: number }) => string } | false | 4.7.0 (formatter: 4.10.0) |
|
| showCount | Whether show text count | boolean \| { formatter: (info: { value: string, count: number, maxLength?: number }) => string } | false | 4.7.0 formatter: 4.10.0 info.value: 4.23.0 |
|
||||||
| value | The input content value | string | - | |
|
| value | The input content value | string | - | |
|
||||||
| onPressEnter | The callback function that is triggered when Enter key is pressed | function(e) | - | |
|
| onPressEnter | The callback function that is triggered when Enter key is pressed | function(e) | - | |
|
||||||
| onResize | The callback function that is triggered when resize | function({ width, height }) | - | |
|
| onResize | The callback function that is triggered when resize | function({ width, height }) | - | |
|
||||||
|
@ -27,7 +27,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/xS9YEJhfe/Input.svg
|
|||||||
| disabled | 是否禁用状态,默认为 false | boolean | false | |
|
| disabled | 是否禁用状态,默认为 false | boolean | false | |
|
||||||
| id | 输入框的 id | string | - | |
|
| id | 输入框的 id | string | - | |
|
||||||
| maxLength | 最大长度 | number | - | |
|
| maxLength | 最大长度 | number | - | |
|
||||||
| showCount | 是否展示字数 | boolean \| { formatter: ({ count: number, maxLength?: number }) => ReactNode } | false | 4.18.0 |
|
| showCount | 是否展示字数 | boolean \| { formatter: (info: { value: string, count: number, maxLength?: number }) => ReactNode } | false | 4.18.0 info.value: 4.23.0 |
|
||||||
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
|
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
|
||||||
| prefix | 带有前缀图标的 input | ReactNode | - | |
|
| prefix | 带有前缀图标的 input | ReactNode | - | |
|
||||||
| size | 控件大小。注:标准表单内的输入框大小限制为 `middle` | `large` \| `middle` \| `small` | - | |
|
| size | 控件大小。注:标准表单内的输入框大小限制为 `middle` | `large` \| `middle` \| `small` | - | |
|
||||||
@ -50,7 +50,7 @@ Input 的其他属性和 React 自带的 [input](https://reactjs.org/docs/dom-el
|
|||||||
| bordered | 是否有边框 | boolean | true | 4.5.0 |
|
| bordered | 是否有边框 | boolean | true | 4.5.0 |
|
||||||
| defaultValue | 输入框默认内容 | string | - | |
|
| defaultValue | 输入框默认内容 | string | - | |
|
||||||
| maxLength | 内容最大长度 | number | - | 4.7.0 |
|
| maxLength | 内容最大长度 | number | - | 4.7.0 |
|
||||||
| showCount | 是否展示字数 | boolean \| { formatter: ({ count: number, maxLength?: number }) => string } | false | 4.7.0 (formatter: 4.10.0) |
|
| showCount | 是否展示字数 | boolean \| { formatter: (info: { value: string, count: number, maxLength?: number }) => string } | false | 4.7.0 formatter: 4.10.0 info.value: 4.23.0 |
|
||||||
| value | 输入框内容 | string | - | |
|
| value | 输入框内容 | string | - | |
|
||||||
| onPressEnter | 按下回车的回调 | function(e) | - | |
|
| onPressEnter | 按下回车的回调 | function(e) | - | |
|
||||||
| onResize | resize 回调 | function({ width, height }) | - | |
|
| onResize | resize 回调 | function({ width, height }) | - | |
|
||||||
|
@ -1,121 +1,132 @@
|
|||||||
import { mount } from 'enzyme';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Progress from '..';
|
import Progress from '..';
|
||||||
import mountTest from '../../../tests/shared/mountTest';
|
import mountTest from '../../../tests/shared/mountTest';
|
||||||
import rtlTest from '../../../tests/shared/rtlTest';
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
import { handleGradient, sortGradient } from '../Line';
|
import { handleGradient, sortGradient } from '../Line';
|
||||||
import ProgressSteps from '../Steps';
|
import ProgressSteps from '../Steps';
|
||||||
|
import { render } from '../../../tests/utils';
|
||||||
|
|
||||||
describe('Progress', () => {
|
describe('Progress', () => {
|
||||||
mountTest(Progress);
|
mountTest(Progress);
|
||||||
rtlTest(Progress);
|
rtlTest(Progress);
|
||||||
|
|
||||||
it('successPercent should decide the progress status when it exists', () => {
|
it('successPercent should decide the progress status when it exists', () => {
|
||||||
const wrapper = mount(<Progress percent={100} success={{ percent: 50 }} />);
|
const { container: wrapper, rerender } = render(
|
||||||
expect(wrapper.find('.ant-progress-status-success')).toHaveLength(0);
|
<Progress percent={100} success={{ percent: 50 }} />,
|
||||||
|
);
|
||||||
|
expect(wrapper.querySelectorAll('.ant-progress-status-success')).toHaveLength(0);
|
||||||
|
|
||||||
wrapper.setProps({ percent: 50, success: { percent: 100 } });
|
rerender(<Progress percent={50} success={{ percent: 100 }} />);
|
||||||
expect(wrapper.find('.ant-progress-status-success')).toHaveLength(1);
|
expect(wrapper.querySelectorAll('.ant-progress-status-success')).toHaveLength(1);
|
||||||
|
|
||||||
wrapper.setProps({ percent: 100, success: { percent: 0 } });
|
rerender(<Progress percent={100} success={{ percent: 0 }} />);
|
||||||
expect(wrapper.find('.ant-progress-status-success')).toHaveLength(0);
|
expect(wrapper.querySelectorAll('.ant-progress-status-success')).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render out-of-range progress', () => {
|
it('render out-of-range progress', () => {
|
||||||
const wrapper = mount(<Progress percent={120} />);
|
const { container: wrapper } = render(<Progress percent={120} />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render out-of-range progress with info', () => {
|
it('render out-of-range progress with info', () => {
|
||||||
const wrapper = mount(<Progress percent={120} showInfo />);
|
const { container: wrapper } = render(<Progress percent={120} showInfo />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render negative progress', () => {
|
it('render negative progress', () => {
|
||||||
const wrapper = mount(<Progress percent={-20} />);
|
const { container: wrapper } = render(<Progress percent={-20} />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render negative successPercent', () => {
|
it('render negative successPercent', () => {
|
||||||
const wrapper = mount(<Progress percent={50} success={{ percent: -20 }} />);
|
const { container: wrapper } = render(<Progress percent={50} success={{ percent: -20 }} />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render format', () => {
|
it('render format', () => {
|
||||||
const wrapper = mount(
|
const { container: wrapper } = render(
|
||||||
<Progress
|
<Progress
|
||||||
percent={50}
|
percent={50}
|
||||||
success={{ percent: 10 }}
|
success={{ percent: 10 }}
|
||||||
format={(percent, successPercent) => `${percent} ${successPercent}`}
|
format={(percent, successPercent) => `${percent} ${successPercent}`}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render strokeColor', () => {
|
it('render strokeColor', () => {
|
||||||
const wrapper = mount(<Progress type="circle" percent={50} strokeColor="red" />);
|
const { container: wrapper, rerender } = render(
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
<Progress type="circle" percent={50} strokeColor="red" />,
|
||||||
wrapper.setProps({
|
);
|
||||||
strokeColor: {
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
from: '#108ee9',
|
rerender(
|
||||||
to: '#87d068',
|
<Progress
|
||||||
},
|
strokeColor={{
|
||||||
type: 'line',
|
from: '#108ee9',
|
||||||
});
|
to: '#87d068',
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
}}
|
||||||
wrapper.setProps({
|
percent={50}
|
||||||
strokeColor: {
|
type="line"
|
||||||
'0%': '#108ee9',
|
/>,
|
||||||
'100%': '#87d068',
|
);
|
||||||
},
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
rerender(
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
<Progress
|
||||||
|
strokeColor={{
|
||||||
|
'0%': '#108ee9',
|
||||||
|
'100%': '#87d068',
|
||||||
|
}}
|
||||||
|
percent={50}
|
||||||
|
type="line"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render normal progress', () => {
|
it('render normal progress', () => {
|
||||||
const wrapper = mount(<Progress status="normal" />);
|
const { container: wrapper } = render(<Progress status="normal" />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render trailColor progress', () => {
|
it('render trailColor progress', () => {
|
||||||
const wrapper = mount(<Progress status="normal" trailColor="#ffffff" />);
|
const { container: wrapper } = render(<Progress status="normal" trailColor="#ffffff" />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render successColor progress', () => {
|
it('render successColor progress', () => {
|
||||||
const wrapper = mount(
|
const { container: wrapper } = render(
|
||||||
<Progress percent={60} success={{ percent: 30, strokeColor: '#ffffff' }} />,
|
<Progress percent={60} success={{ percent: 30, strokeColor: '#ffffff' }} />,
|
||||||
);
|
);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render successColor progress type="circle"', () => {
|
it('render successColor progress type="circle"', () => {
|
||||||
const wrapper = mount(
|
const { container: wrapper } = render(
|
||||||
<Progress percent={60} type="circle" success={{ percent: 30, strokeColor: '#ffffff' }} />,
|
<Progress percent={60} type="circle" success={{ percent: 30, strokeColor: '#ffffff' }} />,
|
||||||
);
|
);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render successColor progress type="dashboard"', () => {
|
it('render successColor progress type="dashboard"', () => {
|
||||||
const wrapper = mount(
|
const { container: wrapper } = render(
|
||||||
<Progress percent={60} type="dashboard" success={{ percent: 30, strokeColor: '#ffffff' }} />,
|
<Progress percent={60} type="dashboard" success={{ percent: 30, strokeColor: '#ffffff' }} />,
|
||||||
);
|
);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render dashboard zero gapDegree', () => {
|
it('render dashboard zero gapDegree', () => {
|
||||||
const wrapper = mount(<Progress type="dashboard" gapDegree={0} />);
|
const { container: wrapper } = render(<Progress type="dashboard" gapDegree={0} />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render dashboard 295 gapDegree', () => {
|
it('render dashboard 295 gapDegree', () => {
|
||||||
const wrapper = mount(<Progress type="dashboard" gapDegree={295} />);
|
const { container: wrapper } = render(<Progress type="dashboard" gapDegree={295} />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('render dashboard 296 gapDegree', () => {
|
it('render dashboard 296 gapDegree', () => {
|
||||||
const wrapper = mount(<Progress type="dashboard" gapDegree={296} />);
|
const { container: wrapper } = render(<Progress type="dashboard" gapDegree={296} />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('get correct line-gradient', () => {
|
it('get correct line-gradient', () => {
|
||||||
@ -138,74 +149,74 @@ describe('Progress', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should show success status when percent is 100', () => {
|
it('should show success status when percent is 100', () => {
|
||||||
const wrapper = mount(<Progress percent={100} />);
|
const { container: wrapper } = render(<Progress percent={100} />);
|
||||||
expect(wrapper.find('.ant-progress-status-success')).toHaveLength(1);
|
expect(wrapper.querySelectorAll('.ant-progress-status-success')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://github.com/ant-design/ant-design/issues/15950
|
// https://github.com/ant-design/ant-design/issues/15950
|
||||||
it('should show success status when percent is 100 and status is undefined', () => {
|
it('should show success status when percent is 100 and status is undefined', () => {
|
||||||
const wrapper = mount(<Progress percent={100} status={undefined} />);
|
const { container: wrapper } = render(<Progress percent={100} status={undefined} />);
|
||||||
expect(wrapper.find('.ant-progress-status-success')).toHaveLength(1);
|
expect(wrapper.querySelectorAll('.ant-progress-status-success')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://github.com/ant-design/ant-design/pull/15951#discussion_r273062969
|
// https://github.com/ant-design/ant-design/pull/15951#discussion_r273062969
|
||||||
it('should show success status when status is invalid', () => {
|
it('should show success status when status is invalid', () => {
|
||||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
const wrapper = mount(<Progress percent={100} status="invalid" />);
|
const { container: wrapper } = render(<Progress percent={100} status="invalid" />);
|
||||||
expect(wrapper.find('.ant-progress-status-success')).toHaveLength(1);
|
expect(wrapper.querySelectorAll('.ant-progress-status-success')).toHaveLength(1);
|
||||||
errorSpy.mockRestore();
|
errorSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support steps', () => {
|
it('should support steps', () => {
|
||||||
const wrapper = mount(<Progress steps={3} />);
|
const { container: wrapper } = render(<Progress steps={3} />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('steps should be changable', () => {
|
it('steps should be changable', () => {
|
||||||
const wrapper = mount(<Progress steps={5} percent={60} />);
|
const { container: wrapper, rerender } = render(<Progress steps={5} percent={60} />);
|
||||||
expect(wrapper.find('.ant-progress-steps-item-active').length).toBe(3);
|
expect(wrapper.querySelectorAll('.ant-progress-steps-item-active').length).toBe(3);
|
||||||
wrapper.setProps({ percent: 40 });
|
rerender(<Progress steps={5} percent={40} />);
|
||||||
expect(wrapper.find('.ant-progress-steps-item-active').length).toBe(2);
|
expect(wrapper.querySelectorAll('.ant-progress-steps-item-active').length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('steps should be changable when has strokeColor', () => {
|
it('steps should be changable when has strokeColor', () => {
|
||||||
const wrapper = mount(<Progress steps={5} percent={60} strokeColor="#1890ff" />);
|
const { container: wrapper, rerender } = render(
|
||||||
expect(wrapper.find('.ant-progress-steps-item').at(0).getDOMNode().style.backgroundColor).toBe(
|
<Progress steps={5} percent={60} strokeColor="#1890ff" />,
|
||||||
|
);
|
||||||
|
expect(wrapper.querySelectorAll('.ant-progress-steps-item')[0].style.backgroundColor).toBe(
|
||||||
'rgb(24, 144, 255)',
|
'rgb(24, 144, 255)',
|
||||||
);
|
);
|
||||||
wrapper.setProps({ percent: 40 });
|
rerender(<Progress steps={5} percent={40} strokeColor="#1890ff" />);
|
||||||
expect(wrapper.find('.ant-progress-steps-item').at(2).getDOMNode().style.backgroundColor).toBe(
|
expect(wrapper.querySelectorAll('.ant-progress-steps-item')[2].style.backgroundColor).toBe('');
|
||||||
'',
|
expect(wrapper.querySelectorAll('.ant-progress-steps-item')[1].style.backgroundColor).toBe(
|
||||||
);
|
|
||||||
expect(wrapper.find('.ant-progress-steps-item').at(1).getDOMNode().style.backgroundColor).toBe(
|
|
||||||
'rgb(24, 144, 255)',
|
'rgb(24, 144, 255)',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('steps should support trailColor', () => {
|
it('steps should support trailColor', () => {
|
||||||
const wrapper = mount(<Progress steps={5} percent={20} trailColor="#1890ee" />);
|
const { container: wrapper } = render(<Progress steps={5} percent={20} trailColor="#1890ee" />);
|
||||||
expect(wrapper.find('.ant-progress-steps-item').at(1).getDOMNode().style.backgroundColor).toBe(
|
expect(wrapper.querySelectorAll('.ant-progress-steps-item')[1].style.backgroundColor).toBe(
|
||||||
'rgb(24, 144, 238)',
|
'rgb(24, 144, 238)',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display correct step', () => {
|
it('should display correct step', () => {
|
||||||
const wrapper = mount(<Progress steps={9} percent={22.22} />);
|
const { container: wrapper, rerender } = render(<Progress steps={9} percent={22.22} />);
|
||||||
expect(wrapper.find('.ant-progress-steps-item-active').length).toBe(2);
|
expect(wrapper.querySelectorAll('.ant-progress-steps-item-active').length).toBe(2);
|
||||||
wrapper.setProps({ percent: 33.33 });
|
rerender(<Progress steps={9} percent={33.33} />);
|
||||||
expect(wrapper.find('.ant-progress-steps-item-active').length).toBe(3);
|
expect(wrapper.querySelectorAll('.ant-progress-steps-item-active').length).toBe(3);
|
||||||
wrapper.setProps({ percent: 44.44 });
|
rerender(<Progress steps={9} percent={44.44} />);
|
||||||
expect(wrapper.find('.ant-progress-steps-item-active').length).toBe(4);
|
expect(wrapper.querySelectorAll('.ant-progress-steps-item-active').length).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('steps should have default percent 0', () => {
|
it('steps should have default percent 0', () => {
|
||||||
const wrapper = mount(<ProgressSteps />);
|
const { container: wrapper } = render(<ProgressSteps />);
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
expect(wrapper.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warnning if use `progress` in success', () => {
|
it('should warnning if use `progress` in success', () => {
|
||||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
mount(<Progress percent={60} success={{ progress: 30 }} />);
|
render(<Progress percent={60} success={{ progress: 30 }} />);
|
||||||
expect(errorSpy).toHaveBeenCalledWith(
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
'Warning: [antd: Progress] `success.progress` is deprecated. Please use `success.percent` instead.',
|
'Warning: [antd: Progress] `success.progress` is deprecated. Please use `success.percent` instead.',
|
||||||
);
|
);
|
||||||
@ -213,7 +224,7 @@ describe('Progress', () => {
|
|||||||
|
|
||||||
it('should warnning if use `progress` in success in type Circle', () => {
|
it('should warnning if use `progress` in success in type Circle', () => {
|
||||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
mount(<Progress percent={60} success={{ progress: 30 }} type="circle" />);
|
render(<Progress percent={60} success={{ progress: 30 }} type="circle" />);
|
||||||
expect(errorSpy).toHaveBeenCalledWith(
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
'Warning: [antd: Progress] `success.progress` is deprecated. Please use `success.percent` instead.',
|
'Warning: [antd: Progress] `success.progress` is deprecated. Please use `success.percent` instead.',
|
||||||
);
|
);
|
||||||
@ -223,8 +234,10 @@ describe('Progress', () => {
|
|||||||
describe('github issues', () => {
|
describe('github issues', () => {
|
||||||
it('"Rendered more hooks than during the previous render"', () => {
|
it('"Rendered more hooks than during the previous render"', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
const wrapper = mount(<Progress percent={60} success={{ percent: 0 }} type="circle" />);
|
const { rerender } = render(
|
||||||
wrapper.setProps({ success: { percent: 10 } });
|
<Progress percent={60} success={{ percent: 0 }} type="circle" />,
|
||||||
|
);
|
||||||
|
rerender(<Progress percent={60} success={{ percent: 10 }} type="circle" />);
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -91,6 +91,16 @@ describe('Select', () => {
|
|||||||
expect(container.querySelectorAll('.anticon-down').length).toBe(0);
|
expect(container.querySelectorAll('.anticon-down').length).toBe(0);
|
||||||
expect(container.querySelectorAll('.anticon-search').length).toBe(1);
|
expect(container.querySelectorAll('.anticon-search').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show warning when use dropdownClassName', () => {
|
||||||
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
render(<Select dropdownClassName="myCustomClassName" />);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
|
'Warning: [antd: Select] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
errorSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
describe('Select Custom Icons', () => {
|
describe('Select Custom Icons', () => {
|
||||||
it('should support customized icons', () => {
|
it('should support customized icons', () => {
|
||||||
|
@ -33,7 +33,7 @@ Select component to select value from options.
|
|||||||
| defaultOpen | Initial open state of dropdown | boolean | - | |
|
| defaultOpen | Initial open state of dropdown | boolean | - | |
|
||||||
| defaultValue | Initial selected option | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue\[] | - | |
|
| defaultValue | Initial selected option | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue\[] | - | |
|
||||||
| disabled | Whether disabled select | boolean | false | |
|
| disabled | Whether disabled select | boolean | false | |
|
||||||
| dropdownClassName | The className of dropdown menu | string | - | |
|
| popupClassName | The className of dropdown menu | string | - | 4.23.0 |
|
||||||
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | true | |
|
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | true | |
|
||||||
| dropdownRender | Customize dropdown content | (originNode: ReactNode) => ReactNode | - | |
|
| dropdownRender | Customize dropdown content | (originNode: ReactNode) => ReactNode | - | |
|
||||||
| dropdownStyle | The style of dropdown menu | CSSProperties | - | |
|
| dropdownStyle | The style of dropdown menu | CSSProperties | - | |
|
||||||
|
@ -19,6 +19,7 @@ import { getTransitionDirection, getTransitionName } from '../_util/motion';
|
|||||||
import type { InputStatus } from '../_util/statusUtils';
|
import type { InputStatus } from '../_util/statusUtils';
|
||||||
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
||||||
import getIcons from './utils/iconUtil';
|
import getIcons from './utils/iconUtil';
|
||||||
|
import warning from '../_util/warning';
|
||||||
|
|
||||||
import useStyle from './style';
|
import useStyle from './style';
|
||||||
import genPurePanel from '../_util/PurePanel';
|
import genPurePanel from '../_util/PurePanel';
|
||||||
@ -56,6 +57,12 @@ export interface SelectProps<
|
|||||||
placement?: SelectCommonPlacement;
|
placement?: SelectCommonPlacement;
|
||||||
mode?: 'multiple' | 'tags';
|
mode?: 'multiple' | 'tags';
|
||||||
status?: InputStatus;
|
status?: InputStatus;
|
||||||
|
/**
|
||||||
|
* @deprecated `dropdownClassName` is deprecated which will be removed in next major
|
||||||
|
* version.Please use `popupClassName` instead.
|
||||||
|
*/
|
||||||
|
dropdownClassName?: string;
|
||||||
|
popupClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SECRET_COMBOBOX_MODE_DO_NOT_USE = 'SECRET_COMBOBOX_MODE_DO_NOT_USE';
|
const SECRET_COMBOBOX_MODE_DO_NOT_USE = 'SECRET_COMBOBOX_MODE_DO_NOT_USE';
|
||||||
@ -67,6 +74,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
|
|||||||
className,
|
className,
|
||||||
getPopupContainer,
|
getPopupContainer,
|
||||||
dropdownClassName,
|
dropdownClassName,
|
||||||
|
popupClassName,
|
||||||
listHeight = 256,
|
listHeight = 256,
|
||||||
placement,
|
placement,
|
||||||
listItemHeight = 24,
|
listItemHeight = 24,
|
||||||
@ -112,6 +120,13 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
|
|||||||
const mergedShowArrow =
|
const mergedShowArrow =
|
||||||
showArrow !== undefined ? showArrow : props.loading || !(isMultiple || mode === 'combobox');
|
showArrow !== undefined ? showArrow : props.loading || !(isMultiple || mode === 'combobox');
|
||||||
|
|
||||||
|
// =================== Warning =====================
|
||||||
|
warning(
|
||||||
|
!dropdownClassName,
|
||||||
|
'Select',
|
||||||
|
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
|
||||||
// ===================== Form Status =====================
|
// ===================== Form Status =====================
|
||||||
const {
|
const {
|
||||||
status: contextStatus,
|
status: contextStatus,
|
||||||
@ -144,7 +159,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
|
|||||||
const selectProps = omit(props as typeof props & { itemIcon: any }, ['suffixIcon', 'itemIcon']);
|
const selectProps = omit(props as typeof props & { itemIcon: any }, ['suffixIcon', 'itemIcon']);
|
||||||
|
|
||||||
const rcSelectRtlDropdownClassName = classNames(
|
const rcSelectRtlDropdownClassName = classNames(
|
||||||
dropdownClassName,
|
popupClassName || dropdownClassName,
|
||||||
{
|
{
|
||||||
[`${prefixCls}-dropdown-${direction}`]: direction === 'rtl',
|
[`${prefixCls}-dropdown-${direction}`]: direction === 'rtl',
|
||||||
},
|
},
|
||||||
|
@ -34,7 +34,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
|
|||||||
| defaultOpen | 是否默认展开下拉菜单 | boolean | - | |
|
| defaultOpen | 是否默认展开下拉菜单 | boolean | - | |
|
||||||
| defaultValue | 指定默认选中的条目 | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue\[] | - | |
|
| defaultValue | 指定默认选中的条目 | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue\[] | - | |
|
||||||
| disabled | 是否禁用 | boolean | false | |
|
| disabled | 是否禁用 | boolean | false | |
|
||||||
| dropdownClassName | 下拉菜单的 className 属性 | string | - | |
|
| popupClassName | 下拉菜单的 className 属性 | string | - | 4.23.0 |
|
||||||
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。false 时会关闭虚拟滚动 | boolean \| number | true | |
|
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。false 时会关闭虚拟滚动 | boolean \| number | true | |
|
||||||
| dropdownRender | 自定义下拉框内容 | (originNode: ReactNode) => ReactNode | - | |
|
| dropdownRender | 自定义下拉框内容 | (originNode: ReactNode) => ReactNode | - | |
|
||||||
| dropdownStyle | 下拉菜单的 style 属性 | CSSProperties | - | |
|
| dropdownStyle | 下拉菜单的 style 属性 | CSSProperties | - | |
|
||||||
|
@ -1,180 +0,0 @@
|
|||||||
import { mount } from 'enzyme';
|
|
||||||
import React from 'react';
|
|
||||||
import Skeleton from '..';
|
|
||||||
import mountTest from '../../../tests/shared/mountTest';
|
|
||||||
import rtlTest from '../../../tests/shared/rtlTest';
|
|
||||||
|
|
||||||
describe('Skeleton', () => {
|
|
||||||
const genSkeleton = props =>
|
|
||||||
mount(
|
|
||||||
<Skeleton loading {...props}>
|
|
||||||
Bamboo
|
|
||||||
</Skeleton>,
|
|
||||||
);
|
|
||||||
const genSkeletonButton = props => mount(<Skeleton.Button {...props} />);
|
|
||||||
const genSkeletonAvatar = props => mount(<Skeleton.Avatar {...props} />);
|
|
||||||
const genSkeletonInput = props => mount(<Skeleton.Input {...props} />);
|
|
||||||
const genSkeletonImage = props => mount(<Skeleton.Image {...props} />);
|
|
||||||
const genSkeletonNode = props => mount(<Skeleton.Node {...props} />);
|
|
||||||
|
|
||||||
mountTest(Skeleton);
|
|
||||||
rtlTest(Skeleton);
|
|
||||||
|
|
||||||
it('should without avatar and paragraph', () => {
|
|
||||||
const wrapperSmall = genSkeleton({ avatar: false, paragraph: false });
|
|
||||||
expect(wrapperSmall.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should square avatar', () => {
|
|
||||||
const wrapperSmall = genSkeleton({ avatar: true, paragraph: false });
|
|
||||||
expect(wrapperSmall.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should round title and paragraph', () => {
|
|
||||||
const wrapperSmall = genSkeleton({ round: true, title: true, paragraph: true });
|
|
||||||
expect(wrapperSmall.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display without children and falsy loading props', () => {
|
|
||||||
const wrapper = mount(<Skeleton loading={false} />);
|
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display with empty children and falsy loading props', () => {
|
|
||||||
const wrapper = mount(<Skeleton loading={false}>{0}</Skeleton>);
|
|
||||||
expect(wrapper.text()).toBe('0');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display children', () => {
|
|
||||||
const wrapper = mount(<Skeleton loading={false}>{[1, 2, 3]}</Skeleton>);
|
|
||||||
expect(wrapper.text()).toBe('123');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('avatar', () => {
|
|
||||||
it('size', () => {
|
|
||||||
const wrapperSmall = genSkeleton({ avatar: { size: 'small' } });
|
|
||||||
expect(wrapperSmall.render()).toMatchSnapshot();
|
|
||||||
const wrapperDefault = genSkeleton({ avatar: { size: 'default' } });
|
|
||||||
expect(wrapperDefault.render()).toMatchSnapshot();
|
|
||||||
const wrapperLarge = genSkeleton({ avatar: { size: 'large' } });
|
|
||||||
expect(wrapperLarge.render()).toMatchSnapshot();
|
|
||||||
const wrapperNumber = genSkeleton({ avatar: { size: 20 } });
|
|
||||||
expect(wrapperNumber.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shape', () => {
|
|
||||||
const wrapperCircle = genSkeleton({ avatar: { shape: 'circle' } });
|
|
||||||
expect(wrapperCircle.render()).toMatchSnapshot();
|
|
||||||
const wrapperSquare = genSkeleton({ avatar: { shape: 'square' } });
|
|
||||||
expect(wrapperSquare.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('title', () => {
|
|
||||||
it('width', () => {
|
|
||||||
const wrapper = genSkeleton({ title: { width: '93%' } });
|
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('paragraph', () => {
|
|
||||||
it('rows', () => {
|
|
||||||
const wrapper = genSkeleton({ paragraph: { rows: 5 } });
|
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('width', () => {
|
|
||||||
const wrapperPure = genSkeleton({ paragraph: { width: '93%' } });
|
|
||||||
expect(wrapperPure.render()).toMatchSnapshot();
|
|
||||||
const wrapperList = genSkeleton({ paragraph: { width: ['28%', '93%'] } });
|
|
||||||
expect(wrapperList.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('button element', () => {
|
|
||||||
it('active', () => {
|
|
||||||
const wrapper = genSkeletonButton({ active: true });
|
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
it('block', () => {
|
|
||||||
const wrapper = genSkeletonButton({ block: true });
|
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
it('size', () => {
|
|
||||||
const wrapperDefault = genSkeletonButton({ size: 'default' });
|
|
||||||
expect(wrapperDefault.render()).toMatchSnapshot();
|
|
||||||
const wrapperLarge = genSkeletonButton({ size: 'large' });
|
|
||||||
expect(wrapperLarge.render()).toMatchSnapshot();
|
|
||||||
const wrapperSmall = genSkeletonButton({ size: 'small' });
|
|
||||||
expect(wrapperSmall.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
it('shape', () => {
|
|
||||||
const wrapperDefault = genSkeletonButton({ shape: 'default' });
|
|
||||||
expect(wrapperDefault.render()).toMatchSnapshot();
|
|
||||||
const wrapperRound = genSkeletonButton({ shape: 'round' });
|
|
||||||
expect(wrapperRound.render()).toMatchSnapshot();
|
|
||||||
const wrapperCircle = genSkeletonButton({ shape: 'circle' });
|
|
||||||
expect(wrapperCircle.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('avatar element', () => {
|
|
||||||
it('active', () => {
|
|
||||||
const wrapper = genSkeletonAvatar({ active: true });
|
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
it('size', () => {
|
|
||||||
const wrapperSmall = genSkeletonAvatar({ size: 'small' });
|
|
||||||
expect(wrapperSmall.render()).toMatchSnapshot();
|
|
||||||
const wrapperDefault = genSkeletonAvatar({ size: 'default' });
|
|
||||||
expect(wrapperDefault.render()).toMatchSnapshot();
|
|
||||||
const wrapperLarge = genSkeletonAvatar({ size: 'large' });
|
|
||||||
expect(wrapperLarge.render()).toMatchSnapshot();
|
|
||||||
const wrapperNumber = genSkeletonAvatar({ size: 20 });
|
|
||||||
expect(wrapperNumber.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shape', () => {
|
|
||||||
const wrapperCircle = genSkeletonAvatar({ shape: 'circle' });
|
|
||||||
expect(wrapperCircle.render()).toMatchSnapshot();
|
|
||||||
const wrapperSquare = genSkeletonAvatar({ shape: 'square' });
|
|
||||||
expect(wrapperSquare.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('input element', () => {
|
|
||||||
it('active', () => {
|
|
||||||
const wrapper = genSkeletonInput({ active: true });
|
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
it('size', () => {
|
|
||||||
const wrapperSmall = genSkeletonInput({ size: 'small' });
|
|
||||||
expect(wrapperSmall.render()).toMatchSnapshot();
|
|
||||||
const wrapperDefault = genSkeletonInput({ size: 'default' });
|
|
||||||
expect(wrapperDefault.render()).toMatchSnapshot();
|
|
||||||
const wrapperLarge = genSkeletonInput({ size: 'large' });
|
|
||||||
expect(wrapperLarge.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('image element', () => {
|
|
||||||
it('should render normal', () => {
|
|
||||||
const wrapper = genSkeletonImage();
|
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('custom node element', () => {
|
|
||||||
it('should render normal', () => {
|
|
||||||
const wrapper = genSkeletonNode();
|
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
|
||||||
const wrapperNode = genSkeletonNode({ children: <span>Custom Content Node</span> });
|
|
||||||
expect(wrapperNode.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support style', () => {
|
|
||||||
const wrapper = genSkeleton({ style: { background: 'blue' } });
|
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
188
components/skeleton/__tests__/index.test.tsx
Normal file
188
components/skeleton/__tests__/index.test.tsx
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Skeleton from '..';
|
||||||
|
import mountTest from '../../../tests/shared/mountTest';
|
||||||
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
|
import { render } from '../../../tests/utils';
|
||||||
|
import type { SkeletonProps } from '../Skeleton';
|
||||||
|
import type { AvatarProps } from '../Avatar';
|
||||||
|
import type { SkeletonButtonProps } from '../Button';
|
||||||
|
import type { SkeletonImageProps } from '../Image';
|
||||||
|
import type { SkeletonInputProps } from '../Input';
|
||||||
|
import type { SkeletonNodeProps } from '../Node';
|
||||||
|
|
||||||
|
describe('Skeleton', () => {
|
||||||
|
const genSkeleton = (props: SkeletonProps) =>
|
||||||
|
render(
|
||||||
|
<Skeleton loading {...props}>
|
||||||
|
Bamboo
|
||||||
|
</Skeleton>,
|
||||||
|
);
|
||||||
|
const genSkeletonButton = (props: SkeletonButtonProps) => render(<Skeleton.Button {...props} />);
|
||||||
|
const genSkeletonAvatar = (props: AvatarProps) => render(<Skeleton.Avatar {...props} />);
|
||||||
|
const genSkeletonInput = (props: SkeletonInputProps) => render(<Skeleton.Input {...props} />);
|
||||||
|
const genSkeletonImage = (props: SkeletonImageProps) => render(<Skeleton.Image {...props} />);
|
||||||
|
const genSkeletonNode = (props: SkeletonNodeProps) => render(<Skeleton.Node {...props} />);
|
||||||
|
|
||||||
|
mountTest(Skeleton);
|
||||||
|
rtlTest(Skeleton);
|
||||||
|
|
||||||
|
it('should without avatar and paragraph', () => {
|
||||||
|
const { asFragment } = genSkeleton({ avatar: false, paragraph: false });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should square avatar', () => {
|
||||||
|
const { asFragment } = genSkeleton({ avatar: true, paragraph: false });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should round title and paragraph', () => {
|
||||||
|
const { asFragment } = genSkeleton({ round: true, title: true, paragraph: true });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display without children and falsy loading props', () => {
|
||||||
|
const { asFragment } = render(<Skeleton loading={false} />);
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display with empty children and falsy loading props', () => {
|
||||||
|
const { container } = render(<Skeleton loading={false}>{0}</Skeleton>);
|
||||||
|
expect(container.textContent).toBe('0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display children', () => {
|
||||||
|
const { container } = render(<Skeleton loading={false}>{[1, 2, 3]}</Skeleton>);
|
||||||
|
expect(container.textContent).toBe('123');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('avatar', () => {
|
||||||
|
it('size', () => {
|
||||||
|
const { asFragment } = genSkeleton({ avatar: { size: 'small' } });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperDefault } = genSkeleton({ avatar: { size: 'default' } });
|
||||||
|
expect(wrapperDefault().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperLarge } = genSkeleton({ avatar: { size: 'large' } });
|
||||||
|
expect(wrapperLarge().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperNumber } = genSkeleton({ avatar: { size: 20 } });
|
||||||
|
expect(wrapperNumber().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shape', () => {
|
||||||
|
const { asFragment: wrapperCircle } = genSkeleton({ avatar: { shape: 'circle' } });
|
||||||
|
expect(wrapperCircle().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperSquare } = genSkeleton({ avatar: { shape: 'square' } });
|
||||||
|
expect(wrapperSquare().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('title', () => {
|
||||||
|
it('width', () => {
|
||||||
|
const { asFragment } = genSkeleton({ title: { width: '93%' } });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('paragraph', () => {
|
||||||
|
it('rows', () => {
|
||||||
|
const { asFragment } = genSkeleton({ paragraph: { rows: 5 } });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('width', () => {
|
||||||
|
const { asFragment: wrapperPure } = genSkeleton({ paragraph: { width: '93%' } });
|
||||||
|
expect(wrapperPure().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperList } = genSkeleton({ paragraph: { width: ['28%', '93%'] } });
|
||||||
|
expect(wrapperList().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('button element', () => {
|
||||||
|
it('active', () => {
|
||||||
|
const { asFragment } = genSkeletonButton({ active: true });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('block', () => {
|
||||||
|
const { asFragment } = genSkeletonButton({ block: true });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('size', () => {
|
||||||
|
const { asFragment: wrapperDefault } = genSkeletonButton({ size: 'default' });
|
||||||
|
expect(wrapperDefault().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperLarge } = genSkeletonButton({ size: 'large' });
|
||||||
|
expect(wrapperLarge().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment } = genSkeletonButton({ size: 'small' });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('shape', () => {
|
||||||
|
const { asFragment: wrapperDefault } = genSkeletonButton({ shape: 'default' });
|
||||||
|
expect(wrapperDefault().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperRound } = genSkeletonButton({ shape: 'round' });
|
||||||
|
expect(wrapperRound().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperCircle } = genSkeletonButton({ shape: 'circle' });
|
||||||
|
expect(wrapperCircle().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('avatar element', () => {
|
||||||
|
it('active', () => {
|
||||||
|
const { asFragment } = genSkeletonAvatar({ active: true });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('size', () => {
|
||||||
|
const { asFragment } = genSkeletonAvatar({ size: 'small' });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperDefault } = genSkeletonAvatar({ size: 'default' });
|
||||||
|
expect(wrapperDefault().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperLarge } = genSkeletonAvatar({ size: 'large' });
|
||||||
|
expect(wrapperLarge().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperNumber } = genSkeletonAvatar({ size: 20 });
|
||||||
|
expect(wrapperNumber().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shape', () => {
|
||||||
|
const { asFragment: wrapperCircle } = genSkeletonAvatar({ shape: 'circle' });
|
||||||
|
expect(wrapperCircle().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperSquare } = genSkeletonAvatar({ shape: 'square' });
|
||||||
|
expect(wrapperSquare().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('input element', () => {
|
||||||
|
it('active', () => {
|
||||||
|
const { asFragment } = genSkeletonInput({ active: true });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('size', () => {
|
||||||
|
const { asFragment } = genSkeletonInput({ size: 'small' });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperDefault } = genSkeletonInput({ size: 'default' });
|
||||||
|
expect(wrapperDefault().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: wrapperLarge } = genSkeletonInput({ size: 'large' });
|
||||||
|
expect(wrapperLarge().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('image element', () => {
|
||||||
|
it('should render normal', () => {
|
||||||
|
const { asFragment } = genSkeletonImage({});
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('custom node element', () => {
|
||||||
|
it('should render normal', () => {
|
||||||
|
const { asFragment } = genSkeletonNode({});
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
const { asFragment: asFragmentNode } = genSkeletonNode({
|
||||||
|
children: <span>Custom Content Node</span>,
|
||||||
|
});
|
||||||
|
expect(asFragmentNode().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support style', () => {
|
||||||
|
const { asFragment } = genSkeleton({ style: { background: 'blue' } });
|
||||||
|
expect(asFragment().firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
@ -1,235 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Slider rtl render component should be rendered correctly in RTL direction 1`] = `
|
|
||||||
<div
|
|
||||||
class="ant-slider ant-slider-rtl ant-slider-horizontal"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-slider-rail"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="ant-slider-track"
|
|
||||||
style="right: 0%; width: 0%;"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="ant-slider-step"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-valuemax="100"
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuenow="0"
|
|
||||||
class="ant-slider-handle"
|
|
||||||
role="slider"
|
|
||||||
style="right: 0%; transform: translateX(50%);"
|
|
||||||
tabindex="0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Slider should render in RTL direction 1`] = `
|
|
||||||
<div
|
|
||||||
class="ant-slider ant-slider-rtl ant-slider-horizontal"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-slider-rail"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="ant-slider-track"
|
|
||||||
style="right: 0%; width: 30%;"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="ant-slider-step"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-valuemax="100"
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuenow="30"
|
|
||||||
class="ant-slider-handle ant-tooltip-open"
|
|
||||||
role="slider"
|
|
||||||
style="right: 30%; transform: translateX(50%);"
|
|
||||||
tabindex="0"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ant-tooltip ant-slider-tooltip ant-tooltip-rtl ant-zoom-down-appear ant-zoom-down-appear-prepare ant-zoom-down"
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
30
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Slider should show correct placement tooltip when set tooltipPlacement 1`] = `
|
|
||||||
Array [
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-valuemax="100"
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuenow="30"
|
|
||||||
class="ant-slider-handle ant-tooltip-open"
|
|
||||||
role="slider"
|
|
||||||
style="bottom: 30%; transform: translateY(50%);"
|
|
||||||
tabindex="0"
|
|
||||||
/>,
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ant-tooltip ant-slider-tooltip ant-zoom-down-appear ant-zoom-down-appear-prepare ant-zoom-down"
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
30
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Slider should show correct placement tooltip when set tooltipPlacement 2`] = `
|
|
||||||
Array [
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-valuemax="100"
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuenow="30"
|
|
||||||
class="ant-slider-handle"
|
|
||||||
role="slider"
|
|
||||||
style="bottom: 30%; transform: translateY(50%);"
|
|
||||||
tabindex="0"
|
|
||||||
/>,
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ant-tooltip ant-slider-tooltip ant-zoom-down-leave ant-zoom-down-leave-start ant-zoom-down"
|
|
||||||
style="pointer-events: none;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-tooltip-content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-tooltip-arrow"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="ant-tooltip-arrow-content"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-tooltip-inner"
|
|
||||||
role="tooltip"
|
|
||||||
>
|
|
||||||
30
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Slider should show tooltip when hovering slider handler 1`] = `
|
|
||||||
Array [
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-valuemax="100"
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuenow="30"
|
|
||||||
class="ant-slider-handle ant-tooltip-open"
|
|
||||||
role="slider"
|
|
||||||
style="left: 30%; transform: translateX(-50%);"
|
|
||||||
tabindex="0"
|
|
||||||
/>,
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ant-tooltip ant-slider-tooltip ant-zoom-down-appear ant-zoom-down-appear-prepare ant-zoom-down"
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
30
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Slider should show tooltip when hovering slider handler 2`] = `
|
|
||||||
Array [
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-valuemax="100"
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuenow="30"
|
|
||||||
class="ant-slider-handle"
|
|
||||||
role="slider"
|
|
||||||
style="left: 30%; transform: translateX(-50%);"
|
|
||||||
tabindex="0"
|
|
||||||
/>,
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ant-tooltip ant-slider-tooltip ant-zoom-down-leave ant-zoom-down-leave-start ant-zoom-down"
|
|
||||||
style="pointer-events: none;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-tooltip-content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-tooltip-arrow"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="ant-tooltip-arrow-content"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ant-tooltip-inner"
|
|
||||||
role="tooltip"
|
|
||||||
>
|
|
||||||
30
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
]
|
|
||||||
`;
|
|
181
components/slider/__tests__/__snapshots__/index.test.tsx.snap
Normal file
181
components/slider/__tests__/__snapshots__/index.test.tsx.snap
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Slider rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-slider ant-slider-rtl ant-slider-horizontal"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-slider-rail"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ant-slider-track"
|
||||||
|
style="right: 0%; width: 0%;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ant-slider-step"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-disabled="false"
|
||||||
|
aria-valuemax="100"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuenow="0"
|
||||||
|
class="ant-slider-handle"
|
||||||
|
role="slider"
|
||||||
|
style="right: 0%; transform: translateX(50%);"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Slider should render in RTL direction 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ant-slider ant-slider-rtl ant-slider-horizontal"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-slider-rail"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ant-slider-track"
|
||||||
|
style="right: 0%; width: 30%;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ant-slider-step"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-disabled="false"
|
||||||
|
aria-valuemax="100"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuenow="30"
|
||||||
|
class="ant-slider-handle ant-tooltip-open"
|
||||||
|
role="slider"
|
||||||
|
style="right: 30%; transform: translateX(50%);"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip ant-slider-tooltip ant-tooltip-rtl ant-zoom-down-appear ant-zoom-down-appear-prepare ant-zoom-down"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
30
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Slider should show correct placement tooltip when set tooltipPlacement 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-tooltip ant-slider-tooltip ant-zoom-down-appear ant-zoom-down-appear-prepare ant-zoom-down"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
30
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Slider should show correct placement tooltip when set tooltipPlacement 2`] = `
|
||||||
|
<div
|
||||||
|
class="ant-tooltip ant-slider-tooltip ant-zoom-down-leave ant-zoom-down-leave-start ant-zoom-down"
|
||||||
|
style="pointer-events: none;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip-arrow"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-tooltip-arrow-content"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip-inner"
|
||||||
|
role="tooltip"
|
||||||
|
>
|
||||||
|
30
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Slider should show tooltip when hovering slider handler 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-tooltip ant-slider-tooltip ant-zoom-down-appear ant-zoom-down-appear-prepare ant-zoom-down"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
30
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Slider should show tooltip when hovering slider handler 2`] = `
|
||||||
|
<div
|
||||||
|
class="ant-tooltip ant-slider-tooltip ant-zoom-down-leave ant-zoom-down-leave-start ant-zoom-down"
|
||||||
|
style="pointer-events: none;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip-arrow"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-tooltip-arrow-content"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tooltip-inner"
|
||||||
|
role="tooltip"
|
||||||
|
>
|
||||||
|
30
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -1,116 +0,0 @@
|
|||||||
import { mount } from 'enzyme';
|
|
||||||
import React from 'react';
|
|
||||||
import Slider from '..';
|
|
||||||
import focusTest from '../../../tests/shared/focusTest';
|
|
||||||
import mountTest from '../../../tests/shared/mountTest';
|
|
||||||
import rtlTest from '../../../tests/shared/rtlTest';
|
|
||||||
import { sleep } from '../../../tests/utils';
|
|
||||||
import ConfigProvider from '../../config-provider';
|
|
||||||
import SliderTooltip from '../SliderTooltip';
|
|
||||||
|
|
||||||
describe('Slider', () => {
|
|
||||||
mountTest(Slider);
|
|
||||||
rtlTest(Slider);
|
|
||||||
focusTest(Slider, { testLib: true });
|
|
||||||
|
|
||||||
it('should show tooltip when hovering slider handler', () => {
|
|
||||||
const wrapper = mount(<Slider defaultValue={30} />);
|
|
||||||
wrapper.find('.ant-slider-handle').at(0).simulate('mouseEnter');
|
|
||||||
expect(wrapper.find('Trigger').render()).toMatchSnapshot();
|
|
||||||
wrapper.find('.ant-slider-handle').at(0).simulate('mouseLeave');
|
|
||||||
expect(wrapper.find('Trigger').render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show correct placement tooltip when set tooltipPlacement', () => {
|
|
||||||
const wrapper = mount(<Slider vertical defaultValue={30} tooltipPlacement="left" />);
|
|
||||||
wrapper.find('.ant-slider-handle').at(0).simulate('mouseEnter');
|
|
||||||
expect(wrapper.find('Trigger').render()).toMatchSnapshot();
|
|
||||||
wrapper.find('.ant-slider-handle').at(0).simulate('mouseLeave');
|
|
||||||
expect(wrapper.find('Trigger').render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('when tooltipVisible is true, tooltip should show always, or should never show', () => {
|
|
||||||
let wrapper = mount(<Slider defaultValue={30} tooltipVisible />);
|
|
||||||
expect(wrapper.find('.ant-tooltip-content').at(0).hasClass('ant-tooltip-hidden')).toBe(false);
|
|
||||||
wrapper.find('.ant-slider-handle').at(0).simulate('mouseEnter');
|
|
||||||
expect(wrapper.find('.ant-tooltip-content').at(0).hasClass('ant-tooltip-hidden')).toBe(false);
|
|
||||||
wrapper.find('.ant-slider-handle').at(0).simulate('click');
|
|
||||||
expect(wrapper.find('.ant-tooltip-content').at(0).hasClass('ant-tooltip-hidden')).toBe(false);
|
|
||||||
wrapper = mount(<Slider defaultValue={30} tooltipVisible={false} />);
|
|
||||||
expect(wrapper.find('.ant-tooltip-content').length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('when step is null, thumb can only be slided to the specific mark', () => {
|
|
||||||
const intentionallyWrongValue = 40;
|
|
||||||
const marks = {
|
|
||||||
0: '0',
|
|
||||||
48: '48',
|
|
||||||
100: '100',
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = mount(
|
|
||||||
<Slider marks={marks} defaultValue={intentionallyWrongValue} step={null} tooltipVisible />,
|
|
||||||
);
|
|
||||||
expect(wrapper.find('.ant-slider-handle').get(0).props).toHaveProperty('aria-valuenow', 48);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('when step is not null, thumb can be slided to the multiples of step', () => {
|
|
||||||
const marks = {
|
|
||||||
0: '0',
|
|
||||||
48: '48',
|
|
||||||
100: '100',
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = mount(<Slider marks={marks} defaultValue={49} step={1} tooltipVisible />);
|
|
||||||
expect(wrapper.find('.ant-slider-handle').get(0).props).toHaveProperty('aria-valuenow', 49);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('when step is undefined, thumb can be slided to the multiples of step', () => {
|
|
||||||
const marks = {
|
|
||||||
0: '0',
|
|
||||||
48: '48',
|
|
||||||
100: '100',
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = mount(
|
|
||||||
<Slider marks={marks} defaultValue={49} step={undefined} tooltipVisible />,
|
|
||||||
);
|
|
||||||
expect(wrapper.find('.ant-slider-handle').get(0).props).toHaveProperty('aria-valuenow', 49);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render in RTL direction', () => {
|
|
||||||
const wrapper = mount(
|
|
||||||
<ConfigProvider direction="rtl">
|
|
||||||
<Slider defaultValue={30} tooltipVisible />
|
|
||||||
</ConfigProvider>,
|
|
||||||
);
|
|
||||||
expect(wrapper.render()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should keepAlign by calling forcePopupAlign', async () => {
|
|
||||||
let ref;
|
|
||||||
mount(
|
|
||||||
<SliderTooltip
|
|
||||||
title="30"
|
|
||||||
visible
|
|
||||||
ref={node => {
|
|
||||||
ref = node;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
ref.forcePopupAlign = jest.fn();
|
|
||||||
await sleep(20);
|
|
||||||
expect(ref.forcePopupAlign).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('tipFormatter should not crash with undefined value', () => {
|
|
||||||
[undefined, null].forEach(value => {
|
|
||||||
mount(<Slider value={value} tooltipVisible />);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('step should not crash with undefined value', () => {
|
|
||||||
[undefined, null].forEach(value => {
|
|
||||||
mount(<Slider step={value} tooltipVisible />);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
131
components/slider/__tests__/index.test.tsx
Normal file
131
components/slider/__tests__/index.test.tsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Slider from '..';
|
||||||
|
import focusTest from '../../../tests/shared/focusTest';
|
||||||
|
import mountTest from '../../../tests/shared/mountTest';
|
||||||
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
|
import { sleep, render, fireEvent } from '../../../tests/utils';
|
||||||
|
import ConfigProvider from '../../config-provider';
|
||||||
|
import SliderTooltip from '../SliderTooltip';
|
||||||
|
|
||||||
|
describe('Slider', () => {
|
||||||
|
mountTest(Slider);
|
||||||
|
rtlTest(Slider);
|
||||||
|
focusTest(Slider, { testLib: true });
|
||||||
|
|
||||||
|
it('should show tooltip when hovering slider handler', () => {
|
||||||
|
const { container } = render(<Slider defaultValue={30} />);
|
||||||
|
|
||||||
|
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||||
|
expect(document.querySelector('.ant-tooltip')).toMatchSnapshot();
|
||||||
|
|
||||||
|
fireEvent.mouseLeave(container.querySelector('.ant-slider-handle')!);
|
||||||
|
|
||||||
|
expect(document.querySelector('.ant-tooltip')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show correct placement tooltip when set tooltipPlacement', () => {
|
||||||
|
const { container } = render(<Slider vertical defaultValue={30} tooltipPlacement="left" />);
|
||||||
|
|
||||||
|
fireEvent.mouseEnter(container.querySelector('.ant-slider-handle')!);
|
||||||
|
expect(document.querySelector('.ant-tooltip')).toMatchSnapshot();
|
||||||
|
|
||||||
|
fireEvent.mouseLeave(container.querySelector('.ant-slider-handle')!);
|
||||||
|
expect(document.querySelector('.ant-tooltip')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when tooltipVisible is true, tooltip should show always, or should never show', () => {
|
||||||
|
const { container: container1 } = render(<Slider defaultValue={30} tooltipVisible />);
|
||||||
|
expect(
|
||||||
|
container1.querySelector('.ant-tooltip-content')!.className.includes('ant-tooltip-hidden'),
|
||||||
|
).toBeFalsy();
|
||||||
|
|
||||||
|
fireEvent.mouseEnter(container1.querySelector('.ant-slider-handle')!);
|
||||||
|
expect(
|
||||||
|
container1.querySelector('.ant-tooltip-content')!.className.includes('ant-tooltip-hidden'),
|
||||||
|
).toBeFalsy();
|
||||||
|
|
||||||
|
fireEvent.click(container1.querySelector('.ant-slider-handle')!);
|
||||||
|
expect(
|
||||||
|
container1.querySelector('.ant-tooltip-content')!.className.includes('ant-tooltip-hidden'),
|
||||||
|
).toBeFalsy();
|
||||||
|
|
||||||
|
const { container: container2 } = render(<Slider defaultValue={30} tooltipVisible={false} />);
|
||||||
|
expect(container2.querySelector('.ant-tooltip-content')!).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when step is null, thumb can only be slided to the specific mark', () => {
|
||||||
|
const intentionallyWrongValue = 40;
|
||||||
|
const marks = {
|
||||||
|
0: '0',
|
||||||
|
48: '48',
|
||||||
|
100: '100',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<Slider marks={marks} defaultValue={intentionallyWrongValue} step={null} tooltipVisible />,
|
||||||
|
);
|
||||||
|
expect(container.querySelector('.ant-slider-handle')!.getAttribute('aria-valuenow')).toBe('48');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when step is not null, thumb can be slided to the multiples of step', () => {
|
||||||
|
const marks = {
|
||||||
|
0: '0',
|
||||||
|
48: '48',
|
||||||
|
100: '100',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<Slider marks={marks} defaultValue={49} step={1} tooltipVisible />,
|
||||||
|
);
|
||||||
|
expect(container.querySelector('.ant-slider-handle')!.getAttribute('aria-valuenow')).toBe('49');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when step is undefined, thumb can be slided to the multiples of step', () => {
|
||||||
|
const marks = {
|
||||||
|
0: '0',
|
||||||
|
48: '48',
|
||||||
|
100: '100',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<Slider marks={marks} defaultValue={49} step={undefined} tooltipVisible />,
|
||||||
|
);
|
||||||
|
expect(container.querySelector('.ant-slider-handle')!.getAttribute('aria-valuenow')).toBe('49');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render in RTL direction', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<ConfigProvider direction="rtl">
|
||||||
|
<Slider defaultValue={30} tooltipVisible />
|
||||||
|
</ConfigProvider>,
|
||||||
|
);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keepAlign by calling forcePopupAlign', async () => {
|
||||||
|
let ref: any;
|
||||||
|
render(
|
||||||
|
<SliderTooltip
|
||||||
|
title="30"
|
||||||
|
visible
|
||||||
|
ref={node => {
|
||||||
|
ref = node;
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
ref.forcePopupAlign = jest.fn();
|
||||||
|
await sleep(20);
|
||||||
|
expect(ref.forcePopupAlign).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tipFormatter should not crash with undefined value', () => {
|
||||||
|
[undefined, null].forEach(value => {
|
||||||
|
render(<Slider value={value as any} tooltipVisible />);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('step should not crash with undefined value', () => {
|
||||||
|
[undefined, null].forEach(value => {
|
||||||
|
render(<Slider step={value} tooltipVisible />);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -63,7 +63,7 @@ interface ChangeEventInfo<RecordType> {
|
|||||||
total?: number;
|
total?: number;
|
||||||
};
|
};
|
||||||
filters: Record<string, FilterValue | null>;
|
filters: Record<string, FilterValue | null>;
|
||||||
sorter: SorterResult<RecordType> | SorterResult<RecordType>[];
|
sorter: SorterResult<RecordType> | SorterResult<RecordType[]>;
|
||||||
|
|
||||||
filterStates: FilterState<RecordType>[];
|
filterStates: FilterState<RecordType>[];
|
||||||
sorterStates: SortState<RecordType>[];
|
sorterStates: SortState<RecordType>[];
|
||||||
@ -94,7 +94,7 @@ export interface TableProps<RecordType>
|
|||||||
onChange?: (
|
onChange?: (
|
||||||
pagination: TablePaginationConfig,
|
pagination: TablePaginationConfig,
|
||||||
filters: Record<string, FilterValue | null>,
|
filters: Record<string, FilterValue | null>,
|
||||||
sorter: SorterResult<RecordType> | SorterResult<RecordType>[],
|
sorter: SorterResult<RecordType> | SorterResult<RecordType[]>,
|
||||||
extra: TableCurrentDataSource<RecordType>,
|
extra: TableCurrentDataSource<RecordType>,
|
||||||
) => void;
|
) => void;
|
||||||
rowSelection?: TableRowSelection<RecordType>;
|
rowSelection?: TableRowSelection<RecordType>;
|
||||||
@ -265,7 +265,7 @@ function InternalTable<RecordType extends object = any>(
|
|||||||
|
|
||||||
// ============================ Sorter =============================
|
// ============================ Sorter =============================
|
||||||
const onSorterChange = (
|
const onSorterChange = (
|
||||||
sorter: SorterResult<RecordType> | SorterResult<RecordType>[],
|
sorter: SorterResult<RecordType> | SorterResult<RecordType[]>,
|
||||||
sorterStates: SortState<RecordType>[],
|
sorterStates: SortState<RecordType>[],
|
||||||
) => {
|
) => {
|
||||||
triggerOnChange(
|
triggerOnChange(
|
||||||
|
@ -36,6 +36,12 @@ describe('Table.typescript', () => {
|
|||||||
const table = <Table<RecordType> dataSource={[{ key: 'Bamboo' }]} />;
|
const table = <Table<RecordType> dataSource={[{ key: 'Bamboo' }]} />;
|
||||||
expect(table).toBeTruthy();
|
expect(table).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Sorter types', () => {
|
||||||
|
const table = <Table onChange={(_pagination, _filters, sorter) => sorter.field} />;
|
||||||
|
|
||||||
|
expect(table).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Table.typescript types', () => {
|
describe('Table.typescript types', () => {
|
||||||
|
@ -247,7 +247,7 @@ function stateToInfo<RecordType>(sorterStates: SortState<RecordType>) {
|
|||||||
|
|
||||||
function generateSorterInfo<RecordType>(
|
function generateSorterInfo<RecordType>(
|
||||||
sorterStates: SortState<RecordType>[],
|
sorterStates: SortState<RecordType>[],
|
||||||
): SorterResult<RecordType> | SorterResult<RecordType>[] {
|
): SorterResult<RecordType> | SorterResult<RecordType[]> {
|
||||||
const list = sorterStates.filter(({ sortOrder }) => sortOrder).map(stateToInfo);
|
const list = sorterStates.filter(({ sortOrder }) => sortOrder).map(stateToInfo);
|
||||||
|
|
||||||
// =========== Legacy compatible support ===========
|
// =========== Legacy compatible support ===========
|
||||||
@ -259,11 +259,7 @@ function generateSorterInfo<RecordType>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.length <= 1) {
|
return list[0] || {};
|
||||||
return list[0] || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSortData<RecordType>(
|
export function getSortData<RecordType>(
|
||||||
@ -324,7 +320,7 @@ interface SorterConfig<RecordType> {
|
|||||||
prefixCls: string;
|
prefixCls: string;
|
||||||
mergedColumns: ColumnsType<RecordType>;
|
mergedColumns: ColumnsType<RecordType>;
|
||||||
onSorterChange: (
|
onSorterChange: (
|
||||||
sorterResult: SorterResult<RecordType> | SorterResult<RecordType>[],
|
sorterResult: SorterResult<RecordType> | SorterResult<RecordType[]>,
|
||||||
sortStates: SortState<RecordType>[],
|
sortStates: SortState<RecordType>[],
|
||||||
) => void;
|
) => void;
|
||||||
sortDirections: SortOrder[];
|
sortDirections: SortOrder[];
|
||||||
@ -343,7 +339,7 @@ export default function useFilterSorter<RecordType>({
|
|||||||
TransformColumns<RecordType>,
|
TransformColumns<RecordType>,
|
||||||
SortState<RecordType>[],
|
SortState<RecordType>[],
|
||||||
ColumnTitleProps<RecordType>,
|
ColumnTitleProps<RecordType>,
|
||||||
() => SorterResult<RecordType> | SorterResult<RecordType>[],
|
() => SorterResult<RecordType> | SorterResult<RecordType[]>,
|
||||||
] {
|
] {
|
||||||
const [sortStates, setSortStates] = React.useState<SortState<RecordType>[]>(
|
const [sortStates, setSortStates] = React.useState<SortState<RecordType>[]>(
|
||||||
collectSortStates(mergedColumns, true),
|
collectSortStates(mergedColumns, true),
|
||||||
|
@ -177,6 +177,7 @@ Properties for expandable.
|
|||||||
| Property | Description | Type | Default | Version |
|
| Property | Description | Type | Default | Version |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| childrenColumnName | The column contains children to display | string | children | |
|
| childrenColumnName | The column contains children to display | string | children | |
|
||||||
|
| columnTitle | Set the title of the expand column | ReactNode | - | 4.23.0 |
|
||||||
| columnWidth | Set the width of the expand column | string \| number | - | |
|
| columnWidth | Set the width of the expand column | string \| number | - | |
|
||||||
| defaultExpandAllRows | Expand all rows initially | boolean | false | |
|
| defaultExpandAllRows | Expand all rows initially | boolean | false | |
|
||||||
| defaultExpandedRowKeys | Initial expanded row keys | string\[] | - | |
|
| defaultExpandedRowKeys | Initial expanded row keys | string\[] | - | |
|
||||||
|
@ -178,6 +178,7 @@ const columns = [
|
|||||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| childrenColumnName | 指定树形结构的列名 | string | children | |
|
| childrenColumnName | 指定树形结构的列名 | string | children | |
|
||||||
|
| columnTitle | 自定义展开列表头 | ReactNode | - | 4.23.0 |
|
||||||
| columnWidth | 自定义展开列宽度 | string \| number | - | |
|
| columnWidth | 自定义展开列宽度 | string \| number | - | |
|
||||||
| defaultExpandAllRows | 初始时,是否展开所有行 | boolean | false | |
|
| defaultExpandAllRows | 初始时,是否展开所有行 | boolean | false | |
|
||||||
| defaultExpandedRowKeys | 默认展开的行 | string\[] | - | |
|
| defaultExpandedRowKeys | 默认展开的行 | string\[] | - | |
|
||||||
|
@ -64,7 +64,6 @@ const genExpandStyle: GenerateStyle<TableToken, CSSObject> = token => {
|
|||||||
background: tableExpandIconBg,
|
background: tableExpandIconBg,
|
||||||
border: tableBorder,
|
border: tableBorder,
|
||||||
borderRadius: radiusBase,
|
borderRadius: radiusBase,
|
||||||
outline: 'none',
|
|
||||||
transform: `scale(${checkboxSize / expandIconSize})`,
|
transform: `scale(${checkboxSize / expandIconSize})`,
|
||||||
transition: `all ${motionDurationSlow}`,
|
transition: `all ${motionDurationSlow}`,
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
|
12
components/tabs/TabPane.tsx
Normal file
12
components/tabs/TabPane.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import type * as React from 'react';
|
||||||
|
import type { TabPaneProps } from 'rc-tabs/lib/TabPanelList/TabPane';
|
||||||
|
|
||||||
|
const TabPane: React.FC<TabPaneProps> = () => null;
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
TabPane.displayName = 'DeprecatedTabPane';
|
||||||
|
}
|
||||||
|
|
||||||
|
export { TabPaneProps };
|
||||||
|
|
||||||
|
export default TabPane;
|
@ -1088,6 +1088,150 @@ exports[`renders ./components/tabs/demo/custom-tab-bar-node.md extend context co
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/tabs/demo/deprecated.md extend context correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-tabs ant-tabs-top"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-nav"
|
||||||
|
role="tablist"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-nav-wrap"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-nav-list"
|
||||||
|
style="transform:translate(0px, 0px)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-tab ant-tabs-tab-active"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-selected="true"
|
||||||
|
class="ant-tabs-tab-btn"
|
||||||
|
role="tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Tab 1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-tab"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-selected="false"
|
||||||
|
class="ant-tabs-tab-btn"
|
||||||
|
role="tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Tab 2
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-tab"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-selected="false"
|
||||||
|
class="ant-tabs-tab-btn"
|
||||||
|
role="tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Tab 3
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-nav-operations ant-tabs-nav-operations-hidden"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-controls="null-more-popup"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ant-tabs-nav-more"
|
||||||
|
id="null-more"
|
||||||
|
style="visibility:hidden;order:1"
|
||||||
|
tabindex="-1"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="ellipsis"
|
||||||
|
class="anticon anticon-ellipsis"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="ellipsis"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-dropdown"
|
||||||
|
style="opacity:0"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
aria-label="expanded dropdown"
|
||||||
|
class="ant-tabs-dropdown-menu ant-tabs-dropdown-menu-root ant-tabs-dropdown-menu-vertical"
|
||||||
|
data-menu-list="true"
|
||||||
|
id="null-more-popup"
|
||||||
|
role="listbox"
|
||||||
|
tabindex="-1"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
style="display:none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-content-holder"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-content ant-tabs-content-top"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-hidden="false"
|
||||||
|
class="ant-tabs-tabpane ant-tabs-tabpane-active"
|
||||||
|
role="tabpanel"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Content of Tab Pane 1
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ant-tabs-tabpane"
|
||||||
|
role="tabpanel"
|
||||||
|
style="display:none"
|
||||||
|
tabindex="-1"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ant-tabs-tabpane"
|
||||||
|
role="tabpanel"
|
||||||
|
style="display:none"
|
||||||
|
tabindex="-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/tabs/demo/disabled.md extend context correctly 1`] = `
|
exports[`renders ./components/tabs/demo/disabled.md extend context correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ant-tabs ant-tabs-top"
|
class="ant-tabs ant-tabs-top"
|
||||||
|
@ -955,6 +955,131 @@ exports[`renders ./components/tabs/demo/custom-tab-bar-node.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/tabs/demo/deprecated.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-tabs ant-tabs-top"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-nav"
|
||||||
|
role="tablist"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-nav-wrap"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-nav-list"
|
||||||
|
style="transform:translate(0px, 0px)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-tab ant-tabs-tab-active"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-selected="true"
|
||||||
|
class="ant-tabs-tab-btn"
|
||||||
|
role="tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Tab 1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-tab"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-selected="false"
|
||||||
|
class="ant-tabs-tab-btn"
|
||||||
|
role="tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Tab 2
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-tab"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-selected="false"
|
||||||
|
class="ant-tabs-tab-btn"
|
||||||
|
role="tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Tab 3
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-nav-operations ant-tabs-nav-operations-hidden"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-controls="null-more-popup"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ant-tabs-nav-more"
|
||||||
|
id="null-more"
|
||||||
|
style="visibility:hidden;order:1"
|
||||||
|
tabindex="-1"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-label="ellipsis"
|
||||||
|
class="anticon anticon-ellipsis"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
data-icon="ellipsis"
|
||||||
|
fill="currentColor"
|
||||||
|
focusable="false"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-content-holder"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-tabs-content ant-tabs-content-top"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-hidden="false"
|
||||||
|
class="ant-tabs-tabpane ant-tabs-tabpane-active"
|
||||||
|
role="tabpanel"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Content of Tab Pane 1
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ant-tabs-tabpane"
|
||||||
|
role="tabpanel"
|
||||||
|
style="display:none"
|
||||||
|
tabindex="-1"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ant-tabs-tabpane"
|
||||||
|
role="tabpanel"
|
||||||
|
style="display:none"
|
||||||
|
tabindex="-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/tabs/demo/disabled.md correctly 1`] = `
|
exports[`renders ./components/tabs/demo/disabled.md correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ant-tabs ant-tabs-top"
|
class="ant-tabs ant-tabs-top"
|
||||||
|
@ -3,6 +3,7 @@ import Tabs from '..';
|
|||||||
import mountTest from '../../../tests/shared/mountTest';
|
import mountTest from '../../../tests/shared/mountTest';
|
||||||
import rtlTest from '../../../tests/shared/rtlTest';
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
import { fireEvent, render } from '../../../tests/utils';
|
import { fireEvent, render } from '../../../tests/utils';
|
||||||
|
import { resetWarned } from '../../_util/warning';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
@ -105,4 +106,23 @@ describe('Tabs', () => {
|
|||||||
);
|
);
|
||||||
expect(wrapper2.firstChild).toMatchSnapshot();
|
expect(wrapper2.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('deprecated warning', () => {
|
||||||
|
resetWarned();
|
||||||
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<Tabs>
|
||||||
|
<TabPane />
|
||||||
|
invalidate
|
||||||
|
</Tabs>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.querySelectorAll('.ant-tabs-tab')).toHaveLength(1);
|
||||||
|
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
|
'Warning: [antd: Tabs] Tabs.TabPane is deprecated. Please use `items` directly.',
|
||||||
|
);
|
||||||
|
errorSpy.mockRestore();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -17,24 +17,32 @@ Default activate first tab.
|
|||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
const onChange = (key: string) => {
|
const onChange = (key: string) => {
|
||||||
console.log(key);
|
console.log(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<Tabs defaultActiveKey="1" onChange={onChange}>
|
<Tabs
|
||||||
<TabPane tab="Tab 1" key="1">
|
defaultActiveKey="1"
|
||||||
Content of Tab Pane 1
|
onChange={onChange}
|
||||||
</TabPane>
|
items={[
|
||||||
<TabPane tab="Tab 2" key="2">
|
{
|
||||||
Content of Tab Pane 2
|
label: `Tab 1`,
|
||||||
</TabPane>
|
key: '1',
|
||||||
<TabPane tab="Tab 3" key="3">
|
children: `Content of Tab Pane 1`,
|
||||||
Content of Tab Pane 3
|
},
|
||||||
</TabPane>
|
{
|
||||||
</Tabs>
|
label: `Tab 2`,
|
||||||
|
key: '2',
|
||||||
|
children: `Content of Tab Pane 2`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `Tab 3`,
|
||||||
|
key: '3',
|
||||||
|
children: `Content of Tab Pane 3`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -17,27 +17,24 @@ Should be used at the top of container, needs to override styles.
|
|||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const items = new Array(3).fill(null).map((_, i) => {
|
||||||
|
const id = String(i + 1);
|
||||||
|
return {
|
||||||
|
label: `Tab Title ${id}`,
|
||||||
|
key: id,
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<p>Content of Tab Pane {id}</p>
|
||||||
|
<p>Content of Tab Pane {id}</p>
|
||||||
|
<p>Content of Tab Pane {id}</p>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<div className="card-container">
|
<div className="card-container">
|
||||||
<Tabs type="card">
|
<Tabs type="card" items={items} />
|
||||||
<TabPane tab="Tab Title 1" key="1">
|
|
||||||
<p>Content of Tab Pane 1</p>
|
|
||||||
<p>Content of Tab Pane 1</p>
|
|
||||||
<p>Content of Tab Pane 1</p>
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab="Tab Title 2" key="2">
|
|
||||||
<p>Content of Tab Pane 2</p>
|
|
||||||
<p>Content of Tab Pane 2</p>
|
|
||||||
<p>Content of Tab Pane 2</p>
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab="Tab Title 3" key="3">
|
|
||||||
<p>Content of Tab Pane 3</p>
|
|
||||||
<p>Content of Tab Pane 3</p>
|
|
||||||
<p>Content of Tab Pane 3</p>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -17,24 +17,23 @@ Another type of Tabs, which doesn't support vertical mode.
|
|||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
const onChange = (key: string) => {
|
const onChange = (key: string) => {
|
||||||
console.log(key);
|
console.log(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<Tabs onChange={onChange} type="card">
|
<Tabs
|
||||||
<TabPane tab="Tab 1" key="1">
|
onChange={onChange}
|
||||||
Content of Tab Pane 1
|
type="card"
|
||||||
</TabPane>
|
items={new Array(3).fill(null).map((_, i) => {
|
||||||
<TabPane tab="Tab 2" key="2">
|
const id = String(i + 1);
|
||||||
Content of Tab Pane 2
|
return {
|
||||||
</TabPane>
|
label: `Tab ${id}`,
|
||||||
<TabPane tab="Tab 3" key="3">
|
key: id,
|
||||||
Content of Tab Pane 3
|
children: `Content of Tab Pane ${id}`,
|
||||||
</TabPane>
|
};
|
||||||
</Tabs>
|
})}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -17,20 +17,19 @@ Centered tabs.
|
|||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<Tabs defaultActiveKey="1" centered>
|
<Tabs
|
||||||
<TabPane tab="Tab 1" key="1">
|
defaultActiveKey="1"
|
||||||
Content of Tab Pane 1
|
centered
|
||||||
</TabPane>
|
items={new Array(3).fill(null).map((_, i) => {
|
||||||
<TabPane tab="Tab 2" key="2">
|
const id = String(i + 1);
|
||||||
Content of Tab Pane 2
|
return {
|
||||||
</TabPane>
|
label: `Tab ${id}`,
|
||||||
<TabPane tab="Tab 3" key="3">
|
key: id,
|
||||||
Content of Tab Pane 3
|
children: `Content of Tab Pane ${id}`,
|
||||||
</TabPane>
|
};
|
||||||
</Tabs>
|
})}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -17,16 +17,14 @@ Hide default plus icon, and bind event for customized trigger.
|
|||||||
import { Button, Tabs } from 'antd';
|
import { Button, Tabs } from 'antd';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const defaultPanes = new Array(2).fill(null).map((_, index) => {
|
||||||
|
|
||||||
const defaultPanes = Array.from({ length: 2 }).map((_, index) => {
|
|
||||||
const id = String(index + 1);
|
const id = String(index + 1);
|
||||||
return { title: `Tab ${id}`, content: `Content of Tab Pane ${index + 1}`, key: id };
|
return { label: `Tab ${id}`, children: `Content of Tab Pane ${index + 1}`, key: id };
|
||||||
});
|
});
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const [activeKey, setActiveKey] = useState(defaultPanes[0].key);
|
const [activeKey, setActiveKey] = useState(defaultPanes[0].key);
|
||||||
const [panes, setPanes] = useState(defaultPanes);
|
const [items, setItems] = useState(defaultPanes);
|
||||||
const newTabIndex = useRef(0);
|
const newTabIndex = useRef(0);
|
||||||
|
|
||||||
const onChange = (key: string) => {
|
const onChange = (key: string) => {
|
||||||
@ -35,18 +33,18 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
const add = () => {
|
const add = () => {
|
||||||
const newActiveKey = `newTab${newTabIndex.current++}`;
|
const newActiveKey = `newTab${newTabIndex.current++}`;
|
||||||
setPanes([...panes, { title: 'New Tab', content: 'New Tab Pane', key: newActiveKey }]);
|
setItems([...items, { label: 'New Tab', children: 'New Tab Pane', key: newActiveKey }]);
|
||||||
setActiveKey(newActiveKey);
|
setActiveKey(newActiveKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
const remove = (targetKey: string) => {
|
const remove = (targetKey: string) => {
|
||||||
const targetIndex = panes.findIndex(pane => pane.key === targetKey);
|
const targetIndex = items.findIndex(pane => pane.key === targetKey);
|
||||||
const newPanes = panes.filter(pane => pane.key !== targetKey);
|
const newPanes = items.filter(pane => pane.key !== targetKey);
|
||||||
if (newPanes.length && targetKey === activeKey) {
|
if (newPanes.length && targetKey === activeKey) {
|
||||||
const { key } = newPanes[targetIndex === newPanes.length ? targetIndex - 1 : targetIndex];
|
const { key } = newPanes[targetIndex === newPanes.length ? targetIndex - 1 : targetIndex];
|
||||||
setActiveKey(key);
|
setActiveKey(key);
|
||||||
}
|
}
|
||||||
setPanes(newPanes);
|
setItems(newPanes);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onEdit = (targetKey: string, action: 'add' | 'remove') => {
|
const onEdit = (targetKey: string, action: 'add' | 'remove') => {
|
||||||
@ -62,13 +60,14 @@ const App: React.FC = () => {
|
|||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<Button onClick={add}>ADD</Button>
|
<Button onClick={add}>ADD</Button>
|
||||||
</div>
|
</div>
|
||||||
<Tabs hideAdd onChange={onChange} activeKey={activeKey} type="editable-card" onEdit={onEdit}>
|
<Tabs
|
||||||
{panes.map(pane => (
|
hideAdd
|
||||||
<TabPane tab={pane.title} key={pane.key}>
|
onChange={onChange}
|
||||||
{pane.content}
|
activeKey={activeKey}
|
||||||
</TabPane>
|
type="editable-card"
|
||||||
))}
|
onEdit={onEdit}
|
||||||
</Tabs>
|
items={items}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -20,8 +20,6 @@ import React, { useRef, useState } from 'react';
|
|||||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
const type = 'DraggableTabNode';
|
const type = 'DraggableTabNode';
|
||||||
interface DraggableTabPaneProps extends React.HTMLAttributes<HTMLDivElement> {
|
interface DraggableTabPaneProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
index: React.Key;
|
index: React.Key;
|
||||||
@ -62,16 +60,16 @@ const DraggableTabNode = ({ index, children, moveNode }: DraggableTabPaneProps)
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DraggableTabs: React.FC<{ children: React.ReactNode }> = props => {
|
const DraggableTabs: React.FC<TabsProps> = props => {
|
||||||
const { children } = props;
|
const { items = [] } = props;
|
||||||
const [order, setOrder] = useState<React.Key[]>([]);
|
const [order, setOrder] = useState<React.Key[]>([]);
|
||||||
|
|
||||||
const moveTabNode = (dragKey: React.Key, hoverKey: React.Key) => {
|
const moveTabNode = (dragKey: React.Key, hoverKey: React.Key) => {
|
||||||
const newOrder = order.slice();
|
const newOrder = order.slice();
|
||||||
|
|
||||||
React.Children.forEach(children, (c: React.ReactElement) => {
|
items.forEach(item => {
|
||||||
if (c.key && newOrder.indexOf(c.key) === -1) {
|
if (item.key && newOrder.indexOf(item.key) === -1) {
|
||||||
newOrder.push(c.key);
|
newOrder.push(item.key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -94,12 +92,7 @@ const DraggableTabs: React.FC<{ children: React.ReactNode }> = props => {
|
|||||||
</DefaultTabBar>
|
</DefaultTabBar>
|
||||||
);
|
);
|
||||||
|
|
||||||
const tabs: React.ReactElement[] = [];
|
const orderItems = [...items].sort((a, b) => {
|
||||||
React.Children.forEach(children, (c: React.ReactElement) => {
|
|
||||||
tabs.push(c);
|
|
||||||
});
|
|
||||||
|
|
||||||
const orderTabs = tabs.slice().sort((a, b) => {
|
|
||||||
const orderA = order.indexOf(a.key!);
|
const orderA = order.indexOf(a.key!);
|
||||||
const orderB = order.indexOf(b.key!);
|
const orderB = order.indexOf(b.key!);
|
||||||
|
|
||||||
@ -113,33 +106,30 @@ const DraggableTabs: React.FC<{ children: React.ReactNode }> = props => {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ia = tabs.indexOf(a);
|
const ia = items.indexOf(a);
|
||||||
const ib = tabs.indexOf(b);
|
const ib = items.indexOf(b);
|
||||||
|
|
||||||
return ia - ib;
|
return ia - ib;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<Tabs renderTabBar={renderTabBar} {...props}>
|
<Tabs renderTabBar={renderTabBar} {...props} items={orderItems} />
|
||||||
{orderTabs}
|
|
||||||
</Tabs>
|
|
||||||
</DndProvider>
|
</DndProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<DraggableTabs>
|
<DraggableTabs
|
||||||
<TabPane tab="tab 1" key="1">
|
items={new Array(3).fill(null).map((_, i) => {
|
||||||
Content of Tab Pane 1
|
const id = String(i + 1);
|
||||||
</TabPane>
|
return {
|
||||||
<TabPane tab="tab 2" key="2">
|
label: `tab ${id}`,
|
||||||
Content of Tab Pane 2
|
key: id,
|
||||||
</TabPane>
|
children: `Content of Tab Pane ${id}`,
|
||||||
<TabPane tab="tab 3" key="3">
|
};
|
||||||
Content of Tab Pane 3
|
})}
|
||||||
</TabPane>
|
/>
|
||||||
</DraggableTabs>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -19,8 +19,6 @@ import { Tabs } from 'antd';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Sticky, StickyContainer } from 'react-sticky';
|
import { Sticky, StickyContainer } from 'react-sticky';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
const renderTabBar: TabsProps['renderTabBar'] = (props, DefaultTabBar) => (
|
const renderTabBar: TabsProps['renderTabBar'] = (props, DefaultTabBar) => (
|
||||||
<Sticky bottomOffset={80}>
|
<Sticky bottomOffset={80}>
|
||||||
{({ style }) => (
|
{({ style }) => (
|
||||||
@ -29,19 +27,19 @@ const renderTabBar: TabsProps['renderTabBar'] = (props, DefaultTabBar) => (
|
|||||||
</Sticky>
|
</Sticky>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const items = new Array(3).fill(null).map((_, i) => {
|
||||||
|
const id = String(i + 1);
|
||||||
|
return {
|
||||||
|
label: `Tab ${id}`,
|
||||||
|
key: id,
|
||||||
|
children: `Content of Tab Pane ${id}`,
|
||||||
|
style: i === 0 ? { height: 200 } : undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<StickyContainer>
|
<StickyContainer>
|
||||||
<Tabs defaultActiveKey="1" renderTabBar={renderTabBar}>
|
<Tabs defaultActiveKey="1" renderTabBar={renderTabBar} items={items} />
|
||||||
<TabPane tab="Tab 1" key="1" style={{ height: 200 }}>
|
|
||||||
Content of Tab Pane 1
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab="Tab 2" key="2">
|
|
||||||
Content of Tab Pane 2
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab="Tab 3" key="3">
|
|
||||||
Content of Tab Pane 3
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</StickyContainer>
|
</StickyContainer>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
36
components/tabs/demo/deprecated.md
Normal file
36
components/tabs/demo/deprecated.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
order: -1
|
||||||
|
title:
|
||||||
|
zh-CN: 基础用法(废弃的语法糖)
|
||||||
|
en-US: Basic usage (deprecated syntactic sugar)
|
||||||
|
version: < 4.23.0
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
默认选中第一项。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Default activate first tab.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Tabs } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const App: React.FC = () => (
|
||||||
|
<Tabs defaultActiveKey="1">
|
||||||
|
<Tabs.TabPane tab="Tab 1" key="1">
|
||||||
|
Content of Tab Pane 1
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="Tab 2" key="2">
|
||||||
|
Content of Tab Pane 2
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="Tab 3" key="3">
|
||||||
|
Content of Tab Pane 3
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
```
|
@ -17,20 +17,28 @@ Disabled a tab.
|
|||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<Tabs defaultActiveKey="1">
|
<Tabs
|
||||||
<TabPane tab="Tab 1" key="1">
|
defaultActiveKey="1"
|
||||||
Tab 1
|
items={[
|
||||||
</TabPane>
|
{
|
||||||
<TabPane tab="Tab 2" disabled key="2">
|
label: 'Tab 1',
|
||||||
Tab 2
|
key: '1',
|
||||||
</TabPane>
|
children: 'Tab 1',
|
||||||
<TabPane tab="Tab 3" key="3">
|
},
|
||||||
Tab 3
|
{
|
||||||
</TabPane>
|
label: 'Tab 2',
|
||||||
</Tabs>
|
key: '2',
|
||||||
|
children: 'Tab 2',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tab 3',
|
||||||
|
key: '3',
|
||||||
|
children: 'Tab 3',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -17,22 +17,20 @@ Only card type Tabs support adding & closable. +Use `closable={false}` to disabl
|
|||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const initialItems = [
|
||||||
|
{ label: 'Tab 1', children: 'Content of Tab 1', key: '1' },
|
||||||
const initialPanes = [
|
{ label: 'Tab 2', children: 'Content of Tab 2', key: '2' },
|
||||||
{ title: 'Tab 1', content: 'Content of Tab 1', key: '1' },
|
|
||||||
{ title: 'Tab 2', content: 'Content of Tab 2', key: '2' },
|
|
||||||
{
|
{
|
||||||
title: 'Tab 3',
|
label: 'Tab 3',
|
||||||
content: 'Content of Tab 3',
|
children: 'Content of Tab 3',
|
||||||
key: '3',
|
key: '3',
|
||||||
closable: false,
|
closable: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const [activeKey, setActiveKey] = useState(initialPanes[0].key);
|
const [activeKey, setActiveKey] = useState(initialItems[0].key);
|
||||||
const [panes, setPanes] = useState(initialPanes);
|
const [items, setItems] = useState(initialItems);
|
||||||
const newTabIndex = useRef(0);
|
const newTabIndex = useRef(0);
|
||||||
|
|
||||||
const onChange = (newActiveKey: string) => {
|
const onChange = (newActiveKey: string) => {
|
||||||
@ -41,21 +39,21 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
const add = () => {
|
const add = () => {
|
||||||
const newActiveKey = `newTab${newTabIndex.current++}`;
|
const newActiveKey = `newTab${newTabIndex.current++}`;
|
||||||
const newPanes = [...panes];
|
const newPanes = [...items];
|
||||||
newPanes.push({ title: 'New Tab', content: 'Content of new Tab', key: newActiveKey });
|
newPanes.push({ label: 'New Tab', children: 'Content of new Tab', key: newActiveKey });
|
||||||
setPanes(newPanes);
|
setItems(newPanes);
|
||||||
setActiveKey(newActiveKey);
|
setActiveKey(newActiveKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
const remove = (targetKey: string) => {
|
const remove = (targetKey: string) => {
|
||||||
let newActiveKey = activeKey;
|
let newActiveKey = activeKey;
|
||||||
let lastIndex = -1;
|
let lastIndex = -1;
|
||||||
panes.forEach((pane, i) => {
|
items.forEach((item, i) => {
|
||||||
if (pane.key === targetKey) {
|
if (item.key === targetKey) {
|
||||||
lastIndex = i - 1;
|
lastIndex = i - 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const newPanes = panes.filter(pane => pane.key !== targetKey);
|
const newPanes = items.filter(item => item.key !== targetKey);
|
||||||
if (newPanes.length && newActiveKey === targetKey) {
|
if (newPanes.length && newActiveKey === targetKey) {
|
||||||
if (lastIndex >= 0) {
|
if (lastIndex >= 0) {
|
||||||
newActiveKey = newPanes[lastIndex].key;
|
newActiveKey = newPanes[lastIndex].key;
|
||||||
@ -63,7 +61,7 @@ const App: React.FC = () => {
|
|||||||
newActiveKey = newPanes[0].key;
|
newActiveKey = newPanes[0].key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setPanes(newPanes);
|
setItems(newPanes);
|
||||||
setActiveKey(newActiveKey);
|
setActiveKey(newActiveKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -76,13 +74,13 @@ const App: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs type="editable-card" onChange={onChange} activeKey={activeKey} onEdit={onEdit}>
|
<Tabs
|
||||||
{panes.map(pane => (
|
type="editable-card"
|
||||||
<TabPane tab={pane.title} key={pane.key} closable={pane.closable}>
|
onChange={onChange}
|
||||||
{pane.content}
|
activeKey={activeKey}
|
||||||
</TabPane>
|
onEdit={onEdit}
|
||||||
))}
|
items={items}
|
||||||
</Tabs>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,8 +17,6 @@ You can add extra actions to the right or left or even both side of Tabs.
|
|||||||
import { Button, Checkbox, Divider, Tabs } from 'antd';
|
import { Button, Checkbox, Divider, Tabs } from 'antd';
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
const CheckboxGroup = Checkbox.Group;
|
const CheckboxGroup = Checkbox.Group;
|
||||||
|
|
||||||
const operations = <Button>Extra Action</Button>;
|
const operations = <Button>Extra Action</Button>;
|
||||||
@ -32,6 +30,15 @@ const options = ['left', 'right'];
|
|||||||
|
|
||||||
type PositionType = 'left' | 'right';
|
type PositionType = 'left' | 'right';
|
||||||
|
|
||||||
|
const items = new Array(3).fill(null).map((_, i) => {
|
||||||
|
const id = String(i + 1);
|
||||||
|
return {
|
||||||
|
label: `Tab ${id}`,
|
||||||
|
key: id,
|
||||||
|
children: `Content of tab ${id}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const [position, setPosition] = useState<PositionType[]>(['left', 'right']);
|
const [position, setPosition] = useState<PositionType[]>(['left', 'right']);
|
||||||
|
|
||||||
@ -46,17 +53,7 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tabs tabBarExtraContent={operations}>
|
<Tabs tabBarExtraContent={operations} items={items} />
|
||||||
<TabPane tab="Tab 1" key="1">
|
|
||||||
Content of tab 1
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab="Tab 2" key="2">
|
|
||||||
Content of tab 2
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab="Tab 3" key="3">
|
|
||||||
Content of tab 3
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
@ -71,17 +68,7 @@ const App: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<Tabs tabBarExtraContent={slot}>
|
<Tabs tabBarExtraContent={slot} items={items} />
|
||||||
<TabPane tab="Tab 1" key="1">
|
|
||||||
Content of tab 1
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab="Tab 2" key="2">
|
|
||||||
Content of tab 2
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab="Tab 3" key="3">
|
|
||||||
Content of tab 3
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,33 +18,24 @@ import { AndroidOutlined, AppleOutlined } from '@ant-design/icons';
|
|||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<Tabs defaultActiveKey="2">
|
<Tabs
|
||||||
<TabPane
|
defaultActiveKey="2"
|
||||||
tab={
|
items={[AppleOutlined, AndroidOutlined].map((Icon, i) => {
|
||||||
<span>
|
const id = String(i + 1);
|
||||||
<AppleOutlined />
|
|
||||||
Tab 1
|
return {
|
||||||
</span>
|
label: (
|
||||||
}
|
<span>
|
||||||
key="1"
|
<Icon />
|
||||||
>
|
Tab {id}
|
||||||
Tab 1
|
</span>
|
||||||
</TabPane>
|
),
|
||||||
<TabPane
|
key: id,
|
||||||
tab={
|
children: `Tab ${id}`,
|
||||||
<span>
|
};
|
||||||
<AndroidOutlined />
|
})}
|
||||||
Tab 2
|
/>
|
||||||
</span>
|
|
||||||
}
|
|
||||||
key="2"
|
|
||||||
>
|
|
||||||
Tab 2
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -18,13 +18,10 @@ Default activate first tab.
|
|||||||
import { Select, Tabs } from 'antd';
|
import { Select, Tabs } from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
const positionList = ['left', 'right', 'top', 'bottom'];
|
const positionList = ['left', 'right', 'top', 'bottom'];
|
||||||
|
|
||||||
const list = Array.from({ length: 20 }).map((_, index) => index);
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const [parentPos, setParentPos] = useState(undefined);
|
const [parentPos, setParentPos] = useState(undefined);
|
||||||
const [childPos, setChildPos] = useState(undefined);
|
const [childPos, setChildPos] = useState(undefined);
|
||||||
@ -81,25 +78,39 @@ const App: React.FC = () => {
|
|||||||
<Option value="editable-card">Parent - card edit</Option>
|
<Option value="editable-card">Parent - card edit</Option>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Tabs defaultActiveKey="1" tabPosition={parentPos} type={parentType}>
|
<Tabs
|
||||||
<TabPane tab="Tab 1" key="1">
|
defaultActiveKey="1"
|
||||||
<Tabs
|
tabPosition={parentPos}
|
||||||
defaultActiveKey="1"
|
type={parentType}
|
||||||
tabPosition={childPos}
|
items={[
|
||||||
type={childType}
|
{
|
||||||
style={{ height: 300 }}
|
label: 'Tab 1',
|
||||||
>
|
key: '1',
|
||||||
{list.map(key => (
|
children: (
|
||||||
<TabPane tab={`Tab ${key}`} key={key}>
|
<Tabs
|
||||||
TTTT {key}
|
defaultActiveKey="1"
|
||||||
</TabPane>
|
tabPosition={childPos}
|
||||||
))}
|
type={childType}
|
||||||
</Tabs>
|
style={{ height: 300 }}
|
||||||
</TabPane>
|
items={new Array(20).fill(null).map((_, index) => {
|
||||||
<TabPane tab="Tab 2" key="2">
|
const key = String(index);
|
||||||
Content of Tab Pane 2
|
|
||||||
</TabPane>
|
return {
|
||||||
</Tabs>
|
label: `Tab ${key}`,
|
||||||
|
key,
|
||||||
|
children: `TTTT ${key}`,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tab 2',
|
||||||
|
key: '2',
|
||||||
|
children: 'Content of Tab Pane 2',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,8 +18,6 @@ import type { RadioChangeEvent } from 'antd';
|
|||||||
import { Radio, Space, Tabs } from 'antd';
|
import { Radio, Space, Tabs } from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
type TabPosition = 'left' | 'right' | 'top' | 'bottom';
|
type TabPosition = 'left' | 'right' | 'top' | 'bottom';
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
@ -40,17 +38,17 @@ const App: React.FC = () => {
|
|||||||
<Radio.Button value="right">right</Radio.Button>
|
<Radio.Button value="right">right</Radio.Button>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Space>
|
</Space>
|
||||||
<Tabs tabPosition={tabPosition}>
|
<Tabs
|
||||||
<TabPane tab="Tab 1" key="1">
|
tabPosition={tabPosition}
|
||||||
Content of Tab 1
|
items={new Array(3).fill(null).map((_, i) => {
|
||||||
</TabPane>
|
const id = String(i + 1);
|
||||||
<TabPane tab="Tab 2" key="2">
|
return {
|
||||||
Content of Tab 2
|
label: `Tab ${id}`,
|
||||||
</TabPane>
|
key: id,
|
||||||
<TabPane tab="Tab 3" key="3">
|
children: `Content of Tab ${id}`,
|
||||||
Content of Tab 3
|
};
|
||||||
</TabPane>
|
})}
|
||||||
</Tabs>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -19,8 +19,6 @@ import { Radio, Tabs } from 'antd';
|
|||||||
import type { SizeType } from 'antd/es/config-provider/SizeContext';
|
import type { SizeType } from 'antd/es/config-provider/SizeContext';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const [size, setSize] = useState<SizeType>('small');
|
const [size, setSize] = useState<SizeType>('small');
|
||||||
|
|
||||||
@ -35,28 +33,32 @@ const App: React.FC = () => {
|
|||||||
<Radio.Button value="middle">Middle</Radio.Button>
|
<Radio.Button value="middle">Middle</Radio.Button>
|
||||||
<Radio.Button value="large">Large</Radio.Button>
|
<Radio.Button value="large">Large</Radio.Button>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
<Tabs defaultActiveKey="1" size={size} style={{ marginBottom: 32 }}>
|
<Tabs
|
||||||
<TabPane tab="Tab 1" key="1">
|
defaultActiveKey="1"
|
||||||
Content of tab 1
|
size={size}
|
||||||
</TabPane>
|
style={{ marginBottom: 32 }}
|
||||||
<TabPane tab="Tab 2" key="2">
|
items={new Array(3).fill(null).map((_, i) => {
|
||||||
Content of tab 2
|
const id = String(i + 1);
|
||||||
</TabPane>
|
return {
|
||||||
<TabPane tab="Tab 3" key="3">
|
label: `Tab ${id}`,
|
||||||
Content of tab 3
|
key: id,
|
||||||
</TabPane>
|
children: `Content of tab ${id}`,
|
||||||
</Tabs>
|
};
|
||||||
<Tabs defaultActiveKey="1" type="card" size={size}>
|
})}
|
||||||
<TabPane tab="Card Tab 1" key="1">
|
/>
|
||||||
Content of card tab 1
|
<Tabs
|
||||||
</TabPane>
|
defaultActiveKey="1"
|
||||||
<TabPane tab="Card Tab 2" key="2">
|
type="card"
|
||||||
Content of card tab 2
|
size={size}
|
||||||
</TabPane>
|
items={new Array(3).fill(null).map((_, i) => {
|
||||||
<TabPane tab="Card Tab 3" key="3">
|
const id = String(i + 1);
|
||||||
Content of card tab 3
|
return {
|
||||||
</TabPane>
|
label: `Card Tab ${id}`,
|
||||||
</Tabs>
|
key: id,
|
||||||
|
children: `Content of card tab ${id}`,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,8 +18,6 @@ import type { RadioChangeEvent } from 'antd';
|
|||||||
import { Radio, Tabs } from 'antd';
|
import { Radio, Tabs } from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
type TabPosition = 'left' | 'right' | 'top' | 'bottom';
|
type TabPosition = 'left' | 'right' | 'top' | 'bottom';
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
@ -35,13 +33,20 @@ const App: React.FC = () => {
|
|||||||
<Radio.Button value="top">Horizontal</Radio.Button>
|
<Radio.Button value="top">Horizontal</Radio.Button>
|
||||||
<Radio.Button value="left">Vertical</Radio.Button>
|
<Radio.Button value="left">Vertical</Radio.Button>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
<Tabs defaultActiveKey="1" tabPosition={mode} style={{ height: 220 }}>
|
<Tabs
|
||||||
{[...Array.from({ length: 30 }, (_, i) => i)].map(i => (
|
defaultActiveKey="1"
|
||||||
<TabPane tab={`Tab-${i}`} key={i} disabled={i === 28}>
|
tabPosition={mode}
|
||||||
Content of tab {i}
|
style={{ height: 220 }}
|
||||||
</TabPane>
|
items={new Array(30).fill(null).map((_, i) => {
|
||||||
))}
|
const id = String(i);
|
||||||
</Tabs>
|
return {
|
||||||
|
label: `Tab-${id}`,
|
||||||
|
key: id,
|
||||||
|
disabled: i === 28,
|
||||||
|
children: `Content of tab ${id}`,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
35
components/tabs/hooks/useLegacyItems.ts
Normal file
35
components/tabs/hooks/useLegacyItems.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import toArray from 'rc-util/lib/Children/toArray';
|
||||||
|
import type { Tab } from 'rc-tabs/lib/interface';
|
||||||
|
import type { TabsProps, TabPaneProps } from '..';
|
||||||
|
import warning from '../../_util/warning';
|
||||||
|
|
||||||
|
function filter<T>(items: (T | null)[]): T[] {
|
||||||
|
return items.filter(item => item) as T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useLegacyItems(items?: TabsProps['items'], children?: React.ReactNode) {
|
||||||
|
if (items) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
warning(!children, 'Tabs', 'Tabs.TabPane is deprecated. Please use `items` directly.');
|
||||||
|
|
||||||
|
const childrenItems = toArray(children).map((node: React.ReactElement<TabPaneProps>) => {
|
||||||
|
if (React.isValidElement(node)) {
|
||||||
|
const { key, props } = node;
|
||||||
|
const { tab, ...restProps } = props || {};
|
||||||
|
|
||||||
|
const item: Tab = {
|
||||||
|
key: String(key),
|
||||||
|
...restProps,
|
||||||
|
label: tab,
|
||||||
|
};
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filter(childrenItems);
|
||||||
|
}
|
@ -16,6 +16,32 @@ Ant Design has 3 types of Tabs for different situations.
|
|||||||
- Normal Tabs: for functional aspects of a page.
|
- Normal Tabs: for functional aspects of a page.
|
||||||
- [Radio.Button](/components/radio/#components-radio-demo-radiobutton): for secondary tabs.
|
- [Radio.Button](/components/radio/#components-radio-demo-radiobutton): for secondary tabs.
|
||||||
|
|
||||||
|
### Usage upgrade after 4.23.0
|
||||||
|
|
||||||
|
```__react
|
||||||
|
import Alert from '../alert';
|
||||||
|
ReactDOM.render(<Alert message="After version 4.23.0, we provide a simpler usage <Tabs items={[...]} /> with better performance and potential of writing simpler code style in your applications. Meanwhile, we deprecated the old usage in browser console, we will remove it in antd 5.0." />, mountNode);
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// works when >=4.23.0, recommended ✅
|
||||||
|
const items = [
|
||||||
|
{ label: 'Tab 1', key: 'item-1', children: 'Content 1' }, // remember to pass the key prop
|
||||||
|
{ label: 'Tab 2', key: 'item-2', children: 'Content 2' },
|
||||||
|
];
|
||||||
|
return <Tabs items={items} />;
|
||||||
|
|
||||||
|
// works when <4.23.0, deprecated when >=4.23.0 🙅🏻♀️
|
||||||
|
<Tabs>
|
||||||
|
<Tabs.TabPane tab="Tab 1" key="item-1">
|
||||||
|
Content 1
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="Tab 2" key="item-2">
|
||||||
|
Content 2
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>;
|
||||||
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### Tabs
|
### Tabs
|
||||||
|
@ -3,7 +3,7 @@ import EllipsisOutlined from '@ant-design/icons/EllipsisOutlined';
|
|||||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { TabsProps as RcTabsProps } from 'rc-tabs';
|
import type { TabsProps as RcTabsProps } from 'rc-tabs';
|
||||||
import RcTabs, { TabPane, TabPaneProps } from 'rc-tabs';
|
import RcTabs from 'rc-tabs';
|
||||||
import type { EditableConfig } from 'rc-tabs/lib/interface';
|
import type { EditableConfig } from 'rc-tabs/lib/interface';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
@ -11,6 +11,8 @@ import { ConfigContext } from '../config-provider';
|
|||||||
import type { SizeType } from '../config-provider/SizeContext';
|
import type { SizeType } from '../config-provider/SizeContext';
|
||||||
import SizeContext from '../config-provider/SizeContext';
|
import SizeContext from '../config-provider/SizeContext';
|
||||||
import warning from '../_util/warning';
|
import warning from '../_util/warning';
|
||||||
|
import useLegacyItems from './hooks/useLegacyItems';
|
||||||
|
import TabPane, { TabPaneProps } from './TabPane';
|
||||||
|
|
||||||
import useStyle from './style';
|
import useStyle from './style';
|
||||||
|
|
||||||
@ -26,6 +28,7 @@ export interface TabsProps extends Omit<RcTabsProps, 'editable'> {
|
|||||||
centered?: boolean;
|
centered?: boolean;
|
||||||
addIcon?: React.ReactNode;
|
addIcon?: React.ReactNode;
|
||||||
onEdit?: (e: React.MouseEvent | React.KeyboardEvent | string, action: 'add' | 'remove') => void;
|
onEdit?: (e: React.MouseEvent | React.KeyboardEvent | string, action: 'add' | 'remove') => void;
|
||||||
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Tabs({
|
function Tabs({
|
||||||
@ -37,6 +40,8 @@ function Tabs({
|
|||||||
centered,
|
centered,
|
||||||
addIcon,
|
addIcon,
|
||||||
popupClassName,
|
popupClassName,
|
||||||
|
children,
|
||||||
|
items,
|
||||||
...props
|
...props
|
||||||
}: TabsProps) {
|
}: TabsProps) {
|
||||||
const { prefixCls: customizePrefixCls, moreIcon = <EllipsisOutlined /> } = props;
|
const { prefixCls: customizePrefixCls, moreIcon = <EllipsisOutlined /> } = props;
|
||||||
@ -63,6 +68,8 @@ function Tabs({
|
|||||||
'`onPrevClick` and `onNextClick` has been removed. Please use `onTabScroll` instead.',
|
'`onPrevClick` and `onNextClick` has been removed. Please use `onTabScroll` instead.',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const mergedItems = useLegacyItems(items, children);
|
||||||
|
|
||||||
return wrapSSR(
|
return wrapSSR(
|
||||||
<SizeContext.Consumer>
|
<SizeContext.Consumer>
|
||||||
{contextSize => {
|
{contextSize => {
|
||||||
@ -72,6 +79,7 @@ function Tabs({
|
|||||||
direction={direction}
|
direction={direction}
|
||||||
moreTransitionName={`${rootPrefixCls}-slide-up`}
|
moreTransitionName={`${rootPrefixCls}-slide-up`}
|
||||||
{...props}
|
{...props}
|
||||||
|
items={mergedItems}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
{
|
{
|
||||||
[`${prefixCls}-${size}`]: size,
|
[`${prefixCls}-${size}`]: size,
|
||||||
|
@ -19,6 +19,32 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
|
|||||||
- 既可用于容器顶部,也可用于容器内部,是最通用的 Tabs。
|
- 既可用于容器顶部,也可用于容器内部,是最通用的 Tabs。
|
||||||
- [Radio.Button](/components/radio/#components-radio-demo-radiobutton) 可作为更次级的页签来使用。
|
- [Radio.Button](/components/radio/#components-radio-demo-radiobutton) 可作为更次级的页签来使用。
|
||||||
|
|
||||||
|
### 4.23.0 用法升级
|
||||||
|
|
||||||
|
```__react
|
||||||
|
import Alert from '../alert';
|
||||||
|
ReactDOM.render(<Alert message="在 4.23.0 版本后,我们提供了 <Tabs items={[...]} /> 的简写方式,有更好的性能和更方便的数据组织方式,开发者不再需要自行拼接 JSX。同时我们废弃了原先的写法,你还是可以在 4.x 继续使用,但会在控制台看到警告,并会在 5.0 后移除。" />, mountNode);
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// >=4.23.0 可用,推荐的写法 ✅
|
||||||
|
const items = [
|
||||||
|
{ label: '项目 1', key: 'item-1', children: '内容 1' }, // 务必填写 key
|
||||||
|
{ label: '项目 2', key: 'item-2', children: '内容 2' },
|
||||||
|
];
|
||||||
|
return <Tabs items={items} />;
|
||||||
|
|
||||||
|
// <4.23.0 可用,>=4.23.0 时不推荐 🙅🏻♀️
|
||||||
|
<Tabs>
|
||||||
|
<Tabs.TabPane tab="项目 1" key="item-1">
|
||||||
|
内容 1
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="项目 2" key="item-2">
|
||||||
|
内容 2
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>;
|
||||||
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### Tabs
|
### Tabs
|
||||||
@ -31,6 +57,7 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
|
|||||||
| centered | 标签居中展示 | boolean | false | 4.4.0 |
|
| centered | 标签居中展示 | boolean | false | 4.4.0 |
|
||||||
| defaultActiveKey | 初始化选中面板的 key,如果没有设置 activeKey | string | `第一个面板` | |
|
| defaultActiveKey | 初始化选中面板的 key,如果没有设置 activeKey | string | `第一个面板` | |
|
||||||
| hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false | |
|
| hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false | |
|
||||||
|
| items | 配置选项卡内容 | [TabItem](#TabItem) | [] | 4.23.0 |
|
||||||
| moreIcon | 自定义折叠 icon | ReactNode | <EllipsisOutlined /> | 4.14.0 |
|
| moreIcon | 自定义折叠 icon | ReactNode | <EllipsisOutlined /> | 4.14.0 |
|
||||||
| popupClassName | 更多菜单的 `className` | string | - | 4.21.0 |
|
| popupClassName | 更多菜单的 `className` | string | - | 4.21.0 |
|
||||||
| renderTabBar | 替换 TabBar,用于二次封装标签头 | (props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement | - | |
|
| renderTabBar | 替换 TabBar,用于二次封装标签头 | (props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement | - | |
|
||||||
@ -48,7 +75,7 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
|
|||||||
|
|
||||||
> 更多属性查看 [rc-tabs tabs](https://github.com/react-component/tabs#tabs)
|
> 更多属性查看 [rc-tabs tabs](https://github.com/react-component/tabs#tabs)
|
||||||
|
|
||||||
### Tabs.TabPane
|
### TabItem
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 默认值 |
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
| ----------- | ----------------------------------------------- | --------- | ------ |
|
| ----------- | ----------------------------------------------- | --------- | ------ |
|
||||||
@ -56,6 +83,5 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
|
|||||||
| disabled | 禁用某一项 | boolean | false |
|
| disabled | 禁用某一项 | boolean | false |
|
||||||
| forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false |
|
| forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false |
|
||||||
| key | 对应 activeKey | string | - |
|
| key | 对应 activeKey | string | - |
|
||||||
| tab | 选项卡头显示文字 | ReactNode | - |
|
| label | 选项卡头显示文字 | ReactNode | - |
|
||||||
|
| children | 选项卡头显示内容 | ReactNode | - |
|
||||||
> 更多属性查看 [rc-tabs tabpane](https://github.com/react-component/tabs#tabpane)
|
|
||||||
|
@ -87,6 +87,20 @@ describe('TimePicker', () => {
|
|||||||
expect(wrapper.find('RangePicker').last().prop('dropdownClassName')).toEqual(popupClassName);
|
expect(wrapper.find('RangePicker').last().prop('dropdownClassName')).toEqual(popupClassName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('RangePicker should show warning when use dropdownClassName', () => {
|
||||||
|
mount(<TimePicker.RangePicker dropdownClassName="myCustomClassName" />);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
|
'Warning: [antd: RangePicker] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TimePicker should show warning when use dropdownClassName', () => {
|
||||||
|
mount(<TimePicker dropdownClassName="myCustomClassName" />);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
|
'Warning: [antd: TimePicker] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should support bordered', () => {
|
it('should support bordered', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<TimePicker
|
<TimePicker
|
||||||
|
@ -14,31 +14,50 @@ export interface TimePickerLocale {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TimeRangePickerProps extends Omit<RangePickerTimeProps<Dayjs>, 'picker'> {
|
export interface TimeRangePickerProps extends Omit<RangePickerTimeProps<Dayjs>, 'picker'> {
|
||||||
|
/**
|
||||||
|
* @deprecated `dropdownClassName` is deprecated which will be removed in next major
|
||||||
|
* version.Please use `popupClassName` instead.
|
||||||
|
*/
|
||||||
|
dropdownClassName?: string;
|
||||||
popupClassName?: string;
|
popupClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RangePicker = React.forwardRef<any, TimeRangePickerProps>((props, ref) => (
|
const RangePicker = React.forwardRef<any, TimeRangePickerProps>((props, ref) => {
|
||||||
<InternalRangePicker
|
const { dropdownClassName, popupClassName } = props;
|
||||||
{...props}
|
warning(
|
||||||
dropdownClassName={props.popupClassName}
|
!dropdownClassName,
|
||||||
picker="time"
|
'RangePicker',
|
||||||
mode={undefined}
|
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
ref={ref}
|
);
|
||||||
/>
|
return (
|
||||||
));
|
<InternalRangePicker
|
||||||
|
{...props}
|
||||||
|
dropdownClassName={popupClassName || dropdownClassName}
|
||||||
|
picker="time"
|
||||||
|
mode={undefined}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export interface TimePickerProps extends Omit<PickerTimeProps<Dayjs>, 'picker'> {
|
export interface TimePickerProps extends Omit<PickerTimeProps<Dayjs>, 'picker'> {
|
||||||
addon?: () => React.ReactNode;
|
addon?: () => React.ReactNode;
|
||||||
popupClassName?: string;
|
popupClassName?: string;
|
||||||
|
/**
|
||||||
|
* @deprecated `dropdownClassName` is deprecated which will be removed in next major
|
||||||
|
* version.Please use `popupClassName` instead.
|
||||||
|
*/
|
||||||
|
dropdownClassName?: string;
|
||||||
status?: InputStatus;
|
status?: InputStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimePicker = React.forwardRef<any, TimePickerProps>(
|
const TimePicker = React.forwardRef<any, TimePickerProps>(
|
||||||
({ addon, renderExtraFooter, popupClassName, ...restProps }, ref) => {
|
({ addon, renderExtraFooter, popupClassName, dropdownClassName, ...restProps }, ref) => {
|
||||||
const internalRenderExtraFooter = React.useMemo(() => {
|
const internalRenderExtraFooter = React.useMemo(() => {
|
||||||
if (renderExtraFooter) {
|
if (renderExtraFooter) {
|
||||||
return renderExtraFooter;
|
return renderExtraFooter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addon) {
|
if (addon) {
|
||||||
warning(
|
warning(
|
||||||
false,
|
false,
|
||||||
@ -50,10 +69,16 @@ const TimePicker = React.forwardRef<any, TimePickerProps>(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}, [addon, renderExtraFooter]);
|
}, [addon, renderExtraFooter]);
|
||||||
|
|
||||||
|
warning(
|
||||||
|
!dropdownClassName,
|
||||||
|
'TimePicker',
|
||||||
|
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InternalTimePicker
|
<InternalTimePicker
|
||||||
|
dropdownClassName={popupClassName || dropdownClassName}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
dropdownClassName={popupClassName}
|
|
||||||
mode={undefined}
|
mode={undefined}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
renderExtraFooter={internalRenderExtraFooter}
|
renderExtraFooter={internalRenderExtraFooter}
|
||||||
|
@ -2,7 +2,7 @@ import type { CSSObject } from '@ant-design/cssinjs';
|
|||||||
|
|
||||||
import type { FullToken, GenerateStyle } from '../../theme';
|
import type { FullToken, GenerateStyle } from '../../theme';
|
||||||
import { genComponentStyleHook, mergeToken } from '../../theme';
|
import { genComponentStyleHook, mergeToken } from '../../theme';
|
||||||
import { operationUnit, resetComponent, resetIcon } from '../../style';
|
import { resetComponent, resetIcon } from '../../style';
|
||||||
|
|
||||||
export interface ComponentToken {
|
export interface ComponentToken {
|
||||||
listWidth: number;
|
listWidth: number;
|
||||||
@ -105,6 +105,7 @@ const genTransferListStyle: GenerateStyle<TransferToken> = (token: TransferToken
|
|||||||
paddingSM,
|
paddingSM,
|
||||||
controlLineType,
|
controlLineType,
|
||||||
iconCls,
|
iconCls,
|
||||||
|
motionDurationSlow,
|
||||||
} = token;
|
} = token;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -195,7 +196,7 @@ const genTransferListStyle: GenerateStyle<TransferToken> = (token: TransferToken
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minHeight: transferItemHeight,
|
minHeight: transferItemHeight,
|
||||||
padding: `${transferItemPaddingVertical}px ${paddingSM}px`,
|
padding: `${transferItemPaddingVertical}px ${paddingSM}px`,
|
||||||
transition: `all ${token.motionDurationSlow}`,
|
transition: `all ${motionDurationSlow}`,
|
||||||
|
|
||||||
'> *:not(:last-child)': {
|
'> *:not(:last-child)': {
|
||||||
marginInlineEnd: marginXS,
|
marginInlineEnd: marginXS,
|
||||||
@ -213,19 +214,21 @@ const genTransferListStyle: GenerateStyle<TransferToken> = (token: TransferToken
|
|||||||
},
|
},
|
||||||
|
|
||||||
'&-remove': {
|
'&-remove': {
|
||||||
...operationUnit(token),
|
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
color: colorBorder,
|
color: colorBorder,
|
||||||
|
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: `all ${motionDurationSlow}`,
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
color: token.colorLinkHover,
|
||||||
|
},
|
||||||
|
|
||||||
'&::after': {
|
'&::after': {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
insert: `-${transferItemPaddingVertical}px -50%`,
|
insert: `-${transferItemPaddingVertical}px -50%`,
|
||||||
content: '""',
|
content: '""',
|
||||||
},
|
},
|
||||||
|
|
||||||
'&:hover': {
|
|
||||||
color: token.colorLinkHover,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'&:not(&-disabled)': {
|
'&:not(&-disabled)': {
|
||||||
|
@ -50,4 +50,13 @@ describe('TreeSelect', () => {
|
|||||||
const wrapper = mount(<TreeSelect treeIcon open notFoundContent="notFoundContent" />);
|
const wrapper = mount(<TreeSelect treeIcon open notFoundContent="notFoundContent" />);
|
||||||
expect(wrapper.text()).toBe('notFoundContent');
|
expect(wrapper.text()).toBe('notFoundContent');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show warning when use dropdownClassName', () => {
|
||||||
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
mount(<TreeSelect dropdownClassName="myCustomClassName" />);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
|
'Warning: [antd: TreeSelect] `dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
errorSpy.mockRestore();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,7 @@ Tree selection control.
|
|||||||
| bordered | Whether has border style | boolean | true | |
|
| bordered | Whether has border style | boolean | true | |
|
||||||
| defaultValue | To set the initial selected treeNode(s) | string \| string\[] | - | |
|
| defaultValue | To set the initial selected treeNode(s) | string \| string\[] | - | |
|
||||||
| disabled | Disabled or not | boolean | false | |
|
| disabled | Disabled or not | boolean | false | |
|
||||||
| dropdownClassName | The className of dropdown menu | string | - | |
|
| popupClassName | The className of dropdown menu | string | - | 4.23.0 |
|
||||||
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | true | |
|
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | true | |
|
||||||
| dropdownRender | Customize dropdown content | (originNode: ReactNode, props) => ReactNode | - | |
|
| dropdownRender | Customize dropdown content | (originNode: ReactNode, props) => ReactNode | - | |
|
||||||
| dropdownStyle | To set the style of the dropdown menu | CSSProperties | - | |
|
| dropdownStyle | To set the style of the dropdown menu | CSSProperties | - | |
|
||||||
|
@ -54,6 +54,12 @@ export interface TreeSelectProps<
|
|||||||
size?: SizeType;
|
size?: SizeType;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
placement?: SelectCommonPlacement;
|
placement?: SelectCommonPlacement;
|
||||||
|
/**
|
||||||
|
* @deprecated `dropdownClassName` is deprecated which will be removed in next major
|
||||||
|
* version.Please use `popupClassName` instead.
|
||||||
|
*/
|
||||||
|
dropdownClassName?: string;
|
||||||
|
popupClassName?: string;
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
treeLine?: TreeProps['showLine'];
|
treeLine?: TreeProps['showLine'];
|
||||||
status?: InputStatus;
|
status?: InputStatus;
|
||||||
@ -77,6 +83,7 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
|
|||||||
treeLine,
|
treeLine,
|
||||||
getPopupContainer,
|
getPopupContainer,
|
||||||
dropdownClassName,
|
dropdownClassName,
|
||||||
|
popupClassName,
|
||||||
treeIcon = false,
|
treeIcon = false,
|
||||||
transitionName,
|
transitionName,
|
||||||
choiceTransitionName = '',
|
choiceTransitionName = '',
|
||||||
@ -104,6 +111,13 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
|
|||||||
);
|
);
|
||||||
|
|
||||||
const rootPrefixCls = getPrefixCls();
|
const rootPrefixCls = getPrefixCls();
|
||||||
|
|
||||||
|
warning(
|
||||||
|
!dropdownClassName,
|
||||||
|
'TreeSelect',
|
||||||
|
'`dropdownClassName` is deprecated which will be removed in next major version. Please use `popupClassName` instead.',
|
||||||
|
);
|
||||||
|
|
||||||
const prefixCls = getPrefixCls('select', customizePrefixCls);
|
const prefixCls = getPrefixCls('select', customizePrefixCls);
|
||||||
const treePrefixCls = getPrefixCls('select-tree', customizePrefixCls);
|
const treePrefixCls = getPrefixCls('select-tree', customizePrefixCls);
|
||||||
const treeSelectPrefixCls = getPrefixCls('tree-select', customizePrefixCls);
|
const treeSelectPrefixCls = getPrefixCls('tree-select', customizePrefixCls);
|
||||||
@ -112,7 +126,7 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
|
|||||||
const [wrapTreeSelectSSR] = useStyle(treeSelectPrefixCls, treePrefixCls);
|
const [wrapTreeSelectSSR] = useStyle(treeSelectPrefixCls, treePrefixCls);
|
||||||
|
|
||||||
const mergedDropdownClassName = classNames(
|
const mergedDropdownClassName = classNames(
|
||||||
dropdownClassName,
|
popupClassName || dropdownClassName,
|
||||||
`${treeSelectPrefixCls}-dropdown`,
|
`${treeSelectPrefixCls}-dropdown`,
|
||||||
{
|
{
|
||||||
[`${treeSelectPrefixCls}-dropdown-rtl`]: direction === 'rtl',
|
[`${treeSelectPrefixCls}-dropdown-rtl`]: direction === 'rtl',
|
||||||
|
@ -23,7 +23,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Ax4DA0njr/TreeSelect.svg
|
|||||||
| bordered | 是否显示边框 | boolean | true | |
|
| bordered | 是否显示边框 | boolean | true | |
|
||||||
| defaultValue | 指定默认选中的条目 | string \| string\[] | - | |
|
| defaultValue | 指定默认选中的条目 | string \| string\[] | - | |
|
||||||
| disabled | 是否禁用 | boolean | false | |
|
| disabled | 是否禁用 | boolean | false | |
|
||||||
| dropdownClassName | 下拉菜单的 className 属性 | string | - | |
|
| popupClassName | 下拉菜单的 className 属性 | string | - | 4.23.0 |
|
||||||
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。false 时会关闭虚拟滚动 | boolean \| number | true | |
|
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。false 时会关闭虚拟滚动 | boolean \| number | true | |
|
||||||
| dropdownRender | 自定义下拉框内容 | (originNode: ReactNode, props) => ReactNode | - | |
|
| dropdownRender | 自定义下拉框内容 | (originNode: ReactNode, props) => ReactNode | - | |
|
||||||
| dropdownStyle | 下拉菜单的样式 | object | - | |
|
| dropdownStyle | 下拉菜单的样式 | object | - | |
|
||||||
|
@ -99,6 +99,19 @@ Yes, you can [import `antd` with script tag](https://ant.design/docs/react/intro
|
|||||||
|
|
||||||
If you need some features which should not be included in antd, try to extend antd's component with [HOC](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775). [more](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.eeu8q01s1)
|
If you need some features which should not be included in antd, try to extend antd's component with [HOC](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775). [more](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.eeu8q01s1)
|
||||||
|
|
||||||
|
## How to get the definition which is not export?
|
||||||
|
|
||||||
|
antd 会透出组件定义,但是随着重构可能导致内部一些定义命名或者属性变化。因而更推荐直接使用 Typescript 原生能力获取: antd will export mainly definitions, but not export internal definitions which may be rename or changed. So we recommend you to use Typescript's native ability to get the definition if needed:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Table } from 'antd';
|
||||||
|
|
||||||
|
type Props<T extends (...args: any) => any> = Parameters<T>[0];
|
||||||
|
|
||||||
|
type TableProps = Props<typeof Table<{ key: string, name: string, age: number }>>;
|
||||||
|
type DataSource = TableProps['dataSource'];
|
||||||
|
```
|
||||||
|
|
||||||
## Date-related components locale is not working?
|
## Date-related components locale is not working?
|
||||||
|
|
||||||
Please check whether import dayjs locale correctly.
|
Please check whether import dayjs locale correctly.
|
||||||
|
@ -113,6 +113,19 @@ antd 内部会对 props 进行浅比较实现性能优化。当状态变更,
|
|||||||
|
|
||||||
如果你需要一些 antd 没有包含的功能,你可以尝试通过 [HOC](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775) 拓展 antd 的组件。 [更多](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.eeu8q01s1)
|
如果你需要一些 antd 没有包含的功能,你可以尝试通过 [HOC](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775) 拓展 antd 的组件。 [更多](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.eeu8q01s1)
|
||||||
|
|
||||||
|
## 如何获取未导出的属性定义?
|
||||||
|
|
||||||
|
antd 会透出组件定义,但是随着重构可能导致内部一些定义命名或者属性变化。因而更推荐直接使用 Typescript 原生能力获取:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Table } from 'antd';
|
||||||
|
|
||||||
|
type Props<T extends (...args: any) => any> = Parameters<T>[0];
|
||||||
|
|
||||||
|
type TableProps = Props<typeof Table<{ key: string, name: string, age: number }>>;
|
||||||
|
type DataSource = TableProps['dataSource'];
|
||||||
|
```
|
||||||
|
|
||||||
## 我的组件默认语言是英文的?如何切回中文的。
|
## 我的组件默认语言是英文的?如何切回中文的。
|
||||||
|
|
||||||
请尝试使用 [ConfigProvider](/components/config-provider/#components-config-provider-demo-locale) 组件来包裹你的应用。
|
请尝试使用 [ConfigProvider](/components/config-provider/#components-config-provider-demo-locale) 组件来包裹你的应用。
|
||||||
|
@ -192,6 +192,14 @@ For parts that cannot be modified automatically, codemod will prompt on the comm
|
|||||||
|
|
||||||
`@ant-design/codemod-v4` will help you migrate to antd v4. Obsolete components will be kept running through @ant-design/compatible. Generally, you don't need to migrate manually. The following sections detail the overall migration and changes.
|
`@ant-design/codemod-v4` will help you migrate to antd v4. Obsolete components will be kept running through @ant-design/compatible. Generally, you don't need to migrate manually. The following sections detail the overall migration and changes.
|
||||||
|
|
||||||
|
#### Install compatible package
|
||||||
|
|
||||||
|
Install `@ant-design/compatible` with `v4-compatible-v3` tag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --save @ant-design/compatible@v4-compatible-v3
|
||||||
|
```
|
||||||
|
|
||||||
#### Import the obsolete Form and Mention components via @ant-design/compatible package
|
#### Import the obsolete Form and Mention components via @ant-design/compatible package
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
|
@ -193,6 +193,14 @@ antd4-codemod src
|
|||||||
|
|
||||||
`@ant-design/codemod-v4` 会帮你迁移到 antd v4, 废弃的组件则通过 `@ant-design/compatible` 保持运行, 一般来说你无需手动迁移。下方内容详细介绍了整体的迁移和变化,你也可以参照变动手动修改。
|
`@ant-design/codemod-v4` 会帮你迁移到 antd v4, 废弃的组件则通过 `@ant-design/compatible` 保持运行, 一般来说你无需手动迁移。下方内容详细介绍了整体的迁移和变化,你也可以参照变动手动修改。
|
||||||
|
|
||||||
|
#### 安装兼容包
|
||||||
|
|
||||||
|
安装 `@ant-design/compatible` 通过指定 `v4-compatible-v3` tag 确认为 v4 兼容 v3 版本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --save @ant-design/compatible@v4-compatible-v3
|
||||||
|
```
|
||||||
|
|
||||||
#### 将已废弃的 `Form` 和 `Mention` 组件通过 `@ant-design/compatible` 包引入
|
#### 将已废弃的 `Form` 和 `Mention` 组件通过 `@ant-design/compatible` 包引入
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
|
@ -128,9 +128,9 @@
|
|||||||
"rc-dropdown": "~4.0.0",
|
"rc-dropdown": "~4.0.0",
|
||||||
"rc-field-form": "~1.27.0",
|
"rc-field-form": "~1.27.0",
|
||||||
"rc-image": "~5.7.0",
|
"rc-image": "~5.7.0",
|
||||||
"rc-input": "~0.0.1-alpha.5",
|
"rc-input": "~0.1.2",
|
||||||
"rc-input-number": "~7.3.5",
|
"rc-input-number": "~7.3.5",
|
||||||
"rc-mentions": "~1.9.0",
|
"rc-mentions": "~1.9.1",
|
||||||
"rc-menu": "~9.6.0",
|
"rc-menu": "~9.6.0",
|
||||||
"rc-motion": "^2.6.1",
|
"rc-motion": "^2.6.1",
|
||||||
"rc-notification": "~5.0.0-alpha.9",
|
"rc-notification": "~5.0.0-alpha.9",
|
||||||
@ -144,8 +144,8 @@
|
|||||||
"rc-slider": "~10.0.0",
|
"rc-slider": "~10.0.0",
|
||||||
"rc-steps": "~4.1.0",
|
"rc-steps": "~4.1.0",
|
||||||
"rc-switch": "~3.2.0",
|
"rc-switch": "~3.2.0",
|
||||||
"rc-table": "~7.25.3",
|
"rc-table": "~7.26.0",
|
||||||
"rc-tabs": "~11.16.0",
|
"rc-tabs": "~12.0.0-alpha.1",
|
||||||
"rc-textarea": "~0.3.0",
|
"rc-textarea": "~0.3.0",
|
||||||
"rc-tooltip": "~5.2.0",
|
"rc-tooltip": "~5.2.0",
|
||||||
"rc-tree": "~5.6.5",
|
"rc-tree": "~5.6.5",
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
.operation-unit() {
|
.operation-unit() {
|
||||||
color: @link-color;
|
color: @link-color;
|
||||||
text-decoration: none;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color 0.3s;
|
transition: color 0.3s;
|
||||||
|
|
||||||
&:focus,
|
&:focus-visible,
|
||||||
&:hover {
|
&:hover {
|
||||||
color: @link-hover-color;
|
color: @link-hover-color;
|
||||||
}
|
}
|
||||||
|
@ -497,7 +497,11 @@ ReactDOM.render(<Demo />, document.getElementById('container'));
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (meta.version) {
|
if (meta.version) {
|
||||||
codeBox = <Badge.Ribbon text={meta.version}>{codeBox}</Badge.Ribbon>;
|
codeBox = (
|
||||||
|
<Badge.Ribbon text={meta.version} color={meta.version.includes('<') ? 'red' : null}>
|
||||||
|
{codeBox}
|
||||||
|
</Badge.Ribbon>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return codeBox;
|
return codeBox;
|
||||||
|
@ -13,60 +13,46 @@ interface CategoryProps {
|
|||||||
intl: any;
|
intl: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CategoryState {
|
const Category: React.FC<CategoryProps> = props => {
|
||||||
justCopied: string | null;
|
const { icons, title, newIcons, theme, intl } = props;
|
||||||
}
|
const [justCopied, setJustCopied] = React.useState<string | null>(null);
|
||||||
|
const copyId = React.useRef<NodeJS.Timeout | null>(null);
|
||||||
class Category extends React.Component<CategoryProps, CategoryState> {
|
const onCopied = React.useCallback((type: string, text: string) => {
|
||||||
copyId?: number;
|
|
||||||
|
|
||||||
state = {
|
|
||||||
justCopied: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
window.clearTimeout(this.copyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
onCopied = (type: string, text: string) => {
|
|
||||||
message.success(
|
message.success(
|
||||||
<span>
|
<span>
|
||||||
<code className="copied-code">{text}</code> copied 🎉
|
<code className="copied-code">{text}</code> copied 🎉
|
||||||
</span>,
|
</span>,
|
||||||
);
|
);
|
||||||
this.setState({ justCopied: type }, () => {
|
setJustCopied(type);
|
||||||
this.copyId = window.setTimeout(() => {
|
copyId.current = setTimeout(() => {
|
||||||
this.setState({ justCopied: null });
|
setJustCopied(null);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
}, []);
|
||||||
};
|
React.useEffect(
|
||||||
|
() => () => {
|
||||||
render() {
|
if (copyId.current) {
|
||||||
const {
|
clearTimeout(copyId.current);
|
||||||
icons,
|
}
|
||||||
title,
|
},
|
||||||
newIcons,
|
[],
|
||||||
theme,
|
);
|
||||||
intl: { messages },
|
return (
|
||||||
} = this.props;
|
<div>
|
||||||
const items = icons.map(name => (
|
<h3>{intl.messages[`app.docs.components.icon.category.${title}`]}</h3>
|
||||||
<CopyableIcon
|
<ul className="anticons-list">
|
||||||
key={name}
|
{icons.map(name => (
|
||||||
name={name}
|
<CopyableIcon
|
||||||
theme={theme}
|
key={name}
|
||||||
isNew={newIcons.indexOf(name) >= 0}
|
name={name}
|
||||||
justCopied={this.state.justCopied}
|
theme={theme}
|
||||||
onCopied={this.onCopied}
|
isNew={newIcons.includes(name)}
|
||||||
/>
|
justCopied={justCopied}
|
||||||
));
|
onCopied={onCopied}
|
||||||
|
/>
|
||||||
return (
|
))}
|
||||||
<div>
|
</ul>
|
||||||
<h3>{messages[`app.docs.components.icon.category.${title}`]}</h3>
|
</div>
|
||||||
<ul className="anticons-list">{items}</ul>
|
);
|
||||||
</div>
|
};
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(Category);
|
export default injectIntl(Category);
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Upload, Tooltip, Popover, Modal, Progress, message, Spin, Result } from 'antd';
|
import { Upload, Tooltip, Popover, Modal, Progress, message, Spin, Result } from 'antd';
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
import { injectIntl } from 'react-intl';
|
import { injectIntl } from 'react-intl';
|
||||||
import * as AntdIcons from '@ant-design/icons';
|
import * as AntdIcons from '@ant-design/icons';
|
||||||
|
|
||||||
const allIcons: {
|
const allIcons: { [key: string]: any } = AntdIcons;
|
||||||
[key: string]: any;
|
|
||||||
} = AntdIcons;
|
|
||||||
|
|
||||||
const { Dragger } = Upload;
|
const { Dragger } = Upload;
|
||||||
interface AntdIconClassifier {
|
interface AntdIconClassifier {
|
||||||
@ -27,8 +25,8 @@ interface PicSearcherState {
|
|||||||
loading: boolean;
|
loading: boolean;
|
||||||
modalVisible: boolean;
|
modalVisible: boolean;
|
||||||
popoverVisible: boolean;
|
popoverVisible: boolean;
|
||||||
icons: Array<string>;
|
icons: iconObject[];
|
||||||
fileList: Array<any>;
|
fileList: any[];
|
||||||
error: boolean;
|
error: boolean;
|
||||||
modelLoaded: boolean;
|
modelLoaded: boolean;
|
||||||
}
|
}
|
||||||
@ -38,8 +36,9 @@ interface iconObject {
|
|||||||
score: number;
|
score: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PicSearcher extends Component<PicSearcherProps, PicSearcherState> {
|
const PicSearcher: React.FC<PicSearcherProps> = ({ intl }) => {
|
||||||
state = {
|
const { messages } = intl;
|
||||||
|
const [state, setState] = useState<PicSearcherState>({
|
||||||
loading: false,
|
loading: false,
|
||||||
modalVisible: false,
|
modalVisible: false,
|
||||||
popoverVisible: false,
|
popoverVisible: false,
|
||||||
@ -47,83 +46,64 @@ class PicSearcher extends Component<PicSearcherProps, PicSearcherState> {
|
|||||||
fileList: [],
|
fileList: [],
|
||||||
error: false,
|
error: false,
|
||||||
modelLoaded: false,
|
modelLoaded: false,
|
||||||
};
|
});
|
||||||
|
const predict = (imgEl: HTMLImageElement) => {
|
||||||
componentDidMount() {
|
|
||||||
this.loadModel();
|
|
||||||
this.setState({ popoverVisible: !localStorage.getItem('disableIconTip') });
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
document.removeEventListener('paste', this.onPaste);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadModel = () => {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.onload = async () => {
|
|
||||||
await window.antdIconClassifier.load();
|
|
||||||
this.setState({ modelLoaded: true });
|
|
||||||
document.addEventListener('paste', this.onPaste);
|
|
||||||
};
|
|
||||||
script.src = 'https://cdn.jsdelivr.net/gh/lewis617/antd-icon-classifier@0.0/dist/main.js';
|
|
||||||
document.head.appendChild(script);
|
|
||||||
};
|
|
||||||
|
|
||||||
onPaste = (event: ClipboardEvent) => {
|
|
||||||
const items = event.clipboardData && event.clipboardData.items;
|
|
||||||
let file = null;
|
|
||||||
if (items && items.length) {
|
|
||||||
for (let i = 0; i < items.length; i += 1) {
|
|
||||||
if (items[i].type.indexOf('image') !== -1) {
|
|
||||||
file = items[i].getAsFile();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (file) this.uploadFile(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadFile = (file: File) => {
|
|
||||||
this.setState(() => ({ loading: true }));
|
|
||||||
const reader: FileReader = new FileReader();
|
|
||||||
reader.onload = () => {
|
|
||||||
this.toImage(reader.result).then(this.predict);
|
|
||||||
this.setState(() => ({
|
|
||||||
fileList: [{ uid: 1, name: file.name, status: 'done', url: reader.result }],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
toImage = (url: any) =>
|
|
||||||
new Promise(resolve => {
|
|
||||||
const img = new Image();
|
|
||||||
img.setAttribute('crossOrigin', 'anonymous');
|
|
||||||
img.src = url;
|
|
||||||
img.onload = function onload() {
|
|
||||||
resolve(img);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
predict = (imgEl: any) => {
|
|
||||||
try {
|
try {
|
||||||
let icons = window.antdIconClassifier.predict(imgEl);
|
let icons: any[] = window.antdIconClassifier.predict(imgEl);
|
||||||
if (gtag && icons.length >= 1) {
|
if (gtag && icons.length) {
|
||||||
gtag('event', 'icon', {
|
gtag('event', 'icon', {
|
||||||
event_category: 'search-by-image',
|
event_category: 'search-by-image',
|
||||||
event_label: icons[0].className,
|
event_label: icons[0].className,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
icons = icons.map((i: any) => ({ score: i.score, type: i.className.replace(/\s/g, '-') }));
|
icons = icons.map(i => ({ score: i.score, type: i.className.replace(/\s/g, '-') }));
|
||||||
this.setState(() => ({ icons, loading: false, error: false }));
|
setState(prev => ({ ...prev, loading: false, error: false, icons }));
|
||||||
} catch (err) {
|
} catch {
|
||||||
this.setState(() => ({ loading: false, error: true }));
|
setState(prev => ({ ...prev, loading: false, error: true }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
const toImage = (url: string) =>
|
||||||
|
new Promise(resolve => {
|
||||||
|
const img = new Image();
|
||||||
|
img.setAttribute('crossOrigin', 'anonymous');
|
||||||
|
img.src = url;
|
||||||
|
img.onload = () => {
|
||||||
|
resolve(img);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
toggleModal = () => {
|
const uploadFile = useCallback((file: File) => {
|
||||||
this.setState(prev => ({
|
setState(prev => ({ ...prev, loading: true }));
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
toImage(reader.result as string).then(predict);
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
fileList: [{ uid: 1, name: file.name, status: 'done', url: reader.result }],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onPaste = useCallback((event: ClipboardEvent) => {
|
||||||
|
const items = event.clipboardData && event.clipboardData.items;
|
||||||
|
let file = null;
|
||||||
|
if (items && items.length) {
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (items[i].type.includes('image')) {
|
||||||
|
file = items[i].getAsFile();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (file) {
|
||||||
|
uploadFile(file);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
const toggleModal = useCallback(() => {
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
modalVisible: !prev.modalVisible,
|
modalVisible: !prev.modalVisible,
|
||||||
popoverVisible: false,
|
popoverVisible: false,
|
||||||
fileList: [],
|
fileList: [],
|
||||||
@ -132,120 +112,128 @@ class PicSearcher extends Component<PicSearcherProps, PicSearcherState> {
|
|||||||
if (!localStorage.getItem('disableIconTip')) {
|
if (!localStorage.getItem('disableIconTip')) {
|
||||||
localStorage.setItem('disableIconTip', 'true');
|
localStorage.setItem('disableIconTip', 'true');
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
onCopied = (text: string) => {
|
const onCopied = useCallback((text: string) => {
|
||||||
message.success(
|
message.success(
|
||||||
<span>
|
<span>
|
||||||
<code className="copied-code">{text}</code> copied 🎉
|
<code className="copied-code">{text}</code> copied 🎉
|
||||||
</span>,
|
</span>,
|
||||||
);
|
);
|
||||||
};
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.onload = async () => {
|
||||||
|
await window.antdIconClassifier.load();
|
||||||
|
setState(prev => ({ ...prev, modelLoaded: true }));
|
||||||
|
document.addEventListener('paste', onPaste);
|
||||||
|
};
|
||||||
|
script.src = 'https://cdn.jsdelivr.net/gh/lewis617/antd-icon-classifier@0.0/dist/main.js';
|
||||||
|
document.head.appendChild(script);
|
||||||
|
setState(prev => ({ ...prev, popoverVisible: !localStorage.getItem('disableIconTip') }));
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('paste', onPaste);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const {
|
<div className="icon-pic-searcher">
|
||||||
intl: { messages },
|
<Popover
|
||||||
} = this.props;
|
content={messages[`app.docs.components.icon.pic-searcher.intro`]}
|
||||||
const { modalVisible, popoverVisible, icons, fileList, loading, modelLoaded, error } =
|
visible={state.popoverVisible}
|
||||||
this.state;
|
>
|
||||||
return (
|
<AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} />
|
||||||
<div className="icon-pic-searcher">
|
</Popover>
|
||||||
<Popover
|
<Modal
|
||||||
content={messages[`app.docs.components.icon.pic-searcher.intro`]}
|
title={messages[`app.docs.components.icon.pic-searcher.title`]}
|
||||||
visible={popoverVisible}
|
visible={state.modalVisible}
|
||||||
>
|
onCancel={toggleModal}
|
||||||
<AntdIcons.CameraOutlined className="icon-pic-btn" onClick={this.toggleModal} />
|
footer={null}
|
||||||
</Popover>
|
>
|
||||||
<Modal
|
{state.modelLoaded || (
|
||||||
title={messages[`app.docs.components.icon.pic-searcher.title`]}
|
<Spin
|
||||||
visible={modalVisible}
|
spinning={!state.modelLoaded}
|
||||||
onCancel={this.toggleModal}
|
tip={messages['app.docs.components.icon.pic-searcher.modelloading']}
|
||||||
footer={null}
|
>
|
||||||
>
|
<div style={{ height: 100 }} />
|
||||||
{modelLoaded || (
|
|
||||||
<Spin
|
|
||||||
spinning={!modelLoaded}
|
|
||||||
tip={messages['app.docs.components.icon.pic-searcher.modelloading']}
|
|
||||||
>
|
|
||||||
<div style={{ height: 100 }} />
|
|
||||||
</Spin>
|
|
||||||
)}
|
|
||||||
{modelLoaded && (
|
|
||||||
<Dragger
|
|
||||||
accept="image/jpeg, image/png"
|
|
||||||
listType="picture"
|
|
||||||
customRequest={(o: any) => this.uploadFile(o.file)}
|
|
||||||
fileList={fileList}
|
|
||||||
showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
|
|
||||||
>
|
|
||||||
<p className="ant-upload-drag-icon">
|
|
||||||
<AntdIcons.InboxOutlined />
|
|
||||||
</p>
|
|
||||||
<p className="ant-upload-text">
|
|
||||||
{messages['app.docs.components.icon.pic-searcher.upload-text']}
|
|
||||||
</p>
|
|
||||||
<p className="ant-upload-hint">
|
|
||||||
{messages['app.docs.components.icon.pic-searcher.upload-hint']}
|
|
||||||
</p>
|
|
||||||
</Dragger>
|
|
||||||
)}
|
|
||||||
<Spin spinning={loading} tip={messages['app.docs.components.icon.pic-searcher.matching']}>
|
|
||||||
<div className="icon-pic-search-result">
|
|
||||||
{icons.length > 0 && (
|
|
||||||
<div className="result-tip">
|
|
||||||
{messages['app.docs.components.icon.pic-searcher.result-tip']}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<table>
|
|
||||||
{icons.length > 0 && (
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="col-icon">
|
|
||||||
{messages['app.docs.components.icon.pic-searcher.th-icon']}
|
|
||||||
</th>
|
|
||||||
<th>{messages['app.docs.components.icon.pic-searcher.th-score']}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
)}
|
|
||||||
<tbody>
|
|
||||||
{icons.map((icon: iconObject) => {
|
|
||||||
const { type } = icon;
|
|
||||||
const iconName = `${type
|
|
||||||
.split('-')
|
|
||||||
.map(str => `${str[0].toUpperCase()}${str.slice(1)}`)
|
|
||||||
.join('')}Outlined`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr key={iconName}>
|
|
||||||
<td className="col-icon">
|
|
||||||
<CopyToClipboard text={`<${iconName} />`} onCopy={this.onCopied}>
|
|
||||||
<Tooltip title={icon.type} placement="right">
|
|
||||||
{React.createElement(allIcons[iconName])}
|
|
||||||
</Tooltip>
|
|
||||||
</CopyToClipboard>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Progress percent={Math.ceil(icon.score * 100)} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{error && (
|
|
||||||
<Result
|
|
||||||
status="500"
|
|
||||||
title="503"
|
|
||||||
subTitle={messages['app.docs.components.icon.pic-searcher.server-error']}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Spin>
|
</Spin>
|
||||||
</Modal>
|
)}
|
||||||
</div>
|
{state.modelLoaded && (
|
||||||
);
|
<Dragger
|
||||||
}
|
accept="image/jpeg, image/png"
|
||||||
}
|
listType="picture"
|
||||||
|
customRequest={o => uploadFile(o.file as File)}
|
||||||
|
fileList={state.fileList}
|
||||||
|
showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
|
||||||
|
>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<AntdIcons.InboxOutlined />
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text">
|
||||||
|
{messages['app.docs.components.icon.pic-searcher.upload-text']}
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-hint">
|
||||||
|
{messages['app.docs.components.icon.pic-searcher.upload-hint']}
|
||||||
|
</p>
|
||||||
|
</Dragger>
|
||||||
|
)}
|
||||||
|
<Spin
|
||||||
|
spinning={state.loading}
|
||||||
|
tip={messages['app.docs.components.icon.pic-searcher.matching']}
|
||||||
|
>
|
||||||
|
<div className="icon-pic-search-result">
|
||||||
|
{state.icons.length > 0 && (
|
||||||
|
<div className="result-tip">
|
||||||
|
{messages['app.docs.components.icon.pic-searcher.result-tip']}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<table>
|
||||||
|
{state.icons.length > 0 && (
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="col-icon">
|
||||||
|
{messages['app.docs.components.icon.pic-searcher.th-icon']}
|
||||||
|
</th>
|
||||||
|
<th>{messages['app.docs.components.icon.pic-searcher.th-score']}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
)}
|
||||||
|
<tbody>
|
||||||
|
{state.icons.map(icon => {
|
||||||
|
const { type } = icon;
|
||||||
|
const iconName = `${type
|
||||||
|
.split('-')
|
||||||
|
.map(str => `${str[0].toUpperCase()}${str.slice(1)}`)
|
||||||
|
.join('')}Outlined`;
|
||||||
|
return (
|
||||||
|
<tr key={iconName}>
|
||||||
|
<td className="col-icon">
|
||||||
|
<CopyToClipboard text={`<${iconName} />`} onCopy={onCopied}>
|
||||||
|
<Tooltip title={icon.type} placement="right">
|
||||||
|
{React.createElement(allIcons[iconName])}
|
||||||
|
</Tooltip>
|
||||||
|
</CopyToClipboard>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Progress percent={Math.ceil(icon.score * 100)} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{state.error && (
|
||||||
|
<Result
|
||||||
|
status="500"
|
||||||
|
title="503"
|
||||||
|
subTitle={messages['app.docs.components.icon.pic-searcher.server-error']}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default injectIntl(PicSearcher);
|
export default injectIntl(PicSearcher);
|
||||||
|
@ -7,7 +7,7 @@ import debounce from 'lodash/debounce';
|
|||||||
import Category from './Category';
|
import Category from './Category';
|
||||||
import IconPicSearcher from './IconPicSearcher';
|
import IconPicSearcher from './IconPicSearcher';
|
||||||
import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
|
import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
|
||||||
import type { Categories, CategoriesKeys } from './fields';
|
import type { CategoriesKeys } from './fields';
|
||||||
import { categories } from './fields';
|
import { categories } from './fields';
|
||||||
|
|
||||||
export enum ThemeType {
|
export enum ThemeType {
|
||||||
@ -16,9 +16,7 @@ export enum ThemeType {
|
|||||||
TwoTone = 'TwoTone',
|
TwoTone = 'TwoTone',
|
||||||
}
|
}
|
||||||
|
|
||||||
const allIcons: {
|
const allIcons: { [key: string]: any } = AntdIcons;
|
||||||
[key: string]: any;
|
|
||||||
} = AntdIcons;
|
|
||||||
|
|
||||||
interface IconDisplayProps {
|
interface IconDisplayProps {
|
||||||
intl: any;
|
intl: any;
|
||||||
@ -29,36 +27,28 @@ interface IconDisplayState {
|
|||||||
searchKey: string;
|
searchKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class IconDisplay extends React.PureComponent<IconDisplayProps, IconDisplayState> {
|
const IconDisplay: React.FC<IconDisplayProps> = ({ intl }) => {
|
||||||
static categories: Categories = categories;
|
const { messages } = intl;
|
||||||
|
const [displayState, setDisplayState] = React.useState<IconDisplayState>({
|
||||||
static newIconNames: string[] = [];
|
|
||||||
|
|
||||||
state: IconDisplayState = {
|
|
||||||
theme: ThemeType.Outlined,
|
theme: ThemeType.Outlined,
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
};
|
});
|
||||||
|
|
||||||
constructor(props: IconDisplayProps) {
|
const newIconNames: string[] = [];
|
||||||
super(props);
|
|
||||||
this.handleSearchIcon = debounce(this.handleSearchIcon, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChangeTheme = (e: RadioChangeEvent) => {
|
const handleSearchIcon = React.useCallback(
|
||||||
this.setState({
|
debounce((searchKey: string) => {
|
||||||
theme: e.target.value as ThemeType,
|
setDisplayState(prevState => ({ ...prevState, searchKey }));
|
||||||
});
|
}),
|
||||||
};
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
handleSearchIcon = (searchKey: string) => {
|
const handleChangeTheme = React.useCallback((e: RadioChangeEvent) => {
|
||||||
this.setState(prevState => ({
|
setDisplayState(prevState => ({ ...prevState, theme: e.target.value as ThemeType }));
|
||||||
...prevState,
|
}, []);
|
||||||
searchKey,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
renderCategories() {
|
const renderCategories = React.useMemo<React.ReactNode | React.ReactNode[]>(() => {
|
||||||
const { searchKey = '', theme } = this.state;
|
const { searchKey = '', theme } = displayState;
|
||||||
|
|
||||||
const categoriesResult = Object.keys(categories)
|
const categoriesResult = Object.keys(categories)
|
||||||
.map((key: CategoriesKeys) => {
|
.map((key: CategoriesKeys) => {
|
||||||
@ -87,50 +77,43 @@ class IconDisplay extends React.PureComponent<IconDisplayProps, IconDisplayState
|
|||||||
title={category as CategoriesKeys}
|
title={category as CategoriesKeys}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
icons={icons}
|
icons={icons}
|
||||||
newIcons={IconDisplay.newIconNames}
|
newIcons={newIconNames}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
|
return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
|
||||||
}
|
}, [displayState.searchKey, displayState.theme]);
|
||||||
|
return (
|
||||||
render() {
|
<>
|
||||||
const {
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
intl: { messages },
|
<Radio.Group
|
||||||
} = this.props;
|
value={displayState.theme}
|
||||||
return (
|
onChange={handleChangeTheme}
|
||||||
<>
|
size="large"
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
buttonStyle="solid"
|
||||||
<Radio.Group
|
>
|
||||||
value={this.state.theme}
|
<Radio.Button value={ThemeType.Outlined}>
|
||||||
onChange={this.handleChangeTheme}
|
<Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']}
|
||||||
size="large"
|
</Radio.Button>
|
||||||
buttonStyle="solid"
|
<Radio.Button value={ThemeType.Filled}>
|
||||||
>
|
<Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']}
|
||||||
<Radio.Button value={ThemeType.Outlined}>
|
</Radio.Button>
|
||||||
<Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']}
|
<Radio.Button value={ThemeType.TwoTone}>
|
||||||
</Radio.Button>
|
<Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']}
|
||||||
<Radio.Button value={ThemeType.Filled}>
|
</Radio.Button>
|
||||||
<Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']}
|
</Radio.Group>
|
||||||
</Radio.Button>
|
<Input.Search
|
||||||
<Radio.Button value={ThemeType.TwoTone}>
|
placeholder={messages['app.docs.components.icon.search.placeholder']}
|
||||||
<Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']}
|
style={{ margin: '0 10px', flex: 1 }}
|
||||||
</Radio.Button>
|
allowClear
|
||||||
</Radio.Group>
|
onChange={e => handleSearchIcon(e.currentTarget.value)}
|
||||||
<Input.Search
|
size="large"
|
||||||
placeholder={messages['app.docs.components.icon.search.placeholder']}
|
autoFocus
|
||||||
style={{ margin: '0 10px', flex: 1 }}
|
suffix={<IconPicSearcher />}
|
||||||
allowClear
|
/>
|
||||||
onChange={e => this.handleSearchIcon(e.currentTarget.value)}
|
</div>
|
||||||
size="large"
|
{renderCategories}
|
||||||
autoFocus
|
</>
|
||||||
suffix={<IconPicSearcher />}
|
);
|
||||||
/>
|
};
|
||||||
</div>
|
|
||||||
{this.renderCategories()}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(IconDisplay);
|
export default injectIntl(IconDisplay);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { message } from 'antd';
|
|
||||||
import RcFooter from 'rc-footer';
|
import RcFooter from 'rc-footer';
|
||||||
import { Link } from 'bisheng/router';
|
import { Link } from 'bisheng/router';
|
||||||
import { presetPalettes } from '@ant-design/colors';
|
|
||||||
import type { WrappedComponentProps } from 'react-intl';
|
import type { WrappedComponentProps } from 'react-intl';
|
||||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||||
import {
|
import {
|
||||||
@ -19,21 +17,13 @@ import {
|
|||||||
QuestionCircleOutlined,
|
QuestionCircleOutlined,
|
||||||
BgColorsOutlined,
|
BgColorsOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import ColorPicker from '../Color/ColorPicker';
|
import type { FooterColumn } from 'rc-footer/lib/column';
|
||||||
import { loadScript, getLocalizedPathname } from '../utils';
|
import { getLocalizedPathname } from '../utils';
|
||||||
|
|
||||||
class Footer extends React.Component<WrappedComponentProps & { location: any }> {
|
|
||||||
lessLoaded = false;
|
|
||||||
|
|
||||||
state = {
|
|
||||||
color: presetPalettes.blue.primary,
|
|
||||||
};
|
|
||||||
|
|
||||||
getColumns() {
|
|
||||||
const { intl, location } = this.props;
|
|
||||||
|
|
||||||
|
const Footer: React.FC<WrappedComponentProps & { location: any }> = props => {
|
||||||
|
const { intl, location } = props;
|
||||||
|
const getColumns = useMemo<FooterColumn[]>(() => {
|
||||||
const isZhCN = intl.locale === 'zh-CN';
|
const isZhCN = intl.locale === 'zh-CN';
|
||||||
|
|
||||||
const getLinkHash = (path: string, hash: { zhCN: string; enUS: string }) => {
|
const getLinkHash = (path: string, hash: { zhCN: string; enUS: string }) => {
|
||||||
const pathName = getLocalizedPathname(path, isZhCN, location.query, hash);
|
const pathName = getLocalizedPathname(path, isZhCN, location.query, hash);
|
||||||
const { pathname, query = {} } = pathName;
|
const { pathname, query = {} } = pathName;
|
||||||
@ -182,7 +172,7 @@ class Footer extends React.Component<WrappedComponentProps & { location: any }>
|
|||||||
enUS: 'JoinUs',
|
enUS: 'JoinUs',
|
||||||
}),
|
}),
|
||||||
LinkComponent: Link,
|
LinkComponent: Link,
|
||||||
} as any);
|
} as unknown as typeof col2['items'][number]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const col3 = {
|
const col3 = {
|
||||||
@ -318,82 +308,23 @@ class Footer extends React.Component<WrappedComponentProps & { location: any }>
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
return [col1, col2, col3, col4];
|
return [col1, col2, col3, col4];
|
||||||
}
|
}, [intl.locale, location.query]);
|
||||||
|
|
||||||
handleColorChange = (color: string) => {
|
return (
|
||||||
const {
|
<RcFooter
|
||||||
intl: { messages },
|
columns={getColumns}
|
||||||
} = this.props;
|
bottom={
|
||||||
message.loading({
|
<>
|
||||||
content: messages['app.footer.primary-color-changing'] as string,
|
Made with <span style={{ color: '#fff' }}>❤</span> by
|
||||||
key: 'change-primary-color',
|
{/* eslint-disable-next-line react/jsx-curly-brace-presence */}{' '}
|
||||||
});
|
<a target="_blank" rel="noopener noreferrer" href="https://xtech.antfin.com">
|
||||||
const changeColor = () => {
|
<FormattedMessage id="app.footer.company" />
|
||||||
(window as any).less
|
</a>
|
||||||
.modifyVars({
|
</>
|
||||||
'@primary-color': color,
|
}
|
||||||
})
|
/>
|
||||||
.then(() => {
|
);
|
||||||
message.success({
|
};
|
||||||
content: messages['app.footer.primary-color-changed'] as string,
|
|
||||||
key: 'change-primary-color',
|
|
||||||
});
|
|
||||||
this.setState({ color });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const lessUrl = 'https://gw.alipayobjects.com/os/lib/less/3.10.3/dist/less.min.js';
|
|
||||||
|
|
||||||
if (this.lessLoaded) {
|
|
||||||
changeColor();
|
|
||||||
} else {
|
|
||||||
(window as any).less = {
|
|
||||||
async: true,
|
|
||||||
javascriptEnabled: true,
|
|
||||||
};
|
|
||||||
loadScript(lessUrl).then(() => {
|
|
||||||
this.lessLoaded = true;
|
|
||||||
changeColor();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
renderThemeChanger() {
|
|
||||||
const { color } = this.state;
|
|
||||||
const colors = Object.keys(presetPalettes).filter(item => item !== 'grey');
|
|
||||||
return (
|
|
||||||
<ColorPicker
|
|
||||||
small
|
|
||||||
color={color}
|
|
||||||
position="top"
|
|
||||||
presetColors={[
|
|
||||||
...colors.map(c => presetPalettes[c][5]),
|
|
||||||
...colors.map(c => presetPalettes[c][4]),
|
|
||||||
...colors.map(c => presetPalettes[c][6]),
|
|
||||||
]}
|
|
||||||
onChangeComplete={this.handleColorChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<RcFooter
|
|
||||||
columns={this.getColumns()}
|
|
||||||
bottom={
|
|
||||||
<>
|
|
||||||
Made with <span style={{ color: '#fff' }}>❤</span> by
|
|
||||||
{/* eslint-disable-next-line react/jsx-curly-brace-presence */}{' '}
|
|
||||||
<a target="_blank" rel="noopener noreferrer" href="https://xtech.antfin.com">
|
|
||||||
<FormattedMessage id="app.footer.company" />
|
|
||||||
</a>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(Footer);
|
export default injectIntl(Footer);
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import React from 'react';
|
import React, { useCallback, useContext, useEffect, useRef, useState, useMemo } from 'react';
|
||||||
|
import type { WrappedComponentProps } from 'react-intl';
|
||||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Select, Row, Col, Popover, Button, Modal } from 'antd';
|
import { Select, Row, Col, Popover, Button, Modal } from 'antd';
|
||||||
import { MenuOutlined } from '@ant-design/icons';
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
import canUseDom from 'rc-util/lib/Dom/canUseDom';
|
import canUseDom from 'rc-util/lib/Dom/canUseDom';
|
||||||
|
import type { DirectionType } from 'antd/es/config-provider';
|
||||||
import * as utils from '../../utils';
|
import * as utils from '../../utils';
|
||||||
import packageJson from '../../../../../package.json';
|
import packageJson from '../../../../../package.json';
|
||||||
import Logo from './Logo';
|
import Logo from './Logo';
|
||||||
@ -26,13 +28,11 @@ const { Option } = Select;
|
|||||||
const antdVersion: string = packageJson.version;
|
const antdVersion: string = packageJson.version;
|
||||||
|
|
||||||
export interface HeaderProps {
|
export interface HeaderProps {
|
||||||
intl: {
|
intl: { locale: string };
|
||||||
locale: string;
|
|
||||||
};
|
|
||||||
location: { pathname: string; query: any };
|
location: { pathname: string; query: any };
|
||||||
router: any;
|
router: any;
|
||||||
themeConfig: { docVersions: Record<string, string> };
|
themeConfig: { docVersions: Record<string, string> };
|
||||||
changeDirection: (direction: string) => void;
|
changeDirection: (direction: DirectionType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let docsearch: any;
|
let docsearch: any;
|
||||||
@ -61,7 +61,7 @@ function initDocSearch({ isZhCN, router }: { isZhCN: boolean; router: any }) {
|
|||||||
transformData: AlgoliaConfig.transformData,
|
transformData: AlgoliaConfig.transformData,
|
||||||
debug: AlgoliaConfig.debug,
|
debug: AlgoliaConfig.debug,
|
||||||
// https://docsearch.algolia.com/docs/behavior#handleselected
|
// https://docsearch.algolia.com/docs/behavior#handleselected
|
||||||
handleSelected: (input: any, _$1: unknown, suggestion: any) => {
|
handleSelected(input: any, _$1: unknown, suggestion: any) {
|
||||||
router.push(suggestion.url);
|
router.push(suggestion.url);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
input.setVal('');
|
input.setVal('');
|
||||||
@ -88,37 +88,44 @@ interface HeaderState {
|
|||||||
showTechUIButton: boolean;
|
showTechUIButton: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Header extends React.Component<HeaderProps, HeaderState> {
|
const Header: React.FC<HeaderProps & WrappedComponentProps<'intl'>> = props => {
|
||||||
static contextType = SiteContext;
|
const { intl, router, location, themeConfig, changeDirection } = props;
|
||||||
|
const [headerState, setHeaderState] = useState<HeaderState>({
|
||||||
pingTimer: NodeJS.Timeout;
|
|
||||||
|
|
||||||
state = {
|
|
||||||
menuVisible: false,
|
menuVisible: false,
|
||||||
windowWidth: 1400,
|
windowWidth: 1400,
|
||||||
searching: false,
|
searching: false,
|
||||||
showTechUIButton: false,
|
showTechUIButton: false,
|
||||||
};
|
});
|
||||||
|
const { direction } = useContext<SiteContextProps>(SiteContext);
|
||||||
|
const pingTimer = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
context: SiteContextProps;
|
const handleHideMenu = useCallback(() => {
|
||||||
|
setHeaderState(prev => ({ ...prev, menuVisible: false }));
|
||||||
|
}, []);
|
||||||
|
const onWindowResize = useCallback(() => {
|
||||||
|
setHeaderState(prev => ({ ...prev, windowWidth: window.innerWidth }));
|
||||||
|
}, []);
|
||||||
|
const onTriggerSearching = useCallback((searching: boolean) => {
|
||||||
|
setHeaderState(prev => ({ ...prev, searching }));
|
||||||
|
}, []);
|
||||||
|
const handleShowMenu = useCallback(() => {
|
||||||
|
setHeaderState(prev => ({ ...prev, menuVisible: true }));
|
||||||
|
}, []);
|
||||||
|
const onMenuVisibleChange = useCallback((visible: boolean) => {
|
||||||
|
setHeaderState(prev => ({ ...prev, menuVisible: visible }));
|
||||||
|
}, []);
|
||||||
|
const onDirectionChange = useCallback(() => {
|
||||||
|
changeDirection(direction !== 'rtl' ? 'rtl' : 'ltr');
|
||||||
|
}, [direction]);
|
||||||
|
|
||||||
componentDidMount() {
|
useEffect(() => {
|
||||||
const { intl, router } = this.props;
|
router.listen(handleHideMenu);
|
||||||
router.listen(this.handleHideMenu);
|
initDocSearch({ isZhCN: intl.locale === 'zh-CN', router });
|
||||||
|
onWindowResize();
|
||||||
initDocSearch({
|
window.addEventListener('resize', onWindowResize);
|
||||||
isZhCN: intl.locale === 'zh-CN',
|
pingTimer.current = ping(status => {
|
||||||
router,
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('resize', this.onWindowResize);
|
|
||||||
this.onWindowResize();
|
|
||||||
|
|
||||||
this.pingTimer = ping(status => {
|
|
||||||
if (status !== 'timeout' && status !== 'error') {
|
if (status !== 'timeout' && status !== 'error') {
|
||||||
this.setState({
|
setHeaderState(prev => ({ ...prev, showTechUIButton: true }));
|
||||||
showTechUIButton: true,
|
|
||||||
});
|
|
||||||
if (
|
if (
|
||||||
process.env.NODE_ENV === 'production' &&
|
process.env.NODE_ENV === 'production' &&
|
||||||
window.location.host !== 'ant-design.antgroup.com' &&
|
window.location.host !== 'ant-design.antgroup.com' &&
|
||||||
@ -128,86 +135,29 @@ class Header extends React.Component<HeaderProps, HeaderState> {
|
|||||||
title: '提示',
|
title: '提示',
|
||||||
content: '内网用户推荐访问国内镜像以获得极速体验~',
|
content: '内网用户推荐访问国内镜像以获得极速体验~',
|
||||||
okText: '🚀 立刻前往',
|
okText: '🚀 立刻前往',
|
||||||
onOk: () => {
|
cancelText: '不再弹出',
|
||||||
|
closable: true,
|
||||||
|
onOk() {
|
||||||
window.open('https://ant-design.antgroup.com', '_self');
|
window.open('https://ant-design.antgroup.com', '_self');
|
||||||
disableAntdMirrorModal();
|
disableAntdMirrorModal();
|
||||||
},
|
},
|
||||||
cancelText: '不再弹出',
|
onCancel() {
|
||||||
onCancel: () => {
|
|
||||||
disableAntdMirrorModal();
|
disableAntdMirrorModal();
|
||||||
},
|
},
|
||||||
closable: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
return () => {
|
||||||
|
window.removeEventListener('resize', onWindowResize);
|
||||||
componentWillUnmount() {
|
if (pingTimer.current) {
|
||||||
window.removeEventListener('resize', this.onWindowResize);
|
clearTimeout(pingTimer.current);
|
||||||
clearTimeout(this.pingTimer);
|
}
|
||||||
}
|
};
|
||||||
|
}, []);
|
||||||
onWindowResize = () => {
|
|
||||||
this.setState({
|
|
||||||
windowWidth: window.innerWidth,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onTriggerSearching = (searching: boolean) => {
|
|
||||||
this.setState({ searching });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleShowMenu = () => {
|
|
||||||
this.setState({
|
|
||||||
menuVisible: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleHideMenu = () => {
|
|
||||||
this.setState({
|
|
||||||
menuVisible: false,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onDirectionChange = () => {
|
|
||||||
const { changeDirection } = this.props;
|
|
||||||
const { direction } = this.context;
|
|
||||||
if (direction !== 'rtl') {
|
|
||||||
changeDirection('rtl');
|
|
||||||
} else {
|
|
||||||
changeDirection('ltr');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getNextDirectionText = () => {
|
|
||||||
const { direction } = this.context;
|
|
||||||
|
|
||||||
if (direction !== 'rtl') {
|
|
||||||
return 'RTL';
|
|
||||||
}
|
|
||||||
return 'LTR';
|
|
||||||
};
|
|
||||||
|
|
||||||
getDropdownStyle = (): React.CSSProperties => {
|
|
||||||
const { direction } = this.context;
|
|
||||||
if (direction === 'rtl') {
|
|
||||||
return {
|
|
||||||
direction: 'ltr',
|
|
||||||
textAlign: 'right',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
onMenuVisibleChange = (visible: boolean) => {
|
|
||||||
this.setState({
|
|
||||||
menuVisible: visible,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
handleVersionChange = (url: string) => {
|
const handleVersionChange = useCallback((url: string) => {
|
||||||
const currentUrl = window.location.href;
|
const currentUrl = window.location.href;
|
||||||
const currentPathname = window.location.pathname;
|
const currentPathname = window.location.pathname;
|
||||||
if (/overview/.test(currentPathname) && /0?[1-39][0-3]?x/.test(url)) {
|
if (/overview/.test(currentPathname) && /0?[1-39][0-3]?x/.test(url)) {
|
||||||
@ -218,184 +168,168 @@ class Header extends React.Component<HeaderProps, HeaderState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.location.href = currentUrl.replace(window.location.origin, url);
|
window.location.href = currentUrl.replace(window.location.origin, url);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
onLangChange = () => {
|
const onLangChange = useCallback(() => {
|
||||||
const {
|
const { pathname, query } = location;
|
||||||
location: { pathname, query },
|
|
||||||
} = this.props;
|
|
||||||
const currentProtocol = `${window.location.protocol}//`;
|
const currentProtocol = `${window.location.protocol}//`;
|
||||||
const currentHref = window.location.href.slice(currentProtocol.length);
|
const currentHref = window.location.href.slice(currentProtocol.length);
|
||||||
|
|
||||||
if (utils.isLocalStorageNameSupported()) {
|
if (utils.isLocalStorageNameSupported()) {
|
||||||
localStorage.setItem('locale', utils.isZhCN(pathname) ? 'en-US' : 'zh-CN');
|
localStorage.setItem('locale', utils.isZhCN(pathname) ? 'en-US' : 'zh-CN');
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.href =
|
window.location.href =
|
||||||
currentProtocol +
|
currentProtocol +
|
||||||
currentHref.replace(
|
currentHref.replace(
|
||||||
window.location.pathname,
|
window.location.pathname,
|
||||||
utils.getLocalizedPathname(pathname, !utils.isZhCN(pathname), query).pathname,
|
utils.getLocalizedPathname(pathname, !utils.isZhCN(pathname), query).pathname,
|
||||||
);
|
);
|
||||||
};
|
}, [location]);
|
||||||
|
|
||||||
render() {
|
const getNextDirectionText = useMemo<string>(
|
||||||
return (
|
() => (direction !== 'rtl' ? 'RTL' : 'LTR'),
|
||||||
<SiteContext.Consumer>
|
[direction],
|
||||||
{({ isMobile }) => {
|
);
|
||||||
const { menuVisible, windowWidth, searching, showTechUIButton } = this.state;
|
|
||||||
const { direction } = this.context;
|
|
||||||
const {
|
|
||||||
location,
|
|
||||||
themeConfig,
|
|
||||||
intl: { locale },
|
|
||||||
router,
|
|
||||||
} = this.props;
|
|
||||||
const docVersions: Record<string, string> = {
|
|
||||||
[antdVersion]: antdVersion,
|
|
||||||
...themeConfig.docVersions,
|
|
||||||
};
|
|
||||||
const versionOptions = Object.keys(docVersions).map(version => (
|
|
||||||
<Option value={docVersions[version]} key={version}>
|
|
||||||
{version}
|
|
||||||
</Option>
|
|
||||||
));
|
|
||||||
|
|
||||||
const pathname = location.pathname.replace(/(^\/|\/$)/g, '');
|
const getDropdownStyle = useMemo<React.CSSProperties>(
|
||||||
|
() => (direction === 'rtl' ? { direction: 'ltr', textAlign: 'right' } : {}),
|
||||||
|
[direction],
|
||||||
|
);
|
||||||
|
|
||||||
const isHome = ['', 'index', 'index-cn'].includes(pathname);
|
return (
|
||||||
|
<SiteContext.Consumer>
|
||||||
|
{({ isMobile }) => {
|
||||||
|
const { menuVisible, windowWidth, searching, showTechUIButton } = headerState;
|
||||||
|
const docVersions: Record<string, string> = {
|
||||||
|
[antdVersion]: antdVersion,
|
||||||
|
...themeConfig.docVersions,
|
||||||
|
};
|
||||||
|
const versionOptions = Object.keys(docVersions).map(version => (
|
||||||
|
<Option value={docVersions[version]} key={version}>
|
||||||
|
{version}
|
||||||
|
</Option>
|
||||||
|
));
|
||||||
|
|
||||||
const isZhCN = locale === 'zh-CN';
|
const pathname = location.pathname.replace(/(^\/|\/$)/g, '');
|
||||||
const isRTL = direction === 'rtl';
|
|
||||||
let responsive: null | 'narrow' | 'crowded' = null;
|
|
||||||
if (windowWidth < RESPONSIVE_XS) {
|
|
||||||
responsive = 'crowded';
|
|
||||||
} else if (windowWidth < RESPONSIVE_SM) {
|
|
||||||
responsive = 'narrow';
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerClassName = classNames({
|
const isHome = ['', 'index', 'index-cn'].includes(pathname);
|
||||||
clearfix: true,
|
|
||||||
'home-header': isHome,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sharedProps = {
|
const isZhCN = intl.locale === 'zh-CN';
|
||||||
isZhCN,
|
const isRTL = direction === 'rtl';
|
||||||
isRTL,
|
let responsive: null | 'narrow' | 'crowded' = null;
|
||||||
};
|
if (windowWidth < RESPONSIVE_XS) {
|
||||||
|
responsive = 'crowded';
|
||||||
|
} else if (windowWidth < RESPONSIVE_SM) {
|
||||||
|
responsive = 'narrow';
|
||||||
|
}
|
||||||
|
|
||||||
const navigationNode = (
|
const headerClassName = classNames({
|
||||||
<Navigation
|
clearfix: true,
|
||||||
key="nav"
|
'home-header': isHome,
|
||||||
{...sharedProps}
|
});
|
||||||
location={location}
|
|
||||||
responsive={responsive}
|
|
||||||
isMobile={isMobile}
|
|
||||||
showTechUIButton={showTechUIButton}
|
|
||||||
pathname={pathname}
|
|
||||||
directionText={this.getNextDirectionText()}
|
|
||||||
onLangChange={this.onLangChange}
|
|
||||||
onDirectionChange={this.onDirectionChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
let menu: (React.ReactElement | null)[] = [
|
const sharedProps = {
|
||||||
navigationNode,
|
isZhCN,
|
||||||
<Select
|
isRTL,
|
||||||
key="version"
|
};
|
||||||
className="version"
|
|
||||||
size="small"
|
|
||||||
defaultValue={antdVersion}
|
|
||||||
onChange={this.handleVersionChange}
|
|
||||||
dropdownStyle={this.getDropdownStyle()}
|
|
||||||
getPopupContainer={trigger => trigger.parentNode}
|
|
||||||
>
|
|
||||||
{versionOptions}
|
|
||||||
</Select>,
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={this.onLangChange}
|
|
||||||
className="header-button header-lang-button"
|
|
||||||
key="lang-button"
|
|
||||||
>
|
|
||||||
<FormattedMessage id="app.header.lang" />
|
|
||||||
</Button>,
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={this.onDirectionChange}
|
|
||||||
className="header-button header-direction-button"
|
|
||||||
key="direction-button"
|
|
||||||
>
|
|
||||||
{this.getNextDirectionText()}
|
|
||||||
</Button>,
|
|
||||||
<More key="more" {...sharedProps} />,
|
|
||||||
<Github key="github" responsive={responsive} />,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (windowWidth < RESPONSIVE_XS) {
|
const navigationNode = (
|
||||||
menu = searching ? [] : [navigationNode];
|
<Navigation
|
||||||
} else if (windowWidth < RESPONSIVE_SM) {
|
key="nav"
|
||||||
menu = searching ? [] : menu;
|
{...sharedProps}
|
||||||
}
|
location={location}
|
||||||
|
responsive={responsive}
|
||||||
|
isMobile={isMobile}
|
||||||
|
showTechUIButton={showTechUIButton}
|
||||||
|
pathname={pathname}
|
||||||
|
directionText={getNextDirectionText}
|
||||||
|
onLangChange={onLangChange}
|
||||||
|
onDirectionChange={onDirectionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
const colProps = isHome
|
let menu: (React.ReactElement | null)[] = [
|
||||||
? [{ flex: 'none' }, { flex: 'auto' }]
|
navigationNode,
|
||||||
: [
|
<Select
|
||||||
{
|
key="version"
|
||||||
xxl: 4,
|
className="version"
|
||||||
xl: 5,
|
size="small"
|
||||||
lg: 6,
|
defaultValue={antdVersion}
|
||||||
md: 6,
|
onChange={handleVersionChange}
|
||||||
sm: 24,
|
dropdownStyle={getDropdownStyle}
|
||||||
xs: 24,
|
getPopupContainer={trigger => trigger.parentNode}
|
||||||
},
|
>
|
||||||
{
|
{versionOptions}
|
||||||
xxl: 20,
|
</Select>,
|
||||||
xl: 19,
|
<Button
|
||||||
lg: 18,
|
size="small"
|
||||||
md: 18,
|
onClick={onLangChange}
|
||||||
sm: 0,
|
className="header-button header-lang-button"
|
||||||
xs: 0,
|
key="lang-button"
|
||||||
},
|
>
|
||||||
];
|
<FormattedMessage id="app.header.lang" />
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={onDirectionChange}
|
||||||
|
className="header-button header-direction-button"
|
||||||
|
key="direction-button"
|
||||||
|
>
|
||||||
|
{getNextDirectionText}
|
||||||
|
</Button>,
|
||||||
|
<More key="more" {...sharedProps} />,
|
||||||
|
<Github key="github" responsive={responsive} />,
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
if (windowWidth < RESPONSIVE_XS) {
|
||||||
<header id="header" className={headerClassName}>
|
menu = searching ? [] : [navigationNode];
|
||||||
{isMobile && (
|
} else if (windowWidth < RESPONSIVE_SM) {
|
||||||
<Popover
|
menu = searching ? [] : menu;
|
||||||
overlayClassName="popover-menu"
|
}
|
||||||
placement="bottomRight"
|
|
||||||
content={menu}
|
|
||||||
trigger="click"
|
|
||||||
visible={menuVisible}
|
|
||||||
arrowPointAtCenter
|
|
||||||
onVisibleChange={this.onMenuVisibleChange}
|
|
||||||
>
|
|
||||||
<MenuOutlined className="nav-phone-icon" onClick={this.handleShowMenu} />
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
<Row style={{ flexFlow: 'nowrap', height: 64 }}>
|
|
||||||
<Col {...colProps[0]}>
|
|
||||||
<Logo {...sharedProps} location={location} />
|
|
||||||
</Col>
|
|
||||||
<Col {...colProps[1]} className="menu-row">
|
|
||||||
<SearchBar
|
|
||||||
key="search"
|
|
||||||
{...sharedProps}
|
|
||||||
router={router}
|
|
||||||
algoliaConfig={AlgoliaConfig}
|
|
||||||
responsive={responsive}
|
|
||||||
onTriggerFocus={this.onTriggerSearching}
|
|
||||||
/>
|
|
||||||
{!isMobile && menu}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</SiteContext.Consumer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(Header as any);
|
const colProps = isHome
|
||||||
|
? [{ flex: 'none' }, { flex: 'auto' }]
|
||||||
|
: [
|
||||||
|
{ xxl: 4, xl: 5, lg: 6, md: 6, sm: 24, xs: 24 },
|
||||||
|
{ xxl: 20, xl: 19, lg: 18, md: 18, sm: 0, xs: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header id="header" className={headerClassName}>
|
||||||
|
{isMobile && (
|
||||||
|
<Popover
|
||||||
|
overlayClassName="popover-menu"
|
||||||
|
placement="bottomRight"
|
||||||
|
content={menu}
|
||||||
|
trigger="click"
|
||||||
|
visible={menuVisible}
|
||||||
|
arrowPointAtCenter
|
||||||
|
onVisibleChange={onMenuVisibleChange}
|
||||||
|
>
|
||||||
|
<MenuOutlined className="nav-phone-icon" onClick={handleShowMenu} />
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
<Row style={{ flexFlow: 'nowrap', height: 64 }}>
|
||||||
|
<Col {...colProps[0]}>
|
||||||
|
<Logo {...sharedProps} location={location} />
|
||||||
|
</Col>
|
||||||
|
<Col {...colProps[1]} className="menu-row">
|
||||||
|
<SearchBar
|
||||||
|
key="search"
|
||||||
|
{...sharedProps}
|
||||||
|
router={router}
|
||||||
|
algoliaConfig={AlgoliaConfig}
|
||||||
|
responsive={responsive}
|
||||||
|
onTriggerFocus={onTriggerSearching}
|
||||||
|
/>
|
||||||
|
{!isMobile && menu}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</SiteContext.Consumer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(Header);
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import type { DirectionType } from 'antd/es/config-provider';
|
||||||
|
|
||||||
export interface SiteContextProps {
|
export interface SiteContextProps {
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
direction: string;
|
direction: DirectionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SiteContext = React.createContext<SiteContextProps>({
|
const SiteContext = React.createContext<SiteContextProps>({
|
||||||
|
Loading…
Reference in New Issue
Block a user