diff --git a/src/switch/Switch.tsx b/src/switch/Switch.tsx index 4a1e0986e5..397ff45179 100644 --- a/src/switch/Switch.tsx +++ b/src/switch/Switch.tsx @@ -18,7 +18,19 @@ export interface SwitchProps extends TdSwit const Switch = React.forwardRef((originalProps, ref) => { const { classPrefix } = useConfig(); const props = useDefaultProps>(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'; @@ -34,13 +46,30 @@ const Switch = React.forwardRef((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 = (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(() => { diff --git a/src/switch/__tests__/switch.test.tsx b/src/switch/__tests__/switch.test.tsx index ad1dbb60d0..a3b7df537b 100644 --- a/src/switch/__tests__/switch.test.tsx +++ b/src/switch/__tests__/switch.test.tsx @@ -46,4 +46,30 @@ describe('Switch 组件测试', () => { expect(logSpy).toBeCalledTimes(1); logSpy.mockRestore(); }); + test('beforeChange resolve', async () => { + const clickFn = vi.fn(); + const beforeChangeResolve = (): Promise => + new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, 80); + }); + const { container } = render(); + 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 => + new Promise((resolve, reject) => { + setTimeout(() => { + reject(); + }, 80); + }); + const { container } = render(); + fireEvent.click(container.firstChild); + await new Promise((resolve) => setTimeout(resolve, 100)); + expect(container.firstChild.classList.contains('t-is-checked')).toBeFalsy(); + }); }); diff --git a/src/switch/_example/beforeChange.tsx b/src/switch/_example/beforeChange.tsx new file mode 100644 index 0000000000..e98b3321f4 --- /dev/null +++ b/src/switch/_example/beforeChange.tsx @@ -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 => { + setLoadingResolve(true); + return new Promise((resolve) => { + setTimeout(() => { + setLoadingResolve(false); + resolve(true); + }, 1000); + }); + }; + + const beforeChangeReject = (): Promise => { + 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 ( + + + + + ); +} diff --git a/src/switch/switch.en-US.md b/src/switch/switch.en-US.md index 3a4eae7f08..aee25586f4 100644 --- a/src/switch/switch.en-US.md +++ b/src/switch/switch.en-US.md @@ -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` | N customValue | Array | - | Typescript:`Array` | N disabled | Boolean | - | \- | N label | TNode | [] | Typescript:`Array \| TNode<{ value: SwitchValue }>`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N diff --git a/src/switch/switch.md b/src/switch/switch.md index d6f2c84c04..ed0c83ce3a 100644 --- a/src/switch/switch.md +++ b/src/switch/switch.md @@ -7,6 +7,7 @@ -- | -- | -- | -- | -- className | String | - | 类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N +beforeChange | Function | - | Switch 切换状态前的回调方法,常用于需要发起异步请求的场景,返回值支持布尔和 Promise 类型,返回`false`或 Promise reject不继续执行change,否则则继续执行。。TS 类型:`() => boolean \| Promise` | N customValue | Array | - | 用于自定义开关的值,[打开时的值,关闭时的值]。默认为 [true, false]。示例:[1, 0]、['open', 'close']。TS 类型:`Array` | N disabled | Boolean | - | 是否禁用组件,默认为 false | N label | TNode | [] | 开关内容,[开启时内容,关闭时内容]。示例:['开', '关'] 或 (value) => value ? '开' : '关'。TS 类型:`Array \| TNode<{ value: SwitchValue }>`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N diff --git a/src/switch/type.ts b/src/switch/type.ts index f3940c8df1..3a1b76e8b6 100644 --- a/src/switch/type.ts +++ b/src/switch/type.ts @@ -7,6 +7,10 @@ import { TNode } from '../common'; import { MouseEvent } from 'react'; export interface TdSwitchProps { + /** + * Switch 切换状态前的回调方法,常用于需要发起异步请求的场景,返回值支持布尔和 Promise 类型,返回`false`或 Promise reject不继续执行change,否则则继续执行。 + */ + beforeChange?: () => boolean | Promise; /** * 用于自定义开关的值,[打开时的值,关闭时的值]。默认为 [true, false]。示例:[1, 0]、['open', 'close'] */ diff --git a/test/snap/__snapshots__/csr.test.jsx.snap b/test/snap/__snapshots__/csr.test.jsx.snap index ac50a00a77..355d3abef9 100644 --- a/test/snap/__snapshots__/csr.test.jsx.snap +++ b/test/snap/__snapshots__/csr.test.jsx.snap @@ -85497,6 +85497,48 @@ exports[`csr snapshot test > csr test src/switch/_example/base.tsx 1`] = ` `; +exports[`csr snapshot test > csr test src/switch/_example/beforeChange.tsx 1`] = ` +
+
+
+ +
+
+ +
+
+
+`; + exports[`csr snapshot test > csr test src/switch/_example/describe.tsx 1`] = `
ssr test src/swiper/_example/vertical.tsx 1`] = `"< exports[`ssr snapshot test > ssr test src/switch/_example/base.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/switch/_example/beforeChange.tsx 1`] = `"
"`; + exports[`ssr snapshot test > ssr test src/switch/_example/describe.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/switch/_example/size.tsx 1`] = `"
"`; diff --git a/test/snap/__snapshots__/ssr.test.jsx.snap b/test/snap/__snapshots__/ssr.test.jsx.snap index 152daaa315..f27a5b73ff 100644 --- a/test/snap/__snapshots__/ssr.test.jsx.snap +++ b/test/snap/__snapshots__/ssr.test.jsx.snap @@ -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`] = `"
"`; +exports[`ssr snapshot test > ssr test src/switch/_example/beforeChange.tsx 1`] = `"
"`; + exports[`ssr snapshot test > ssr test src/switch/_example/describe.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/switch/_example/size.tsx 1`] = `"
"`;