Skip to content

Commit

Permalink
feat(switch): add before-change API (#3167)
Browse files Browse the repository at this point in the history
* feat(switch): support beforeChange API

* test(switch): 更新测试快照
  • Loading branch information
centuryPark authored Nov 5, 2024
1 parent 4f54dc3 commit 291c790
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 4 deletions.
37 changes: 33 additions & 4 deletions src/switch/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,19 @@ export interface SwitchProps<T extends SwitchValue = SwitchValue> extends TdSwit
const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>((originalProps, ref) => {
const { classPrefix } = useConfig();
const props = useDefaultProps<SwitchProps<SwitchValue>>(originalProps, switchDefaultProps);
const { className, value, defaultValue, disabled, loading, size, label, customValue, onChange, ...restProps } = props;
const {
className,
value,
defaultValue,
disabled,
loading,
size,
label,
customValue,
onChange,
beforeChange,
...restProps
} = props;
const [activeValue = true, inactiveValue = false] = customValue || [];

const isControlled = typeof value !== 'undefined';
Expand All @@ -34,13 +46,30 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>((originalProps,
return parseTNode(label, { value });
}, [label, innerChecked, value]);

const handleChange = (e: React.MouseEvent) => {
!isControlled && setInnerChecked(!innerChecked);
const changedValue = !innerChecked ? activeValue : inactiveValue;
onChange?.(changedValue, { e });
};

const onInternalClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
if (disabled) {
return;
}
!isControlled && setInnerChecked(!innerChecked);
const changedValue = !innerChecked ? activeValue : inactiveValue;
onChange?.(changedValue, { e });

if (!beforeChange) {
handleChange(e);
return;
}
Promise.resolve(beforeChange())
.then((v) => {
if (v) {
handleChange(e);
}
})
.catch((e) => {
log.error('Switch', `some error occurred: ${e}`);
});
};

useEffect(() => {
Expand Down
26 changes: 26 additions & 0 deletions src/switch/__tests__/switch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,30 @@ describe('Switch 组件测试', () => {
expect(logSpy).toBeCalledTimes(1);
logSpy.mockRestore();
});
test('beforeChange resolve', async () => {
const clickFn = vi.fn();
const beforeChangeResolve = (): Promise<boolean> =>
new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 80);
});
const { container } = render(<Switch onChange={clickFn} beforeChange={beforeChangeResolve} />);
fireEvent.click(container.firstChild);
await new Promise((resolve) => setTimeout(resolve, 100));
expect(container.firstChild.classList.contains('t-is-checked')).toBeTruthy();
});
test('beforeChange reject', async () => {
const clickFn = vi.fn();
const beforeChangeResolve = (): Promise<boolean> =>
new Promise((resolve, reject) => {
setTimeout(() => {
reject();
}, 80);
});
const { container } = render(<Switch onChange={clickFn} beforeChange={beforeChangeResolve} />);
fireEvent.click(container.firstChild);
await new Promise((resolve) => setTimeout(resolve, 100));
expect(container.firstChild.classList.contains('t-is-checked')).toBeFalsy();
});
});
58 changes: 58 additions & 0 deletions src/switch/_example/beforeChange.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState } from 'react';
import { Switch, Space } from 'tdesign-react';

export default function SwitchBeforeChange() {
const [resolveChecked, setResolveChecked] = useState(true);
const [rejectedChecked, setRejectedChecked] = useState(true);
const [loadingResolve, setLoadingResolve] = useState(false);
const [loadingReject, setLoadingReject] = useState(false);

const beforeChangeResolve = (): Promise<boolean> => {
setLoadingResolve(true);
return new Promise((resolve) => {
setTimeout(() => {
setLoadingResolve(false);
resolve(true);
}, 1000);
});
};

const beforeChangeReject = (): Promise<boolean> => {
setLoadingReject(true);
return new Promise((_resolve, reject) => {
setTimeout(() => {
setLoadingReject(false);
reject(new Error('reject'));
}, 1000);
});
};

const onChangeResolve = (v: boolean) => {
console.log(v);
setResolveChecked(v);
};

const onChangeReject = (v: boolean) => {
console.log(v);
setRejectedChecked(v);
};

return (
<Space>
<Switch
size="large"
loading={loadingResolve}
onChange={onChangeResolve}
value={resolveChecked}
beforeChange={beforeChangeResolve}
/>
<Switch
size="large"
loading={loadingReject}
onChange={onChangeReject}
value={rejectedChecked}
beforeChange={beforeChangeReject}
/>
</Space>
);
}
1 change: 1 addition & 0 deletions src/switch/switch.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ name | type | default | description | required
-- | -- | -- | -- | --
className | String | - | 类名 | N
style | Object | - | 样式,Typescript:`React.CSSProperties` | N
beforeChange | Function | - | stop checked change。Typescript:`() => boolean \| Promise<boolean>` | N
customValue | Array | - | Typescript:`Array<SwitchValue>` | N
disabled | Boolean | - | \- | N
label | TNode | [] | Typescript:`Array<string \| TNode> \| TNode<{ value: SwitchValue }>`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
Expand Down
1 change: 1 addition & 0 deletions src/switch/switch.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
-- | -- | -- | -- | --
className | String | - | 类名 | N
style | Object | - | 样式,TS 类型:`React.CSSProperties` | N
beforeChange | Function | - | Switch 切换状态前的回调方法,常用于需要发起异步请求的场景,返回值支持布尔和 Promise 类型,返回`false`或 Promise reject不继续执行change,否则则继续执行。。TS 类型:`() => boolean \| Promise<boolean>` | N
customValue | Array | - | 用于自定义开关的值,[打开时的值,关闭时的值]。默认为 [true, false]。示例:[1, 0]['open', 'close']。TS 类型:`Array<SwitchValue>` | N
disabled | Boolean | - | 是否禁用组件,默认为 false | N
label | TNode | [] | 开关内容,[开启时内容,关闭时内容]。示例:['开', '关'] 或 (value) => value ? '开' : '关'。TS 类型:`Array<string \| TNode> \| TNode<{ value: SwitchValue }>`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
Expand Down
4 changes: 4 additions & 0 deletions src/switch/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { TNode } from '../common';
import { MouseEvent } from 'react';

