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

chore(Switch): complete refactoring #527

Merged
merged 3 commits into from
Oct 9, 2024
Merged
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 @@ -99,7 +99,7 @@ export default {
title: 'Switch 开关',
name: 'switch',
path: '/switch',
component: () => import('tdesign-mobile-react/switch/_example/index.jsx'),
component: () => import('tdesign-mobile-react/switch/_example/index.tsx'),
},
{
title: 'Cell 单元格',
Expand Down
2 changes: 2 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export type TreeOptionData<T = string | number> = {

export type SizeEnum = 'small' | 'medium' | 'large';

export type ShapeEnum = 'circle' | 'round';

export type HorizontalAlignEnum = 'left' | 'center' | 'right';

export type VerticalAlignEnum = 'top' | 'middle' | 'bottom';
Expand Down
99 changes: 54 additions & 45 deletions src/switch/Switch.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,84 @@
import React, { forwardRef, useMemo } from 'react';
import classNames from 'classnames';
import { TdSwitchProps } from './type';
import isArray from 'lodash/isArray';
import Loading from '../loading';
import { TdSwitchProps, SwitchValue } from './type';
import { switchDefaultProps } from './defaultProps';
import { StyledProps } from '../common';
import useConfig from '../_util/useConfig';
import useDefault from '../_util/useDefault';
import parseTNode from '../_util/parseTNode';
import { usePrefixClass } from '../hooks/useClass';
import useDefaultProps from '../hooks/useDefaultProps';

export interface SwitchProps extends TdSwitchProps, StyledProps {}
export interface SwitchProps<T extends SwitchValue = SwitchValue> extends TdSwitchProps<T>, StyledProps {}

const Switch = forwardRef<HTMLButtonElement, SwitchProps>((props, ref) => {
const { customValue, label, disabled, className, colors, style } = props;
const Switch = forwardRef<HTMLDivElement, SwitchProps>((originalProps, ref) => {
const props = useDefaultProps<SwitchProps<SwitchValue>>(originalProps, switchDefaultProps);
const { className, style, value, defaultValue, customValue, disabled, label, loading, size, onChange } = props;

const { classPrefix } = useConfig();
const switchClass = usePrefixClass('switch');

const switchBaseClassName = `${classPrefix}-switch`;
const [activeValue = true, inactiveValue = false] = customValue || [];

const [activeValue, inactiveValue] = customValue;

const [value, onChange] = useDefault(props.value, props.defaultValue, props.onChange);
const [innerValue, setInnerValue] = useDefault(value, defaultValue, onChange);

const checked = useMemo(() => {
if (typeof value !== 'undefined') {
if (Array.isArray(customValue) && !customValue.includes(value)) {
throw `${value} is not in customValue: ${JSON.stringify(customValue)}`;
if (typeof innerValue !== 'undefined') {
if (Array.isArray(customValue) && !customValue.includes(innerValue)) {
throw `${innerValue} is not in customValue: ${JSON.stringify(customValue)}`;
}
return value === customValue[0];
return innerValue === activeValue;
}
}, [value, customValue]);
}, [innerValue, customValue, activeValue]);

const switchClasses = classNames(
`${switchClass}`,
`${switchClass}--${props.size}`,
{
[`${switchClass}--checked`]: checked,
[`${switchClass}--disabled`]: disabled || loading,
},
className,
);

const onInternalClick = () => {
if (disabled) return;
const dotClasses = classNames(`${switchClass}__dot`, `${switchClass}__dot--${props.size}`, {
[`${switchClass}__dot--checked`]: checked,
[`${switchClass}__dot--plain`]: isArray(label) && label.length !== 2 && !loading,
});

onChange?.(!checked ? activeValue : inactiveValue);
};
const labelClasses = classNames(`${switchClass}__label`, `${switchClass}__label--${size}`, {
[`${switchClass}__label--checked`]: checked,
});

const renderSwitchText = (checked: boolean, label: SwitchProps['label']) => {
if (typeof label === 'function') {
return label({ value: checked ? activeValue : inactiveValue });
const handleToggle: React.MouseEventHandler<HTMLDivElement> = (e) => {
if (disabled || loading) {
return;
}
const changedValue = !checked ? activeValue : inactiveValue;
setInnerValue(changedValue, { e });
};

if (typeof label === 'string') return label;
const readerContent = React.useMemo<React.ReactNode>(() => {
if (loading) return <Loading inheritColor size="small" />;

if (Array.isArray(label)) {
const [activeContent, inactiveContent] = label;
const [activeContent = '', inactiveContent = ''] = label;
const content = checked ? activeContent : inactiveContent;

if (typeof content === 'function') return content();

return content;
return parseTNode(content, { value });
}

return null;
};

const switchClassName = classNames(switchBaseClassName, className, {
[`${classPrefix}-is-checked`]: checked,
[`${classPrefix}-is-disabled`]: disabled,
});
return parseTNode(label, { value });
}, [loading, label, checked, value]);

return (
<button ref={ref} className={switchClassName} style={style} onClick={onInternalClick}>
<span className={`${classPrefix}-switch__text`}>{renderSwitchText(checked, label)}</span>
<span
className={`${classPrefix}-switch__node`}
style={{ backgroundColor: checked ? colors?.[0] : colors?.[1] }}
></span>
</button>
<div ref={ref} className={switchClasses} style={style} onClick={handleToggle}>
<div className={dotClasses}>
<div className={labelClasses}>{readerContent}</div>
</div>
</div>
);
});

Switch.defaultProps = {
customValue: [true, false],
};
Switch.displayName = 'Switch';

export default Switch;
12 changes: 0 additions & 12 deletions src/switch/_example/base.jsx

This file was deleted.

18 changes: 18 additions & 0 deletions src/switch/_example/base.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React, { useState } from 'react';
import { Switch, Cell } from 'tdesign-mobile-react';
import type { SwitchValue } from 'tdesign-mobile-react';

export default function SwitchBase() {
const [checked, setChecked] = useState<SwitchValue>(1);

const onChange = (value: SwitchValue) => {
console.log('value', value);
setChecked(value);
};

return (
<>
<Cell title="基础开关" rightIcon={<Switch value={checked} customValue={[1, 0]} onChange={onChange} />}></Cell>
</>
);
}
10 changes: 10 additions & 0 deletions src/switch/_example/color.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import { Switch, Cell } from 'tdesign-mobile-react';

export default function SwitchColor() {
return (
<>
<Cell title="自定义颜色开关" rightIcon={<Switch defaultValue={true} className="custom-color" />}></Cell>
</>
);
}
15 changes: 0 additions & 15 deletions src/switch/_example/desc.jsx

This file was deleted.

13 changes: 0 additions & 13 deletions src/switch/_example/disabled.jsx

This file was deleted.

23 changes: 0 additions & 23 deletions src/switch/_example/index.jsx

This file was deleted.

33 changes: 33 additions & 0 deletions src/switch/_example/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import TDemoHeader from '../../../site/mobile/components/DemoHeader';
import TDemoBlock from '../../../site/mobile/components/DemoBlock';
import Base from './base';
import Color from './color';
import Label from './label';
import Status from './status';
import Size from './size';

import './style/index.less';

export default function () {
return (
<div className="tdesign-mobile-demo">
<TDemoHeader title="Switch 开关" summary="开关用于切换当个设置项的状态,开启、关闭为两个互斥的操作" />
<TDemoBlock title="01 组件类型" summary="基础开关">
<Base />
</TDemoBlock>
<TDemoBlock summary="带描述开关">
<Label />
</TDemoBlock>
<TDemoBlock summary="自定义颜色开关">
<Color />
</TDemoBlock>
<TDemoBlock title="02 状态" summary="加载状态">
<Status />
</TDemoBlock>
<TDemoBlock title="03 组件样式" summary="开关尺寸">
<Size />
</TDemoBlock>
</div>
);
}
28 changes: 28 additions & 0 deletions src/switch/_example/label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { useState } from 'react';
import { Switch, Cell } from 'tdesign-mobile-react';
import { Icon } from 'tdesign-icons-react';

export default function SwitchLabel() {
const [checked, setChecked] = useState(true);

const onChange = (value: boolean) => {
console.log('value', value);
setChecked(value);
};

const renderActiveContent = () => <Icon name="check" />;
const renderInactiveContent = () => <Icon name="close" />;

return (
<>
<Cell
title="带文字开关"
rightIcon={<Switch value={checked} label={({ value }) => (value ? '开' : '关')} onChange={onChange} />}
></Cell>
<Cell
title="带图标开关"
rightIcon={<Switch defaultValue label={[renderActiveContent(), renderInactiveContent()]} />}
></Cell>
</>
);
}
12 changes: 12 additions & 0 deletions src/switch/_example/size.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { Switch, Cell } from 'tdesign-mobile-react';

export default function SwitchSize() {
return (
<>
<Cell title="大尺寸 32" rightIcon={<Switch defaultValue size="large" />}></Cell>
<Cell title="中尺寸 28" rightIcon={<Switch defaultValue />}></Cell>
<Cell title="小尺寸 24" rightIcon={<Switch defaultValue size="small" />}></Cell>
</>
);
}
16 changes: 16 additions & 0 deletions src/switch/_example/status.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import { Switch, Cell } from 'tdesign-mobile-react';

export default function SwitchStatus() {
return (
<>
<Cell title="加载状态" rightIcon={<Switch loading />}></Cell>
<Cell title="开关开启禁用" note={<Switch defaultValue loading />}></Cell>

<div className="demo__title">禁用状态</div>

<Cell title="禁用状态" note={<Switch disabled />}></Cell>
<Cell title="禁用状态" note={<Switch disabled value={true} />}></Cell>
</>
);
}
14 changes: 14 additions & 0 deletions src/switch/_example/style/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.custom-color {
--td-switch-checked-color: #00a870;
}

.t-cell + .t-cell {
margin-top: 16px;
}

.demo__title {
margin: 24px 16px 16px;
color: var(--td-text-color-secondary, rgba(0, 0, 0, 0.6));
font-size: 14px;
line-height: 22px;
}
7 changes: 7 additions & 0 deletions src/switch/defaultProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */

import { TdSwitchProps } from './type';

export const switchDefaultProps: TdSwitchProps = { disabled: undefined, label: [], loading: false, size: 'medium' };
2 changes: 1 addition & 1 deletion src/switch/style/index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import '../../_common/style/mobile/components/switch/_index.less';
import '../../_common/style/mobile/components/switch/v2/_index.less';
19 changes: 19 additions & 0 deletions src/switch/switch.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
:: BASE_DOC ::

## API

### Switch Props

name | type | default | description | required
-- | -- | -- | -- | --
className | String | - | className of component | N
style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N
colors | Array | - | `deprecated`。Typescript:`string[]` | N
customValue | Array | - | Typescript:`Array<SwitchValue>` | N
disabled | Boolean | undefined | \- | N
label | TNode | [] | Typescript:`Array<string \| TNode> \| TNode<{ value: SwitchValue }>`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
loading | Boolean | false | \- | N
size | String | medium | options: small/medium/large | N
value | String / Number / Boolean | - | Typescript:`T` `type SwitchValue = string \| number \| boolean`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/switch/type.ts) | N
defaultValue | String / Number / Boolean | - | uncontrolled property。Typescript:`T` `type SwitchValue = string \| number \| boolean`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/switch/type.ts) | N
onChange | Function | | Typescript:`(value: T, context: { e: MouseEvent }) => void`<br/> | N
Loading
Loading