From 1cd3e80d034553c9d0a62d15cad654b1fa417f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B9=A4=E4=BB=99?= Date: Fri, 17 Nov 2023 15:46:24 +0800 Subject: [PATCH] feat: add `closeOnBack` to Popup, CenterPopup and other components built on them --- src/components/action-sheet/action-sheet.tsx | 3 +++ src/components/action-sheet/index.en.md | 1 + src/components/action-sheet/index.zh.md | 1 + .../calendar-picker/calendar-picker.en.md | 1 + .../calendar-picker/calendar-picker.tsx | 3 +++ .../calendar-picker/calendar-picker.zh.md | 1 + src/components/center-popup/center-popup.tsx | 17 ++++++++++++++++- src/components/date-picker/date-picker.tsx | 2 ++ src/components/dialog/dialog.tsx | 3 +++ src/components/dialog/index.en.md | 1 + src/components/dialog/index.zh.md | 1 + src/components/modal/demos/demo1.tsx | 1 + src/components/modal/index.en.md | 1 + src/components/modal/index.zh.md | 1 + src/components/modal/modal.tsx | 3 +++ src/components/picker/index.en.md | 1 + src/components/picker/index.zh.md | 1 + src/components/picker/picker.tsx | 3 +++ src/components/popup/index.en.md | 1 + src/components/popup/index.zh.md | 1 + src/components/popup/popup-base-props.ts | 2 ++ src/components/popup/popup.tsx | 17 ++++++++++++++++- 22 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/components/action-sheet/action-sheet.tsx b/src/components/action-sheet/action-sheet.tsx index 7c38ee6c2f..d32965eaa2 100644 --- a/src/components/action-sheet/action-sheet.tsx +++ b/src/components/action-sheet/action-sheet.tsx @@ -28,6 +28,7 @@ export type ActionSheetProps = { onClose?: () => void onMaskClick?: () => void closeOnAction?: boolean + closeOnBack?: boolean closeOnMaskClick?: boolean safeArea?: boolean popupClassName?: string @@ -45,6 +46,7 @@ const defaultProps = { actions: [], cancelText: '', closeOnAction: false, + closeOnBack: false, closeOnMaskClick: true, safeArea: true, destroyOnClose: false, @@ -64,6 +66,7 @@ export const ActionSheet: FC = p => { props.onClose?.() } }} + closeOnBack={props.closeOnBack} afterClose={props.afterClose} className={classNames(`${classPrefix}-popup`, props.popupClassName)} style={props.popupStyle} diff --git a/src/components/action-sheet/index.en.md b/src/components/action-sheet/index.en.md index 252a9e644d..c8871fbce7 100644 --- a/src/components/action-sheet/index.en.md +++ b/src/components/action-sheet/index.en.md @@ -20,6 +20,7 @@ Triggered by user operation, it provides a set of two or more options related to | afterClose | Triggered when completely closed | `() => void` | - | | cancelText | The text of the cancel button , if it is null, the cancel button would not be displayed | `ReactNode` | - | | closeOnAction | Whether to close after clicking the option | `boolean` | `false` | +| closeOnBack | Whether to close after clicking browser back button or use back swipe guesture | `boolean` | `false` | | closeOnMaskClick | Whether to close after clicking the mask layer | `boolean` | `true` | | destroyOnClose | Destroy `dom` when not visible | `boolean` | `false` | | forceRender | Render content forcely | `boolean` | `false` | diff --git a/src/components/action-sheet/index.zh.md b/src/components/action-sheet/index.zh.md index 4100c5f26d..98a159ad68 100644 --- a/src/components/action-sheet/index.zh.md +++ b/src/components/action-sheet/index.zh.md @@ -20,6 +20,7 @@ | afterClose | 完全关闭后触发 | `() => void` | - | | cancelText | 取消按钮文字,如果设置为空则不显示取消按钮 | `ReactNode` | - | | closeOnAction | 点击选项后是否关闭 | `boolean` | `false` | +| closeOnBack | 点击后退按钮或滑动后退手势是否关闭 | `boolean` | `false` | | closeOnMaskClick | 点击背景蒙层后是否关闭 | `boolean` | `true` | | destroyOnClose | 不可见时是否销毁 `DOM` 结构 | `boolean` | `false` | | forceRender | 强制渲染内容 | `boolean` | `false` | diff --git a/src/components/calendar-picker/calendar-picker.en.md b/src/components/calendar-picker/calendar-picker.en.md index 9ecb697a2f..4f7dd6394f 100644 --- a/src/components/calendar-picker/calendar-picker.en.md +++ b/src/components/calendar-picker/calendar-picker.en.md @@ -28,6 +28,7 @@ When the user needs to enter a date, he can select it in the pop-up date panel. | popupStyle | The custom style of the popup | `React.CSSProperties` | - | | popupBodyStyle | The custom style of the popup body | `React.CSSProperties` | - | | forceRender | Render content forcely,When ref is passed,always be true | `boolean` | `false` | +| closeOnBack | Whether to close after clicking browser back button or use back swipe guesture | `boolean` | `false` | | closeOnMaskClick | Whether to close after clicking the mask layer | `boolean` | `true` | | onClose | Triggered when closed | `() => void` | - | | onMaskClick | Triggered when the mask is clicked | `() => void` | - | diff --git a/src/components/calendar-picker/calendar-picker.tsx b/src/components/calendar-picker/calendar-picker.tsx index 808e408bdd..ca1cc743ae 100644 --- a/src/components/calendar-picker/calendar-picker.tsx +++ b/src/components/calendar-picker/calendar-picker.tsx @@ -22,6 +22,7 @@ export type CalendarPickerProps = CalendarPickerViewProps & { popupStyle?: React.CSSProperties popupBodyStyle?: React.CSSProperties forceRender?: true + closeOnBack?: boolean closeOnMaskClick?: boolean onClose?: () => void onMaskClick?: () => void @@ -64,6 +65,7 @@ export const CalendarPicker = forwardRef< popupStyle, popupBodyStyle, forceRender, + closeOnBack, closeOnMaskClick, onClose, onConfirm, @@ -117,6 +119,7 @@ export const CalendarPicker = forwardRef< onClose?.() } }} + closeOnBack={closeOnBack} > {footer} diff --git a/src/components/calendar-picker/calendar-picker.zh.md b/src/components/calendar-picker/calendar-picker.zh.md index 5b19f55524..1c9fc3d915 100644 --- a/src/components/calendar-picker/calendar-picker.zh.md +++ b/src/components/calendar-picker/calendar-picker.zh.md @@ -28,6 +28,7 @@ | popupStyle | 弹层容器的自定义样式 | `React.CSSProperties` | - | | popupBodyStyle | 容器内部自定义样式 | `React.CSSProperties` | - | | forceRender | 强制渲染内容,当传递 ref 时,会强制设为 true | `boolean` | `false` | +| closeOnBack | 点击后退按钮或滑动后退手势是否关闭 | `boolean` | `false` | | closeOnMaskClick | 点击背景蒙层后是否关闭 | `boolean` | `true` | | onClose | 关闭时触发 | `() => void` | - | | onMaskClick | 点击背景蒙层时触发 | `() => void` | - | diff --git a/src/components/center-popup/center-popup.tsx b/src/components/center-popup/center-popup.tsx index 193600c029..5b61ed6227 100644 --- a/src/components/center-popup/center-popup.tsx +++ b/src/components/center-popup/center-popup.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import type { FC, PropsWithChildren } from 'react' import { renderToContainer } from '../../utils/render-to-container' import Mask from '../mask' @@ -144,6 +144,21 @@ export const CenterPopup: FC = p => { ) ) + useEffect(() => { + if (maskVisible && props.closeOnBack) { + const handlePopState = (e: PopStateEvent) => { + // prevent history back + history.go(1) + // close popup instead + props.onClose?.() + } + window.addEventListener('popstate', handlePopState) + return () => { + window.removeEventListener('popstate', handlePopState) + } + } + }, [maskVisible]) + return ( ( value={pickerValue} onCancel={props.onCancel} onClose={props.onClose} + closeOnBack={props.closeOnBack} closeOnMaskClick={props.closeOnMaskClick} visible={props.visible} confirmText={props.confirmText} diff --git a/src/components/dialog/dialog.tsx b/src/components/dialog/dialog.tsx index 5f3a718542..449791b6d3 100644 --- a/src/components/dialog/dialog.tsx +++ b/src/components/dialog/dialog.tsx @@ -31,12 +31,14 @@ export type DialogProps = Pick< onAction?: (action: Action, index: number) => void | Promise onClose?: () => void closeOnAction?: boolean + closeOnBack?: boolean closeOnMaskClick?: boolean } & NativeProps const defaultProps = { actions: [] as Action[], closeOnAction: false, + closeOnBack: false, closeOnMaskClick: false, getContainer: null, } @@ -109,6 +111,7 @@ export const Dialog: FC = p => { } : undefined } + closeOnBack={props.closeOnBack} visible={props.visible} getContainer={props.getContainer} bodyStyle={props.bodyStyle} diff --git a/src/components/dialog/index.en.md b/src/components/dialog/index.en.md index 4a2375f800..1090e6fab9 100644 --- a/src/components/dialog/index.en.md +++ b/src/components/dialog/index.en.md @@ -26,6 +26,7 @@ When users need to process transactions, but do not want to jump to pages to int | bodyClassName | `Dialog` content class name | `string` | - | | bodyStyle | `Dialog` content style | `React.CSSProperties` | - | | closeOnAction | Whether to close after clicking the operation button | `boolean` | `false` | +| closeOnBack | Whether to close after clicking browser back button or use back swipe guesture | `boolean` | `false` | | closeOnMaskClick | Whether to support clicking the mask to close the dialog box | `boolean` | `false` | | content | The content of the Dialog | `React.ReactNode` | - | | destroyOnClose | Destroy `dom` when not visible | `boolean` | `false` | diff --git a/src/components/dialog/index.zh.md b/src/components/dialog/index.zh.md index c22ecbd005..80f1246cc0 100644 --- a/src/components/dialog/index.zh.md +++ b/src/components/dialog/index.zh.md @@ -26,6 +26,7 @@ | bodyClassName | `Dialog` 内容类名 | `string` | - | | bodyStyle | `Dialog` 内容样式 | `React.CSSProperties` | - | | closeOnAction | 点击操作按钮后后是否关闭 | `boolean` | `false` | +| closeOnBack | 点击后退按钮或滑动后退手势是否关闭 | `boolean` | `false` | | closeOnMaskClick | 是否支持点击遮罩关闭对话框 | `boolean` | `false` | | content | 对话框内容 | `React.ReactNode` | - | | destroyOnClose | 不可见时是否销毁 `DOM` 结构 | `boolean` | `false` | diff --git a/src/components/modal/demos/demo1.tsx b/src/components/modal/demos/demo1.tsx index 93ac353d1d..b6f27f841d 100644 --- a/src/components/modal/demos/demo1.tsx +++ b/src/components/modal/demos/demo1.tsx @@ -120,6 +120,7 @@ export default () => { Modal.show({ content: '点击遮罩关闭', closeOnMaskClick: true, + closeOnBack: true, }) }} > diff --git a/src/components/modal/index.en.md b/src/components/modal/index.en.md index 47549a6e3c..7cf3f4ee40 100644 --- a/src/components/modal/index.en.md +++ b/src/components/modal/index.en.md @@ -26,6 +26,7 @@ When users need to process transactions, but do not want to jump to pages to int | bodyClassName | `Modal` content class name | `string` | - | | bodyStyle | `Modal` content style | `React.CSSProperties` | - | | closeOnAction | Whether to close after clicking the operation button | `boolean` | `false` | +| closeOnBack | Whether to close after clicking browser back button or use back swipe guesture | `boolean` | `false` | | closeOnMaskClick | Whether to support clicking the mask to close the modal box | `boolean` | `false` | | content | The content of the Modal | `React.ReactNode` | - | | destroyOnClose | Destroy `dom` when not visible | `boolean` | `false` | diff --git a/src/components/modal/index.zh.md b/src/components/modal/index.zh.md index 75724b9e85..3ae7fe99e4 100644 --- a/src/components/modal/index.zh.md +++ b/src/components/modal/index.zh.md @@ -26,6 +26,7 @@ | bodyClassName | `Modal` 内容类名 | `string` | - | | bodyStyle | `Modal` 内容样式 | `React.CSSProperties` | - | | closeOnAction | 点击操作按钮后后是否关闭 | `boolean` | `false` | +| closeOnBack | 点击后退按钮或滑动后退手势是否关闭 | `boolean` | `false` | | closeOnMaskClick | 是否支持点击遮罩关闭弹窗 | `boolean` | `false` | | content | 弹窗内容 | `React.ReactNode` | - | | destroyOnClose | 不可见时是否销毁 `DOM` 结构 | `boolean` | `false` | diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx index b55b8b223a..a966a98403 100644 --- a/src/components/modal/modal.tsx +++ b/src/components/modal/modal.tsx @@ -32,6 +32,7 @@ export type ModalProps = Pick< onAction?: (action: Action, index: number) => void | Promise onClose?: () => void closeOnAction?: boolean + closeOnBack?: boolean closeOnMaskClick?: boolean showCloseButton?: boolean } & NativeProps @@ -39,6 +40,7 @@ export type ModalProps = Pick< const defaultProps = { actions: [] as Action[], closeOnAction: false, + closeOnBack: false, closeOnMaskClick: false, getContainer: null, } @@ -99,6 +101,7 @@ export const Modal: FC = p => { afterClose={props.afterClose} afterShow={props.afterShow} showCloseButton={props.showCloseButton} + closeOnBack={props.closeOnBack} closeOnMaskClick={props.closeOnMaskClick} onClose={props.onClose} visible={props.visible} diff --git a/src/components/picker/index.en.md b/src/components/picker/index.en.md index 6089776dda..94b25726c6 100644 --- a/src/components/picker/index.en.md +++ b/src/components/picker/index.en.md @@ -39,6 +39,7 @@ type PickerValueExtend = { | --- | --- | --- | --- | | cancelText | Text of the cancel button | `ReactNode` | `'取消'` | | children | Render function of the selected options | `(items: PickerColumnItem[], actions: PickerActions) => ReactNode` | - | +| closeOnBack | Whether to close after clicking browser back button or use back swipe guesture | `boolean` | `false` | | closeOnMaskClick | Whether to close after clicking the mask layer | `boolean` | `true` | | columns | Options to configure each column | `PickerColumn[] \| ((value: PickerValue[]) => PickerColumn[])` | - | | confirmText | Text of the ok button | `ReactNode` | `'确定'` | diff --git a/src/components/picker/index.zh.md b/src/components/picker/index.zh.md index 79876746cc..25183f81e1 100644 --- a/src/components/picker/index.zh.md +++ b/src/components/picker/index.zh.md @@ -39,6 +39,7 @@ type PickerValueExtend = { | --- | --- | --- | --- | | cancelText | 取消按钮的文字 | `ReactNode` | `'取消'` | | children | 所选项的渲染函数 | `(items: PickerColumnItem[], actions: PickerActions) => ReactNode` | - | +| closeOnBack | 点击后退按钮或滑动后退手势是否关闭 | `boolean` | `false` | | closeOnMaskClick | 点击背景蒙层后是否关闭 | `boolean` | `true` | | columns | 配置每一列的选项 | `PickerColumn[] \| ((value: PickerValue[]) => PickerColumn[])` | - | | confirmText | 确定按钮的文字 | `ReactNode` | `'确定'` | diff --git a/src/components/picker/picker.tsx b/src/components/picker/picker.tsx index 22ad6d2b8e..abf3ca7738 100644 --- a/src/components/picker/picker.tsx +++ b/src/components/picker/picker.tsx @@ -46,6 +46,7 @@ export type PickerProps = { onConfirm?: (value: PickerValue[], extend: PickerValueExtend) => void onCancel?: () => void onClose?: () => void + closeOnBack?: boolean closeOnMaskClick?: boolean visible?: boolean title?: ReactNode @@ -78,6 +79,7 @@ export type PickerProps = { const defaultProps = { defaultValue: [], + closeOnBack: false, closeOnMaskClick: true, renderLabel: defaultRenderLabel, destroyOnClose: false, @@ -205,6 +207,7 @@ export const Picker = memo( props.onCancel?.() setVisible(false) }} + closeOnBack={props.closeOnBack} getContainer={props.getContainer} destroyOnClose={props.destroyOnClose} afterShow={props.afterShow} diff --git a/src/components/popup/index.en.md b/src/components/popup/index.en.md index 33bc6e6cdd..b2a92f33a2 100644 --- a/src/components/popup/index.en.md +++ b/src/components/popup/index.en.md @@ -23,6 +23,7 @@ It is suitable for displaying pop-up windows, information prompts, selection inp | bodyClassName | Content section class name | `string` | - | | bodyStyle | Content section style | `React.CSSProperties` | - | | className | Container class name | `string` | - | +| closeOnBack | Whether to close after clicking browser back button or use back swipe guesture | `boolean` | `false` | | closeOnMaskClick | Whether to close after clicking the mask layer | `boolean` | `false` | | destroyOnClose | Destroy `dom` when not visible | `boolean` | `false` | | forceRender | Render content forcely | `boolean` | `false` | diff --git a/src/components/popup/index.zh.md b/src/components/popup/index.zh.md index 6c699b774c..f5a5fcc6be 100644 --- a/src/components/popup/index.zh.md +++ b/src/components/popup/index.zh.md @@ -23,6 +23,7 @@ | bodyClassName | 内容区域类名 | `string` | - | | bodyStyle | 内容区域样式 | `React.CSSProperties` | - | | className | 容器类名 | `string` | - | +| closeOnBack | 点击后退按钮或滑动后退手势是否关闭 | `boolean` | `false` | | closeOnMaskClick | 点击背景蒙层后是否关闭 | `boolean` | `false` | | destroyOnClose | 不可见时是否销毁 `DOM` 结构 | `boolean` | `false` | | forceRender | 强制渲染内容 | `boolean` | `false` | diff --git a/src/components/popup/popup-base-props.ts b/src/components/popup/popup-base-props.ts index dc3817366a..4c7d99f63c 100644 --- a/src/components/popup/popup-base-props.ts +++ b/src/components/popup/popup-base-props.ts @@ -9,6 +9,7 @@ export type PopupBaseProps = { afterShow?: () => void bodyClassName?: string bodyStyle?: CSSProperties + closeOnBack?: boolean closeOnMaskClick?: boolean destroyOnClose?: boolean disableBodyScroll?: boolean @@ -26,6 +27,7 @@ export type PopupBaseProps = { } export const defaultPopupBaseProps = { + closeOnBack: false, closeOnMaskClick: false, destroyOnClose: false, disableBodyScroll: true, diff --git a/src/components/popup/popup.tsx b/src/components/popup/popup.tsx index 45bb0570e9..9d14f1d410 100644 --- a/src/components/popup/popup.tsx +++ b/src/components/popup/popup.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames' -import React, { useState, useRef } from 'react' +import React, { useState, useRef, useEffect } from 'react' import type { FC, PropsWithChildren } from 'react' import { useIsomorphicLayoutEffect, useUnmountedRef } from 'ahooks' import { NativeProps, withNativeProps } from '../../utils/native-props' @@ -165,6 +165,21 @@ export const Popup: FC = p => { ) ) + useEffect(() => { + if (maskVisible && props.closeOnBack) { + const handlePopState = () => { + // prevent history back + history.go(1) + // close popup instead + props.onClose?.() + } + window.addEventListener('popstate', handlePopState) + return () => { + window.removeEventListener('popstate', handlePopState) + } + } + }, [maskVisible]) + return (