mirror of
https://github.com/zhigang1992/react-native-paper.git
synced 2026-06-12 09:08:12 +08:00
feat: improvements to portal (#90)
- layer management according to position - preserve theme context via ThemedPortal
This commit is contained in:
committed by
Ahmed Elhanafy
parent
4732b02a0f
commit
a5519eefbd
@@ -11,7 +11,11 @@ import {
|
||||
NavigationProvider,
|
||||
StackNavigation,
|
||||
} from '@exponent/ex-navigation';
|
||||
import { Colors, ThemeProvider, Drawer } from 'react-native-paper';
|
||||
import {
|
||||
Colors,
|
||||
Drawer,
|
||||
Provider as PaperProvider,
|
||||
} from 'react-native-paper';
|
||||
import Router from './src/Router';
|
||||
|
||||
|
||||
@@ -53,7 +57,7 @@ class App extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<PaperProvider>
|
||||
<NavigationProvider router={Router}>
|
||||
<Drawer
|
||||
onOpen={this._handleOpenDrawer}
|
||||
@@ -73,7 +77,7 @@ class App extends Component {
|
||||
/>
|
||||
</Drawer>
|
||||
</NavigationProvider>
|
||||
</ThemeProvider>
|
||||
</PaperProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,24 @@ import {
|
||||
PureComponent,
|
||||
PropTypes,
|
||||
} from 'react';
|
||||
import { manager } from '../core/PortalHost';
|
||||
import { manager } from './PortalHost';
|
||||
|
||||
type Props = {
|
||||
children?: any;
|
||||
export type PortalProps = {
|
||||
children: any;
|
||||
position: number;
|
||||
}
|
||||
|
||||
type Props = PortalProps;
|
||||
|
||||
/**
|
||||
* Portal allows to render a component at a different place in the parent tree.
|
||||
*/
|
||||
export default class Portal extends PureComponent<void, Props, void> {
|
||||
static propTypes = {
|
||||
/**
|
||||
* Position of the element in the z-axis
|
||||
*/
|
||||
position: PropTypes.number,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
@@ -26,11 +36,11 @@ export default class Portal extends PureComponent<void, Props, void> {
|
||||
'You need to wrap your root component in \'<PortalHost />\''
|
||||
);
|
||||
}
|
||||
this._key = this.context[manager].mount(this.props.children);
|
||||
this._key = this.context[manager].mount(this.props);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.context[manager].update(this._key, this.props.children);
|
||||
this.context[manager].update(this._key, this.props);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
115
src/components/Portal/PortalHost.js
Normal file
115
src/components/Portal/PortalHost.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/* @flow */
|
||||
|
||||
import React, {
|
||||
PureComponent,
|
||||
PropTypes,
|
||||
} from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import type { PortalProps } from './Portal';
|
||||
|
||||
type Props = {
|
||||
children?: any;
|
||||
}
|
||||
|
||||
type State = {
|
||||
portals: Array<{
|
||||
key: number;
|
||||
props: PortalProps;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const manager = 'react-native-paper$portal-manager';
|
||||
|
||||
/**
|
||||
* Portal host is the component which actually renders all Portals.
|
||||
*/
|
||||
export default class Portals extends PureComponent<void, Props, State> {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
[manager]: PropTypes.object,
|
||||
};
|
||||
|
||||
state = {
|
||||
portals: [],
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
[manager]: {
|
||||
mount: this._mountPortal,
|
||||
unmount: this._unmountPortal,
|
||||
update: this._updatePortal,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_nextId = 0;
|
||||
|
||||
_mountPortal = (props: PortalProps) => {
|
||||
const portals = this.state.portals;
|
||||
this.setState({
|
||||
portals: portals.concat({ key: this._nextId, props }),
|
||||
});
|
||||
return this._nextId++;
|
||||
};
|
||||
|
||||
_unmountPortal = (key: number) => {
|
||||
const portals = this.state.portals;
|
||||
this.setState({
|
||||
portals: portals.filter(item => item.key !== key),
|
||||
});
|
||||
};
|
||||
|
||||
_updatePortal = (key: number, props: PortalProps) => {
|
||||
const portals = this.state.portals;
|
||||
this.setState({
|
||||
portals: portals.map(item => {
|
||||
if (item.key === key) {
|
||||
return { ...item, props };
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { portals } = this.state;
|
||||
return (
|
||||
<View {...this.props}>
|
||||
{this.props.children}
|
||||
{portals
|
||||
.reduce((acc, curr) => {
|
||||
const { position = 0, children } = curr.props;
|
||||
let group = acc.find(it => it.position === position);
|
||||
if (group) {
|
||||
group = {
|
||||
position,
|
||||
items: group.items.concat([ children ]),
|
||||
};
|
||||
return acc.map(g => {
|
||||
if (group && g.position === position) {
|
||||
return group;
|
||||
}
|
||||
return g;
|
||||
});
|
||||
} else {
|
||||
group = { position, items: [ children ] };
|
||||
return [ ...acc, group ];
|
||||
}
|
||||
}, [])
|
||||
.map(({ position, items }) => (
|
||||
<View key={position} style={StyleSheet.absoluteFill}>
|
||||
{items}
|
||||
</View>
|
||||
))
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
38
src/components/Portal/ThemedPortal.js
Normal file
38
src/components/Portal/ThemedPortal.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/* @flow */
|
||||
|
||||
import React, {
|
||||
Component,
|
||||
Children,
|
||||
PropTypes,
|
||||
} from 'react';
|
||||
import Portal from './Portal';
|
||||
import ThemeProvider from '../../core/ThemeProvider';
|
||||
import withTheme from '../../core/withTheme';
|
||||
import type { Theme } from '../../types/Theme';
|
||||
|
||||
type Props = {
|
||||
children?: any;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Themed portal is a special portal which preserves the theme in the context.
|
||||
*/
|
||||
class ThemedPortal extends Component <void, Props, void> {
|
||||
static propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Portal {...this.props}>
|
||||
<ThemeProvider theme={this.props.theme}>
|
||||
{Children.only(this.props.children)}
|
||||
</ThemeProvider>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(ThemedPortal);
|
||||
@@ -1,111 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
import React, {
|
||||
PureComponent,
|
||||
isValidElement,
|
||||
PropTypes,
|
||||
} from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
|
||||
type Props = {
|
||||
children?: any;
|
||||
style?: any;
|
||||
}
|
||||
|
||||
type State = {
|
||||
portals: { [key: string]: ?React.Element<*> };
|
||||
}
|
||||
|
||||
export const manager = 'react-native-paper$portal-manager';
|
||||
|
||||
export default class PortalHost extends PureComponent<void, Props, State> {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
style: View.propTypes.style,
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
[manager]: PropTypes.object,
|
||||
};
|
||||
|
||||
state = {
|
||||
portals: {},
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
[manager]: {
|
||||
mount: this._mountPortal,
|
||||
unmount: this._unmountPortal,
|
||||
update: this._updatePortal,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_nextId = 0;
|
||||
|
||||
_mountPortal = (portal: ?React.Element<*>) => {
|
||||
const { portals } = this.state;
|
||||
|
||||
if (isValidElement(portal)) {
|
||||
this.setState({
|
||||
portals: {
|
||||
...portals,
|
||||
[this._nextId]: portal,
|
||||
},
|
||||
});
|
||||
return this._nextId++;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
_unmountPortal = (key: string) => {
|
||||
let { portals } = this.state;
|
||||
|
||||
if (portals.hasOwnProperty(key)) {
|
||||
portals = { ...portals };
|
||||
delete portals[key];
|
||||
|
||||
this.setState({
|
||||
portals,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_updatePortal = (key: string, portal: ?React.Element<*>) => {
|
||||
const { portals } = this.state;
|
||||
|
||||
if (isValidElement(portal)) {
|
||||
this.setState({
|
||||
portals: {
|
||||
...portals,
|
||||
[key]: portal,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { portals } = this.state;
|
||||
return (
|
||||
<View style={[ styles.container, this.props.style ]}>
|
||||
{this.props.children}
|
||||
<View style={StyleSheet.absoluteFill}>
|
||||
{Object.keys(portals).map(key =>
|
||||
portals[key]
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
32
src/core/Provider.js
Normal file
32
src/core/Provider.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/* @flow */
|
||||
|
||||
import React, {
|
||||
PureComponent,
|
||||
PropTypes,
|
||||
Children,
|
||||
} from 'react';
|
||||
import ThemeProvider from './ThemeProvider';
|
||||
import PortalHost from '../components/Portal/PortalHost';
|
||||
import type { Theme } from '../types/Theme';
|
||||
|
||||
type Props = {
|
||||
children?: any;
|
||||
theme?: Theme
|
||||
}
|
||||
|
||||
export default class Provider extends PureComponent<void, Props, void> {
|
||||
static propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
theme: PropTypes.object,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<PortalHost>
|
||||
<ThemeProvider theme={this.props.theme}>
|
||||
{Children.only(this.props.children)}
|
||||
</ThemeProvider>
|
||||
</PortalHost>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ export const theme = 'react-native-paper$theme';
|
||||
|
||||
export default class ThemeProvider extends PureComponent<DefaultProps, Props, void> {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
children: PropTypes.element.isRequired,
|
||||
theme: PropTypes.object,
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
export { default as withTheme } from './core/withTheme';
|
||||
export { default as ThemeProvider } from './core/ThemeProvider';
|
||||
export { default as PortalHost } from './core/PortalHost';
|
||||
export { default as Provider } from './core/Provider';
|
||||
|
||||
export * as Colors from './styles/colors';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user