Skip to content

Commit

Permalink
feat(portal): add new portal component (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitrsavk committed Apr 14, 2020
1 parent 0094f2e commit 80d5499
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/portal/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@alfalab/core-components-portal",
"version": "1.0.0",
"description": "Portal component",
"keywords": [],
"license": "ISC",
"main": "index.js",
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"react": "^16.9.0",
"react-dom": "^16.9.0"
}
}
38 changes: 38 additions & 0 deletions src/portal/src/Component.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';

import { withKnobs } from '@storybook/addon-knobs';

import { Button } from '../../button/src';
import { Portal } from './index';

export default {
title: 'Common|Portal',
component: Portal,
decorators: [withKnobs],
};

export const Basic = () => {
const [show, setShow] = React.useState(false);

const handleClick = () => {
setShow(!show);
};

return (
<div>
<Button onClick={handleClick}>{show ? 'Unmount children' : 'Mount children'}</Button>
<div style={{ marginBottom: '200px' }}>
It looks like I will render here.
{show ? (
<Portal>
<span>But I actually render here!</span>
</Portal>
) : null}
</div>
</div>
);
};

Basic.story = {
name: 'Portal',
};
65 changes: 65 additions & 0 deletions src/portal/src/Component.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react';
import { render } from '@testing-library/react';

import { Portal } from './index';
import { PORTAL_CONTAINER_ATTRIBUTE } from './portalContainer';

describe('Portal tests', () => {
it('should render in a different node', () => {
const normalText = 'Normal text';
const textInPortal = 'Text in portal';

const { container, getByText } = render(
<div>
<span>{normalText}</span>
<Portal>
<span>{textInPortal}</span>
</Portal>
</div>,
);

const rootElement = container.firstElementChild;

expect(rootElement).toContainElement(getByText(normalText));
expect(rootElement).not.toContainElement(getByText(textInPortal));
});

it('should render overlay into document', () => {
const textInPortal = 'Text in portal';

const { getByText } = render(
<div>
<h1>Title</h1>
<Portal>
<span>{textInPortal}</span>
</Portal>
</div>,
);

const portalChild = getByText(textInPortal);

expect(document.querySelector(`div[${PORTAL_CONTAINER_ATTRIBUTE}]`)).toContainElement(
portalChild,
);
});

it('should render overlay into container (DOMNode)', () => {
const textInPortal = 'Text in portal';

const getPortalContainer = () => document.querySelector(`#portal-container`);

const { getByText } = render(
<div>
<h1>Title</h1>
<div id='portal-container' />
<Portal getPortalContainer={getPortalContainer}>
<span>{textInPortal}</span>
</Portal>
</div>,
);

const portalChild = getByText(textInPortal);

expect(document.querySelector(`#portal-container`)).toContainElement(portalChild);
});
});
24 changes: 24 additions & 0 deletions src/portal/src/Component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';

import { getDefaultPortalContainer } from './portalContainer';

export type PortalProps = {
/**
* Функция, возвращающая контейнер, в который будут рендериться дочерние элементы
*/
getPortalContainer?: () => Element;
};

export const Portal: React.FC<PortalProps> = ({
children,
getPortalContainer = getDefaultPortalContainer,
}) => {
const [isMount, setIsMount] = useState(false);

useEffect(() => {
setIsMount(true);
}, []);

return isMount ? createPortal(children, getPortalContainer()) : null;
};
1 change: 1 addition & 0 deletions src/portal/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Component';
14 changes: 14 additions & 0 deletions src/portal/src/portalContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const PORTAL_CONTAINER_ATTRIBUTE = 'alfa-portal-container';

function createPortalContainer() {
const portalContainer = document.createElement('div');

portalContainer.setAttribute(PORTAL_CONTAINER_ATTRIBUTE, '');

document.body.appendChild(portalContainer);

return portalContainer;
}

export const getDefaultPortalContainer = (): Element =>
document.querySelector(`[${PORTAL_CONTAINER_ATTRIBUTE}]`) || createPortalContainer();
13 changes: 13 additions & 0 deletions src/portal/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": false,
"composite": false,
"emitDeclarationOnly": true,
"declaration": true,
"declarationDir": "dist",
"types": ["../../typings/module"]
},
"exclude": ["src/Component.stories.tsx", "src/Component.test.tsx"],
"include": ["src"]
}

0 comments on commit 80d5499

Please sign in to comment.