[type] make type of components compatible with ComponentType<P>

This commit is contained in:
frezc 2018-12-22 21:21:42 +08:00 committed by 偏右
parent e6fd745c26
commit 5f9b376456
24 changed files with 107 additions and 81 deletions

View File

@ -1 +1,3 @@
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// https://stackoverflow.com/questions/46176165/ways-to-get-string-literal-type-of-array-values-without-enum-overhead
export const tuple = <T extends string[]>(...args: T) => args;

View File

@ -35,8 +35,8 @@ export interface AvatarState {
export default class Avatar extends React.Component<AvatarProps, AvatarState> {
static defaultProps = {
prefixCls: 'ant-avatar',
shape: 'circle',
size: 'default',
shape: 'circle' as AvatarProps['shape'],
size: 'default' as AvatarProps['size'],
};
state = {

View File

@ -4,6 +4,7 @@ import classNames from 'classnames';
import Wave from '../_util/wave';
import Icon from '../icon';
import Group from './button-group';
import { tuple } from '../_util/type';
const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/;
const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar);
@ -36,10 +37,14 @@ function insertSpace(child: React.ReactChild, needInserted: boolean) {
return child;
}
export type ButtonType = 'default' | 'primary' | 'ghost' | 'dashed' | 'danger';
export type ButtonShape = 'circle' | 'circle-outline';
export type ButtonSize = 'small' | 'default' | 'large';
export type ButtonHTMLType = 'submit' | 'button' | 'reset';
const ButtonTypes = tuple('default', 'primary', 'ghost', 'dashed', 'danger');
export type ButtonType = (typeof ButtonTypes)[number];
const ButtonShapes = tuple('circle', 'circle-outline');
export type ButtonShape = (typeof ButtonShapes)[number];
const ButtonSizes = tuple('large', 'default', 'small');
export type ButtonSize = (typeof ButtonSizes)[number];
const ButtonHTMLTypes = tuple('submit', 'button', 'reset');
export type ButtonHTMLType = (typeof ButtonHTMLTypes)[number];
export interface BaseButtonProps {
type?: ButtonType;
@ -81,10 +86,10 @@ export default class Button extends React.Component<ButtonProps, any> {
};
static propTypes = {
type: PropTypes.string,
shape: PropTypes.oneOf(['circle', 'circle-outline']),
size: PropTypes.oneOf(['large', 'default', 'small']),
htmlType: PropTypes.oneOf(['submit', 'button', 'reset']),
type: PropTypes.oneOf(ButtonTypes),
shape: PropTypes.oneOf(ButtonShapes),
size: PropTypes.oneOf(ButtonSizes),
htmlType: PropTypes.oneOf(ButtonHTMLTypes),
onClick: PropTypes.func,
loading: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
className: PropTypes.string,

View File

@ -53,7 +53,7 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
locale: {},
fullscreen: true,
prefixCls: PREFIX_CLS,
mode: 'month',
mode: 'month' as CalendarMode,
onSelect: noop,
onPanelChange: noop,
onChange: noop,
@ -70,7 +70,7 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
className: PropTypes.string,
style: PropTypes.object,
onPanelChange: PropTypes.func,
value: PropTypes.object,
value: PropTypes.object as PropTypes.Requireable<moment.Moment>,
onSelect: PropTypes.func,
onChange: PropTypes.func,
};

View File

@ -5,6 +5,7 @@ import createReactContext, { Context } from 'create-react-context';
import warning from 'warning';
import classNames from 'classnames';
import Icon from '../icon';
import { tuple } from '../_util/type';
const DrawerContext: Context<Drawer | null> = createReactContext(null);
@ -12,7 +13,8 @@ type EventType = React.MouseEvent<HTMLDivElement> | React.MouseEvent<HTMLButtonE
type getContainerfunc = () => HTMLElement;
type placementType = 'top' | 'right' | 'bottom' | 'left';
const PlacementTypes = tuple('top', 'right', 'bottom', 'left');
type placementType = (typeof PlacementTypes)[number];
export interface DrawerProps {
closable?: boolean;
destroyOnClose?: boolean;
@ -45,9 +47,8 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
destroyOnClose: PropTypes.bool,
getContainer: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
PropTypes.object as PropTypes.Requireable<HTMLElement>,
PropTypes.func,
PropTypes.bool,
]),
maskClosable: PropTypes.bool,
mask: PropTypes.bool,
@ -58,7 +59,7 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
zIndex: PropTypes.number,
prefixCls: PropTypes.string,
placement: PropTypes.string,
placement: PropTypes.oneOf(PlacementTypes),
onClose: PropTypes.func,
className: PropTypes.string,
};
@ -68,7 +69,7 @@ export default class Drawer extends React.Component<DrawerProps, IDrawerState> {
width: 256,
height: 256,
closable: true,
placement: 'right',
placement: 'right' as placementType,
maskClosable: true,
mask: true,
level: null,

View File

@ -7,8 +7,10 @@ import Dropdown, { DropDownProps } from './dropdown';
import classNames from 'classnames';
const ButtonGroup = Button.Group;
type DropdownButtonType = 'primary' | 'ghost' | 'dashed';
export interface DropdownButtonProps extends ButtonGroupProps, DropDownProps {
type?: 'primary' | 'ghost' | 'dashed';
type?: DropdownButtonType;
htmlType?: ButtonHTMLType;
disabled?: boolean;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
@ -17,8 +19,8 @@ export interface DropdownButtonProps extends ButtonGroupProps, DropDownProps {
export default class DropdownButton extends React.Component<DropdownButtonProps, any> {
static defaultProps = {
placement: 'bottomRight',
type: 'default',
placement: 'bottomRight' as DropDownProps['placement'],
type: 'default' as DropdownButtonType,
prefixCls: 'ant-dropdown-button',
};

View File

@ -5,7 +5,17 @@ import DropdownButton from './dropdown-button';
import { ConfigConsumer, ConfigProviderProps } from '../config-provider';
import warning from '../_util/warning';
import Icon from '../icon';
import { tuple } from '../_util/type';
const Placements = tuple(
'topLeft',
'topCenter',
'topRight',
'bottomLeft',
'bottomCenter',
'bottomRight',
);
type Placement = (typeof Placements)[number];
export interface DropDownProps {
trigger?: ('click' | 'hover' | 'contextMenu')[];
overlay: React.ReactNode;
@ -17,7 +27,7 @@ export interface DropDownProps {
prefixCls?: string;
className?: string;
transitionName?: string;
placement?: 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight';
placement?: Placement;
overlayClassName?: string;
overlayStyle?: React.CSSProperties;
forceRender?: boolean;
@ -31,7 +41,7 @@ export default class Dropdown extends React.Component<DropDownProps, any> {
prefixCls: 'ant-dropdown',
mouseEnterDelay: 0.15,
mouseLeaveDelay: 0.1,
placement: 'bottomLeft',
placement: 'bottomLeft' as Placement,
};
getTransitionName() {

View File

@ -7,7 +7,7 @@ import omit from 'omit.js';
import warning from '../_util/warning';
import FormItem from './FormItem';
import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants';
import { Omit } from '../_util/type';
import { Omit, tuple } from '../_util/type';
type FormCreateOptionMessagesCallback = (...args: any[]) => string;
@ -23,7 +23,8 @@ export interface FormCreateOption<T> {
withRef?: boolean;
}
export type FormLayout = 'horizontal' | 'inline' | 'vertical';
const FormLayouts = tuple('horizontal', 'inline', 'vertical');
export type FormLayout = (typeof FormLayouts)[number];
export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
layout?: FormLayout;
@ -161,7 +162,7 @@ export default class Form extends React.Component<FormProps, any> {
static propTypes = {
prefixCls: PropTypes.string,
layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']),
layout: PropTypes.oneOf(FormLayouts),
children: PropTypes.any,
onSubmit: PropTypes.func,
hideRequiredMark: PropTypes.bool,

View File

@ -9,6 +9,9 @@ import Col, { ColProps } from '../grid/col';
import warning from '../_util/warning';
import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants';
import Icon from '../icon';
import { tuple } from '../_util/type';
const ValidateStatuses = tuple('success', 'warning', 'error', 'validating');
export interface FormItemProps {
prefixCls?: string;
@ -19,7 +22,7 @@ export interface FormItemProps {
wrapperCol?: ColProps;
help?: React.ReactNode;
extra?: React.ReactNode;
validateStatus?: 'success' | 'warning' | 'error' | 'validating';
validateStatus?: (typeof ValidateStatuses)[number];
hasFeedback?: boolean;
required?: boolean;
style?: React.CSSProperties;
@ -42,7 +45,7 @@ export default class FormItem extends React.Component<FormItemProps, any> {
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
labelCol: PropTypes.object,
help: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]),
validateStatus: PropTypes.oneOf(['', 'success', 'warning', 'error', 'validating']),
validateStatus: PropTypes.oneOf(ValidateStatuses),
hasFeedback: PropTypes.bool,
wrapperCol: PropTypes.object,
className: PropTypes.string,

View File

@ -3,7 +3,6 @@ import * as PropTypes from 'prop-types';
import classNames from 'classnames';
import RowContext from './RowContext';
const stringOrNumber = PropTypes.oneOfType([PropTypes.string, PropTypes.number]);
const objectOrNumber = PropTypes.oneOfType([PropTypes.object, PropTypes.number]);
export interface ColSize {
@ -31,11 +30,11 @@ export interface ColProps extends React.HTMLAttributes<HTMLDivElement> {
export default class Col extends React.Component<ColProps, {}> {
static propTypes = {
span: stringOrNumber,
order: stringOrNumber,
offset: stringOrNumber,
push: stringOrNumber,
pull: stringOrNumber,
span: PropTypes.number,
order: PropTypes.number,
offset: PropTypes.number,
push: PropTypes.number,
pull: PropTypes.number,
className: PropTypes.string,
children: PropTypes.node,
xs: objectOrNumber,

View File

@ -18,15 +18,17 @@ import * as React from 'react';
import classNames from 'classnames';
import * as PropTypes from 'prop-types';
import RowContext from './RowContext';
import { tuple } from '../_util/type';
export type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';
export type BreakpointMap = Partial<Record<Breakpoint, string>>;
const RowAligns = tuple('top', 'middle', 'bottom');
const RowJustify = tuple('start', 'end', 'center', 'space-around', 'space-between');
export interface RowProps extends React.HTMLAttributes<HTMLDivElement> {
gutter?: number | Partial<Record<Breakpoint, number>>;
type?: 'flex';
align?: 'top' | 'middle' | 'bottom';
justify?: 'start' | 'end' | 'center' | 'space-around' | 'space-between';
align?: (typeof RowAligns)[number];
justify?: (typeof RowJustify)[number];
prefixCls?: string;
}
@ -51,9 +53,9 @@ export default class Row extends React.Component<RowProps, RowState> {
};
static propTypes = {
type: PropTypes.string,
align: PropTypes.string,
justify: PropTypes.string,
type: PropTypes.oneOf<'flex'>(['flex']),
align: PropTypes.oneOf(RowAligns),
justify: PropTypes.oneOf(RowJustify),
className: PropTypes.string,
children: PropTypes.node,
gutter: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),

View File

@ -5,7 +5,7 @@ import omit from 'omit.js';
import Group from './Group';
import Search from './Search';
import TextArea from './TextArea';
import { Omit } from '../_util/type';
import { Omit, tuple } from '../_util/type';
function fixControlledValue<T>(value: T) {
if (typeof value === 'undefined' || value === null) {
@ -14,10 +14,12 @@ function fixControlledValue<T>(value: T) {
return value;
}
const InputSizes = tuple('small', 'default', 'large');
export interface InputProps
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix'> {
prefixCls?: string;
size?: 'large' | 'default' | 'small';
size?: (typeof InputSizes)[number];
onPressEnter?: React.KeyboardEventHandler<HTMLInputElement>;
addonBefore?: React.ReactNode;
addonAfter?: React.ReactNode;
@ -38,9 +40,9 @@ export default class Input extends React.Component<InputProps, any> {
static propTypes = {
type: PropTypes.string,
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
size: PropTypes.oneOf(['small', 'default', 'large']),
maxLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
id: PropTypes.string,
size: PropTypes.oneOf(InputSizes),
maxLength: PropTypes.number,
disabled: PropTypes.bool,
value: PropTypes.any,
defaultValue: PropTypes.any,

View File

@ -48,21 +48,9 @@ function getGrid(grid: ListGridType, t: ColumnType) {
return grid[t] && Math.floor(24 / grid[t]!);
}
const GridColumns = ['', 1, 2, 3, 4, 6, 8, 12, 24];
export default class Item extends React.Component<ListItemProps, any> {
static Meta: typeof Meta = Meta;
static propTypes = {
column: PropTypes.oneOf(GridColumns),
xs: PropTypes.oneOf(GridColumns),
sm: PropTypes.oneOf(GridColumns),
md: PropTypes.oneOf(GridColumns),
lg: PropTypes.oneOf(GridColumns),
xl: PropTypes.oneOf(GridColumns),
xxl: PropTypes.oneOf(GridColumns),
};
static contextTypes = {
grid: PropTypes.any,
};

View File

@ -41,7 +41,7 @@ export interface ListProps {
itemLayout?: string;
loading?: boolean | SpinProps;
loadMore?: React.ReactNode;
pagination?: PaginationConfig;
pagination?: PaginationConfig | false;
prefixCls?: string;
rowKey?: any;
renderItem: any;
@ -69,7 +69,7 @@ export default class List extends React.Component<ListProps> {
bordered: false,
split: true,
loading: false,
pagination: false,
pagination: false as ListProps['pagination'],
};
state = {

View File

@ -42,7 +42,7 @@ class Mention extends React.Component<MentionProps, MentionState> {
notFoundContent: '无匹配结果,轻敲空格完成输入',
loading: false,
multiLines: false,
placement: 'bottom',
placement: 'bottom' as MentionPlacement,
};
static Nav = Nav;
static toString = toString;

View File

@ -123,8 +123,8 @@ export default class Modal extends React.Component<ModalProps, {}> {
prefixCls: PropTypes.string,
onOk: PropTypes.func,
onCancel: PropTypes.func,
okText: PropTypes.node,
cancelText: PropTypes.node,
okText: PropTypes.string,
cancelText: PropTypes.string,
centered: PropTypes.bool,
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
confirmLoading: PropTypes.bool,

View File

@ -33,9 +33,9 @@ class Popconfirm extends React.Component<PopconfirmProps, PopconfirmState> {
static defaultProps = {
prefixCls: 'ant-popover',
transitionName: 'zoom-big',
placement: 'top',
trigger: 'click',
okType: 'primary',
placement: 'top' as PopconfirmProps['placement'],
trigger: 'click' as PopconfirmProps['trigger'],
okType: 'primary' as PopconfirmProps['okType'],
icon: <Icon type="exclamation-circle" theme="filled" />,
};

View File

@ -3,6 +3,7 @@ import * as React from 'react';
import Icon from '../icon';
import { Circle } from 'rc-progress';
import classNames from 'classnames';
import { tuple } from '../_util/type';
const statusColorMap: Record<string, string> = {
normal: '#108ee9',
@ -10,7 +11,9 @@ const statusColorMap: Record<string, string> = {
success: '#87d068',
};
export type ProgressType = 'line' | 'circle' | 'dashboard';
const ProgressTypes = tuple('line', 'circle', 'dashboard');
export type ProgressType = (typeof ProgressTypes)[number];
const ProgressStatuses = tuple('normal', 'exception', 'active', 'success');
export type ProgressSize = 'default' | 'small';
export interface ProgressProps {
@ -20,7 +23,7 @@ export interface ProgressProps {
percent?: number;
successPercent?: number;
format?: (percent?: number, successPercent?: number) => React.ReactNode;
status?: 'success' | 'active' | 'exception' | 'normal';
status?: (typeof ProgressStatuses)[number];
showInfo?: boolean;
strokeWidth?: number;
strokeLinecap?: string;
@ -44,17 +47,17 @@ const validProgress = (progress: number | undefined) => {
export default class Progress extends React.Component<ProgressProps, {}> {
static defaultProps = {
type: 'line',
type: 'line' as ProgressProps['type'],
percent: 0,
showInfo: true,
trailColor: '#f3f3f3',
prefixCls: 'ant-progress',
size: 'default',
size: 'default' as ProgressSize,
};
static propTypes = {
status: PropTypes.oneOf(['normal', 'exception', 'active', 'success']),
type: PropTypes.oneOf(['line', 'circle', 'dashboard']),
status: PropTypes.oneOf(ProgressStatuses),
type: PropTypes.oneOf(ProgressTypes),
showInfo: PropTypes.bool,
percent: PropTypes.number,
width: PropTypes.number,

View File

@ -8,12 +8,15 @@ import { ConfigConsumer, ConfigProviderProps } from '../config-provider';
import omit from 'omit.js';
import warning from 'warning';
import Icon from '../icon';
import { tuple } from '../_util/type';
const SelectSizes = tuple('default', 'large', 'small');
export interface AbstractSelectProps {
prefixCls?: string;
className?: string;
showAction?: string | string[];
size?: 'default' | 'large' | 'small';
size?: (typeof SelectSizes)[number];
notFoundContent?: React.ReactNode | null;
transitionName?: string;
choiceTransitionName?: string;
@ -97,7 +100,7 @@ export interface SelectLocale {
const SelectPropTypes = {
prefixCls: PropTypes.string,
className: PropTypes.string,
size: PropTypes.oneOf(['default', 'large', 'small']),
size: PropTypes.oneOf(SelectSizes),
notFoundContent: PropTypes.any,
showSearch: PropTypes.bool,
optionLabelProp: PropTypes.string,

View File

@ -2,8 +2,10 @@ import * as React from 'react';
import * as PropTypes from 'prop-types';
import classNames from 'classnames';
import omit from 'omit.js';
import { tuple } from '../_util/type';
export type SpinSize = 'small' | 'default' | 'large';
const SpinSizes = tuple('small', 'default', 'large');
export type SpinSize = (typeof SpinSizes)[number];
export type SpinIndicator = React.ReactElement<any>;
export interface SpinProps {
@ -67,9 +69,9 @@ class Spin extends React.Component<SpinProps, SpinState> {
prefixCls: PropTypes.string,
className: PropTypes.string,
spinning: PropTypes.bool,
size: PropTypes.oneOf(['small', 'default', 'large']),
size: PropTypes.oneOf(SpinSizes),
wrapperClassName: PropTypes.string,
indicator: PropTypes.node,
indicator: PropTypes.element,
};
static setDefaultIndicator(indicator: React.ReactNode) {

View File

@ -30,7 +30,9 @@ export default class Switch extends React.Component<SwitchProps, {}> {
prefixCls: PropTypes.string,
// HACK: https://github.com/ant-design/ant-design/issues/5368
// size=default and size=large are the same
size: PropTypes.oneOf(['small', 'default', 'large']),
size: PropTypes.oneOf(['small', 'default', 'large']) as PropTypes.Requireable<
SwitchProps['size']
>,
className: PropTypes.string,
};

View File

@ -75,7 +75,7 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
useFixedHeader: PropTypes.bool,
rowSelection: PropTypes.object,
className: PropTypes.string,
size: PropTypes.string,
size: PropTypes.string as PropTypes.Requireable<TableSize>,
loading: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
bordered: PropTypes.bool,
onChange: PropTypes.func,

View File

@ -51,7 +51,7 @@ export default class Tabs extends React.Component<TabsProps, any> {
static defaultProps = {
prefixCls: 'ant-tabs',
hideAdd: false,
tabPosition: 'top',
tabPosition: 'top' as TabsPosition,
};
removeTab = (targetKey: string, e: React.MouseEvent<HTMLElement>) => {

View File

@ -15,6 +15,7 @@ export { TransferSearchProps } from './search';
function noop() {}
export type TransferDirection = 'left' | 'right';
type TransferRender = (record: TransferItem) => React.ReactNode;
export interface TransferItem {
key: string;
@ -30,7 +31,7 @@ export interface TransferProps {
dataSource: TransferItem[];
targetKeys?: string[];
selectedKeys?: string[];
render?: (record: TransferItem) => React.ReactNode;
render?: TransferRender;
onChange?: (targetKeys: string[], direction: string, moveKeys: any) => void;
onSelectChange?: (sourceSelectedKeys: string[], targetSelectedKeys: string[]) => void;
style?: React.CSSProperties;
@ -68,7 +69,7 @@ export default class Transfer extends React.Component<TransferProps, any> {
static defaultProps = {
dataSource: [],
render: noop,
render: noop as TransferRender,
locale: {},
showSearch: false,
};
@ -76,7 +77,7 @@ export default class Transfer extends React.Component<TransferProps, any> {
static propTypes = {
prefixCls: PropTypes.string,
disabled: PropTypes.bool,
dataSource: PropTypes.array,
dataSource: PropTypes.array as PropTypes.Validator<TransferItem[]>,
render: PropTypes.func,
targetKeys: PropTypes.array,
onChange: PropTypes.func,