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
77 changes: 57 additions & 20 deletions packages/components/checkbox/CheckboxGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
import classNames from 'classnames';
import { isNumber } from 'lodash-es';
import { CheckContext, type CheckContextValue, type CheckProps } from '../common/Check';
import useConfig from '../hooks/useConfig';
import { CheckContext, CheckContextValue, CheckProps } from '../common/Check';
import { CheckboxGroupValue, CheckboxOption, CheckboxOptionObj, TdCheckboxGroupProps, TdCheckboxProps } from './type';
import { StyledProps } from '../common';
import useControlled from '../hooks/useControlled';
import useDefaultProps from '../hooks/useDefaultProps';
import Checkbox from './Checkbox';
import { checkboxGroupDefaultProps } from './defaultProps';
import useDefaultProps from '../hooks/useDefaultProps';

import type { StyledProps } from '../common';
import type { CheckboxProps } from './Checkbox';
import type {
CheckboxGroupValue,
CheckboxOption,
CheckboxOptionObj,
TdCheckboxGroupProps,
TdCheckboxProps,
} from './type';

export interface CheckboxGroupProps<T extends CheckboxGroupValue = CheckboxGroupValue>
extends TdCheckboxGroupProps<T>,
StyledProps {
children?: React.ReactNode;
}

// 将 checkBox 的 value 转换为 string|number
const getCheckboxValue = (v: CheckboxOption): string | number => {
const getCheckboxValue = (v: CheckboxOption) => {
switch (typeof v) {
case 'number':
return v as number;
Expand Down Expand Up @@ -48,6 +53,7 @@ const CheckboxGroup = <T extends CheckboxGroupValue = CheckboxGroupValue>(props:
children,
max,
options = [],
readonly,
} = useDefaultProps<CheckboxGroupProps<T>>(props, checkboxGroupDefaultProps);

// 去掉所有 checkAll 之后的 options
Expand All @@ -67,6 +73,22 @@ const CheckboxGroup = <T extends CheckboxGroupValue = CheckboxGroupValue>(props:
optionsWithoutCheckAllValues.push(vs);
});

const { enabledValues, disabledValues } = useMemo(() => {
const enabledValues = [];
const disabledValues = [];
optionsWithoutCheckAll.forEach((option) => {
const isOptionDisabled = typeof option === 'object' && (option.disabled || option.readonly);
const value = getCheckboxValue(option);

if (isOptionDisabled || disabled || readonly) {
disabledValues.push(value);
} else {
enabledValues.push(value);
}
});
return { enabledValues, disabledValues };
}, [optionsWithoutCheckAll, disabled, readonly]);

const [internalValue, setInternalValue] = useControlled(props, 'value', onChange);
const [localMax, setLocalMax] = useState(max);

Expand All @@ -78,16 +100,16 @@ const CheckboxGroup = <T extends CheckboxGroupValue = CheckboxGroupValue>(props:
}, [internalValue]);
const checkedSet = useMemo(() => getCheckedSet(), [getCheckedSet]);

// 用于决定全选状态的属性
const indeterminate = useMemo(() => {
const list = Array.from(checkedSet);
return list.length !== 0 && list.length !== optionsWithoutCheckAll.length;
}, [checkedSet, optionsWithoutCheckAll]);
const indeterminate = useMemo(() => {
const allValues = [...enabledValues, ...disabledValues];
const checkedCount = allValues.filter((value) => checkedSet.has(value)).length;
return checkedCount > 0 && checkedCount < allValues.length;
}, [checkedSet, enabledValues, disabledValues]);

const checkAllChecked = useMemo(() => {
const list = Array.from(checkedSet);
return list.length === optionsWithoutCheckAll.length;
}, [checkedSet, optionsWithoutCheckAll]);
const checkableValues = enabledValues.filter((value) => checkedSet.has(value));
return enabledValues.length > 0 && checkableValues.length === enabledValues.length;
}, [checkedSet, enabledValues]);

