├── .eslintrc.json
├── .gitignore
├── README.md
├── lib
├── index.js
└── portal
│ ├── portal-context.js
│ ├── portal-enter.js
│ ├── portal-exit.js
│ └── portal-provider.js
├── package.json
├── tests
├── __snapshots__
│ ├── example.test.js.snap
│ ├── portal-enter.test.js.snap
│ └── portal-exit.test.js.snap
├── example.test.js
├── portal-enter.test.js
├── portal-exit.test.js
└── setup.js
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "plugins": ["react", "prettier"],
4 | "extends": ["plugin:prettier/recommended"]
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-gateway
2 |
3 | > Render React-Native component into a new context (aka "Portal")
4 |
5 | This can be used to implement various UI components such as modals.
6 |
7 | ## Installation
8 |
9 | ```sh
10 | $ yarn add react-native-gateway
11 | ```
12 |
13 | ## Example
14 |
15 | ```js
16 | import React from "react";
17 | import { PortalEnter, PortalExit, PortalProvider } from "react-native-gateway";
18 |
19 | const App = (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | ```
30 |
31 | Will render as:
32 |
33 | ```js
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ```
43 |
44 | ## Usage
45 |
46 | To get started with react-native-gateway, first wrap your application in the
47 | ``.
48 |
49 | ```diff
50 | import React from 'react';
51 | + import {
52 | + PortalProvider
53 | + } from 'react-native-gateway';
54 |
55 | const App = () => {
56 | return (
57 | +
58 |
59 | {this.props.children}
60 |
61 | +
62 | );
63 | }
64 | ```
65 |
66 | Then insert a `` whereever you want it to render.
67 |
68 | ```diff
69 | import React from 'react';
70 | import {
71 | PortalProvider,
72 | + PortalExit
73 | } from 'react-gateway';
74 |
75 | const App = () => {
76 | return (
77 |
78 |
79 | {this.props.children}
80 | +
81 |
82 |
83 | );
84 | }
85 | ```
86 |
87 | Then in any of your components (that get rendered inside of the
88 | ``) add a `` and add `name` prop to it.
89 |
90 | ```diff
91 | import React from 'react';
92 | + import {PortalEnter} from 'react-native-gateway';
93 |
94 | const App = () => {
95 | return (
96 |
97 | +
98 | + Will render into the PortalExit.
99 | +
100 |
101 | );
102 | }
103 | ```
104 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | export PortalContext from "./portal/portal-context";
2 | export PortalEnter from "./portal/portal-enter";
3 | export PortalExit from "./portal/portal-exit";
4 | export PortalProvider from "./portal/portal-provider";
5 |
--------------------------------------------------------------------------------
/lib/portal/portal-context.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const PortalContext = React.createContext({
4 | elements: {},
5 | addElement: () => {},
6 | removeElement: () => {}
7 | });
8 |
9 | export default PortalContext;
10 |
--------------------------------------------------------------------------------
/lib/portal/portal-enter.js:
--------------------------------------------------------------------------------
1 | import { useContext, useEffect } from "react";
2 | import PortalContext from "./portal-context";
3 |
4 | const PortalEnter = ({ children, name }) => {
5 | const { addElement, removeElement } = useContext(PortalContext);
6 |
7 | useEffect(() => {
8 | removeElement({ name });
9 | addElement({
10 | name,
11 | component: children
12 | });
13 |
14 | return () => removeElement({ name });
15 | }, [children]);
16 |
17 | return null;
18 | };
19 |
20 | export default PortalEnter;
21 |
--------------------------------------------------------------------------------
/lib/portal/portal-exit.js:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | import PortalContext from "./portal-context";
3 |
4 | const PortalExit = () => {
5 | const { elements } = useContext(PortalContext);
6 | return Object.keys(elements).map(name => elements[name]);
7 | };
8 |
9 | export default PortalExit;
10 |
--------------------------------------------------------------------------------
/lib/portal/portal-provider.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PortalContext from "./portal-context";
3 |
4 | class PortalProvider extends Component {
5 | addElement = ({ name, component }) => {
6 | this.setState(state => ({
7 | elements: {
8 | ...state.elements,
9 | [name]: component
10 | }
11 | }));
12 | };
13 |
14 | removeElement = ({ name }) => {
15 | this.setState(state => {
16 | const { [name]: removedElement, ...restElements } = state.elements;
17 | return {
18 | elements: restElements
19 | };
20 | });
21 | };
22 |
23 | state = {
24 | addElement: this.addElement,
25 | removeElement: this.removeElement,
26 | elements: {}
27 | };
28 |
29 | render() {
30 | return (
31 |
32 | {this.props.children}
33 |
34 | );
35 | }
36 | }
37 |
38 | export default PortalProvider;
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-gateway",
3 | "version": "1.0.0",
4 | "description": "Render React-Native component into new context",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "test": "yarn lint && yarn unit",
8 | "unit": "yarn jest",
9 | "lint": "eslint lib/",
10 | "format": "eslint lib/ --fix"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/headfire94/react-native-gateway.git"
15 | },
16 | "keywords": [
17 | "react",
18 | "portal",
19 | "gateway",
20 | "modal",
21 | "dialog",
22 | "react-native",
23 | "ios",
24 | "android"
25 | ],
26 | "author": "Egor Vodopyanov egorvodopyanov@gmail.com",
27 | "license": "ISC",
28 | "bugs": {
29 | "url": "https://github.com/headfire94/react-native-gateway/issues"
30 | },
31 | "homepage": "https://github.com/headfire94/react-native-gateway#readme",
32 | "peerDependencies": {
33 | "react": "^16.8.1",
34 | "react-native": "^0.59.2"
35 | },
36 | "devDependencies": {
37 | "babel-eslint": "^10.0.3",
38 | "babel-jest": "^24.9.0",
39 | "babel-preset-react-native": "^4.0.1",
40 | "enzyme": "^3.10.0",
41 | "enzyme-adapter-react-16": "^1.14.0",
42 | "enzyme-to-json": "^3.4.0",
43 | "eslint": "^6.2.2",
44 | "eslint-config-prettier": "^6.1.0",
45 | "eslint-plugin-prettier": "^3.1.0",
46 | "eslint-plugin-react": "^7.14.3",
47 | "jest": "^24.9.0",
48 | "jsdom": "^15.1.1",
49 | "prettier": "^1.18.2",
50 | "react": "^16.9.0",
51 | "react-dom": "^16.9.0",
52 | "react-native": "0.59.2",
53 | "react-test-renderer": "^16.9.0"
54 | },
55 | "jest": {
56 | "preset": "react-native",
57 | "setupFiles": [
58 | "./tests/setup.js"
59 | ],
60 | "moduleFileExtensions": [
61 | "js",
62 | "json"
63 | ],
64 | "transform": {
65 | "^.+\\.js$": "/node_modules/react-native/jest/preprocessor.js"
66 | },
67 | "testMatch": [
68 | "**/*.test.js"
69 | ],
70 | "snapshotSerializers": [
71 | "./node_modules/enzyme-to-json/serializer"
72 | ]
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/__snapshots__/example.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`PortalExit contains ToPort component 1`] = `
4 |
5 |
8 |
11 |
12 |
13 | `;
14 |
15 | exports[`mounts correctly 1`] = `
16 |
17 |
20 |
23 |
26 |
27 |
28 |
29 |
30 |
33 |
36 |
37 |
38 |
39 |
40 | `;
41 |
--------------------------------------------------------------------------------
/tests/__snapshots__/portal-enter.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders correctly 1`] = `null`;
4 |
--------------------------------------------------------------------------------
/tests/__snapshots__/portal-exit.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders correctly 1`] = `null`;
4 |
--------------------------------------------------------------------------------
/tests/example.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment jsdom
3 | */
4 | import { View } from "react-native";
5 | import { mount } from "enzyme";
6 | import React from "react";
7 |
8 | import { PortalEnter, PortalExit, PortalProvider } from "../lib/index";
9 |
10 | test("mounts correctly", () => {
11 | const ToPort = () => ;
12 | const App = (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | const wrapper = mount(App);
23 |
24 | expect(wrapper).toMatchSnapshot();
25 | });
26 |
27 | test("PortalExit contains ToPort component", () => {
28 | const ToPort = () => ;
29 | const App = (
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | const wrapper = mount(App);
40 |
41 | expect(wrapper.find(PortalExit).find(ToPort)).toMatchSnapshot();
42 | });
43 |
--------------------------------------------------------------------------------
/tests/portal-enter.test.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import PortalEnter from '../lib/portal/portal-enter';
4 |
5 | import renderer from 'react-test-renderer';
6 |
7 | test('renders correctly', () => {
8 | const tree = renderer.create().toJSON();
9 | expect(tree).toMatchSnapshot();
10 | });
--------------------------------------------------------------------------------
/tests/portal-exit.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PortalExit from "../lib/portal/portal-exit";
3 |
4 | import renderer from "react-test-renderer";
5 |
6 | test("renders correctly", () => {
7 | const tree = renderer.create().toJSON();
8 | expect(tree).toMatchSnapshot();
9 | });
10 |
--------------------------------------------------------------------------------
/tests/setup.js:
--------------------------------------------------------------------------------
1 | import Enzyme from "enzyme";
2 | import Adapter from "enzyme-adapter-react-16";
3 |
4 | /**
5 | * Set up DOM in node.js environment for Enzyme to mount to
6 | */
7 | const { JSDOM } = require('jsdom');
8 |
9 | const jsdom = new JSDOM('');
10 | const { window } = jsdom;
11 |
12 | function copyProps(src, target) {
13 | Object.defineProperties(target, {
14 | ...Object.getOwnPropertyDescriptors(src),
15 | ...Object.getOwnPropertyDescriptors(target),
16 | });
17 | }
18 |
19 | global.window = window;
20 | global.document = window.document;
21 | global.navigator = {
22 | userAgent: 'node.js',
23 | };
24 | copyProps(window, global);
25 |
26 |
27 | Enzyme.configure({ adapter: new Adapter() });
28 |
--------------------------------------------------------------------------------