Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/chilled-rules-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

Feat: popover implement click outside
4 changes: 4 additions & 0 deletions e2e/components/Popover.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const stories = [
title: 'Playground',
id: 'components-popover--playground',
},
{
title: 'Close On Click Outside',
id: 'components-popover-features--close-on-click-outside',
},
] as const

test.describe('Popover', () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/PageLayout/PageLayout.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ body[data-page-layout-dragging='true'] * {
margin-right: auto;
margin-left: auto;
flex-wrap: wrap;
/* the wrapper should match the Root's dimensions by default */
width: 100%;
height: 100%;

&:where([data-width='medium']) {
max-width: 768px;
Expand Down
10 changes: 10 additions & 0 deletions packages/react/src/Popover/Popover.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@
"type": "| 'auto' | 'hidden' | 'scroll' | 'visible'",
"defaultValue": "'auto'",
"description": "Sets the overflow behavior of the popover content."
},
{
"name": "onClickOutside",
"type": "(event: MouseEvent | TouchEvent) => void",
"description": "Callback fired when a click is detected outside the popover content"
},
{
"name": "ignoreClickRefs",
"type": "React.RefObject<HTMLElement>[]",
"description": "Refs to elements that should be ignored when detecting outside clicks"
}
]
}
Expand Down
38 changes: 38 additions & 0 deletions packages/react/src/Popover/Popover.features.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type {Meta, StoryFn} from '@storybook/react-vite'
import Heading from '../Heading'
import Popover from './Popover'
import Text from '../Text'
import {Button} from '../Button'
import React from 'react'

export default {
title: 'Components/Popover/Features',
component: Popover,
} as Meta<typeof Popover>

export const CloseOnClickOutside = () => {
const [open, setOpen] = React.useState(true)
const buttonRef = React.useRef<HTMLButtonElement>(null)
return (
<>
<Button
style={{marginLeft: 'auto', marginRight: 'auto', marginBottom: 'var(--base-size-4)'}}
ref={buttonRef}
onClick={() => setOpen(prev => !prev)}
>
Toggle Popover
</Button>
<Popover relative open={open} caret="top">
<Popover.Content
style={{marginTop: 'var(--base-size-8)'}}
onClickOutside={() => setOpen(false)}
ignoreClickRefs={[buttonRef]}
>
<Heading style={{fontSize: 'var(--text-title-size-small)'}}>Popover heading</Heading>
<Text as="p">Message about popovers</Text>
<Button>Got it!</Button>
</Popover.Content>
</Popover>
</>
)
}
31 changes: 29 additions & 2 deletions packages/react/src/Popover/Popover.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {clsx} from 'clsx'
import classes from './Popover.module.css'
import type {HTMLProps} from 'react'
import React from 'react'
import React, {useRef} from 'react'
import {useOnOutsideClick} from '../hooks'

type CaretPosition =
| 'top'
Expand Down Expand Up @@ -51,15 +52,41 @@ export type PopoverContentProps = {
width?: 'xsmall' | 'small' | 'large' | 'medium' | 'auto' | 'xlarge'
height?: 'small' | 'large' | 'medium' | 'auto' | 'xlarge' | 'fit-content'
overflow?: 'auto' | 'hidden' | 'scroll' | 'visible'
/*
* Callback fired when a click is detected outside the popover content
*/
onClickOutside?: (event: MouseEvent | TouchEvent) => void
/*
* Refs to elements that should be ignored when detecting outside clicks
*/
ignoreClickRefs?: React.RefObject<HTMLElement>[]
} & HTMLProps<HTMLDivElement>

const PopoverContent: React.FC<React.PropsWithChildren<PopoverContentProps>> = ({
className,
width = 'small',
height = 'fit-content',
onClickOutside,
ignoreClickRefs,
...props
}) => {
return <div data-width={width} data-height={height} className={clsx(className, classes.PopoverContent)} {...props} />
const divRef = useRef(null)

useOnOutsideClick({
onClickOutside: onClickOutside ?? (() => null),
Comment thread
francinelucca marked this conversation as resolved.
Outdated
containerRef: divRef,
ignoreClickRefs: ignoreClickRefs ?? [],
Comment thread
francinelucca marked this conversation as resolved.
Outdated
})

return (
<div
ref={divRef}
data-width={width}
data-height={height}
className={clsx(className, classes.PopoverContent)}
{...props}
/>
)
}

PopoverContent.displayName = 'Popover.Content'
Expand Down
Loading