Skip to content

Commit

Permalink
feat: Add MaskLayer component.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaywcjlove committed Jul 31, 2021
1 parent 3aea541 commit 5955375
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 2 deletions.
63 changes: 63 additions & 0 deletions components/MaskLayer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
MaskLayer 遮罩层
---

用于模态对话框,中的遮罩层。

### 基础示例

<!--DemoStart-->
```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 (
<Fragment>
<MaskLayer
visible={visible}
onDismiss={() => {
setVisible(false);
}}>
<Text style={{color: '#fff'}}>展示内容</Text>
</MaskLayer>
<Button
onPress={() => {
setVisible(true);
}}>
{visible ? '隐藏模态框' : '显示模态框'}
</Button>
</Fragment>
);
}
```
<!--End-->

## 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;
}
```
134 changes: 134 additions & 0 deletions components/MaskLayer/index.tsx
Original file line number Diff line number Diff line change
@@ -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<boolean | undefined>(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 = (
<Animated.View
style={[styles.position, styles.backdrop, {opacity: bgOpacity}]}
/>
);
let backdrop = (
<TouchableOpacity
activeOpacity={1}
style={[styles.position]}
onPress={() => onDismiss && onDismiss()}>
{backdropContent}
</TouchableOpacity>
);
let isTrue = true;

if (!visible && visibleModal) {
isTrue = false;
}
return (
<Modal
transparent={true}
animationType="none"
{...otherProps}
visible={isTrue}>
{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]},
});
})}
</Modal>
);
};
2 changes: 2 additions & 0 deletions components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
1 change: 1 addition & 0 deletions components/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ import color from 'color';
import * as colors from './colors';

export {color, colors};
export * from './hooks';
2 changes: 1 addition & 1 deletion src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const Link = ({
<View style={styles.block}>
<Text>{title || ''}</Text>
<View>
<Text>{description}</Text>
<Text style={{color: '#969696'}}>{description}</Text>
</View>
</View>
</Button>
Expand Down
8 changes: 8 additions & 0 deletions src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
72 changes: 72 additions & 0 deletions src/routes/MaskLayer/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Container>
<Layout>
<Header title={title} description={description} />
<Body>
<Card title="基础实例" style={styles.card}>
<MaskLayer
visible={visible}
onDismiss={() => {
setVisible(false);
}}>
<View style={styles.top}>
<Button
type="danger"
onPress={() => {
setVisible(false);
}}>
<Text style={styles.color}>隐藏</Text>
</Button>
<Text style={styles.color}>内容</Text>
</View>
</MaskLayer>
<Button
onPress={() => {
setVisible(true);
}}>
{String(true)}
显示模态框
</Button>
<Button
onPress={() => {
setVisible(false);
}}>
{String(false)}
隐藏模态框
</Button>
</Card>
</Body>
<Footer />
</Layout>
</Container>
);
}

const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
paddingLeft: 20,
paddingRight: 20,
},
color: {
color: '#fff',
},
top: {
top: 100,
},
});
3 changes: 2 additions & 1 deletion src/routes/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ export default class ModalView extends Component<ModalViewProps> {
<Body>
<Card title="基础实例">
<Modal
placement="top"
// maskClosable={false}
visible={this.state.modalVisible}
onClosed={() => this.setState({modalVisible: false})}
onRequestClose={() => {
this.setState({modalVisible: false});
// this.setState({modalVisible: false});
// Alert.alert('Modal has been closed.');
}}>
<SafeAreaView>
Expand Down

0 comments on commit 5955375

Please sign in to comment.