-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(portal): add new portal component (#75)
- Loading branch information
Showing
7 changed files
with
170 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './Component'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] | ||
} |