Skip to content

Commit

Permalink
Checkbox组件样式及功能对齐Vue (#504)
Browse files Browse the repository at this point in the history
* refactor(checkbox): type.ts定义对齐vue

* refactor(checkbox): checkbox及checkboxgroup结构对齐vue

re #459

* refactor(checkbox): 调整checkboxgroup结构

* refactor(checkbox): 类型及状态示例对齐vue

* refactor(checkbox): 样式及特殊样式示例对齐vue

* refactor(checkbox): 修复非受控选择bug

* refactor(checkbox): checkbox代码优化

* refactor(checkbox): 修复全选图标错误bug

* refactor(checkbox): 选中样式调整

* refactor(checkbox): 统一checked状态判断逻辑

* refactor(checkbox): 示例文件修改为tsx

* perf: 修改checkbox类名获取方式

* chore: 更新checkbox测试快照

* chore: 合并develop分支, 重新更新demo快照

* docs(checkbox): checkbox api文档对齐vue

* perf(checkbox): checkbox组件添加displayName属性

* docs(checkbox): 文档移除vue相关路径

* perf(checkbox): 根据review优化相关逻辑#504

* docs(checkbox): 根据tdesign-api重新生成文档及移除多余样式

* fix(checkboxGroup): 修复类型错误

* feat(checkbox): 新增支持readonly属性
  • Loading branch information
epoll-j authored Sep 18, 2024
1 parent c0f72e1 commit 335272c
Show file tree
Hide file tree
Showing 30 changed files with 10,988 additions and 7,447 deletions.
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
225 changes: 133 additions & 92 deletions src/checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import React, { useContext, useMemo, Ref, forwardRef, CSSProperties } from 'react';
import React, { useContext, useMemo, Ref, forwardRef } from 'react';
import classNames from 'classnames';
import { Icon } from 'tdesign-icons-react';
import {
CheckIcon,
MinusIcon,
CheckCircleFilledIcon,
CircleIcon,
MinusCircleFilledIcon,
MinusRectangleFilledIcon,
CheckRectangleFilledIcon,
} from 'tdesign-icons-react';
import { TdCheckboxProps } from './type';
import forwardRefWithStatics from '../_util/forwardRefWithStatics';
import CheckboxGroup from './CheckboxGroup';
import useConfig from '../_util/useConfig';
import useDefault from '../_util/useDefault';
import { parseContentTNode } from '../_util/parseTNode';
import { usePrefixClass } from '../hooks/useClass';
import useDefaultProps from '../hooks/useDefaultProps';
import { checkboxDefaultProps } from './defaultProps';

export interface CheckBoxProps extends TdCheckboxProps {
ref: Ref<HTMLLabelElement>;
Expand All @@ -17,130 +29,159 @@ 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 props = useDefaultProps(context ? context.inject(_props) : _props, checkboxDefaultProps);
const { classPrefix } = useConfig();
const classPrefixCheckBox = usePrefixClass('checkbox');
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,
contentDisabled,
block,
borderless,
checkAll,
disabled,
readonly,
} = props;
const [internalChecked, setInternalChecked] = useDefault(checked, defaultChecked, onChange);

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

const isChecked = useMemo(() => (checkAll ? checked : internalChecked), [internalChecked, checkAll, checked]);

const checkIcons = useMemo(() => {
if (Array.isArray(icon) && icon.length > 1) {
return icon.map((i) =>
typeof i === 'string' ? <img key={i} className={`${classPrefixCheckBox}__icon-image`} src={i}></img> : i,
);
}
if (internalChecked) {
return 'check-circle-filled';
return [<CheckCircleFilledIcon key="check" />, <CircleIcon key="uncheck" />];
}, [classPrefixCheckBox, icon]);

const checkIcon = useMemo(() => {
if (icon === 'circle' || icon === true) {
return indeterminate ? <MinusCircleFilledIcon /> : <CheckCircleFilledIcon />;
}
if (icon === 'rectangle') {
return indeterminate ? <MinusRectangleFilledIcon /> : <CheckRectangleFilledIcon />;
}
return 'circle';
}, [indeterminate, internalChecked]);
const renderIcon = () => {
if (icon === 'line') {
return indeterminate ? <MinusIcon /> : <CheckIcon />;
}
return null;
}, [icon, indeterminate]);

const renderIconArray = () => {
const className = `${classPrefixCheckBox}__icon-wrapper`;
if (Array.isArray(icon)) {
if (internalChecked) {
return icon[0];
}
return icon[1];
return parseContentTNode(isChecked ? checkIcons[0] : checkIcons[1], {
className,
});
}
if (isChecked) {
return parseContentTNode(checkIcon, {
className,
});
}
return (
<Icon
name={iconName}
className={classNames({
[`${classPrefix}-checkbox__checked__disable-icon`]: disabled,
})}
/>
<>
{(icon === 'circle' || icon === true || icon === 'rectangle') && (
<div
className={classNames({
[`${classPrefixCheckBox}__icon-circle`]: icon === true,
[`${classPrefixCheckBox}__icon-${icon}`]: typeof icon === 'string',
[`${classPrefixCheckBox}__icon-${icon}--disabled`]: disabled,
})}
></div>
)}
{icon === 'line' && <div className="placeholder"></div>}
</>
);
};
const labelStyle: CSSProperties = {
color: disabled ? '#dcdcdc' : 'inherit',
...getLimitRowStyle(maxLabelRow),
};

const renderIconNode = () => (
<div
className={classNames({
[`${classPrefixCheckBox}__icon`]: true,
[`${classPrefixCheckBox}__icon--${placement}`]: true,
[`${classPrefixCheckBox}__icon--checked`]: isChecked,
[`${classPrefixCheckBox}__icon--disabled`]: disabled,
})}
>
{renderIconArray()}
</div>
);

const handleClick = (e) => {
if (contentDisabled) {
if (contentDisabled || disabled || readonly) {
e.preventDefault();
return;
}

setInternalChecked(!internalChecked, { e })
setInternalChecked(!internalChecked, { e });
};

const renderCheckBoxContent = () => (
<div
className={classNames({
[`${classPrefixCheckBox}__content`]: true,
})}
onClick={(event) => {
event.stopPropagation();
handleClick(event);
}}
>
<div
className={classNames({
[`${classPrefixCheckBox}__title`]: true,
[`${classPrefixCheckBox}__title--checked`]: isChecked,
[`${classPrefixCheckBox}__title--disabled`]: disabled,
})}
style={{ WebkitLineClamp: maxLabelRow }}
>
{label}
</div>
<div
className={classNames({
[`${classPrefixCheckBox}__description`]: true,
[`${classPrefixCheckBox}__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>
<div className={checkboxClassName} onClick={handleClick}>
{icon && renderIconNode()}
{renderCheckBoxContent()}
{/* 下边框 */}
{/* { !borderless && <div className={`${classPrefix}-checkbox__border ${classPrefix}-checkbox__border--${align}`}></div>} */}
{ <div className={`${classPrefix}-checkbox__border ${classPrefix}-checkbox__border--${align}`}></div> }
{!borderless && (
<div className={`${classPrefixCheckBox}__border ${classPrefixCheckBox}__border--${placement}`}></div>
)}
</div>
</>
);
});

Checkbox.displayName = 'Checkbox';

export default forwardRefWithStatics(
(props: TdCheckboxProps, ref: Ref<HTMLInputElement>) => <Checkbox ref={ref} {...props} />,
{ Group: CheckboxGroup },
Expand Down
Loading

0 comments on commit 335272c

Please sign in to comment.