useEffect(() => {
if (!isNumber(max)) {
Expand Down Expand Up @@ -120,18 +142,28 @@ const CheckboxGroup = <T extends CheckboxGroupValue = CheckboxGroupValue>(props:
checked: checkProps.checkAll ? checkAllChecked : checkedSet.has(checkValue),
indeterminate: checkProps.checkAll ? indeterminate : checkProps.indeterminate,
disabled: checkProps.disabled || disabled || (checkedSet.size >= localMax && !checkedSet.has(checkValue)),
readonly: checkProps.readonly || readonly,
onChange(checked, { e }) {
if (typeof checkProps.onChange === 'function') {
checkProps.onChange(checked, { e });
}

const checkedSet = getCheckedSet();
// 全选时的逻辑处理

if (checkProps.checkAll) {
checkedSet.clear();
if (checked) {
optionsWithoutCheckAllValues.forEach((v) => {
checkedSet.add(v);
const checkedEnabledValues = enabledValues.filter((value) => checkedSet.has(value));
const allEnabledChecked = enabledValues.length > 0 && checkedEnabledValues.length === enabledValues.length;
if (!allEnabledChecked) {
enabledValues.forEach((value) => {
if (!checkedSet.has(value)) {
checkedSet.add(value);
}
});
} else {
enabledValues.forEach((value) => {
if (checkedSet.has(value)) {
checkedSet.delete(value);
}
});
}
} else if (checked) {
Expand Down Expand Up @@ -183,7 +215,12 @@ const CheckboxGroup = <T extends CheckboxGroupValue = CheckboxGroupValue>(props:
return vs.checkAll ? (
<Checkbox {...vs} key={`checkAll_${index}`} indeterminate={indeterminate} />
) : (
<Checkbox {...vs} key={index} disabled={vs.disabled || disabled} />
<Checkbox
{...vs}
key={index}
disabled={vs.disabled || disabled}
readonly={vs.readonly || readonly}
/>
);
}
default:
Expand Down
67 changes: 48 additions & 19 deletions packages/components/checkbox/_example/group.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React, { useState } from 'react';
import { Checkbox, Space } from 'tdesign-react';
import { Checkbox, Divider, Space } from 'tdesign-react';

const options = [
{
label: '全选',
checkAll: true,
},
{
value: '北京',
label: '北京',
Expand All @@ -13,34 +17,59 @@ const options = [
{
value: '广州',
label: '广州',
},
{
label: '全选',
checkAll: true as const,
disabled: true,
},
];

export default function CheckboxExample() {
const [disabled, setDisabled] = useState(false);
const [city, setCity] = useState(['北京']);
const [city, setCity] = useState(['广州']);
const [city2, setCity2] = useState(['上海']);

return (
<Space direction="vertical">
<div>选中值: {city.join('、')}</div>
<div>
<Checkbox checked={disabled} onChange={(value) => setDisabled(value)}>
禁用全部
</Checkbox>
</div>

<Checkbox.Group
disabled={disabled}
value={city}
<Checkbox
checked={disabled}
onChange={(value) => {
setCity(value);
setDisabled(value);
}}
options={options}
/>
>
禁用全部
</Checkbox>

<Space direction="vertical">
<strong>写法一:使用 options</strong>
<div>选中值: {city.join('、')}</div>
<Checkbox.Group
disabled={disabled}
value={city}
onChange={(value) => {
setCity(value);
}}
options={options}
/>
</Space>

<Divider />

<Space direction="vertical">
<strong>写法二:使用插槽</strong>
<div>选中值: {city2.join('、')}</div>
<Checkbox.Group
disabled={disabled}
value={city2}
onChange={(value) => {
setCity2(value);
}}
>
<Checkbox checkAll>全选</Checkbox>
<Checkbox value="北京">北京</Checkbox>
<Checkbox value="上海">上海</Checkbox>
<Checkbox value="广州" disabled>
广州
</Checkbox>
</Checkbox.Group>
</Space>
</Space>
);
}
2 changes: 2 additions & 0 deletions packages/components/checkbox/checkbox.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ 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
value | Array | [] | Typescript:`T` `type
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
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
1 change: 1 addition & 0 deletions packages/components/checkbox/checkbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ disabled | Boolean | - | 是否禁用组件,默认为 false。CheckboxGroup.di
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
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
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
9 changes: 7 additions & 2 deletions packages/components/checkbox/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ export interface TdCheckboxGroupProps<T = CheckboxGroupValue> {
* 以配置形式设置子元素。示例1:`['北京', '上海']` ,示例2: `[{ label: '全选', checkAll: true }, { label: '上海', value: 'shanghai' }]`。checkAll 值为 true 表示当前选项为「全选选项」
*/
options?: Array<CheckboxOption>;
/**
* 只读状态
*/
readonly?: boolean;
/**
* 选中值
* @default []
Expand All @@ -107,10 +111,11 @@ export type CheckboxOption = string | number | CheckboxOptionObj;

export interface CheckboxOptionObj {
label?: string | TNode;
value?: string | number;
value?: string | number | boolean;
disabled?: boolean;
readonly?: boolean;
name?: string;
checkAll?: true;
checkAll?: boolean;
}

export type CheckboxGroupValue = Array<string | number | boolean>;
Expand Down
21 changes: 12 additions & 9 deletions packages/components/form/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import React, { useRef, useImperativeHandle } from 'react';
import React, { useImperativeHandle, useRef } from 'react';
import classNames from 'classnames';
import useConfig from '../hooks/useConfig';
import noop from '../_util/noop';
import forwardRefWithStatics from '../_util/forwardRefWithStatics';
import type { TdFormProps } from './type';
import useInstance from './hooks/useInstance';
import useForm, { HOOK_MARK } from './hooks/useForm';
import useWatch from './hooks/useWatch';
import { StyledProps } from '../common';
import noop from '../_util/noop';
import useConfig from '../hooks/useConfig';
import useDefaultProps from '../hooks/useDefaultProps';
import FormContext from './FormContext';
import FormItem from './FormItem';
import FormList from './FormList';
import { formDefaultProps } from './defaultProps';
import useDefaultProps from '../hooks/useDefaultProps';
import useForm, { HOOK_MARK } from './hooks/useForm';
import useInstance from './hooks/useInstance';
import useWatch from './hooks/useWatch';

import type { StyledProps } from '../common';
import type { TdFormProps } from './type';

export interface FormProps extends TdFormProps, StyledProps {
children?: React.ReactNode;
Expand Down Expand Up @@ -40,6 +41,7 @@ const Form = forwardRefWithStatics(
errorMessage = globalFormConfig.errorMessage,
preventSubmitDefault,
disabled,
readonly,
children,
id,
onReset,
Expand Down Expand Up @@ -106,6 +108,7 @@ const Form = forwardRefWithStatics(
resetType,
rules,
disabled,
readonly,
formMapRef,
floatingFormDataRef,
onFormItemValueChange,
Expand Down
8 changes: 5 additions & 3 deletions packages/components/form/FormContext.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { TdFormProps, TdFormListProps, NamePath } from './type';
import { FormItemInstance } from './FormItem';
import { InternalFormInstance } from './hooks/interface';
import type { FormItemInstance } from './FormItem';
import type { InternalFormInstance } from './hooks/interface';
import type { NamePath, TdFormListProps, TdFormProps } from './type';

const FormContext = React.createContext<{
form?: InternalFormInstance;
Expand All @@ -17,6 +17,7 @@ const FormContext = React.createContext<{
showErrorMessage: TdFormProps['showErrorMessage'];
resetType: TdFormProps['resetType'];
disabled: TdFormProps['disabled'];
readonly: TdFormProps['readonly'];
rules: TdFormProps['rules'];
errorMessage: TdFormProps['errorMessage'];
formMapRef: React.RefObject<Map<any, React.RefObject<FormItemInstance>>>;
Expand All @@ -35,6 +36,7 @@ const FormContext = React.createContext<{
showErrorMessage: undefined,
resetType: 'empty',
disabled: undefined,
readonly: undefined,
rules: undefined,
errorMessage: undefined,
statusIcon: undefined,
Expand Down
7 changes: 5 additions & 2 deletions packages/components/form/FormItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
ErrorCircleFilledIcon as TdErrorCircleFilledIcon,
} from 'tdesign-icons-react';
import { flattenDeep, get, isEqual, isFunction, isObject, isString, merge, set, unset } from 'lodash-es';
import { StyledProps } from '../common';
import useConfig from '../hooks/useConfig';
import useDefaultProps from '../hooks/useDefaultProps';
import useGlobalIcon from '../hooks/useGlobalIcon';
Expand All @@ -17,6 +16,9 @@ import { parseMessage, validate as validateModal } from './formModel';
import { HOOK_MARK } from './hooks/useForm';
import useFormItemInitialData, { ctrlKeyMap } from './hooks/useFormItemInitialData';
import useFormItemStyle from './hooks/useFormItemStyle';
import { calcFieldValue } from './utils';

import type { StyledProps } from '../common';
import type {
FormInstanceFunctions,
FormItemValidateMessage,
Expand All @@ -25,7 +27,6 @@ import type {
TdFormItemProps,
ValueType,
} from './type';
import { calcFieldValue } from './utils';

export interface FormItemProps extends TdFormItemProps, StyledProps {
children?: React.ReactNode | React.ReactNode[] | ((form: FormInstanceFunctions) => React.ReactElement);
Expand Down Expand Up @@ -65,6 +66,7 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
labelWidth: labelWidthFromContext,
showErrorMessage: showErrorMessageFromContext,
disabled: disabledFromContext,
readonly: readonlyFromContext,
resetType: resetTypeFromContext,
rules: rulesFromContext,
statusIcon: statusIconFromContext,
Expand Down Expand Up @@ -521,6 +523,7 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
const childProps = child.props as any;
return React.cloneElement(child, {
disabled: disabledFromContext,
readonly: readonlyFromContext,
...childProps,
[ctrlKey]: formValue,
onChange: (value: any, ...args: any[]) => {
Expand Down
1 change: 1 addition & 0 deletions packages/components/form/form.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ labelAlign | String | right | options: left/right/top | N
labelWidth | String / Number | '100px' | \- | N
layout | String | vertical | options: vertical/inline | N
preventSubmitDefault | Boolean | true | \- | N
readonly | Boolean | undefined | \- | N
requiredMark | Boolean | true | \- | N
requiredMarkPosition | String | left | Display position of required symbols。options: left/right | N
resetType | String | empty | options: empty/initial | N
Expand Down
Loading
Loading