From 59553753b737601592094d9e004e63e68e0c7c45 Mon Sep 17 00:00:00 2001 From: jaywcjlove <398188662@qq.com> Date: Fri, 22 May 2020 17:24:36 +0800 Subject: [PATCH] feat: Add MaskLayer component. --- components/MaskLayer/README.md | 63 ++++++++++++++++ components/MaskLayer/index.tsx | 134 +++++++++++++++++++++++++++++++++ components/index.tsx | 2 + components/utils/index.ts | 1 + src/Home.tsx | 2 +- src/routes.tsx | 8 ++ src/routes/MaskLayer/index.tsx | 72 ++++++++++++++++++ src/routes/Modal/index.tsx | 3 +- 8 files changed, 283 insertions(+), 2 deletions(-) create mode 100644 components/MaskLayer/README.md create mode 100644 components/MaskLayer/index.tsx create mode 100644 src/routes/MaskLayer/index.tsx diff --git a/components/MaskLayer/README.md b/components/MaskLayer/README.md new file mode 100644 index 000000000..1600fd111 --- /dev/null +++ b/components/MaskLayer/README.md @@ -0,0 +1,63 @@ +MaskLayer 遮罩层 +--- + +用于模态对话框,中的遮罩层。 + +### 基础示例 + + +```jsx +import React, { Component, Fragment } from 'react'; +import { View, Text, Alert, SafeAreaView } from 'react-native'; +import { Modal, Button, MaskLayer } from '@uiw/react-native'; + +export default () => { + const [visible, setVisible] = useState(true); + return ( + + { + setVisible(false); + }}> + 展示内容 + + + + ); +} +``` + + +## Props + +继承原生 Modal 属性 [`ModalProps`](https://facebook.github.io/react-native/docs/modal.html#props) + +```typescript +interface MaskLayerProps extends RNModalProps { + /** + * 遮罩层是否禁止点击 + * defult: `true` + */ + maskClosable?: boolean; + /** + * 是否隐藏 + */ + visible?: boolean; + /** + * 遮罩层透明度 + * defult: `0.6` + */ + opacity?: number; + /** + * 隐藏消除回调事件 + */ + onDismiss?: () => void; + children?: JSX.Element; +} +``` diff --git a/components/MaskLayer/index.tsx b/components/MaskLayer/index.tsx new file mode 100644 index 000000000..80ef09c36 --- /dev/null +++ b/components/MaskLayer/index.tsx @@ -0,0 +1,134 @@ +import React, {useState, useMemo} from 'react'; +import { + Modal, + ModalProps as RNModalProps, + Animated, + TouchableOpacity, + StyleSheet, +} from 'react-native'; +import {usePrevious} from '../utils'; + +const styles = StyleSheet.create({ + position: { + position: 'absolute', + backgroundColor: 'transparent', + top: 0, + bottom: 0, + left: 0, + right: 0, + zIndex: 9998, + }, + backdrop: { + backgroundColor: '#000', + }, + content: { + backgroundColor: '#fff', + position: 'absolute', + }, +}); + +export interface MaskLayerProps extends RNModalProps { + /** + * 遮罩层是否禁止点击 + * defult: `true` + */ + maskClosable?: boolean; + /** + * 是否隐藏 + */ + visible?: boolean; + /** + * 遮罩层透明度 + * defult: `0.6` + */ + opacity?: number; + /** + * 隐藏消除回调事件 + */ + onDismiss?: () => void; + animatedParallelShow?: Animated.CompositeAnimation[]; + animatedParallelHide?: Animated.CompositeAnimation[]; + children?: JSX.Element; +} + +export default (props: MaskLayerProps = {}) => { + const { + maskClosable = true, + children, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + visible: _vis, + opacity = 0.6, + onDismiss, + animatedParallelShow = [], + animatedParallelHide = [], + ...otherProps + } = props; + const [visible, setVisible] = useState(!!props.visible); + const preVisible = usePrevious(props.visible); + const [visibleModal, setVisibleModal] = useState(false); + const [bgOpacity] = useState(new Animated.Value(0)); + useMemo(() => { + if (preVisible !== props.visible && props.visible) { + setVisible(!!props.visible); + setVisibleModal(false); + Animated.parallel([ + Animated.spring(bgOpacity, { + toValue: opacity, + overshootClamping: true, + useNativeDriver: true, + }), + ...animatedParallelShow, + ]).start(); + } else if (preVisible !== props.visible && !props.visible) { + Animated.parallel([ + Animated.spring(bgOpacity, { + toValue: 0, + overshootClamping: true, + useNativeDriver: true, + }), + ...animatedParallelHide, + ]).start(() => { + setVisible(!!props.visible); + setVisibleModal(true); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [props.visible]); + const backdropContent = ( + + ); + let backdrop = ( + onDismiss && onDismiss()}> + {backdropContent} + + ); + let isTrue = true; + + if (!visible && visibleModal) { + isTrue = false; + } + return ( + + {maskClosable ? backdrop : backdropContent} + {children && + React.Children.toArray(children).map((child) => { + if (!React.isValidElement(child)) { + return; + } + return React.cloneElement(child, { + ...child.props, + ...{style: [{zIndex: 9999}, child.props.style]}, + }); + })} + + ); +}; diff --git a/components/index.tsx b/components/index.tsx index 5a81e260a..1c4092672 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -13,6 +13,8 @@ export {default as Icon} from './Icon'; export {default as List} from './List'; export {default as Loader} from './Loader'; export {default as Modal} from './Modal'; +export {default as MaskLayer} from './MaskLayer'; +export * from './MaskLayer'; export {default as Radio} from './Radio'; export {default as Result} from './Result'; export {default as SegmentedControl} from './SegmentedControl'; diff --git a/components/utils/index.ts b/components/utils/index.ts index 5da263649..b8d1e8dbd 100644 --- a/components/utils/index.ts +++ b/components/utils/index.ts @@ -2,3 +2,4 @@ import color from 'color'; import * as colors from './colors'; export {color, colors}; +export * from './hooks'; diff --git a/src/Home.tsx b/src/Home.tsx index 77c47009c..3bbf2d8df 100644 --- a/src/Home.tsx +++ b/src/Home.tsx @@ -41,7 +41,7 @@ const Link = ({ {title || ''} - {description} + {description} diff --git a/src/routes.tsx b/src/routes.tsx index f89061979..c31da0cee 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -144,6 +144,14 @@ export const stackPageData: Routes[] = [ description: '模态对话框,React Native 原生组件。', }, }, + { + name: 'MaskLayer', + component: require('./routes/MaskLayer').default, + params: { + title: 'MaskLayer 遮罩层', + description: '用于模态对话框,中的遮罩层。', + }, + }, { name: 'Radio', component: require('./routes/Radio').default, diff --git a/src/routes/MaskLayer/index.tsx b/src/routes/MaskLayer/index.tsx new file mode 100644 index 000000000..b8ab8d208 --- /dev/null +++ b/src/routes/MaskLayer/index.tsx @@ -0,0 +1,72 @@ +import React, {useState} from 'react'; +import {Text, View, StyleSheet} from 'react-native'; +import Layout, {Container} from '../../Layout'; +import {Button, MaskLayer} from '../../../components'; +import {ComProps} from '../../typings'; + +const {Header, Body, Card, Footer} = Layout; + +export interface MenuDropdownProps extends ComProps {} + +export default function MenuDropdownView(props: MenuDropdownProps) { + const {route} = props || {}; + const description = route.params.description; + const title = route.params.title; + const [visible, setVisible] = useState(true); + return ( + + +
+ + + { + setVisible(false); + }}> + + + 内容 + + + + + + +