export interface TdSwitchProps<T = SwitchValue> {
/**
* Switch 切换状态前的回调方法,常用于需要发起异步请求的场景,返回值支持布尔和 Promise 类型,返回`false`或 Promise reject不继续执行change,否则则继续执行。
*/
beforeChange?: () => boolean | Promise<boolean>;
/**
* 用于自定义开关的值,[打开时的值,关闭时的值]。默认为 [true, false]。示例:[1, 0]、['open', 'close']
*/
Expand Down
44 changes: 44 additions & 0 deletions test/snap/__snapshots__/csr.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -85497,6 +85497,48 @@ exports[`csr snapshot test > csr test src/switch/_example/base.tsx 1`] = `
</div>
`;

exports[`csr snapshot test > csr test src/switch/_example/beforeChange.tsx 1`] = `
<div>
<div
class="t-space t-space-horizontal"
style="gap: 16px;"
>
<div
class="t-space-item"
>
<button
class="t-switch t-is-checked t-size-l"
role="switch"
type="button"
>
<span
class="t-switch__handle"
/>
<div
class="t-switch__content"
/>
</button>
</div>
<div
class="t-space-item"
>
<button
class="t-switch t-is-checked t-size-l"
role="switch"
type="button"
>
<span
class="t-switch__handle"
/>
<div
class="t-switch__content"
/>
</button>
</div>
</div>
</div>
`;

exports[`csr snapshot test > csr test src/switch/_example/describe.tsx 1`] = `
<div>
<div
Expand Down Expand Up @@ -135197,6 +135239,8 @@ exports[`ssr snapshot test > ssr test src/swiper/_example/vertical.tsx 1`] = `"<

exports[`ssr snapshot test > ssr test src/switch/_example/base.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-horizontal"><div class="t-space-item"><button type="button" role="switch" class="t-switch t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div></div>"`;

exports[`ssr snapshot test > ssr test src/switch/_example/beforeChange.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-horizontal"><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div></div>"`;

exports[`ssr snapshot test > ssr test src/switch/_example/describe.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-vertical"><div class="t-space-item"><div style="gap:16px" class="t-space t-space-horizontal"><div class="t-space-item"><button type="button" role="switch" class="t-switch t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content">关</div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content">开</div></button></div></div></div><div class="t-space-item"><div style="gap:16px" class="t-space t-space-horizontal"><div class="t-space-item"><button type="button" role="switch" class="t-switch t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"><svg class="t-icon t-icon-close"><use xlink:href="#t-icon-close"></use></svg></div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"><svg class="t-icon t-icon-check"><use xlink:href="#t-icon-check"></use></svg></div></button></div></div></div></div>"`;

exports[`ssr snapshot test > ssr test src/switch/_example/size.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-horizontal"><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-size-m"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-size-s"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div></div>"`;
Expand Down
2 changes: 2 additions & 0 deletions test/snap/__snapshots__/ssr.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,8 @@ exports[`ssr snapshot test > ssr test src/swiper/_example/vertical.tsx 1`] = `"<

exports[`ssr snapshot test > ssr test src/switch/_example/base.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-horizontal"><div class="t-space-item"><button type="button" role="switch" class="t-switch t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div></div>"`;

exports[`ssr snapshot test > ssr test src/switch/_example/beforeChange.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-horizontal"><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div></div>"`;

exports[`ssr snapshot test > ssr test src/switch/_example/describe.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-vertical"><div class="t-space-item"><div style="gap:16px" class="t-space t-space-horizontal"><div class="t-space-item"><button type="button" role="switch" class="t-switch t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content">关</div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content">开</div></button></div></div></div><div class="t-space-item"><div style="gap:16px" class="t-space t-space-horizontal"><div class="t-space-item"><button type="button" role="switch" class="t-switch t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"><svg class="t-icon t-icon-close"><use xlink:href="#t-icon-close"></use></svg></div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"><svg class="t-icon t-icon-check"><use xlink:href="#t-icon-check"></use></svg></div></button></div></div></div></div>"`;

exports[`ssr snapshot test > ssr test src/switch/_example/size.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-horizontal"><div class="t-space-item"><button type="button" role="switch" class="t-switch t-is-checked t-size-l"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-size-m"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div><div class="t-space-item"><button type="button" role="switch" class="t-switch t-size-s"><span class="t-switch__handle"></span><div class="t-switch__content"></div></button></div></div>"`;
Expand Down

0 comments on commit 291c790

Please sign in to comment.