Skip to content

Commit

Permalink
feat(popover): popover portal subcomponent
Browse files Browse the repository at this point in the history
  • Loading branch information
Powerplex committed Jun 9, 2023
1 parent fdd63c9 commit 2968e4f
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 38 deletions.
3 changes: 2 additions & 1 deletion packages/components/popover/src/Popover.doc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ import { Popover } from '@spark-ui/popover'
of={Popover}
subcomponents={{
'Popover.Trigger': Popover.Trigger,
'Popover.Content': Popover.Content,
'Popover.Anchor': Popover.Anchor,
'Popover.Portal': Popover.Portal,
'Popover.Content': Popover.Content,
'Popover.Arrow': Popover.Arrow,
}}
/>
Expand Down
65 changes: 39 additions & 26 deletions packages/components/popover/src/Popover.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ export const Default: StoryFn = _args => {
<Popover.Trigger asChild>
<Button>Trigger popover</Button>
</Popover.Trigger>
<Popover.Content>
some text
<Popover.Arrow />
</Popover.Content>
<Popover.Portal>
<Popover.Content>
Popover contents
<Popover.Arrow />
</Popover.Content>
</Popover.Portal>
</Popover>
</ShowcaseContainer>
)
Expand All @@ -61,10 +63,12 @@ export const Controlled: StoryFn = () => {
<Popover.Anchor asChild>
<p>Popover is attached to this text (anchor)</p>
</Popover.Anchor>
<Popover.Content onInteractOutside={() => setOpen(false)}>
some text
<Popover.Arrow />
</Popover.Content>
<Popover.Portal>
<Popover.Content onInteractOutside={() => setOpen(false)}>
Popover contents
<Popover.Arrow />
</Popover.Content>
</Popover.Portal>
</Popover>
</ShowcaseContainer>
</>
Expand All @@ -90,10 +94,13 @@ export const Anchored: StoryFn = _args => {
<Popover.Trigger asChild>
<Button intent="secondary">Anchor element</Button>
</Popover.Trigger>
<Popover.Content>
some text
<Popover.Arrow />
</Popover.Content>

<Popover.Portal>
<Popover.Content>
Popover contents
<Popover.Arrow />
</Popover.Content>
</Popover.Portal>
</Popover>
</ShowcaseContainer>
)
Expand All @@ -106,11 +113,13 @@ export const MatchTriggerWidth: StoryFn = _args => {
<Popover.Trigger asChild>
<Button>Check the width of this popover</Button>
</Popover.Trigger>
<Popover.Content matchTriggerWidth>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua.
<Popover.Arrow />
</Popover.Content>
<Popover.Portal>
<Popover.Content matchTriggerWidth>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
<Popover.Arrow />
</Popover.Content>
</Popover.Portal>
</Popover>
</ShowcaseContainer>
)
Expand All @@ -125,11 +134,13 @@ export const Boundaries: StoryFn = () => {
<Popover.Trigger asChild>
<Button>Trigger popover</Button>
</Popover.Trigger>
<Popover.Content collisionBoundary={[boundaryContainer]}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua.
<Popover.Arrow />
</Popover.Content>
<Popover.Portal>
<Popover.Content collisionBoundary={[boundaryContainer]}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
<Popover.Arrow />
</Popover.Content>
</Popover.Portal>
</Popover>
</ShowcaseContainer>
)
Expand Down Expand Up @@ -176,10 +187,12 @@ export const Positionning: StoryFn = _args => {
<Popover.Trigger asChild>
<Button>Trigger popover</Button>
</Popover.Trigger>
<Popover.Content side={currentSide} align={currentAlign}>
some text
<Popover.Arrow />
</Popover.Content>
<Popover.Portal>
<Popover.Content side={currentSide} align={currentAlign}>
Popover contents
<Popover.Arrow />
</Popover.Content>
</Popover.Portal>
</Popover>
</ShowcaseContainer>
</div>
Expand Down
27 changes: 26 additions & 1 deletion packages/components/popover/src/Popover.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, screen } from '@testing-library/react'
import { render, screen, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { mockResizeObserver } from 'jsdom-testing-mocks'
import { useState } from 'react'
Expand Down Expand Up @@ -258,6 +258,31 @@ describe('Popover', () => {
})
})

describe('Popover.Portal', () => {
it('should render the Popover into the body of the document', () => {
// Given a popover rendered into a Portal
render(
<div data-testid="popover-container">
<Popover open>
<Popover.Trigger asChild>
<button type="button">Click me</button>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content>Popover content</Popover.Content>
</Popover.Portal>
</Popover>
</div>
)

// When we search for the popover inside of its original container
const originalContainer = screen.getByTestId('popover-container')

// Then it is rendered outside of it (inside document.body)
expect(within(originalContainer).queryByText('Popover content')).not.toBeInTheDocument()
expect(screen.getByText('Popover content')).toBeInTheDocument()
})
})

describe('controlled mode (stateless)', () => {
const ControlledImplementation = ({ defaultOpen = true } = {}) => {
const [open] = useState(defaultOpen)
Expand Down
24 changes: 14 additions & 10 deletions packages/components/popover/src/Popover.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import type { FC } from 'react'

import { Anchor, type AnchorProps } from './PopoverAnchor'
import { Arrow, type ArrowProps } from './PopoverArrow'
import { Content, type ContentProps } from './PopoverContent'
import { Anchor } from './PopoverAnchor'
import { Arrow } from './PopoverArrow'
import { Content } from './PopoverContent'
import { Portal } from './PopoverPortal'
import { Root, type RootProps } from './PopoverRoot'
import { Trigger, type TriggerProps } from './PopoverTrigger'
import { Trigger } from './PopoverTrigger'

Trigger.displayName = 'Popover.Trigger'
Anchor.displayName = 'Popover.Anchor'
Arrow.displayName = 'Popover.Arrow'
Content.displayName = 'Popover.Content'
Portal.displayName = 'Popover.Portal'
Trigger.displayName = 'Popover.Trigger'

export const Popover: FC<RootProps> & {
Trigger: FC<TriggerProps>
Anchor: FC<AnchorProps>
Arrow: FC<ArrowProps>
Content: FC<ContentProps>
Anchor: typeof Anchor
Arrow: typeof Arrow
Content: typeof Content
Portal: typeof Portal
Trigger: typeof Trigger
} = Object.assign(Root, {
Trigger,
Anchor,
Arrow,
Content,
Portal,
Trigger,
})
10 changes: 10 additions & 0 deletions packages/components/popover/src/PopoverPortal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as RadixPopover from '@radix-ui/react-popover'
import { ReactElement } from 'react'

export type PortalProps = RadixPopover.PopoverPortalProps

export const Portal = ({ children, ...rest }: PortalProps): ReactElement => (
<RadixPopover.Portal {...rest}>{children}</RadixPopover.Portal>
)

Portal.displayName = 'Popover.Portal'

0 comments on commit 2968e4f

Please sign in to comment.