Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(checkbox): 重构checkbox 对齐vue版本 #508

Closed
wants to merge 3 commits into from
Closed
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
2 changes: 1 addition & 1 deletion site/mobile/mobile.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export default {
{
title: 'Checkbox 多选框',
name: 'checkbox',
component: () => import('tdesign-mobile-react/checkbox/_example/index.jsx'),
component: () => import('tdesign-mobile-react/checkbox/_example/index.tsx'),
},
{
title: 'Dialog 对话框',
Expand Down
18 changes: 18 additions & 0 deletions src/checkbox-group/checkbox-group.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
:: BASE_DOC ::

## API

### CheckboxGroup Props

name | type | default | description | required
-- | -- | -- | -- | --
className | String | - | className of component | N
style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N
borderless | Boolean | false | \- | N
disabled | Boolean | undefined | \- | N
max | Number | undefined | \- | N
name | String | - | \- | N
options | Array | - | Typescript:`Array<CheckboxOption>` `type CheckboxOption = string \| number \| CheckboxOptionObj` `interface CheckboxOptionObj extends TdCheckboxProps { text?: string; }`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/checkbox/type.ts) | N
value | Array | [] | Typescript:`T` `type CheckboxGroupValue = Array<string \| number \| boolean>`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/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-mobile-react/tree/develop/src/checkbox/type.ts) | N
onChange | Function | | Typescript:`(value: T, context: CheckboxGroupChangeContext) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/checkbox/type.ts)。<br/>`interface CheckboxGroupChangeContext { e: ChangeEvent; current: CheckboxOption \| TdCheckboxProps; option: CheckboxOption \| TdCheckboxProps; type: 'check' \| 'uncheck' }`<br/> | N
18 changes: 18 additions & 0 deletions src/checkbox-group/checkbox-group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
:: BASE_DOC ::

## API

### CheckboxGroup Props

名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
className | String | - | 类名 | N
style | Object | - | 样式,TS 类型:`React.CSSProperties` | N
borderless | Boolean | false | 是否开启无边框模式 | N
disabled | Boolean | undefined | 是否禁用组件。优先级:Form.disabled < 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 extends TdCheckboxProps { text?: string; }`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/checkbox/type.ts) | N
value | Array | [] | 选中值。TS 类型:`T` `type CheckboxGroupValue = Array<string \| number \| boolean>`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/checkbox/type.ts) | N
defaultValue | Array | [] | 选中值。非受控属性。TS 类型:`T` `type CheckboxGroupValue = Array<string \| number \| boolean>`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/checkbox/type.ts) | N
onChange | Function | | TS 类型:`(value: T, context: CheckboxGroupChangeContext) => void`<br/>值变化时触发,`context.current` 表示当前变化的数据值,如果是全选则为空;`context.type` 表示引起选中数据变化的是选中或是取消选中;`context.option` 表示当前变化的数据项;`context.current` 即将废弃,请勿使用。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/checkbox/type.ts)。<br/>`interface CheckboxGroupChangeContext { e: ChangeEvent; current: CheckboxOption \| TdCheckboxProps; option: CheckboxOption \| TdCheckboxProps; type: 'check' \| 'uncheck' }`<br/> | N
216 changes: 120 additions & 96 deletions src/checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useContext, useMemo, Ref, forwardRef, CSSProperties } from 'react';
import React, { useContext, Ref, forwardRef } from 'react';
import classNames from 'classnames';
import { Icon } from 'tdesign-icons-react';
import useDefaultProps from 'tdesign-mobile-react/hooks/useDefaultProps';
import { TdCheckboxProps } from './type';
import forwardRefWithStatics from '../_util/forwardRefWithStatics';
import CheckboxGroup from './CheckboxGroup';
import useConfig from '../_util/useConfig';
import useDefault from '../_util/useDefault';
import { checkboxDefaultProps } from './defaultProps';

