diff --git a/components/checkbox/__docs__/demo/all-check/index.tsx b/components/checkbox/__docs__/demo/all-check/index.tsx index 1b6bcd378f..6d2b6dd4fd 100644 --- a/components/checkbox/__docs__/demo/all-check/index.tsx +++ b/components/checkbox/__docs__/demo/all-check/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Checkbox, Divider } from '@alifd/next'; +import type { CheckboxProps, GroupProps } from '@alifd/next/lib/checkbox'; const CheckboxGroup = Checkbox.Group; @@ -12,13 +13,13 @@ const App = () => { const [indeterminate, setIndeterminate] = React.useState(true); const [checkAll, setCheckAll] = React.useState(false); - const onChange = list => { + const onChange: GroupProps['onChange'] = (list: string[]) => { setCheckedList(list); setIndeterminate(!!list.length && list.length < plainOptions.length); setCheckAll(list.length === plainOptions.length); }; - const onCheckAllChange = (checked, e) => { + const onCheckAllChange: CheckboxProps['onChange'] = (checked, e) => { setCheckedList(e.target.checked ? plainOptions : []); setIndeterminate(false); setCheckAll(e.target.checked); diff --git a/components/checkbox/__docs__/demo/control/index.tsx b/components/checkbox/__docs__/demo/control/index.tsx index 986b656c67..5d7a9974a2 100644 --- a/components/checkbox/__docs__/demo/control/index.tsx +++ b/components/checkbox/__docs__/demo/control/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Checkbox } from '@alifd/next'; +import { type GroupProps } from '@alifd/next/lib/checkbox'; const list = [ { @@ -18,26 +19,16 @@ const list = [ ]; class ControlApp extends React.Component { - constructor(props) { - super(props); - - this.state = { - value: 'orange', - }; - - this.onChange = this.onChange.bind(this); - } + state = { + value: 'orange', + }; - onChange(value) { + onChange: GroupProps['onChange'] = value => { this.setState({ value: value, }); console.log('onChange', value); - } - - onClick(e) { - console.log('onClick', e); - } + }; render() { return ( diff --git a/components/checkbox/__docs__/demo/dataSource/index.tsx b/components/checkbox/__docs__/demo/dataSource/index.tsx index 3a0192677c..33c8112540 100644 --- a/components/checkbox/__docs__/demo/dataSource/index.tsx +++ b/components/checkbox/__docs__/demo/dataSource/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Checkbox } from '@alifd/next'; +import { type GroupProps } from '@alifd/next/lib/checkbox'; const list = [ { @@ -20,15 +21,11 @@ const list = [ ]; class App extends React.Component { - constructor(props) { - super(props); - - this.state = { - value: 'apple', - }; - } + state = { + value: 'apple', + }; - onChange = value => { + onChange: GroupProps['onChange'] = value => { this.setState({ value: value, }); @@ -36,12 +33,7 @@ class App extends React.Component { render() { return ( - + ); } } diff --git a/components/checkbox/__docs__/demo/group/index.tsx b/components/checkbox/__docs__/demo/group/index.tsx index af18e679a4..ce3df04a38 100644 --- a/components/checkbox/__docs__/demo/group/index.tsx +++ b/components/checkbox/__docs__/demo/group/index.tsx @@ -1,23 +1,18 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Checkbox } from '@alifd/next'; +import { type GroupProps } from '@alifd/next/lib/checkbox'; class App extends React.Component { - constructor(props) { - super(props); + state = { + value: 'orange', + }; - this.state = { - value: 'orange', - }; - - this.onChange = this.onChange.bind(this); - } - - onChange(value) { + onChange: GroupProps['onChange'] = value => { this.setState({ value: value, }); - } + }; render() { return ( diff --git a/components/checkbox/__docs__/demo/indeterminate/index.tsx b/components/checkbox/__docs__/demo/indeterminate/index.tsx index 8af9451534..c7069f50f0 100644 --- a/components/checkbox/__docs__/demo/indeterminate/index.tsx +++ b/components/checkbox/__docs__/demo/indeterminate/index.tsx @@ -3,15 +3,11 @@ import ReactDOM from 'react-dom'; import { Checkbox, Button } from '@alifd/next'; class IndeterminateApp extends React.Component { - constructor(props) { - super(props); - - this.state = { - checked: false, - indeterminate: true, - disabled: false, - }; - } + state = { + checked: false, + indeterminate: true, + disabled: false, + }; toggle = () => { if (this.state.indeterminate) { diff --git a/components/checkbox/__docs__/demo/isPreview/index.tsx b/components/checkbox/__docs__/demo/isPreview/index.tsx index 54372ad59c..11b62cec06 100644 --- a/components/checkbox/__docs__/demo/isPreview/index.tsx +++ b/components/checkbox/__docs__/demo/isPreview/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Checkbox, Switch } from '@alifd/next'; +import { type CheckboxProps, type GroupProps } from '@alifd/next/lib/checkbox'; class App extends React.Component { state = { @@ -20,10 +21,10 @@ class App extends React.Component { }); }; - renderChecked = (checked, props) => + renderChecked: CheckboxProps['renderPreview'] = (checked, props) => checked ? {props.children} : null; - renderPreview = (previewed, props) => + renderPreview: GroupProps['renderPreview'] = previewed => previewed.length ? previewed.map((Item, index) => ( diff --git a/components/checkbox/__docs__/demo/uncontrol/index.tsx b/components/checkbox/__docs__/demo/uncontrol/index.tsx index f4cbdc7677..14eb902b08 100644 --- a/components/checkbox/__docs__/demo/uncontrol/index.tsx +++ b/components/checkbox/__docs__/demo/uncontrol/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Checkbox } from '@alifd/next'; +import { type GroupProps } from '@alifd/next/lib/checkbox'; const { Group: CheckboxGroup } = Checkbox; const list = [ @@ -21,15 +22,9 @@ const list = [ ]; class UnControlApp extends React.Component { - constructor(props) { - super(props); - - this.onChange = this.onChange.bind(this); - } - - onChange(selectedItems) { + onChange: GroupProps['onChange'] = selectedItems => { console.log('onChange callback', selectedItems); - } + }; render() { return ( diff --git a/components/checkbox/__docs__/index.en-us.md b/components/checkbox/__docs__/index.en-us.md index 7defeb1fd2..6c5c70286b 100644 --- a/components/checkbox/__docs__/index.en-us.md +++ b/components/checkbox/__docs__/index.en-us.md @@ -13,41 +13,67 @@ Checkbox ### When to Use Checkbox is used to verify which options you want selected from a group. If you have only a single option, do not use the checkbox, use the on/off switch. + ## API ### Checkbox -| Param | Description | Type | Default Value | -| ------------------------ |---------------------------- | ------------ | ------------- | -| id | checkbox id, mounted on input | String | - | -| checked | Set the status to be checked | Boolean | - | -| defaultChecked | Set the default status to be checked | Boolean | false | -| disabled | Set the status to be disabled | Boolean | - | -| label | Set the label by property | String | - | -| indeterminate | The intermediate state of the Checkbox will only affect the style of the Checkbox and does not affect its checked property. | Boolean | - | -| defaultIndeterminate | Set the default status to intermediate, it will only affect the style of the Checkbox and does not affect its checked property. | Boolean | false | -| onChange | Callback function triggered when the state changes

**signatures**:
Function(checked: Boolean, e: Event) => void
**params**:
_checked_: {Boolean} The checked value of the underlying checkbox input
_e_: {Event} Dom event object | Function | func.noop | -| onMouseEnter | Callback function triggered when the mouse pointer enters the element.

**signatures**:
Function(e: Event) => void
**params**:
_e_: {Event} Dom event object | Function | func.noop | -| onMouseLeave | Callback function triggered when the mouse pointer leaves the element.

**signatures**:
Function(e: Event) => void
**params**:
_e_: {Event} Dom event object | Function | func.noop | -|value | The value of the Checkbox | String/Number/Boolean | - | +| Param | Description | Type | Default Value | Required | Supported Version | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------- | -------- | ----------------- | +| className | ClassName | string | - | | - | +| id | Checkbox id, mounted on the input | string | - | | - | +| style | Custom inline style | React.CSSProperties | - | | - | +| checked | Checked status | boolean | - | | - | +| value | Checkbox value | IValue | - | | - | +| name | Name | string | - | | - | +| defaultChecked | Default checked status | boolean | false | | - | +| disabled | Disabled | boolean | - | | - | +| label | Label | React.ReactNode | - | | - | +| indeterminate | Checkbox middle status, only affects the style of Checkbox, and does not affect its checked property | boolean | - | | - | +| defaultIndeterminate | Checkbox default middle status, only affects the style of Checkbox, and does not affect its checked property | boolean | false | | - | +| onChange | Status change event | (checked: boolean, e: React.ChangeEvent) => void | - | | - | +| onMouseEnter | Mouse enter event | (e: React.MouseEvent) => void | - | | - | +| onMouseLeave | Mouse leave event | (e: React.MouseEvent) => void | - | | - | +| isPreview | Is preview | boolean | false | | 1.19 | +| renderPreview | Custom rendering content

**signature**:
**params**:
_checked_: Is checked
_props_: All props
**return**:
Custom rendering content | (checked: boolean, props: CheckboxProps) => React.ReactNode | - | | 1.19 | ### Checkbox.Group -| params | desc | type | default | -| ---------------- | --------------------------------------------------- | -------- | ------------- | -| disabled | Set the status of all checkbox in group to be checked | Boolean | - | -| dataSource | Optional list, data item can be String or Object, for example `['apple', 'pear', 'orange']` or `[{value: 'apple', label: 'Apple',}, {value: 'pear', label: 'Pear'}, {value: 'orange', label: 'Orange'}]` | Array<any> | \[] | -| value | The values of selected optional list | Array/String/Number/Boolean | - | -| defaultValue | The values of default selected optional list | Array/String/Number/Boolean | - | -| children | To set nested checkbox by children components | Array<ReactElement> | - | -| onChange | Callback function triggered when the selected value changes

**signatures**:
Function(value: Array, e: Event) => void
**params**:
_value_: {Array} values of selected optional list
_e_: {Event} Dom event object | Function | () => { } | -| direction | The direction of item's aligning
- hoz: horizontal (default)
- ver: vertical

**Allowed values**:
'hoz', 'ver' | Enum | 'hoz' | +| Param | Description | Type | Default Value | Required | Supported Version | +| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------- | ----------------- | +| className | Custom className | string | - | | - | +| style | Custom inline style | React.CSSProperties | - | | - | +| disabled | Entirely disabled | boolean | - | | - | +| dataSource | Option list | Array \| Array | - | | - | +| value | Selected value list | IValue[] \| IValue | - | | - | +| defaultValue | Default selected value list | IValue[] \| IValue | - | | - | +| name | Name | string | - | | - | +| children | Set internal checkbox through child elements | React.ReactNode | - | | - | +| onChange | Selected value change event | (value: IValue[], e: React.ChangeEvent) => void | - | | - | +| direction | Arrangement of subitems | 'hoz' \| 'ver' | - | | - | +| itemDirection | [Deprecated] Arrangement of subitems | 'hoz' \| 'ver' | - | | - | +| isPreview | Is preview | boolean | - | | 1.19 | +| renderPreview | Custom rendering content

**signature**:
**params**:
_previewed_: Previewed value [\{label: '', value:''\},...]
_props_: All props
**return**:
Custom rendering content | (
previewed: {
label: string \| React.ReactNode;
value: string \| React.ReactNode;
}[],
props: object
) => React.ReactNode | - | | 1.19 | + +### IValue +```typescript +export type IValue = string | number | boolean; +``` +### CheckboxData +```typescript +export type CheckboxData = { + value: IValue; + label?: React.ReactNode; + disabled?: boolean; + [propName: string]: unknown; +}; +``` ## ARIA and KeyBoard -| KeyBoard | Descripiton | -| :---------- | :------------------------------ | -| SPACE | Select or cancel the current item | +| KeyBoard | Descripiton | +| :------- | :-------------------------------- | +| SPACE | Select or cancel the current item | diff --git a/components/checkbox/__docs__/index.md b/components/checkbox/__docs__/index.md index a8b1db55ab..46969e002d 100644 --- a/components/checkbox/__docs__/index.md +++ b/components/checkbox/__docs__/index.md @@ -18,39 +18,62 @@ ### Checkbox -| 参数 | 说明 | 类型 | 默认值 | 版本支持 | -| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | --------- | ---- | -| id | checkbox id, 挂载在input上 | String | - | | -| checked | 选中状态 | Boolean | - | | -| defaultChecked | 默认选中状态 | Boolean | false | | -| disabled | 禁用 | Boolean | - | | -| label | 通过属性配置label, | ReactNode | - | | -| indeterminate | Checkbox 的中间状态,只会影响到 Checkbox 的样式,并不影响其 checked 属性 | Boolean | - | | -| defaultIndeterminate | Checkbox 的默认中间态,只会影响到 Checkbox 的样式,并不影响其 checked 属性 | Boolean | false | | -| onChange | 状态变化时触发的事件

**签名**:
Function(checked: Boolean, e: Event) => void
**参数**:
_checked_: {Boolean} 是否选中
_e_: {Event} Dom 事件对象 | Function | func.noop | | -| onMouseEnter | 鼠标进入enter事件

**签名**:
Function(e: Event) => void
**参数**:
_e_: {Event} Dom 事件对象 | Function | func.noop | | -| onMouseLeave | 鼠标离开Leave事件

**签名**:
Function(e: Event) => void
**参数**:
_e_: {Event} Dom 事件对象 | Function | func.noop | | -| value | checkbox 的value | String/Number/Boolean | - | | -| name | name | String | - | | -| isPreview | 是否为预览态 | Boolean | false | 1.19 | -| renderPreview | 预览态模式下渲染的内容

**签名**:
Function(checked: Boolean, props: Object) => reactNode
**参数**:
_checked_: {Boolean} 是否选中
_props_: {Object} 所有传入的参数
**返回值**:
{reactNode} Element 渲染内容
| Function | - | 1.19 | +| 参数 | 说明 | 类型 | 默认值 | 是否必填 | 支持版本 | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ------ | -------- | -------- | +| className | 自定义类名 | string | - | | - | +| id | checkbox id, 挂载在 input 上 | string | - | | - | +| style | 自定义内联样式 | React.CSSProperties | - | | - | +| checked | 选中状态 | boolean | - | | - | +| value | checkbox 的 value | IValue | - | | - | +| name | name | string | - | | - | +| defaultChecked | 默认选中状态 | boolean | false | | - | +| disabled | 禁用 | boolean | - | | - | +| label | 通过属性配置 label, | React.ReactNode | - | | - | +| indeterminate | Checkbox 的中间状态,只会影响到 Checkbox 的样式,并不影响其 checked 属性 | boolean | - | | - | +| defaultIndeterminate | Checkbox 的默认中间态,只会影响到 Checkbox 的样式,并不影响其 checked 属性 | boolean | false | | - | +| onChange | 状态变化时触发的事件 | (checked: boolean, e: React.ChangeEvent) => void | - | | - | +| onMouseEnter | 鼠标进入 enter 事件 | (e: React.MouseEvent) => void | - | | - | +| onMouseLeave | 鼠标离开 Leave 事件 | (e: React.MouseEvent) => void | - | | - | +| isPreview | 是否为预览态 | boolean | false | | 1.19 | +| renderPreview | 预览态模式下渲染的内容

**签名**:
**参数**:
_checked_: 是否选中
_props_: 所有传入的参数
**返回值**:
定制渲染内容 | (checked: boolean, props: CheckboxProps) => React.ReactNode | - | | 1.19 | ### Checkbox.Group -| 参数 | 说明 | 类型 | 默认值 | 版本支持 | -| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -------- | ---- | -| disabled | 整体禁用 | Boolean | - | | -| dataSource | 可选项列表, 数据项可为 String 或者 Object, 如 `['apple', 'pear', 'orange']` 或者 `[{value: 'apple', label: '苹果',}, {value: 'pear', label: '梨'}, {value: 'orange', label: '橙子'}]` | Array<String>/Array<Object> | \[] | | -| value | 被选中的值列表 | Array/String/Number/Boolean | - | | -| defaultValue | 默认被选中的值列表 | Array/String/Number/Boolean | - | | -| children | 通过子元素方式设置内部 checkbox | Array<ReactElement> | - | | -| onChange | 选中值改变时的事件

**签名**:
Function(value: Array, e: Event) => void
**参数**:
_value_: {Array} 选中项列表
_e_: {Event} Dom 事件对象 | Function | () => {} | | -| direction | 子项目的排列方式
- hoz: 水平排列 (default)
- ver: 垂直排列

**可选值**:
'hoz', 'ver' | Enum | 'hoz' | | -| isPreview | 是否为预览态 | Boolean | false | 1.19 | -| renderPreview | 预览态模式下渲染的内容

**签名**:
Function(previewed: Array, props: Object) => reactNode
**参数**:
_previewed_: {Array} 预览值 [{label: '', value:''},...]
_props_: {Object} 所有传入的参数
**返回值**:
{reactNode} Element 渲染内容
| Function | - | 1.19 | +| 参数 | 说明 | 类型 | 默认值 | 是否必填 | 支持版本 | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------- | -------- | +| className | 自定义类名 | string | - | | - | +| style | 自定义内联样式 | React.CSSProperties | - | | - | +| disabled | 整体禁用 | boolean | - | | - | +| dataSource | 可选项列表 | Array \| Array | - | | - | +| value | 被选中的值列表 | IValue[] \| IValue | - | | - | +| defaultValue | 默认被选中的值列表 | IValue[] \| IValue | - | | - | +| name | name | string | - | | - | +| children | 通过子元素方式设置内部 checkbox | React.ReactNode | - | | - | +| onChange | 选中值改变时的事件 | (value: IValue[], e: React.ChangeEvent) => void | - | | - | +| direction | 子项目的排列方式 | 'hoz' \| 'ver' | - | | - | +| itemDirection | [废弃] 子项目的排列方式 | 'hoz' \| 'ver' | - | | - | +| isPreview | 是否为预览态 | boolean | - | | 1.19 | +| renderPreview | 预览态模式下渲染的内容

**签名**:
**参数**:
_previewed_: 预览值 [\{label: '', value:''\},...]
_props_: 所有传入的参数
**返回值**:
定制渲染内容 | (
previewed: {
label: string \| React.ReactNode;
value: string \| React.ReactNode;
}[],
props: object
) => React.ReactNode | - | | 1.19 | + +### IValue + +```typescript +export type IValue = string | number | boolean; +``` + +### CheckboxData + +```typescript +export type CheckboxData = { + value: IValue; + label?: React.ReactNode; + disabled?: boolean; + [propName: string]: unknown; +}; +``` ## 无障碍键盘操作指南 -| 按键 | 说明 | -| :---- | :------- | +| 按键 | 说明 | +| :---- | :--------------- | | SPACE | 选择或取消当前项 | diff --git a/components/checkbox/__tests__/a11y-spec.js b/components/checkbox/__tests__/a11y-spec.tsx similarity index 63% rename from components/checkbox/__tests__/a11y-spec.js rename to components/checkbox/__tests__/a11y-spec.tsx index 56cf65c678..9836823891 100644 --- a/components/checkbox/__tests__/a11y-spec.js +++ b/components/checkbox/__tests__/a11y-spec.tsx @@ -1,25 +1,10 @@ import React from 'react'; -import Enzyme from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; import Checkbox from '../index'; -import { unmount, testReact } from '../../util/__tests__/legacy/a11y/validate'; +import { testReact } from '../../util/__tests__/a11y/validate'; -Enzyme.configure({ adapter: new Adapter() }); - -/* eslint-disable no-undef, react/jsx-filename-extension */ describe('Checkbox A11y', () => { - let wrapper; - - afterEach(() => { - if (wrapper) { - wrapper.unmount(); - wrapper = null; - } - unmount(); - }); - it('should not have any violations for grouped checkbox with children', async () => { - wrapper = await testReact( + await testReact( 苹果 @@ -32,7 +17,6 @@ describe('Checkbox A11y', () => { ); - return wrapper; }); it('should not have any violations for grouped checkbox with datasource', async () => { @@ -50,7 +34,6 @@ describe('Checkbox A11y', () => { label: '橙子', }, ]; - wrapper = await testReact(); - return wrapper; + await testReact(); }); }); diff --git a/components/checkbox/__tests__/group-spec.js b/components/checkbox/__tests__/group-spec.js deleted file mode 100644 index 6fcd49c884..0000000000 --- a/components/checkbox/__tests__/group-spec.js +++ /dev/null @@ -1,236 +0,0 @@ -import React from 'react'; -import Enzyme, { shallow, mount } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; -import sinon from 'sinon'; -import assert from 'power-assert'; -import Checkbox from '../index'; - -/* eslint-disable */ -Enzyme.configure({ adapter: new Adapter() }); - -const CheckboxGroup = Checkbox.Group; - -describe('Checkbox.Group', () => { - let list; - beforeEach('mock data', () => { - list = [ - { - value: 'apple', - label: '苹果', - }, - { - value: 'pear', - label: '梨', - }, - { - value: 'orange', - label: '橙子', - }, - ]; - }); - describe('[render] control', () => { - it('should contain `pear`', () => { - const wrapper = shallow().dive(); - assert(wrapper.state().value.indexOf('pear') !== -1); - }); - - it('should have three children with mock data', () => { - const wrapper = mount(); - assert(wrapper.find('.next-checkbox-group').children().length === 3); - }); - - it('should support null child', () => { - const wrapper = mount( - - 1 - 2 - {null} - - ); - assert(wrapper.find('.next-checkbox-group').children().length === 2); - }); - }); - - describe('[render] uncontrol', () => { - it('should have three children with mock data', () => { - const wrapper = mount(); - assert(wrapper.find('.next-checkbox-group').children().length === 3); - }); - }); - - describe('[render] nest', () => { - const wrapper = shallow( - - - 苹果 - - - 梨子 - - - 西瓜 - - - ).dive(); - - it('should contain `pear` and `watermelon`', () => { - assert(wrapper.state().value.indexOf('pear') !== -1); - assert(wrapper.state().value.indexOf('watermelon') !== -1); - }); - - it('should have two children with nest ', () => { - const wrapper = mount( - - - 苹果 - - - 梨子 - - - 西瓜 - - - ); - const target = wrapper.find('.next-checkbox-group'); - assert(target.children().length === 3); - assert(target.find('.disabled').length === 1); - }); - }); - - describe('[events] simulate change', () => { - it('should call `onChange`', () => { - const onChange = sinon.spy(); - const wrapper = mount(); - wrapper - .find('input') - .first() - .simulate('change'); - assert(onChange.calledOnce); - - const onChange1 = sinon.spy(); - const wrapper1 = mount(); - wrapper1 - .find('input') - .first() - .simulate('change'); - assert(onChange.calledOnce); - }); - }); - - describe('[behavior] controlled', () => { - it('should support controlled `value`', () => { - const wrapper = shallow().dive(); - assert(wrapper.state().value[0] === 'pear'); - - wrapper.setProps({ - value: ['apple'], - }); - assert(wrapper.state().value[0] === 'apple'); - wrapper.setProps({ - value: 'orange', - }); - assert(wrapper.state().value[0] === 'orange'); - - wrapper.setProps({ - value: null, - }); - assert(wrapper.state().value.length === 0); - }); - - it('should support controlled `disabled`', () => { - const wrapper = mount(); - assert(!wrapper.props().disabled); - assert(!wrapper.find('.next-checkbox-group').hasClass('disabled')); - - wrapper.setProps({ - disabled: true, - }); - assert(wrapper.find('.next-checkbox-group').hasClass('disabled')); - }); - }); - describe('value === undefined', () => { - it('should support value === undefined', () => { - const wrapper = shallow(); - const wrapper1 = shallow(); - wrapper.setProps({ - value: undefined, - }); - assert.deepEqual(wrapper.dive().state().value, []); - assert.deepEqual(wrapper1.dive().state().value, []); - }); - }); - describe('value === 0', () => { - it('should support value === 0', () => { - const wrapper = shallow(); - assert.deepEqual(wrapper.dive().state().value, [0]); - wrapper.setProps({ - value: 1, - }); - assert.deepEqual(wrapper.dive().state().value, [1]); - }); - }); - - describe("should respect children's indeternimate state", () => { - it('should support value === 0', () => { - const wrapper1 = mount( - - 1 - - ); - const wrapper2 = mount( - - 1 - - ); - - assert(wrapper1.find('.indeterminate').length === 1); - assert(wrapper2.find('.indeterminate').length === 1); - }); - }); - - describe('render in preview mode', () => { - it('should isPreview', () => { - const wrapper = mount(); - assert(wrapper.getDOMNode().innerText === '苹果'); - }); - - it('should renderPreview', () => { - const wrapper = mount( - 'checkbox preview'} defaultValue={0} dataSource={list} /> - ); - assert(wrapper.getDOMNode().innerText === 'checkbox preview'); - }); - }); - it('value support bool`', () => { - let value = null; - const wrapper = mount( - { - value = v; - }} - dataSource={[ - { - value: false, - label: '苹果', - }, - { - value: true, - label: '橙子', - }, - ]} - /> - ); - - wrapper - .find('input') - .at(1) - .simulate('change'); - assert.deepEqual(value, [true]); - wrapper - .find('input') - .at(0) - .simulate('change'); - assert.deepEqual(value, [true, false]); - }); -}); diff --git a/components/checkbox/__tests__/group-spec.tsx b/components/checkbox/__tests__/group-spec.tsx new file mode 100644 index 0000000000..79a034cb63 --- /dev/null +++ b/components/checkbox/__tests__/group-spec.tsx @@ -0,0 +1,236 @@ +import React, { type ReactElement } from 'react'; +import { type MountReturn } from 'cypress/react'; +import Checkbox from '../index'; + +const CheckboxGroup = Checkbox.Group; + +const list = [ + { + value: 'apple', + label: '苹果', + }, + { + value: 'pear', + label: '梨', + }, + { + value: 'orange', + label: '橙子', + }, +]; + +describe('Checkbox.Group', () => { + describe('[render] control', () => { + it('should contain `pear`', () => { + cy.mount(); + cy.get('.next-checkbox-wrapper.checked').should('have.text', '梨'); + }); + + it('should have three children with mock data', () => { + cy.mount(); + cy.get('.next-checkbox-wrapper').should('have.length', 3); + }); + + it('should support null child', () => { + cy.mount( + + 1 + 2 + {null} + + ); + cy.get('.next-checkbox-wrapper').should('have.length', 2); + }); + }); + + describe('[render] uncontrolled', () => { + it('should have three children with mock data', () => { + cy.mount(); + cy.get('.next-checkbox-wrapper').should('have.length', 3); + }); + }); + + describe('[render] nest', () => { + it('should contain `pear` and `watermelon`', () => { + cy.mount( + + + 苹果 + + + 梨子 + + + 西瓜 + + + ); + cy.get('.next-checkbox-wrapper.checked').eq(0).should('have.text', '梨子'); + cy.get('.next-checkbox-wrapper.checked').eq(1).should('have.text', '西瓜'); + }); + + it('should have two children with nest ', () => { + cy.mount( + + + 苹果 + + + 梨子 + + + 西瓜 + + + ); + cy.get('.next-checkbox-wrapper').should('have.length', 3); + cy.get('.next-checkbox-wrapper.disabled').should('have.length', 1); + }); + }); + + describe('[events] simulate change', () => { + it('should call `onChange`', () => { + // const onChange = sinon.spy(); + const onChange = cy.spy().as('onChange'); + cy.mount(); + cy.get('input').eq(0).click(); + cy.get('@onChange').should('be.calledOnce'); + + const onChange1 = cy.spy().as('onChange1'); + cy.mount(); + cy.get('input').eq(0).click(); + cy.get('@onChange1').should('be.calledOnce'); + }); + }); + + describe('[behavior] controlled', () => { + it('should support controlled `value`', () => { + cy.mount().as('Demo'); + + cy.get('.next-checkbox-wrapper.checked').should('have.text', '梨'); + + cy.get('@Demo').then(({ component, rerender }) => { + return rerender( + React.cloneElement(component as ReactElement, { value: ['apple'] }) + ); + }); + + cy.get('.next-checkbox-wrapper.checked').should('have.text', '苹果'); + + cy.get('@Demo').then(({ component, rerender }) => { + return rerender(React.cloneElement(component as ReactElement, { value: 'orange' })); + }); + + cy.get('.next-checkbox-wrapper.checked').should('have.text', '橙子'); + + cy.get('@Demo').then(({ component, rerender }) => { + return rerender(React.cloneElement(component as ReactElement, { value: null })); + }); + + cy.get('.next-checkbox-wrapper.checked').should('have.length', 0); + }); + + it('should support controlled `disabled`', () => { + cy.mount().as( + 'Demo' + ); + + cy.get('.next-checkbox-group').should('not.have.class', 'disabled'); + + cy.get('@Demo').then(({ component, rerender }) => { + return rerender(React.cloneElement(component as ReactElement, { disabled: true })); + }); + + cy.get('.next-checkbox-group').should('have.class', 'disabled'); + }); + }); + describe('value === undefined', () => { + it('should support value === undefined', () => { + cy.mount().as('Demo'); + cy.get('@Demo').then(({ component, rerender }) => { + return rerender( + React.cloneElement(component as ReactElement, { value: undefined }) + ); + }); + cy.get('.next-checkbox-wrapper.checked').should('have.length', 0); + cy.mount(); + cy.get('.next-checkbox-wrapper.checked').should('have.length', 0); + }); + }); + describe('value === 0', () => { + it('should support value === 0', () => { + cy.mount( + + ).as('Demo'); + cy.get('.next-checkbox-wrapper.checked').should('have.text', 0); + cy.get('@Demo').then(({ component, rerender }) => { + return rerender(React.cloneElement(component as ReactElement, { value: 1 })); + }); + cy.get('.next-checkbox-wrapper.checked').should('have.text', 1); + }); + }); + + describe("should respect children's indeterminate state", () => { + it('should support value === 0', () => { + cy.mount( + + 1 + + ); + cy.get('.indeterminate').should('have.length', 1); + cy.mount( + + 1 + + ); + cy.get('.indeterminate').should('have.length', 1); + }); + }); + + describe('render in preview mode', () => { + it('should isPreview', () => { + cy.mount(); + cy.get('.next-form-preview').should('have.text', '苹果'); + }); + + it('should renderPreview', () => { + cy.mount( + 'checkbox preview'} + defaultValue={0} + dataSource={list} + /> + ); + cy.get('.next-form-preview').should('have.text', 'checkbox preview'); + }); + }); + it('value support bool`', () => { + const handleChange = cy.spy().as('handleChange'); + cy.mount( + + ); + cy.get('input').eq(1).click(); + cy.get('@handleChange').should('be.calledWith', [true]); + cy.get('input').eq(0).click(); + cy.get('@handleChange').should('be.calledWith', [true, false]); + }); +}); diff --git a/components/checkbox/__tests__/index-spec.js b/components/checkbox/__tests__/index-spec.js deleted file mode 100644 index 8674ff7722..0000000000 --- a/components/checkbox/__tests__/index-spec.js +++ /dev/null @@ -1,126 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import Enzyme, { mount } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; -import sinon from 'sinon'; -import assert from 'power-assert'; -import Checkbox from '../index'; - -/* eslint-disable */ -Enzyme.configure({ adapter: new Adapter() }); - -describe('Checkbox', () => { - describe('[render] normal', () => { - it('should get a normal checkbox', () => { - const wrapper1 = mount(); - const wrapper2 = mount(香蕉); - assert(wrapper1.find('.next-checkbox').length === 1); - assert(wrapper2.find('.next-checkbox').length === 1); - assert(wrapper2.find('input#banana').length === 1); - }); - }); - - describe('[render] checked', () => { - it('should get a checked checkbox', () => { - const wrapper1 = mount(); - const wrapper2 = mount(); - assert(wrapper1.find('.checked').length === 1); - assert(wrapper2.find('.checked').length === 1); - }); - }); - - describe('[render] indeterminate', () => { - it('should get a indeterminate checkbox', () => { - const wrapper1 = mount(); - const wrapper2 = mount(); - - assert(wrapper1.find('.indeterminate').length === 1); - assert(wrapper2.find('.indeterminate').length === 1); - }); - }); - - describe('[render] disabled', () => { - it('should get a disabled checkbox', () => { - const wrapper = mount(); - assert(wrapper.find('.disabled').length === 1); - }); - }); - - describe('[render] label', () => { - it('should get a checkbox with label', () => { - const wrapper = mount(); - assert(wrapper.find('.next-checkbox-label').length === 1); - }); - }); - - describe('[attribute] set custom `className`', () => { - it('should has className `cumstom-name`', () => { - const wrapper = mount(); - assert(wrapper.props().className === 'cumstom-name'); - assert(wrapper.find('.next-checkbox-wrapper.cumstom-name').length === 1); - }); - }); - - describe('[events] simulate click', () => { - const wrapper = mount(); - - it('should checked after click', () => { - wrapper.find('input').simulate('change', { target: { checked: true } }); - assert(wrapper.find('input').prop('checked')); - }); - it('should call `onChange`', () => { - const onChange = sinon.spy(); - const wrapper = mount(); - wrapper.find('input').simulate('change'); - assert(onChange.calledOnce); - }); - it('should return the passed value', () => { - const onChange = sinon.spy(); - const wrapper = mount(); - wrapper.find('input').simulate('change'); - assert(onChange.getCalls()[0].args[1].target.value === 'banana'); - }); - it('should call `onMouseEnter`', () => { - const onMouseEnter = sinon.spy(); - const wrapper1 = mount(); - wrapper1.find('.next-checkbox-wrapper').simulate('mouseEnter'); - assert(onMouseEnter.calledOnce); - }); - it('should call `onMouseLeave`', () => { - const onMouseLeave = sinon.spy(); - const wrapper1 = mount(); - wrapper1.find('.next-checkbox-wrapper').simulate('mouseLeave'); - assert(onMouseLeave.calledOnce); - }); - }); - - describe('[behavior] controlled', () => { - it('should support controlled `checked` and `indeterminate`', () => { - const wrapper = mount(); - assert(wrapper.find('input').props().checked); - assert(wrapper.find('.checked').length === 1); - - wrapper.setProps({ - checked: false, - }); - assert(!wrapper.find('input').props().checked); - assert(wrapper.find('.checked').length === 0); - wrapper.setProps({ - indeterminate: true, - }); - assert(wrapper.find('.indeterminate').length === 1); - }); - }); - - describe('render in preview mode', () => { - it('should isPreview', () => { - const wrapper = mount(); - assert(wrapper.getDOMNode().innerText === 'apple'); - }); - - it('should renderPreview', () => { - const wrapper = mount( 'checked'} />); - assert(wrapper.getDOMNode().innerText === 'checked'); - }); - }); -}); diff --git a/components/checkbox/__tests__/index-spec.tsx b/components/checkbox/__tests__/index-spec.tsx new file mode 100644 index 0000000000..e49e3b7781 --- /dev/null +++ b/components/checkbox/__tests__/index-spec.tsx @@ -0,0 +1,132 @@ +import React, { type ReactElement } from 'react'; +import { type MountReturn } from 'cypress/react'; +import Checkbox from '../index'; + +describe('Checkbox', () => { + describe('[render] normal', () => { + it('should get a normal checkbox', () => { + cy.mount(); + cy.get('.next-checkbox').should('have.length', 1); + cy.mount(香蕉); + cy.get('.next-checkbox').should('have.length', 1); + cy.get('input#banana').should('have.length', 1); + }); + }); + + describe('[render] checked', () => { + it('should get a checked checkbox', () => { + cy.mount(); + cy.get('.checked').should('have.length', 1); + cy.mount(); + cy.get('.checked').should('have.length', 1); + }); + }); + + describe('[render] indeterminate', () => { + it('should get a indeterminate checkbox', () => { + cy.mount(); + cy.get('.indeterminate').should('have.length', 1); + cy.mount(); + cy.get('.indeterminate').should('have.length', 1); + }); + }); + + describe('[render] disabled', () => { + it('should get a disabled checkbox', () => { + cy.mount(); + cy.get('.disabled').should('have.length', 1); + }); + }); + + describe('[render] label', () => { + it('should get a checkbox with label', () => { + cy.mount(); + cy.get('.next-checkbox-label').should('have.length', 1); + }); + }); + + describe('[attribute] set custom `className`', () => { + it('should has className `custom-name`', () => { + cy.mount(); + cy.get('.next-checkbox-wrapper.custom-name').should('exist'); + }); + }); + + describe('[events] simulate click', () => { + it('should checked after click', () => { + cy.mount(); + cy.get('input').eq(0).check(); + cy.get('input').should('have.prop', 'checked', true); + }); + it('should call `onChange`', () => { + const onChange = cy.spy().as('onChange'); + cy.mount(); + cy.get('input').eq(0).check(); + cy.get('@onChange').should('be.called'); + }); + it('should return the passed value', () => { + const onChange = cy.spy().as('onChange'); + cy.mount( + { + e.persist(); + onChange(e); + }} + value="banana" + /> + ); + cy.get('input').eq(0).check(); + cy.get('@onChange').should( + 'be.calledWithMatch', + (e: React.ChangeEvent) => { + return e.target.value === 'banana'; + } + ); + }); + it('should call `onMouseEnter`', () => { + const onMouseEnter = cy.spy().as('onMouseEnter'); + cy.mount(); + // React 的 mouseEnter 事件是通过监听 mouseover 实现的 + cy.get('.next-checkbox-wrapper').trigger('mouseover'); + cy.get('@onMouseEnter').should('be.calledOnce'); + }); + it('should call `onMouseLeave`', () => { + const onMouseLeave = cy.spy().as('onMouseLeave'); + cy.mount(); + // React 的 mouseLeave 事件是通过监听 mouseout 实现的 + cy.get('.next-checkbox-wrapper').trigger('mouseout'); + cy.get('@onMouseLeave').should('be.calledOnce'); + }); + }); + + describe('[behavior] controlled', () => { + it('should support controlled `checked` and `indeterminate`', () => { + cy.mount().as('Demo'); + cy.get('input').should('be.checked'); + cy.get('.checked').should('have.length', 1); + cy.get('@Demo').then(({ component, rerender }) => { + return rerender(React.cloneElement(component as ReactElement, { checked: false })); + }); + cy.get('input').should('not.be.checked'); + cy.get('.checked').should('have.length', 0); + cy.get('@Demo').then(({ component, rerender }) => { + return rerender( + React.cloneElement(component as ReactElement, { indeterminate: true }) + ); + }); + cy.get('.indeterminate').should('have.length', 1); + }); + }); + + describe('render in preview mode', () => { + it('should isPreview', () => { + cy.mount(); + cy.get('.next-form-preview').should('have.text', 'apple'); + }); + + it('should renderPreview', () => { + cy.mount( 'checked'} />); + cy.get('.next-form-preview').should('have.text', 'checked'); + }); + }); +}); diff --git a/components/checkbox/checkbox-group.jsx b/components/checkbox/checkbox-group.tsx similarity index 64% rename from components/checkbox/checkbox-group.jsx rename to components/checkbox/checkbox-group.tsx index ea2f56a964..0bcde1986e 100644 --- a/components/checkbox/checkbox-group.jsx +++ b/components/checkbox/checkbox-group.tsx @@ -1,70 +1,41 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; +import * as PropTypes from 'prop-types'; import classnames from 'classnames'; import { polyfill } from 'react-lifecycles-compat'; import { obj } from '../util'; import Checkbox from './checkbox'; +import type { CheckboxData, GroupProps, GroupState, ValueItem } from './types'; const { pickOthers } = obj; /** Checkbox.Group */ -class CheckboxGroup extends Component { +class CheckboxGroup extends React.Component { static propTypes = { prefix: PropTypes.string, rtl: PropTypes.bool, - /** - * 自定义类名 - */ className: PropTypes.string, - /** - * 自定义内敛样式 - */ style: PropTypes.object, - /** - * 整体禁用 - */ disabled: PropTypes.bool, - /** - * 可选项列表, 数据项可为 String 或者 Object, 如 `['apple', 'pear', 'orange']` 或者 `[{value: 'apple', label: '苹果',}, {value: 'pear', label: '梨'}, {value: 'orange', label: '橙子'}]` - */ - dataSource: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.object)]), - /** - * 被选中的值列表 - */ - value: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number, PropTypes.bool]), - /** - * 默认被选中的值列表 - */ - defaultValue: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number, PropTypes.bool]), - /** - * 通过子元素方式设置内部 checkbox - */ + dataSource: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.arrayOf(PropTypes.object), + ]), + value: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.string, + PropTypes.number, + PropTypes.bool, + ]), + defaultValue: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.string, + PropTypes.number, + PropTypes.bool, + ]), children: PropTypes.arrayOf(PropTypes.element), - /** - * 选中值改变时的事件 - * @param {Array} value 选中项列表 - * @param {Event} e Dom 事件对象 - */ onChange: PropTypes.func, - - /** - * 子项目的排列方式 - * - hoz: 水平排列 (default) - * - ver: 垂直排列 - */ direction: PropTypes.oneOf(['hoz', 'ver']), - /** - * 是否为预览态 - * @version 1.19 - */ isPreview: PropTypes.bool, - /** - * 预览态模式下渲染的内容 - * @param {Array} previewed 预览值 [{label: '', value:''},...] - * @param {Object} props 所有传入的参数 - * @returns {reactNode} Element 渲染内容 - * @version 1.19 - */ renderPreview: PropTypes.func, }; @@ -83,10 +54,10 @@ class CheckboxGroup extends Component { disabled: PropTypes.bool, }; - constructor(props) { + constructor(props: GroupProps) { super(props); - let value = []; + let value: GroupProps['value'] = []; if ('value' in props) { value = props.value; } else if ('defaultValue' in props) { @@ -115,7 +86,7 @@ class CheckboxGroup extends Component { }; } - static getDerivedStateFromProps(nextProps) { + static getDerivedStateFromProps(nextProps: GroupProps) { if ('value' in nextProps) { let { value } = nextProps; if (!Array.isArray(value)) { @@ -125,14 +96,13 @@ class CheckboxGroup extends Component { value = [value]; } } - return { value }; } return null; } - onChange(currentValue, e) { + onChange(currentValue: ValueItem, event: React.ChangeEvent) { const { value } = this.state; const index = value.indexOf(currentValue); const valTemp = [...value]; @@ -146,41 +116,54 @@ class CheckboxGroup extends Component { if (!('value' in this.props)) { this.setState({ value: valTemp }); } - this.props.onChange(valTemp, e); + this.props.onChange?.(valTemp, event); } render() { - const { className, style, prefix, disabled, direction, rtl, isPreview, renderPreview } = this.props; + const { className, style, prefix, disabled, direction, rtl, isPreview, renderPreview } = + this.props; const others = pickOthers(CheckboxGroup.propTypes, this.props); - // 如果内嵌标签跟dataSource同时存在,以内嵌标签为主 + // 如果内嵌标签跟 dataSource 同时存在,以内嵌标签为主 let children; - const previewed = []; + const previewed: { + label: string | React.ReactNode; + value: string | React.ReactNode; + }[] = []; if (this.props.children) { children = React.Children.map(this.props.children, child => { - if (!React.isValidElement(child)) { + if ( + !React.isValidElement<{ + value: ValueItem; + children?: string; + rtl?: boolean; + }>(child) + ) { return child; } - const checked = this.state.value && this.state.value.indexOf(child.props.value) > -1; + const checked = + this.state.value && this.state.value.indexOf(child.props?.value) > -1; if (checked) { previewed.push({ - label: child.props.children, - value: child.props.value, + label: child.props?.children, + value: child.props?.value, }); } - return React.cloneElement(child, child.props.rtl === undefined ? { rtl } : null); + return React.cloneElement(child, child.props?.rtl === undefined ? { rtl } : {}); }); } else { - children = this.props.dataSource.map((item, index) => { - let option = item; + children = this.props.dataSource?.map((item, index) => { + let option: CheckboxData; if (typeof item !== 'object') { option = { label: item, value: item, disabled, }; + } else { + option = item; } const checked = this.state.value && this.state.value.indexOf(option.value) > -1; @@ -210,7 +193,7 @@ class CheckboxGroup extends Component { if ('renderPreview' in this.props) { return (
- {renderPreview(previewed, this.props)} + {renderPreview?.(previewed, this.props)}
); } @@ -222,10 +205,9 @@ class CheckboxGroup extends Component { ); } - const cls = classnames({ + const cls = classnames(className, { [`${prefix}checkbox-group`]: true, [`${prefix}checkbox-group-${direction}`]: true, - [className]: !!className, disabled, }); diff --git a/components/checkbox/checkbox.jsx b/components/checkbox/checkbox.tsx similarity index 73% rename from components/checkbox/checkbox.jsx rename to components/checkbox/checkbox.tsx index 241cb30e47..1e5e615e79 100644 --- a/components/checkbox/checkbox.jsx +++ b/components/checkbox/checkbox.tsx @@ -1,99 +1,57 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; +import * as PropTypes from 'prop-types'; import classnames from 'classnames'; import { polyfill } from 'react-lifecycles-compat'; -import UIState from '../mixin-ui-state'; +import UIState, { type UIStateState } from '../mixin-ui-state'; import ConfigProvider from '../config-provider'; import Icon from '../icon'; -import withContext from './with-context'; +import withCheckboxContext, { type CheckboxContext } from './with-context'; import { obj, func } from '../util'; +import type { CheckboxProps } from './types'; const noop = func.noop; -function isChecked(selectedValue, value) { +function isChecked( + selectedValue: CheckboxContext['selectedValue'], + value: CheckboxProps['value'] +): boolean { return selectedValue.indexOf(value) > -1; } + +interface CheckboxState extends UIStateState { + value?: CheckboxProps['value']; + checked?: boolean; + indeterminate?: boolean; +} + +export interface PrivateCheckboxProps extends CheckboxProps { + context: CheckboxContext; +} + /** * Checkbox * @order 1 */ -class Checkbox extends UIState { +class Checkbox extends UIState { static displayName = 'Checkbox'; static propTypes = { ...ConfigProvider.propTypes, prefix: PropTypes.string, rtl: PropTypes.bool, - /** - * 自定义类名 - */ className: PropTypes.string, - /** - * checkbox id, 挂载在input上 - */ id: PropTypes.string, - /** - * 自定义内敛样式 - */ style: PropTypes.object, - /** - * 选中状态 - */ checked: PropTypes.bool, - /** - * 默认选中状态 - */ defaultChecked: PropTypes.bool, - /** - * 禁用 - */ disabled: PropTypes.bool, - /** - * 通过属性配置label, - */ label: PropTypes.node, - /** - * Checkbox 的中间状态,只会影响到 Checkbox 的样式,并不影响其 checked 属性 - */ indeterminate: PropTypes.bool, - /** - * Checkbox 的默认中间态,只会影响到 Checkbox 的样式,并不影响其 checked 属性 - */ defaultIndeterminate: PropTypes.bool, - /** - * 状态变化时触发的事件 - * @param {Boolean} checked 是否选中 - * @param {Event} e Dom 事件对象 - */ onChange: PropTypes.func, - /** - * 鼠标进入enter事件 - * @param {Event} e Dom 事件对象 - */ onMouseEnter: PropTypes.func, - /** - * 鼠标离开Leave事件 - * @param {Event} e Dom 事件对象 - */ onMouseLeave: PropTypes.func, - /** - * checkbox 的value - */ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]), - /** - * name - */ name: PropTypes.string, - /** - * 是否为预览态 - * @version 1.19 - */ isPreview: PropTypes.bool, - /** - * 预览态模式下渲染的内容 - * @param {Boolean} checked 是否选中 - * @param {Object} props 所有传入的参数 - * @returns {reactNode} Element 渲染内容 - * @version 1.19 - */ renderPreview: PropTypes.func, }; @@ -107,7 +65,7 @@ class Checkbox extends UIState { isPreview: false, }; - constructor(props) { + constructor(props: PrivateCheckboxProps) { super(props); const { context } = props; let checked, indeterminate; @@ -134,9 +92,9 @@ class Checkbox extends UIState { this.onChange = this.onChange.bind(this); } - static getDerivedStateFromProps(nextProps) { + static getDerivedStateFromProps(nextProps: PrivateCheckboxProps) { const { context: nextContext } = nextProps; - const state = {}; + const state: CheckboxState = {}; if (nextContext.__group__) { if ('selectedValue' in nextContext) { state.checked = isChecked(nextContext.selectedValue, nextProps.value); @@ -159,7 +117,11 @@ class Checkbox extends UIState { return props.disabled || ('disabled' in context && context.disabled); } - shouldComponentUpdate(nextProps, nextState, nextContext) { + shouldComponentUpdate( + nextProps: PrivateCheckboxProps, + nextState: CheckboxState, + nextContext: CheckboxContext + ) { const { shallowEqual } = obj; return ( !shallowEqual(this.props, nextProps) || @@ -168,15 +130,15 @@ class Checkbox extends UIState { ); } - onChange(e) { + onChange(event: React.ChangeEvent) { const { context, value } = this.props; - const checked = e.target.checked; + const checked = event.target.checked; if (this.disabled) { return; } if (context.__group__) { - context.onChange(value, e); + context.onChange(value, event); } else { if (!('checked' in this.props)) { this.setState({ @@ -189,7 +151,7 @@ class Checkbox extends UIState { indeterminate: false, }); } - this.props.onChange(checked, e); + this.props.onChange?.(checked, event); } } @@ -226,7 +188,7 @@ class Checkbox extends UIState { - {renderPreview(checked, this.props)} +
+ {renderPreview?.(checked, this.props)}
); } @@ -290,8 +256,8 @@ class Checkbox extends UIState {
{childInput} - {[label, children].map((item, i) => - [undefined, null].indexOf(item) === -1 ? ( + {[label, children].map((item: React.ReactNode | undefined | null, i) => + item !== undefined && item !== null ? ( {item} @@ -302,4 +268,6 @@ class Checkbox extends UIState { } } -export default ConfigProvider.config(withContext(polyfill(Checkbox))); +export default ConfigProvider.config( + withCheckboxContext(polyfill(Checkbox) as React.ComponentType) +); diff --git a/components/checkbox/index.d.ts b/components/checkbox/index.d.ts deleted file mode 100644 index 950a4df4fa..0000000000 --- a/components/checkbox/index.d.ts +++ /dev/null @@ -1,168 +0,0 @@ -/// - -import React from 'react'; -import { CommonProps } from '../util'; - -interface HTMLAttributesWeak extends React.HTMLAttributes { - defaultValue?: any; - onChange?: any; -} - -type data = { - value?: string | number | boolean; - label?: React.ReactNode; - disabled?: boolean; - [propName: string]: any; -}; - -export type CheckboxData = data; - -export interface GroupProps extends HTMLAttributesWeak, CommonProps { - /** - * 自定义类名 - */ - className?: string; - - /** - * 自定义内敛样式 - */ - style?: React.CSSProperties; - - /** - * 整体禁用 - */ - disabled?: boolean; - - /** - * 是否为预览态 - */ - isPreview?: boolean; - - renderPreview?: (checked: boolean, props: object) => React.ReactNode; - - /** - * 可选项列表, 数据项可为 String 或者 Object, 如 `['apple', 'pear', 'orange']` 或者 `[{value: 'apple', label: '苹果',}, {value: 'pear', label: '梨'}, {value: 'orange', label: '橙子'}]` - */ - dataSource?: Array | Array | Array; - - /** - * 被选中的值列表 - */ - value?: Array | Array | Array | string | number | boolean; - - /** - * 默认被选中的值列表 - */ - defaultValue?: Array | Array | Array | string | number | boolean; - - /** - * name - */ - name?: string; - - /** - * 通过子元素方式设置内部 checkbox - */ - children?: Array; - - /** - * 选中值改变时的事件 - */ - onChange?: (value: Array | Array | Array, e: any) => void; - - /** - * 子项目的排列方式 - * - hoz: 水平排列 (default) - * - ver: 垂直排列 - */ - direction?: 'hoz' | 'ver'; - itemDirection?: 'hoz' | 'ver'; -} - -export class Group extends React.Component {} -interface HTMLAttributesWeak extends React.HTMLAttributes { - onChange?: any; - onMouseEnter?: any; - onMouseLeave?: any; -} - -export interface CheckboxProps extends HTMLAttributesWeak, CommonProps { - /** - * 自定义类名 - */ - className?: string; - - /** - * checkbox id, 挂载在input上 - */ - id?: string; - - /** - * 自定义内敛样式 - */ - style?: React.CSSProperties; - - /** - * 选中状态 - */ - checked?: boolean; - - /** - * checkbox 的value - */ - value?: string | number | boolean; - - /** - * name - */ - name?: string; - - /** - * 默认选中状态 - */ - defaultChecked?: boolean; - - /** - * 禁用 - */ - disabled?: boolean; - - /** - * 通过属性配置label, - */ - label?: React.ReactNode; - - /** - * Checkbox 的中间状态,只会影响到 Checkbox 的样式,并不影响其 checked 属性 - */ - indeterminate?: boolean; - - /** - * Checkbox 的默认中间态,只会影响到 Checkbox 的样式,并不影响其 checked 属性 - */ - defaultIndeterminate?: boolean; - - /** - * 是否为预览态 - */ - isPreview?: boolean; - - /** - * 状态变化时触发的事件 - */ - onChange?: (checked: boolean, e: any) => void; - - /** - * 鼠标进入enter事件 - */ - onMouseEnter?: (e: React.MouseEvent) => void; - - /** - * 鼠标离开Leave事件 - */ - onMouseLeave?: (e: React.MouseEvent) => void; -} - -export default class Checkbox extends React.Component { - static Group: typeof Group; -} diff --git a/components/checkbox/index.jsx b/components/checkbox/index.jsx deleted file mode 100644 index 37c0c44f0b..0000000000 --- a/components/checkbox/index.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import Checkbox from './checkbox'; -import Group from './checkbox-group'; -import ConfigProvider from '../config-provider'; - -Checkbox.Group = ConfigProvider.config(Group, { - transform: /* istanbul ignore next */ (props, deprecated) => { - if ('itemDirection' in props) { - deprecated('itemDirection', 'direction', 'Checkbox'); - const { itemDirection, ...others } = props; - - props = { direction: itemDirection, ...others }; - } - - return props; - }, -}); - -export default Checkbox; diff --git a/components/checkbox/index.tsx b/components/checkbox/index.tsx new file mode 100644 index 0000000000..afb8076fe9 --- /dev/null +++ b/components/checkbox/index.tsx @@ -0,0 +1,23 @@ +import Checkbox from './checkbox'; +import Group from './checkbox-group'; +import ConfigProvider from '../config-provider'; +import { assignSubComponent } from '../util/component'; + +const CheckboxWithGroup = assignSubComponent(Checkbox, { + Group: ConfigProvider.config(Group, { + transform: /* istanbul ignore next */ (props, deprecated) => { + if ('itemDirection' in props) { + deprecated('itemDirection', 'direction', 'Checkbox'); + const { itemDirection, ...others } = props; + + props = { direction: itemDirection, ...others }; + } + + return props; + }, + }), +}); + +export type { CheckboxProps, GroupProps, CheckboxData, ValueItem } from './types'; + +export default CheckboxWithGroup; diff --git a/components/checkbox/mobile/index.jsx b/components/checkbox/mobile/index.tsx similarity index 100% rename from components/checkbox/mobile/index.jsx rename to components/checkbox/mobile/index.tsx diff --git a/components/checkbox/style.js b/components/checkbox/style.ts similarity index 100% rename from components/checkbox/style.js rename to components/checkbox/style.ts diff --git a/components/checkbox/types.ts b/components/checkbox/types.ts new file mode 100644 index 0000000000..c6cc61e414 --- /dev/null +++ b/components/checkbox/types.ts @@ -0,0 +1,237 @@ +import type * as React from 'react'; +import { type CommonProps } from '../util'; + +interface HTMLAttributesWeak + extends Omit, 'onChange' | 'defaultValue'> {} + +/** + * @api + */ +export type ValueItem = string | number | boolean; + +/** + * @api + */ +export type CheckboxData = { + value: ValueItem; + label?: React.ReactNode; + disabled?: boolean; + [propName: string]: unknown; +}; + +/** + * @api Checkbox.Group + */ +export interface GroupProps extends HTMLAttributesWeak, CommonProps { + /** + * 自定义类名 + * @en Custom className + */ + className?: string; + + /** + * 自定义内联样式 + * @en Custom inline style + */ + style?: React.CSSProperties; + + /** + * 整体禁用 + * @en Entirely disabled + */ + disabled?: boolean; + + /** + * 可选项列表 + * @en Option list + * @remarks + * 数据项可为 String 或者 Object, 如 `['apple', 'pear', 'orange']` 或者 `[{value: 'apple', label: '苹果',}, {value: 'pear', label: '梨'}, {value: 'orange', label: '橙子'}]` + * - + * Data item can be String or Object, such as `['apple', 'pear', 'orange']` or `[{value: 'apple', label: 'Apple',}, {value: 'pear', label: 'Pear'}, {value: 'orange', label: 'Orange'}]` + */ + dataSource?: Array | Array; + + /** + * 被选中的值列表 + * @en Selected value list + */ + value?: ValueItem[] | ValueItem; + + /** + * 默认被选中的值列表 + * @en Default selected value list + */ + defaultValue?: ValueItem[] | ValueItem; + + /** + * name + * @en name + */ + name?: string; + + /** + * 通过子元素方式设置内部 checkbox + * @en Set internal checkbox through child elements + */ + children?: React.ReactNode; + + /** + * 选中值改变时的事件 + * @en Selected value change event + */ + onChange?: (value: ValueItem[], e: React.ChangeEvent) => void; + + /** + * 子项目的排列方式 + * @en Arrangement of subitems + * @remarks + * hoz: 水平排列 (default), + * ver: 垂直排列 + * - + * hoz: Horizontal arrangement (default), + * ver: Vertical arrangement + */ + direction?: 'hoz' | 'ver'; + /** + * [废弃] 子项目的排列方式 + * @en [Deprecated] Arrangement of subitems + * @deprecated Use `direction` instead + */ + itemDirection?: 'hoz' | 'ver'; + + /** + * 是否为预览态 + * @en Is preview + * @version 1.19 + */ + isPreview?: boolean; + + /** + * 预览态模式下渲染的内容 + * @en Custom rendering content + * @version 1.19 + * @param previewed - 预览值 [\{label: '', value:''\},...] - Previewed value [\{label: '', value:''\},...] + * @param props - 所有传入的参数 - All props + * @returns 定制渲染内容 - Custom rendering content + */ + renderPreview?: ( + previewed: { + label: string | React.ReactNode; + value: string | React.ReactNode; + }[], + props: object + ) => React.ReactNode; +} + +export interface GroupState { + value: ValueItem[]; +} + +/** + * @api Checkbox + */ +export interface CheckboxProps extends HTMLAttributesWeak, CommonProps { + /** + * 自定义类名 + * @en className + */ + className?: string; + + /** + * checkbox id, 挂载在 input 上 + * @en Checkbox id, mounted on the input + */ + id?: string; + + /** + * 自定义内联样式 + * @en Custom inline style + */ + style?: React.CSSProperties; + + /** + * 选中状态 + * @en Checked status + */ + checked?: boolean; + + /** + * checkbox 的 value + * @en Checkbox value + */ + value?: ValueItem; + + /** + * name + * @en name + */ + name?: string; + + /** + * 默认选中状态 + * @en Default checked status + * @defaultValue false + */ + defaultChecked?: boolean; + + /** + * 禁用 + * @en Disabled + */ + disabled?: boolean; + + /** + * 通过属性配置 label, + * @en Label + */ + label?: React.ReactNode; + + /** + * Checkbox 的中间状态,只会影响到 Checkbox 的样式,并不影响其 checked 属性 + * @en Checkbox middle status, only affects the style of Checkbox, and does not affect its checked property + */ + indeterminate?: boolean; + + /** + * Checkbox 的默认中间态,只会影响到 Checkbox 的样式,并不影响其 checked 属性 + * @en Checkbox default middle status, only affects the style of Checkbox, and does not affect its checked property + * @defaultValue false + */ + defaultIndeterminate?: boolean; + + /** + * 状态变化时触发的事件 + * @en Status change event + */ + onChange?: (checked: boolean, e: React.ChangeEvent) => void; + + /** + * 鼠标进入 enter 事件 + * @en Mouse enter event + */ + onMouseEnter?: (e: React.MouseEvent) => void; + + /** + * 鼠标离开 Leave 事件 + * @en Mouse leave event + */ + onMouseLeave?: (e: React.MouseEvent) => void; + + /** + * 是否为预览态 + * @en Is preview + * @defaultValue false + * @version 1.19 + */ + isPreview?: boolean; + + /** + * 预览态模式下渲染的内容 + * @en Custom rendering content + * @version 1.19 + * @param checked - 是否选中 - Is checked + * @param props - 所有传入的参数 - All props + * @returns 定制渲染内容 - Custom rendering content + */ + renderPreview?: (checked: boolean, props: CheckboxProps) => React.ReactNode; +} diff --git a/components/checkbox/with-context.jsx b/components/checkbox/with-context.jsx deleted file mode 100644 index a5b5857bec..0000000000 --- a/components/checkbox/with-context.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -export default function withContext(Checkbox) { - return class WrappedComp extends React.Component { - static displayName = 'Checkbox'; - static contextTypes = { - onChange: PropTypes.func, - __group__: PropTypes.bool, - selectedValue: PropTypes.array, - disabled: PropTypes.bool, - prefix: PropTypes.string, - }; - - render() { - return ; - } - }; -} diff --git a/components/checkbox/with-context.tsx b/components/checkbox/with-context.tsx new file mode 100644 index 0000000000..261229f91d --- /dev/null +++ b/components/checkbox/with-context.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import * as PropTypes from 'prop-types'; +import { type PrivateCheckboxProps } from './checkbox'; +import { type CheckboxProps } from './types'; + +export interface CheckboxContext { + onChange: ( + value: string | number | boolean | undefined, + event: React.ChangeEvent + ) => void; + __group__: boolean; + selectedValue: CheckboxProps['value'][]; + disabled: boolean; + prefix: string; +} + +export default function withCheckboxContext( + Checkbox: React.ComponentType +): React.ComponentType { + return class WrappedComp extends React.Component { + static displayName = 'Checkbox'; + static contextTypes = { + onChange: PropTypes.func, + __group__: PropTypes.bool, + selectedValue: PropTypes.array, + disabled: PropTypes.bool, + prefix: PropTypes.string, + }; + + render() { + return ; + } + }; +} diff --git a/components/mixin-ui-state/index.tsx b/components/mixin-ui-state/index.tsx index bb0d056a9d..397e364cf7 100644 --- a/components/mixin-ui-state/index.tsx +++ b/components/mixin-ui-state/index.tsx @@ -1,15 +1,15 @@ import React, { Component, - HTMLAttributes, - DetailedReactHTMLElement, - ReactHTMLElement, - ReactSVGElement, - DOMElement, - DOMAttributes, - FunctionComponentElement, - CElement, - ComponentState, - ReactElement, + type HTMLAttributes, + type DetailedReactHTMLElement, + type ReactHTMLElement, + type ReactSVGElement, + type DOMElement, + type DOMAttributes, + type FunctionComponentElement, + type CElement, + type ComponentState, + type ReactElement, } from 'react'; import classnames from 'classnames'; import { func } from '../util'; @@ -26,8 +26,8 @@ type ClonableElement

= | ReactElement

; export interface UIStateProps { - onFocus?: () => unknown; - onBlur?: () => unknown; + onFocus?: (...rest: unknown[]) => unknown; + onBlur?: (...rest: unknown[]) => unknown; } export interface UIStateState {