Skip to content

Commit

Permalink
feat(checkbox): rebuild checkbox & demo
Browse files Browse the repository at this point in the history
  • Loading branch information
byq1213 committed Sep 3, 2024
1 parent 7a81db2 commit 9b84ed9
Show file tree
Hide file tree
Showing 29 changed files with 596 additions and 381 deletions.
2 changes: 1 addition & 1 deletion site/mobile/mobile.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,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

0 comments on commit 9b84ed9

Please sign in to comment.