export interface CheckBoxProps extends TdCheckboxProps {
ref: Ref<HTMLLabelElement>;
Expand All @@ -17,125 +19,147 @@ export interface CheckContextValue {

export const CheckContext = React.createContext<CheckContextValue>(null);

const getLimitRowStyle = (row: number): CSSProperties => ({
display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: row,
});

const Checkbox = forwardRef((_props: CheckBoxProps, ref: Ref<HTMLInputElement>) => {
const Checkbox = forwardRef((_props: CheckBoxProps) => {
const context = useContext(CheckContext);
const props = context ? context.inject(_props) : _props;
const { classPrefix } = useConfig();
const { classPrefix: prefix } = useConfig();
const {
name,
align = 'left',
placement,
content,
children,
disabled,
indeterminate,
label,
onChange,
checked,
defaultChecked = false,
readonly,
value,
maxLabelRow = 3,
maxContentRow = 5,
defaultChecked,
maxLabelRow,
maxContentRow,
icon,
contentDisabled = false,
// borderless = false,
} = props;
const [internalChecked, setInternalChecked] = useDefault(checked, defaultChecked, onChange);

const checkboxClassName = classNames(`${classPrefix}-checkbox`, {
[`${classPrefix}-is-checked`]: internalChecked || indeterminate,
[`${classPrefix}-is-disabled`]: disabled,
block,
borderless,
} = useDefaultProps(props, checkboxDefaultProps);
const [internalChecked, setInternalChecked] = useDefault<Boolean, any[]>(checked, defaultChecked, onChange);

const classPrefix = `${prefix}-checkbox`;

const checkboxClassName = classNames(`${classPrefix}`, {
[`${classPrefix}--checked`]: internalChecked,
[`${classPrefix}--block`]: block,
[`${classPrefix}--${placement}`]: true,
});
const iconName = useMemo(() => {
if (indeterminate) {
return 'minus-circle-filled';
}
if (internalChecked) {
return 'check-circle-filled';

const isIconArray = Array.isArray(icon);
const defaultCheckIcons = [<Icon key={0} name="check-circle-filled"></Icon>, <Icon key={1} name="circle"></Icon>];
const checkIcons = () => {
if (isIconArray && icon.length > 1) {
return icon.map((icon) =>
typeof icon === 'string' ? <img key={icon} src={icon} className={`${name}__icon-image`} alt="" /> : icon,
);
}
return 'circle';
}, [indeterminate, internalChecked]);
return defaultCheckIcons;
};

const checkedIcon = () => {
if (icon === 'circle' || props.icon === true) return indeterminate ? 'minus-circle-filled' : 'check-circle-filled';
if (icon === 'rectangle') return indeterminate ? 'minus-rectangle-filled' : 'check-rectangle-filled';
if (icon === 'line') return indeterminate ? 'minus' : 'check';
};

const renderIcon = () => {
if (Array.isArray(icon)) {
if (!icon) {
return null;
}
const renderIconCircle = () => {
const iconCircleClass = `${classPrefix}__icon-circle`;
const iconStringClass = `${classPrefix}__icon-${icon}`;
const iconDisabledClass = `${classPrefix}__icon-${icon}--disabled`;

return (
<div
className={classNames({
[iconCircleClass]: icon === true,
[iconStringClass]: typeof icon === 'string',
[iconDisabledClass]: disabled,
})}
/>
);
};

const renderCheckedIcon = () => <Icon name={checkedIcon()} className={`${classPrefix}__icon-wrapper`} />;

const renderLinePlaceholder = () => <div className="placeholder"></div>;

const renderIcon = () => {
if (Array.isArray(icon)) {
return checkIcons()[internalChecked ? 0 : 1];
}

if (internalChecked) {
return icon[0];
return renderCheckedIcon();
}
return icon[1];
}

if (icon === 'circle' || icon === true || icon === 'rectangle') {
return renderIconCircle();
}

if (icon === 'line') {
return renderLinePlaceholder();
}

return null;
};
return (
<Icon
name={iconName}
className={classNames({
[`${classPrefix}-checkbox__checked__disable-icon`]: disabled,
<div
className={classNames(`${classPrefix}__icon ${classPrefix}__icon--${placement}`, {
[`${classPrefix}__icon--checked`]: internalChecked,
[`${classPrefix}__icon--disabled`]: disabled,
})}
/>
>
{renderIcon()}
</div>
);
};
const labelStyle: CSSProperties = {
color: disabled ? '#dcdcdc' : 'inherit',
...getLimitRowStyle(maxLabelRow),
};
const handleClick = (e) => {
if (contentDisabled) {
e.preventDefault();
const handleChange = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
if (disabled) {
return;
}

setInternalChecked(!internalChecked, { e })
setInternalChecked(!internalChecked, { e });
e.stopPropagation();
};
const renderCheckBoxContent = () => (
<div
className={`${classPrefix}__content`}
onClick={(event) => {
event.stopPropagation();
handleChange(event);
}}
>
<div
className={classNames(`${classPrefix}__title`, {
[`${classPrefix}__title--checked`]: internalChecked,
[`${classPrefix}__title--disabled`]: disabled,
})}
style={{ WebkitLineClamp: maxLabelRow }}
>
{label}
</div>
<div
className={classNames(`${classPrefix}__description`, {
[`${classPrefix}__description--disabled`]: disabled,
})}
style={{ WebkitLineClamp: maxContentRow }}
>
{content}
</div>
</div>
);
return (
<>
<div className={checkboxClassName}>
<div className={`${classPrefix}-checkbox__content-wrap`}>
{ align ==='left' && <span className={`${classPrefix}-checkbox__icon-left`}>
<input
readOnly={readonly}
value={value}
ref={ref}
type="checkbox"
name={name}
className={`${classPrefix}-checkbox__original-left`}
disabled={disabled}
checked={internalChecked}
onClick={(e) => e.stopPropagation()}
onChange={(e) => setInternalChecked(e.currentTarget.checked, { e })}
/>
{renderIcon()}
</span>}
<span className={ `${classPrefix}-checkbox__label ${classPrefix}-checkbox__label-left`} onClick={handleClick}>
<span style={labelStyle}>
{label}
</span>
<span className={`${classPrefix}-checkbox__description`} style={getLimitRowStyle(maxContentRow)}>
{children || content}
</span>
</span>

{ align ==='right' && <span className={`${classPrefix}-checkbox__icon-right`}>
<input
readOnly={readonly}
value={value}
ref={ref}
type="checkbox"
name={name}
className={`${classPrefix}-checkbox__original-right`}
disabled={disabled}
checked={internalChecked}
onClick={(e) => e.stopPropagation()}
onChange={(e) => setInternalChecked(e.currentTarget.checked, { e })}
/>
{renderIcon()}
</span>}
</div>
{/* 下边框 */}
{/* { !borderless && <div className={`${classPrefix}-checkbox__border ${classPrefix}-checkbox__border--${align}`}></div>} */}
{ <div className={`${classPrefix}-checkbox__border ${classPrefix}-checkbox__border--${align}`}></div> }
<div className={checkboxClassName} onClick={handleChange}>
{renderIcon()}
{renderCheckBoxContent()}
{!borderless && <div className={`${classPrefix}__border ${classPrefix}__border--${placement}`} />}
</div>
</>
);
Expand Down
Loading
Loading