Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions packages/components/checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { forwardRef } from 'react';

import forwardRefWithStatics from '../_util/forwardRefWithStatics';
import Check from '../common/Check';
Expand All @@ -10,11 +10,16 @@ import type { CheckProps } from '../common/Check';

export type CheckboxProps = Omit<CheckProps, 'type'>;

const CheckboxButton = forwardRef<HTMLLabelElement, CheckboxProps>((props, ref) => (
<Check ref={ref} type="checkbox-button" {...useDefaultProps<CheckboxProps>(props, checkboxDefaultProps)} />
));
CheckboxButton.displayName = 'CheckboxButton';

const Checkbox = forwardRefWithStatics(
(props: CheckboxProps, ref: React.Ref<HTMLLabelElement>) => (
<Check ref={ref} type="checkbox" {...useDefaultProps<CheckboxProps>(props, checkboxDefaultProps)} />
),
{ Group: CheckboxGroup },
{ Group: CheckboxGroup, Button: CheckboxButton },
);

Checkbox.displayName = 'Checkbox';
Expand Down
34 changes: 27 additions & 7 deletions packages/components/checkbox/CheckboxGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import classNames from 'classnames';
import { isNumber } from 'lodash-es';

import { CheckContext } from '../common/Check';
import useCommonClassName from '../hooks/useCommonClassName';
import useConfig from '../hooks/useConfig';
import useControlled from '../hooks/useControlled';
import useDefaultProps from '../hooks/useDefaultProps';
Expand Down Expand Up @@ -47,6 +48,7 @@ const getCheckboxValue = (v: CheckboxOption) => {
const CheckboxGroup = <T extends CheckboxGroupValue = CheckboxGroupValue>(props: CheckboxGroupProps<T>) => {
type ItemType = T[number];
const { classPrefix } = useConfig();
const { SIZE: sizeMap } = useCommonClassName();
const {
onChange,
disabled,
Expand All @@ -55,6 +57,10 @@ const CheckboxGroup = <T extends CheckboxGroupValue = CheckboxGroupValue>(props:
children,
max,
options = [],
size,
theme,
variant,
direction,
...resetProps
} = useDefaultProps<CheckboxGroupProps<T>>(props, checkboxGroupDefaultProps);

Expand All @@ -67,7 +73,8 @@ const CheckboxGroup = <T extends CheckboxGroupValue = CheckboxGroupValue>(props:
: React.Children.map(
children,
(child: ReactElement<CheckboxProps>) =>
(child?.type as any)?.displayName === Checkbox.displayName && child.props,
[Checkbox.displayName, Checkbox.Button.displayName].includes((child?.type as any)?.displayName) &&
child.props,
) || [];

const optionsWithoutCheckAll = intervalOptions.filter((t) => typeof t !== 'object' || !t.checkAll);
Expand Down Expand Up @@ -194,23 +201,36 @@ const CheckboxGroup = <T extends CheckboxGroupValue = CheckboxGroupValue>(props:
// options 和 children 的抉择,在未明确说明时,暂时以 options 优先
const useOptions = Array.isArray(options) && options.length !== 0;

// theme 为 button 时,使用按钮风格多选框渲染
const Comp = theme === 'button' ? Checkbox.Button : Checkbox;

return (
<div className={classNames(`${classPrefix}-checkbox-group`, className)} style={style}>
<div
className={classNames(`${classPrefix}-checkbox-group`, className, {
// 以下类名仅在 theme 为 button 时生效
[sizeMap[size]]: theme === 'button',
[`${classPrefix}-checkbox-group__outline`]: theme === 'button' && variant === 'outline',
[`${classPrefix}-checkbox-group--filled`]: theme === 'button' && variant?.includes('filled'),
[`${classPrefix}-checkbox-group--primary-filled`]: theme === 'button' && variant === 'primary-filled',
[`${classPrefix}-checkbox-group--vertical`]: theme === 'button' && direction === 'vertical',
})}
style={style}
>
<CheckContext.Provider value={context}>
{useOptions
? options.map((v: any, index) => {
switch (typeof v) {
case 'string':
return (
<Checkbox key={index} label={v} value={v}>
<Comp key={index} label={v} value={v}>
{v}
</Checkbox>
</Comp>
);
case 'number': {
return (
<Checkbox key={index} label={v} value={v}>
<Comp key={index} label={v} value={v}>
{String(v)}
</Checkbox>
</Comp>
);
}
case 'object': {
Expand All @@ -219,7 +239,7 @@ const CheckboxGroup = <T extends CheckboxGroupValue = CheckboxGroupValue>(props:
return vs.checkAll ? (
<Checkbox {...vs} key={`checkAll_${index}`} indeterminate={indeterminate} />
) : (
<Checkbox
<Comp
{...vs}
key={index}
disabled={vs.disabled || disabled}
Expand Down
81 changes: 81 additions & 0 deletions packages/components/checkbox/__tests__/checkbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,84 @@ describe('CheckboxGroup', () => {
expect(container.firstChild.lastChild).toHaveClass('t-is-disabled');
});
});

describe('CheckboxGroup button theme', () => {
test('theme button renders checkbox-button with options', () => {
const { container } = render(
<Checkbox.Group theme="button" defaultValue={['北京']} options={['北京', '上海']}></Checkbox.Group>,
);
const group = container.firstChild;
expect(group).toHaveClass('t-checkbox-group', 't-size-m', 't-checkbox-group--filled');
expect(group.firstChild).toHaveClass('t-checkbox-button', 't-is-checked');
expect(group.querySelector('.t-checkbox-button .t-checkbox__former')).not.toBeNull();
expect(group.querySelector('.t-checkbox-button .t-checkbox__label')).not.toBeNull();
});

test('theme button works with children', () => {
const { container } = render(
<Checkbox.Group theme="button" defaultValue={['gz']}>
<Checkbox.Button value="gz">广州</Checkbox.Button>
<Checkbox.Button value="sz">深圳</Checkbox.Button>
</Checkbox.Group>,
);
expect(container.firstChild.firstChild).toHaveClass('t-checkbox-button', 't-is-checked');
});

test('theme button works with max and children', () => {
const { container } = render(
<Checkbox.Group theme="button" max={2} defaultValue={['sz']}>
<Checkbox.Button value="gz">广州</Checkbox.Button>
<Checkbox.Button value="sz">深圳</Checkbox.Button>
<Checkbox.Button value="bj">北京</Checkbox.Button>
</Checkbox.Group>,
);
fireEvent.click(container.firstChild.firstChild);
expect(container.firstChild.lastChild).toHaveClass('t-is-disabled');
});

test('variant outline', () => {
const { container } = render(
<Checkbox.Group theme="button" variant="outline" options={['北京', '上海']}></Checkbox.Group>,
);
expect(container.firstChild).toHaveClass('t-checkbox-group__outline');
expect(container.firstChild).not.toHaveClass('t-checkbox-group--filled');
});

test('variant primary-filled', () => {
const { container } = render(
<Checkbox.Group theme="button" variant="primary-filled" options={['北京', '上海']}></Checkbox.Group>,
);
expect(container.firstChild).toHaveClass('t-checkbox-group--filled', 't-checkbox-group--primary-filled');
});

test('size', () => {
const { container } = render(
<Checkbox.Group theme="button" size="small" options={['北京', '上海']}></Checkbox.Group>,
);
expect(container.firstChild).toHaveClass('t-size-s');
});

test('direction vertical', () => {
const { container } = render(
<Checkbox.Group theme="button" direction="vertical" options={['北京', '上海']}></Checkbox.Group>,
);
expect(container.firstChild).toHaveClass('t-checkbox-group--vertical');
});

test('button theme does not affect default checkbox theme', () => {
const { container } = render(<Checkbox.Group options={['北京', '上海']}></Checkbox.Group>);
expect(container.firstChild).toHaveClass('t-checkbox-group');
expect(container.firstChild).not.toHaveClass('t-size-m');
expect(container.firstChild).not.toHaveClass('t-checkbox-group--filled');
expect(container.firstChild.firstChild).toHaveClass('t-checkbox');
});

test('button theme onChange', () => {
const fn = vi.fn();
const { container } = render(
<Checkbox.Group theme="button" defaultValue={['北京']} options={['北京', '上海']} onChange={fn}></Checkbox.Group>,
);
fireEvent.click(container.firstChild.lastChild);
expect(fn).toBeCalledTimes(1);
});
});
29 changes: 29 additions & 0 deletions packages/components/checkbox/_example/button-size.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { Checkbox, Space } from 'tdesign-react';

const options = [
{ value: 'view', label: '查看数据' },
{ value: 'edit', label: '编辑数据' },
{ value: 'export', label: '导出数据' },
];

export default function CheckboxButtonSizeExample() {
return (
<Space direction="vertical" size="large">
<Space direction="vertical">
<strong>大尺寸</strong>
<Checkbox.Group theme="button" size="large" defaultValue={['view']} options={options} />
</Space>

<Space direction="vertical">
<strong>中尺寸(默认)</strong>
<Checkbox.Group theme="button" size="medium" defaultValue={['view']} options={options} />
</Space>

<Space direction="vertical">
<strong>小尺寸</strong>
<Checkbox.Group theme="button" size="small" defaultValue={['view']} options={options} />
</Space>
</Space>
);
}
40 changes: 40 additions & 0 deletions packages/components/checkbox/_example/button-variant.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { Checkbox, Space } from 'tdesign-react';

const options = [
{ value: 'view', label: '查看数据' },
{ value: 'edit', label: '编辑数据' },
{ value: 'export', label: '导出数据' },
];

export default function CheckboxButtonVariantExample() {
return (
<Space direction="vertical" size="large">
<Space direction="vertical">
<strong>描边形态</strong>
<Checkbox.Group theme="button" variant="outline" defaultValue={['view', 'edit']} options={options} />
</Space>

<Space direction="vertical">
<strong>填充形态(默认)</strong>
<Checkbox.Group theme="button" variant="default-filled" defaultValue={['view', 'edit']} options={options} />
</Space>

<Space direction="vertical">
<strong>主色填充形态</strong>
<Checkbox.Group theme="button" variant="primary-filled" defaultValue={['view', 'edit']} options={options} />
</Space>

<Space direction="vertical">
<strong>纵向排列</strong>
<Checkbox.Group
theme="button"
variant="default-filled"
direction="vertical"
defaultValue={['view', 'edit']}
options={options}
/>
</Space>
</Space>
);
}
38 changes: 38 additions & 0 deletions packages/components/checkbox/_example/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useState } from 'react';
import { Checkbox, Space } from 'tdesign-react';

const options = [
{ value: 'view', label: '查看数据' },
{ value: 'edit', label: '编辑数据' },
{ value: 'export', label: '导出数据' },
{ value: 'approve', label: '允许审批' },
{ value: 'reject', label: '允许驳回' },
{ value: 'forward', label: '允许转发' },
{ value: 'delete', label: '删除数据', disabled: true },
];

export default function CheckboxButtonExample() {
const [permissions, setPermissions] = useState(['view', 'edit']);

return (
<Space direction="vertical" size="large">
<Space direction="vertical">
<strong>使用 options 渲染</strong>
<div>已分配权限: {permissions.length ? permissions.join('、') : '无'}</div>
<Checkbox.Group<string[]> theme="button" value={permissions} onChange={setPermissions} options={options} />
</Space>

<Space direction="vertical">
<strong>使用插槽渲染</strong>
<Checkbox.Group theme="button" defaultValue={['Beijing']}>
<Checkbox.Button value="Beijing">北京</Checkbox.Button>
<Checkbox.Button value="Shanghai">上海</Checkbox.Button>
<Checkbox.Button value="Guangzhou">广州</Checkbox.Button>
<Checkbox.Button value="Shenzhen" disabled>
深圳
</Checkbox.Button>
</Checkbox.Group>
</Space>
</Space>
);
}
22 changes: 22 additions & 0 deletions packages/components/checkbox/checkbox.en-US.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
:: BASE_DOC ::

### Button-style Checkbox Groups

Button-style checkbox groups arrange options as independent tag-like buttons horizontally or vertically, wrapping naturally when space is limited. Selected items use a filled background and work well for permission assignment, feature toggles, and other cases where users need to identify multiple selections quickly.

{{ button }}

### Button-style Checkbox Groups in Different Sizes

Button-style checkbox groups are available in large, medium (default), and small sizes.

{{ button-size }}

### Button-style Checkbox Groups in Different Variants

Button-style checkbox groups support outline, default-filled (default), and primary-filled variants, as well as vertical layout.

{{ button-variant }}

## API
### Checkbox Props

Expand Down Expand Up @@ -27,11 +45,15 @@ name | type | default | description | required
-- | -- | -- | -- | --
className | String | - | className of component | N
style | Object | - | CSS(Cascading Style Sheets),Typescript: `React.CSSProperties` | N
direction | String | horizontal | Checkbox option arrangement。options: horizontal/vertical | N
disabled | Boolean | - | \- | N
max | Number | undefined | \- | N
name | String | - | \- | N
options | Array | - | Typescript:`Array<CheckboxOption>` `type CheckboxOption = string \| number \| CheckboxOptionObj` `interface CheckboxOptionObj { label?: string \| TNode; value?: string \| number; disabled?: boolean; name?: string; checkAll?: true }`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts)。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/checkbox/type.ts) | N
readOnly | Boolean | undefined | \- | N
size | String | medium | works only when theme is button。options: small/medium/large。Typescript: `SizeEnum`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
theme | String | checkbox | Determine the style of checkbox when using options API。options: checkbox/button | N
value | Array | [] | Typescript:`T` `type CheckboxGroupValue = Array<string \| number \| boolean>`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/checkbox/type.ts) | N
defaultValue | Array | [] | uncontrolled property。Typescript:`T` `type CheckboxGroupValue = Array<string \| number \| boolean>`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/checkbox/type.ts) | N
variant | String | default-filled | works only when theme is button。options: outline/default-filled/primary-filled | N
onChange | Function | | Typescript:`(value: T, context: CheckboxGroupChangeContext) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/checkbox/type.ts)。<br/>`interface CheckboxGroupChangeContext { e: ChangeEvent; current: CheckboxOption \| TdCheckboxProps; type: 'check' \| 'uncheck' }`<br/> | N
21 changes: 21 additions & 0 deletions packages/components/checkbox/checkbox.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
:: BASE_DOC ::

### 按钮风格的多选框组

多选框组支持按钮风格,选项以独立标签式按钮横向或纵向排列,并可在空间不足时自然换行。选中项使用填充背景突出,适合权限分配、功能开关等需要快速识别多选结果的场景。

{{ button }}

### 不同尺寸的按钮多选框组

提供大、中(默认)、小三种按钮风格的多选框组。

{{ button-size }}

### 不同形态的按钮多选框组

提供描边、填充(默认)、主色填充三种形态,并支持纵向排列。

{{ button-variant }}

### 最多选中的数量

Expand Down Expand Up @@ -32,11 +49,15 @@ onClick | Function | | TS 类型:`(context: { e: MouseEvent }) => void`<br/>
-- | -- | -- | -- | --
className | String | - | 类名 | N
style | Object | - | 样式,TS 类型:`React.CSSProperties` | N
direction | String | horizontal | 多选框按钮排列方式。可选项:horizontal/vertical | N
disabled | Boolean | - | 是否禁用组件,默认为 false。CheckboxGroup.disabled 优先级低于 Checkbox.disabled | N
max | Number | undefined | 支持最多选中的数量 | N
name | String | - | 统一设置内部复选框 HTML 属性 | N
options | Array | - | 以配置形式设置子元素。示例1:`['北京', '上海']` ,示例2: `[{ label: '全选', checkAll: true }, { label: '上海', value: 'shanghai' }]`。checkAll 值为 true 表示当前选项为「全选选项」。TS 类型:`Array<CheckboxOption>` `type CheckboxOption = string \| number \| CheckboxOptionObj` `interface CheckboxOptionObj { label?: string \| TNode; value?: string \| number; disabled?: boolean; name?: string; checkAll?: true }`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts)。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/checkbox/type.ts) | N
readOnly | Boolean | undefined | 只读状态 | N
size | String | medium | 组件尺寸,仅在 theme 为 button 生效。可选项:small/medium/large。TS 类型:`SizeEnum`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
theme | String | checkbox | 用于在使用 options 方式渲染时决定组件的风格。可选项:checkbox/button | N
value | Array | [] | 选中值。TS 类型:`T` `type CheckboxGroupValue = Array<string \| number \| boolean>`。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/checkbox/type.ts) | N
defaultValue | Array | [] | 选中值。非受控属性。TS 类型:`T` `type CheckboxGroupValue = Array<string \| number \| boolean>`。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/checkbox/type.ts) | N
variant | String | default-filled | 多选组件按钮形式,仅在 theme 为 button 生效。可选项:outline/default-filled/primary-filled | N
onChange | Function | | TS 类型:`(value: T, context: CheckboxGroupChangeContext) => void`<br/>值变化时触发,`context.current` 表示当前变化的数据值,如果是全选则为空;`context.type` 表示引起选中数据变化的是选中或是取消选中;`context.option` 表示当前变化的数据项。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/checkbox/type.ts)。<br/>`interface CheckboxGroupChangeContext { e: ChangeEvent; current: CheckboxOption \| TdCheckboxProps; type: 'check' \| 'uncheck' }`<br/> | N
Loading
Loading