-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #338 from omnifed/335-feature-create-portal-component
335 feature create portal component
- Loading branch information
Showing
9 changed files
with
249 additions
and
13 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,57 @@ | ||
'use client' | ||
|
||
import { Close } from '@cerberus-design/icons' | ||
import { Button, IconButton, Portal, Show } from '@cerberus-design/react' | ||
import { hstack } from '@cerberus/styled-system/patterns' | ||
import { useState } from 'react' | ||
|
||
export default function PortalPreview() { | ||
const [showPortal, setShowPortal] = useState<boolean>(false) | ||
|
||
function handleShowPortal() { | ||
setShowPortal(true) | ||
} | ||
|
||
function handleClosePortal() { | ||
setShowPortal(false) | ||
} | ||
|
||
return ( | ||
<> | ||
<Button onClick={handleShowPortal}>Show Portal</Button> | ||
<Show when={showPortal}> | ||
<Portal> | ||
<div | ||
className={hstack({ | ||
backgroundColor: 'info.surface.initial', | ||
color: 'info.text.initial', | ||
justify: 'space-between', | ||
left: 0, | ||
mxi: '4', | ||
p: '4', | ||
position: 'absolute', | ||
right: 0, | ||
rounded: 'md', | ||
shadow: 'md', | ||
top: '4', | ||
zIndex: 'toast', | ||
})} | ||
> | ||
<p> | ||
This is a portal element that is outside of the DOM hierarchy of | ||
the parent component. | ||
</p> | ||
|
||
<IconButton | ||
ariaLabel="Close Portal" | ||
onClick={handleClosePortal} | ||
tooltipPosition="bottom" | ||
> | ||
<Close /> | ||
</IconButton> | ||
</div> | ||
</Portal> | ||
</Show> | ||
</> | ||
) | ||
} |
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,107 @@ | ||
--- | ||
heading: 'Portal' | ||
description: 'Portal displays content in outside of the DOM hierarchy.' | ||
a11y: 'utilities' | ||
npm: '@cerberus-design/react' | ||
source: 'components/Portal.tsx' | ||
recipe: '' | ||
--- | ||
|
||
import { | ||
WhenToUseAdmonition, | ||
WhenNotToUseAdmonition, | ||
} from '@/app/components/Admonition' | ||
import CodePreview from '@/app/components/CodePreview' | ||
import PortalPreview from '@/app/react/portal/components/portal-preview' | ||
|
||
```ts | ||
import { Portal } from '@cerberus-design/react' | ||
``` | ||
|
||
<WhenToUseAdmonition description="When you need display a component outside of the parent it is contained within." /> | ||
|
||
## Usage | ||
|
||
<CodePreview preview={<PortalPreview />}> | ||
```tsx title="nav.tsx" {23} | ||
'use client' | ||
|
||
import { Close } from '@cerberus-design/icons' | ||
import { Button, IconButton, Portal, Show } from '@cerberus-design/react' | ||
import { hstack } from '@cerberus/styled-system/patterns' | ||
import { useState } from 'react' | ||
|
||
function PortalPreview() { | ||
const [showPortal, setShowPortal] = useState<boolean>(false) | ||
|
||
function handleShowPortal() { | ||
setShowPortal(true) | ||
} | ||
|
||
function handleClosePortal() { | ||
setShowPortal(false) | ||
} | ||
|
||
return ( | ||
<> | ||
<Button onClick={handleShowPortal}>Show Portal</Button> | ||
<Show when={showPortal}> | ||
<Portal> | ||
<div | ||
className={hstack({ | ||
backgroundColor: 'info.surface.initial', | ||
color: 'info.text.initial', | ||
justify: 'space-between', | ||
left: 0, | ||
mxi: '4', | ||
p: '4', | ||
position: 'absolute', | ||
right: 0, | ||
rounded: 'md', | ||
shadow: 'md', | ||
top: '4', | ||
zIndex: 'toast', | ||
})} | ||
> | ||
<p> | ||
This is a portal element that is outside of the DOM hierarchy of | ||
the parent component. | ||
</p> | ||
|
||
<IconButton | ||
ariaLabel="Close Portal" | ||
onClick={handleClosePortal} | ||
tooltipPosition="bottom" | ||
> | ||
<Close /> | ||
</IconButton> | ||
</div> | ||
</Portal> | ||
</Show> | ||
</> | ||
) | ||
} | ||
``` | ||
</CodePreview> | ||
|
||
<WhenNotToUseAdmonition description="Portals should only be used for alert/dialog level components. Anything else should try to achieve this without the use of a portal." /> | ||
|
||
## API | ||
|
||
```ts showLineNumbers=false | ||
export interface PortalProps { | ||
container?: Element | DocumentFragment | ||
key?: null | string | ||
} | ||
|
||
define function Portal(props: PropsWithChildren<PortalProps>): ReactPortal | null | ||
``` | ||
|
||
### Props | ||
|
||
The `Portal` component accepts the following props: | ||
|
||
| Name | Default | Description | | ||
| -------- | ------- | ------------------------------------------------------------- | | ||
| container | `document.body` | The mounted element to place the children contents within. | | ||
| key | null | A unique key to attached to the Portal instance. | |
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,28 @@ | ||
import ApiLinks from '@/app/components/ApiLinks' | ||
import OnThisPage from '../../components/OnThisPage' | ||
import { PageMainContent, PageSections } from '../../components/PageLayout' | ||
import Doc, { frontmatter } from './doc.mdx' | ||
import FeatureHeader from '@/app/components/FeatureHeader' | ||
import type { MatchFeatureKind } from '@/app/components/MatchFeatureImg' | ||
|
||
export default function PortalPage() { | ||
return ( | ||
<> | ||
<PageMainContent> | ||
<FeatureHeader | ||
heading={frontmatter.heading} | ||
description={frontmatter.description} | ||
a11y={frontmatter.a11y as MatchFeatureKind} | ||
/> | ||
<ApiLinks {...frontmatter} /> | ||
<main> | ||
<Doc /> | ||
</main> | ||
</PageMainContent> | ||
|
||
<PageSections> | ||
<OnThisPage /> | ||
</PageSections> | ||
</> | ||
) | ||
} |
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
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
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,22 @@ | ||
import type { PropsWithChildren } from 'react' | ||
import { createPortal } from 'react-dom' | ||
|
||
/** | ||
* This module is the Portal component. | ||
* @module | ||
*/ | ||
|
||
export interface PortalProps { | ||
container?: Element | DocumentFragment | ||
key?: null | string | ||
} | ||
|
||
/** | ||
* The Portal component is used to render children into a DOM node that exists outside the DOM hierarchy of the parent component. | ||
* @param container - The props for the Portal component. | ||
* @returns ReactPortal | ||
*/ | ||
export function Portal(props: PropsWithChildren<PortalProps>) { | ||
const container = props.container || document.body | ||
return createPortal(props.children, container, props.key) | ||
} |
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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,18 @@ | ||
import { describe, test, expect, afterEach } from 'bun:test' | ||
import { cleanup, render, screen } from '@testing-library/react' | ||
import { Portal } from '@cerberus-design/react' | ||
import { setupStrictMode } from '@/utils' | ||
|
||
describe('Portal', () => { | ||
setupStrictMode() | ||
afterEach(cleanup) | ||
|
||
test('should render children provided', () => { | ||
render( | ||
<Portal> | ||
<div>children</div> | ||
</Portal>, | ||
) | ||
expect(screen.getByText('children')).toBeTruthy() | ||
}) | ||
}) |