From d42520d25576b1b16a3e9f4ed0f7aea91bd42fd3 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Tue, 25 Mar 2025 16:05:23 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(switch):=20=E5=BC=82=E6=AD=A5=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=B1=95=E7=A4=BAloading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/packages/switch/demos/h5/demo2.tsx | 17 ++++++++--- src/packages/switch/demos/taro/demo2.tsx | 4 +-- src/packages/switch/demos/taro/demo6.tsx | 10 ++---- src/packages/switch/switch.scss | 5 +++ src/packages/switch/switch.taro.tsx | 39 +++++++++++++++--------- src/packages/switch/switch.tsx | 35 +++++++++++++-------- src/types/spec/switch/base.ts | 4 +-- src/types/spec/switch/h5.ts | 2 +- src/types/spec/switch/taro.ts | 4 +-- 9 files changed, 72 insertions(+), 48 deletions(-) diff --git a/src/packages/switch/demos/h5/demo2.tsx b/src/packages/switch/demos/h5/demo2.tsx index faf4eaeb7f..973eee37ec 100644 --- a/src/packages/switch/demos/h5/demo2.tsx +++ b/src/packages/switch/demos/h5/demo2.tsx @@ -4,17 +4,24 @@ import { Cell, Switch, Toast } from '@nutui/nutui-react' const Demo2 = () => { const [checkedAsync, setCheckedAsync] = useState(true) - const onChangeAsync = (value: boolean, event: any) => { + const mockRequest = (): Promise => { + return new Promise((resolve) => { + setTimeout(() => { + resolve() + }, 2000) + }) + } + + const onChangeAsync = async (value: boolean) => { Toast.show(`2秒后异步触发 ${value}`) - setTimeout(() => { - setCheckedAsync(value) - }, 2000) + await mockRequest() + setCheckedAsync(value) } return ( onChangeAsync(value, event)} + onChange={(value) => onChangeAsync(value)} /> ) diff --git a/src/packages/switch/demos/taro/demo2.tsx b/src/packages/switch/demos/taro/demo2.tsx index 33f793cd7a..97291ece49 100644 --- a/src/packages/switch/demos/taro/demo2.tsx +++ b/src/packages/switch/demos/taro/demo2.tsx @@ -6,7 +6,7 @@ const Demo2 = () => { const [value, setValue] = useState(false) const [showToast, setShowToast] = useState(false) - const onChangeAsync = (value: boolean, event: any) => { + const onChangeAsync = (value: boolean) => { setValue(value) setShowToast(true) setTimeout(() => { @@ -18,7 +18,7 @@ const Demo2 = () => { onChangeAsync(value, event)} + onChange={(value) => onChangeAsync(value)} /> { const [value, setValue] = useState(false) const [showToast, setShowToast] = useState(false) - const onChange = ( - value: boolean, - event: React.MouseEvent - ) => { + const onChange = (value: boolean) => { setValue(value) setShowToast(true) } return ( <> - onChange(value, event)} - /> + onChange(value)} /> > = (props) => { defaultValue: defaultChecked, }) + useEffect(() => { + changing && setChanging(false) + }, [value]) + + const [changing, setChanging] = useState(false) + const classes = () => { return classNames([ classPrefix, @@ -49,20 +56,16 @@ export const Switch: FunctionComponent> = (props) => { ]) } - const onClick = ( - event: React.MouseEvent | ITouchEvent - ) => { - if (disabled) return - onChange && onChange(!value, event) + const onClick = () => { + if (disabled || changing) return + if (props.onChange) { + setChanging(true) + props.onChange(!value) + } setValue(!value) } return ( - onClick(e)} - style={style} - {...rest} - > + > = (props) => { }, ])} > - {!value && !activeText && ( - + {changing ? ( + + ) : ( + <> + {!value && !activeText && ( + + )} + )} {activeText && ( diff --git a/src/packages/switch/switch.tsx b/src/packages/switch/switch.tsx index d252b294a9..9b1f32623a 100644 --- a/src/packages/switch/switch.tsx +++ b/src/packages/switch/switch.tsx @@ -1,5 +1,6 @@ -import React, { FunctionComponent } from 'react' +import React, { FunctionComponent, useEffect, useState } from 'react' import classNames from 'classnames' +import { Loading1 } from '@nutui/icons-react' import { ComponentDefaults } from '@/utils/typings' import { usePropsValue } from '@/hooks/use-props-value' import { useRtl } from '@/packages/configprovider' @@ -35,6 +36,12 @@ export const Switch: FunctionComponent> = (props) => { defaultValue: defaultChecked, }) + useEffect(() => { + changing && setChanging(false) + }, [value]) + + const [changing, setChanging] = useState(false) + const classes = () => { return classNames([ classPrefix, @@ -47,18 +54,16 @@ export const Switch: FunctionComponent> = (props) => { ]) } - const onClick = (event: React.MouseEvent) => { - if (disabled) return - onChange && onChange(!value, event) + const onClick = () => { + if (disabled || changing) return + if (props.onChange) { + setChanging(true) + props.onChange(!value) + } setValue(!value) } return ( -
onClick(e)} - style={style} - {...rest} - > +
> = (props) => { }, ])} > - {!value && !activeText && ( -
+ {changing ? ( + + ) : ( + <> + {!value && !activeText && ( +
+ )} + )}
{activeText && ( diff --git a/src/types/spec/switch/base.ts b/src/types/spec/switch/base.ts index 4ae77ee4a9..16a3ee2ab5 100644 --- a/src/types/spec/switch/base.ts +++ b/src/types/spec/switch/base.ts @@ -1,11 +1,11 @@ import { ReactNode } from 'react' import { BaseProps } from '../../base/props' -export interface BaseSwitch extends BaseProps { +export interface BaseSwitch extends BaseProps { checked: boolean defaultChecked: boolean disabled: boolean activeText: ReactNode inactiveText: ReactNode - onChange: (val: boolean, event: EVENT) => void + onChange: (val: boolean) => void } diff --git a/src/types/spec/switch/h5.ts b/src/types/spec/switch/h5.ts index e7612313ab..83982f8094 100644 --- a/src/types/spec/switch/h5.ts +++ b/src/types/spec/switch/h5.ts @@ -1,3 +1,3 @@ import { BaseSwitch } from '../switch/base' -export interface WebSwitchProps extends BaseSwitch {} +export interface WebSwitchProps extends BaseSwitch {} diff --git a/src/types/spec/switch/taro.ts b/src/types/spec/switch/taro.ts index 62a81eeb2c..2ede77b150 100644 --- a/src/types/spec/switch/taro.ts +++ b/src/types/spec/switch/taro.ts @@ -1,5 +1,3 @@ -import { ITouchEvent } from '@tarojs/components' import { BaseSwitch } from './base' -type UnionEvent = React.MouseEvent | ITouchEvent -export interface TaroSwitchProps extends BaseSwitch {} +export interface TaroSwitchProps extends BaseSwitch {} From 84cf7236ac5639abc08b945ccd3dbf9d9703314a Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Tue, 25 Mar 2025 17:01:28 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96demo,=E6=9B=B4?= =?UTF-8?q?=E8=B4=B4=E8=BF=91=E7=94=A8=E6=88=B7=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/packages/switch/demos/taro/demo2.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/packages/switch/demos/taro/demo2.tsx b/src/packages/switch/demos/taro/demo2.tsx index 97291ece49..58214bb5fe 100644 --- a/src/packages/switch/demos/taro/demo2.tsx +++ b/src/packages/switch/demos/taro/demo2.tsx @@ -5,13 +5,19 @@ const Demo2 = () => { const [checkedAsync, setCheckedAsync] = useState(true) const [value, setValue] = useState(false) const [showToast, setShowToast] = useState(false) + const mockRequest = (): Promise => { + return new Promise((resolve) => { + setTimeout(() => { + resolve() + }, 2000) + }) + } - const onChangeAsync = (value: boolean) => { + const onChangeAsync = async (value: boolean) => { setValue(value) setShowToast(true) - setTimeout(() => { - setCheckedAsync(value) - }, 2000) + await mockRequest() + setCheckedAsync(value) } return ( <> From 6f4a65e57bdb6266f11a543647641b78b6d4ef32 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Wed, 26 Mar 2025 10:22:12 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E5=8F=97=E6=8E=A7=20loading=20=E6=80=81?= =?UTF-8?q?=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/packages/switch/doc.en-US.md | 1 + src/packages/switch/doc.md | 1 + src/packages/switch/doc.taro.md | 1 + src/packages/switch/doc.zh-TW.md | 1 + src/packages/switch/switch.taro.tsx | 6 ++++-- src/packages/switch/switch.tsx | 6 ++++-- src/sites/sites-react/doc/docs/react/migrate-from-v2.md | 1 + src/sites/sites-react/doc/docs/taro/migrate-from-v2.md | 1 + src/types/spec/switch/base.ts | 1 + 9 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/packages/switch/doc.en-US.md b/src/packages/switch/doc.en-US.md index 77a2603de2..b4090e42d0 100644 --- a/src/packages/switch/doc.en-US.md +++ b/src/packages/switch/doc.en-US.md @@ -77,6 +77,7 @@ import { Switch } from '@nutui/nutui-react' | disabled | Disabled | `boolean` | `false` | | activeText | Text description when opening | `ReactNode` | `-` | | inactiveText | Text description when closed | `ReactNode` | `-` | +| loadingIcon | Controlled loading state icon | `ReactNode` | `` | | onChange | Trigger when switching switches | `onChange:(value: boolean, event: Event)` | `-` | ## Theming diff --git a/src/packages/switch/doc.md b/src/packages/switch/doc.md index 35f78c57b1..d699265bd2 100644 --- a/src/packages/switch/doc.md +++ b/src/packages/switch/doc.md @@ -77,6 +77,7 @@ import { Switch } from '@nutui/nutui-react' | disabled | 禁用状态 | `boolean` | `false` | | activeText | 打开时文字描述 | `ReactNode` | `-` | | inactiveText | 关闭时文字描述 | `ReactNode` | `-` | +| loadingIcon | 受控 loading 态图标 | `ReactNode` | `` | | onChange | 切换开关时触发 | `onChange:(value: boolean, event: Event)` | `-` | ## 主题定制 diff --git a/src/packages/switch/doc.taro.md b/src/packages/switch/doc.taro.md index 5ff506aa74..77a4646b2f 100644 --- a/src/packages/switch/doc.taro.md +++ b/src/packages/switch/doc.taro.md @@ -77,6 +77,7 @@ import { Switch } from '@nutui/nutui-react-taro' | disabled | 禁用状态 | `boolean` | `false` | | activeText | 打开时文字描述 | `ReactNode` | `-` | | inactiveText | 关闭时文字描述 | `ReactNode` | `-` | +| loadingIcon | 受控 loading 态图标 | `ReactNode` | `` | | onChange | 切换开关时触发 | `onChange:(value: boolean, event: Event)` | `-` | ## 主题定制 diff --git a/src/packages/switch/doc.zh-TW.md b/src/packages/switch/doc.zh-TW.md index e6ccd1f303..0f12e4205a 100644 --- a/src/packages/switch/doc.zh-TW.md +++ b/src/packages/switch/doc.zh-TW.md @@ -77,6 +77,7 @@ import { Switch } from '@nutui/nutui-react' | disabled | 禁用狀態 | `boolean` | `false` | | activeText | 打開時文字描述 | `ReactNode` | `-` | | inactiveText | 關閉時文字描述 | `ReactNode` | `-` | +| loadingIcon | 受控 loading 態圖標 | `ReactNode` | `` | | onChange | 切換開關時觸發 | `onChange:(value: boolean, event: Event)` | `-` | ## 主題定製 diff --git a/src/packages/switch/switch.taro.tsx b/src/packages/switch/switch.taro.tsx index 3265393eb6..a8883ec65e 100644 --- a/src/packages/switch/switch.taro.tsx +++ b/src/packages/switch/switch.taro.tsx @@ -13,6 +13,7 @@ const defaultProps = { disabled: false, activeText: '', inactiveText: '', + loadingIcon: , } as TaroSwitchProps export const Switch: FunctionComponent> = (props) => { const { @@ -21,6 +22,7 @@ export const Switch: FunctionComponent> = (props) => { disabled, activeText, inactiveText, + loadingIcon, className, style, onChange, @@ -80,8 +82,8 @@ export const Switch: FunctionComponent> = (props) => { }, ])} > - {changing ? ( - + {changing && loadingIcon ? ( + <>{loadingIcon} ) : ( <> {!value && !activeText && ( diff --git a/src/packages/switch/switch.tsx b/src/packages/switch/switch.tsx index 9b1f32623a..08e736eecf 100644 --- a/src/packages/switch/switch.tsx +++ b/src/packages/switch/switch.tsx @@ -11,6 +11,7 @@ const defaultProps = { disabled: false, activeText: '', inactiveText: '', + loadingIcon: , } as WebSwitchProps export const Switch: FunctionComponent> = (props) => { const { @@ -19,6 +20,7 @@ export const Switch: FunctionComponent> = (props) => { disabled, activeText, inactiveText, + loadingIcon, className, style, onChange, @@ -78,8 +80,8 @@ export const Switch: FunctionComponent> = (props) => { }, ])} > - {changing ? ( - + {changing && loadingIcon ? ( + <>{loadingIcon} ) : ( <> {!value && !activeText && ( diff --git a/src/sites/sites-react/doc/docs/react/migrate-from-v2.md b/src/sites/sites-react/doc/docs/react/migrate-from-v2.md index a9cdf02f2f..c83d7d3743 100644 --- a/src/sites/sites-react/doc/docs/react/migrate-from-v2.md +++ b/src/sites/sites-react/doc/docs/react/migrate-from-v2.md @@ -225,6 +225,7 @@ plugins: [ - `activeText` 属性类型更改为`ReactNode` - `inactiveText` 属性类型更改为 `ReactNode` +- 新增 `loadingIcon` 属性,受控 loading 态图标 [//]: # '#### Toast' diff --git a/src/sites/sites-react/doc/docs/taro/migrate-from-v2.md b/src/sites/sites-react/doc/docs/taro/migrate-from-v2.md index 4d673d5814..7211a296e3 100644 --- a/src/sites/sites-react/doc/docs/taro/migrate-from-v2.md +++ b/src/sites/sites-react/doc/docs/taro/migrate-from-v2.md @@ -228,6 +228,7 @@ plugins: [ - `activeText` 属性类型更改为`ReactNode` - `inactiveText` 属性类型更改为 `ReactNode` +- 新增 `loadingIcon` 属性,受控 loading 态图标 [//]: # '#### Toast' diff --git a/src/types/spec/switch/base.ts b/src/types/spec/switch/base.ts index 16a3ee2ab5..156821089c 100644 --- a/src/types/spec/switch/base.ts +++ b/src/types/spec/switch/base.ts @@ -7,5 +7,6 @@ export interface BaseSwitch extends BaseProps { disabled: boolean activeText: ReactNode inactiveText: ReactNode + loadingIcon: ReactNode onChange: (val: boolean) => void } From a5b0f57e2cb8539c4021c9bf14b2cd948d12325b Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Fri, 28 Mar 2025 10:03:55 +0800 Subject: [PATCH 4/4] feat: update test --- src/packages/switch/__test__/switch.spec.tsx | 29 ++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/packages/switch/__test__/switch.spec.tsx b/src/packages/switch/__test__/switch.spec.tsx index 37d1f0b62f..094ee8f66d 100644 --- a/src/packages/switch/__test__/switch.spec.tsx +++ b/src/packages/switch/__test__/switch.spec.tsx @@ -1,6 +1,7 @@ import * as React from 'react' import { render, fireEvent, waitFor, act } from '@testing-library/react' import '@testing-library/jest-dom' +import { Loading1 } from '@nutui/icons-react' import { Switch } from '../switch' test('activeText && checked && onChange && inactiveText && className && style test', async () => { @@ -49,8 +50,32 @@ test('activeText && checked && onChange && inactiveText && className && style te } }) -test('disabled test', () => { - render() +test('disabled test', async () => { + const { container } = render() const el = document.getElementsByClassName('nut-switch-disabled') expect(el.length > 0).toBe(true) + const buttonEl: Element | null = container.querySelector('.nut-switch-button') + if (buttonEl) { + await act(() => { + fireEvent.click(buttonEl) + }) + } +}) + +test('loadingIcon test', async () => { + const testFn = vi.fn() + const { container } = render( + } onChange={testFn} /> + ) + const el: Element | null = container.querySelector('.nut-switch-button') + if (el) { + await act(() => { + fireEvent.click(el) + }) + waitFor(() => { + // 异步 + const el = document.getElementsByClassName('.nut-icon') + expect(el.length > 0).toBe(true) + }) + } })