Skip to content

Commit

Permalink
feat(AnchoredOverlay): allow overlay to reflow (#5210)
Browse files Browse the repository at this point in the history
* feat(AnchoredOverlay): allow overlay to reflow

* Create thick-pugs-hammer.md

* test(Overlay): overlay overflow on by default

* test(Overlay): default FF to false (turned on for test only)
  • Loading branch information
francinelucca authored Nov 7, 2024
1 parent 11c455c commit b1950f5
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .changeset/thick-pugs-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

feat(AnchoredOverlay): allow overlay to reflow
6 changes: 6 additions & 0 deletions packages/react/src/AnchoredOverlay/AnchoredOverlay.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
"type": "string",
"defaultValue": "",
"description": "Class name for custom styling."
},
{
"name": "preventOverflow",
"type": "boolean",
"defaultValue": "true",
"description": "Determines if the Overlay width should be adjusted responsively if there is not enough space to display the Overlay. If `preventOverflow` is set to `false`, the Overlay will be displayed at the maximum width that fits within the viewport."
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default {
} as Meta

const hoverCard = (
<Stack gap="condensed" style={{minWidth: '320px', padding: '16px'}}>
<Stack gap="condensed" style={{padding: '16px'}}>
<Stack direction="horizontal" gap="condensed" justify="space-between">
<Avatar src="https://avatars.githubusercontent.com/u/92997159?v=4" size={48} />
<Button size="small">Follow</Button>
Expand Down Expand Up @@ -103,8 +103,9 @@ export const CustomAnchorId = () => {
onClose={() => setOpen(false)}
renderAnchor={props => <Button {...props}>Button</Button>}
anchorId="my-custom-anchor-id"
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay'}}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay', sx: {minWidth: '320px'}}}
focusZoneSettings={{disabled: true}}
preventOverflow={false}
>
<Box sx={{width: '100%', height: '100%', display: 'flex', flexDirection: 'column'}}>{hoverCard}</Box>
</AnchoredOverlay>
Expand All @@ -121,8 +122,9 @@ export const Height = () => {
onClose={() => setOpen(false)}
renderAnchor={props => <Button {...props}>Button</Button>}
height="large"
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay'}}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay', sx: {minWidth: '320px'}}}
focusZoneSettings={{disabled: true}}
preventOverflow={false}
>
<Box sx={{width: '100%', height: '100%', display: 'flex', flexDirection: 'column'}}>{hoverCard}</Box>
</AnchoredOverlay>
Expand All @@ -139,8 +141,9 @@ export const Width = () => {
onClose={() => setOpen(false)}
renderAnchor={props => <Button {...props}>Button</Button>}
width="large"
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay'}}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay', sx: {minWidth: '320px'}}}
focusZoneSettings={{disabled: true}}
preventOverflow={false}
>
<Box
sx={{
Expand Down Expand Up @@ -170,8 +173,9 @@ export const AnchorAlignment = () => {
</Button>
)}
align="center"
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay'}}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay', sx: {minWidth: '320px'}}}
focusZoneSettings={{disabled: true}}
preventOverflow={false}
>
<Box sx={{width: '100%', height: '100%', display: 'flex', flexDirection: 'column'}}>{hoverCard}</Box>
</AnchoredOverlay>
Expand All @@ -188,8 +192,9 @@ export const AnchorSide = () => {
onClose={() => setOpen(false)}
renderAnchor={props => <Button {...props}>Button</Button>}
side="outside-right"
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay'}}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay', sx: {minWidth: '320px'}}}
focusZoneSettings={{disabled: true}}
preventOverflow={false}
>
<Box sx={{width: '100%', height: '100%', display: 'flex', flexDirection: 'column'}}>{hoverCard}</Box>
</AnchoredOverlay>
Expand All @@ -206,8 +211,9 @@ export const OffsetPositionFromAnchor = () => {
onClose={() => setOpen(false)}
renderAnchor={props => <Button {...props}>Button</Button>}
anchorOffset={100}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay'}}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay', sx: {minWidth: '320px'}}}
focusZoneSettings={{disabled: true}}
preventOverflow={false}
>
<Box sx={{width: '100%', height: '100%', display: 'flex', flexDirection: 'column'}}>{hoverCard}</Box>
</AnchoredOverlay>
Expand All @@ -224,8 +230,9 @@ export const OffsetAlignmentFromAnchor = () => {
onClose={() => setOpen(false)}
renderAnchor={props => <Button {...props}>Button</Button>}
alignmentOffset={100}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay'}}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay', sx: {minWidth: '320px'}}}
focusZoneSettings={{disabled: true}}
preventOverflow={false}
>
<Box sx={{width: '100%', height: '100%', display: 'flex', flexDirection: 'column'}}>{hoverCard}</Box>
</AnchoredOverlay>
Expand All @@ -245,6 +252,7 @@ export const FocusTrapOverrides = () => {
focusTrapSettings={{initialFocusRef}}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'Focus Trap Demo Overlay'}}
focusZoneSettings={{disabled: true}}
preventOverflow={false}
>
<Button>First button</Button>
<Button ref={initialFocusRef}>Initial focus</Button>
Expand All @@ -263,6 +271,7 @@ export const FocusZoneOverrides = () => {
renderAnchor={props => <Button {...props}>Button</Button>}
focusZoneSettings={{bindKeys: FocusKeys.JK}}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'Focus Zone Demo Overlay'}}
preventOverflow={false}
>
<p>
Use <kbd>J</kbd> and <kbd>K</kbd> keys to move focus.
Expand All @@ -289,8 +298,10 @@ export const OverlayPropsOverrides = () => {
role: 'dialog',
'aria-modal': true,
'aria-label': 'User Card Overlay',
sx: {minWidth: '320px'},
}}
focusZoneSettings={{disabled: true}}
preventOverflow={false}
>
<div>Overlay props have been overridden to set: </div>
<pre>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default {
} as Meta

const hoverCard = (
<Stack gap="condensed" style={{minWidth: '320px', padding: '16px'}}>
<Stack gap="condensed" style={{padding: '16px'}}>
<Stack direction="horizontal" gap="condensed" justify="space-between">
<Avatar src="https://avatars.githubusercontent.com/u/92997159?v=4" size={48} />
<Button size="small">Follow</Button>
Expand Down Expand Up @@ -53,8 +53,9 @@ export const Default = () => {
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
renderAnchor={props => <Button {...props}>Button</Button>}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay'}}
overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay', sx: {minWidth: '320px'}}}
focusZoneSettings={{disabled: true}}
preventOverflow={false}
>
{hoverCard}
</AnchoredOverlay>
Expand Down Expand Up @@ -83,9 +84,11 @@ export const Playground = (args: Args) => {
role: 'dialog',
'aria-modal': true,
'aria-label': 'User Card Overlay',
sx: {minWidth: '320px'},
}}
side={args.side}
focusZoneSettings={{disabled: true}}
preventOverflow={false}
>
{hoverCard}
</AnchoredOverlay>
Expand Down
8 changes: 7 additions & 1 deletion packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ interface AnchoredOverlayBaseProps extends Pick<OverlayProps, 'height' | 'width'
* Optional className to be added to the overlay component.
*/
className?: string
/**
* preventOverflow Optional. The Overlay width will be adjusted responsively if there is not enough space to display the Overlay.
* If `preventOverflow` is `true`, the width of the `Overlay` will not be adjusted.
*/
preventOverflow?: boolean
}

export type AnchoredOverlayProps = AnchoredOverlayBaseProps &
Expand Down Expand Up @@ -112,6 +117,7 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
alignmentOffset,
anchorOffset,
className,
preventOverflow = true,
}) => {
const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
const [overlayRef, updateOverlayRef] = useRenderForcingRef<HTMLDivElement>()
Expand Down Expand Up @@ -198,7 +204,7 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
left={position?.left || 0}
anchorSide={position?.anchorSide}
className={className}
preventOverflow={true}
preventOverflow={preventOverflow}
{...overlayProps}
>
{children}
Expand Down
14 changes: 0 additions & 14 deletions packages/react/src/Overlay/Overlay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -313,18 +313,4 @@ describe('Overlay', () => {
const container = getByRole('none')
expect(container).toHaveAttribute('data-reflow-container')
})

it('should not have `data-reflow-container` if FF is enabled but the overlay is above `medium`', async () => {
const user = userEvent.setup()
const {getByRole} = render(
<FeatureFlags flags={{primer_react_overlay_overflow: true}}>
<TestComponent width="large" />
</FeatureFlags>,
)

await user.click(getByRole('button', {name: 'open overlay'}))

const container = getByRole('none')
expect(container).not.toHaveAttribute('data-reflow-container')
})
})
6 changes: 2 additions & 4 deletions packages/react/src/Overlay/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ type OwnOverlayProps = Merge<StyledOverlayProps, BaseOverlayProps>
* @param bottom Optional. Vertical bottom position of the overlay, relative to its closest positioned ancestor (often its `Portal`).
* @param position Optional. Sets how an element is positioned in a document. Defaults to `absolute` positioning.
* @param portalContainerName Optional. The name of the portal container to render the Overlay into.
* @param preventOverflow Optional. The Overlay width will be adjusted responsively if width is `auto`, `medium` or lower and there is not enough space to display the Overlay. If `preventOverflow` is `true`, the width of the `Overlay` will not be adjusted.
* @param preventOverflow Optional. The Overlay width will be adjusted responsively if there is not enough space to display the Overlay. If `preventOverflow` is `true`, the width of the `Overlay` will not be adjusted.
*/
const Overlay = React.forwardRef<HTMLDivElement, OwnOverlayProps>(
(
Expand Down Expand Up @@ -207,10 +207,8 @@ const Overlay = React.forwardRef<HTMLDivElement, OwnOverlayProps>(

// To be backwards compatible with the old Overlay, we need to set the left prop if x-position is not specified
const leftPosition: React.CSSProperties = left === undefined && right === undefined ? {left: 0} : {left}
const reflowSize = ['xsmall', 'small', 'medium', 'auto'].includes(width)

const enabled = useFeatureFlag('primer_react_overlay_overflow')
const overflow = enabled && reflowSize ? true : undefined

return (
<Portal containerName={portalContainerName}>
Expand All @@ -231,7 +229,7 @@ const Overlay = React.forwardRef<HTMLDivElement, OwnOverlayProps>(
...styleFromProps,
} as React.CSSProperties
}
data-reflow-container={overflow || (reflowSize && !preventOverflow) ? true : undefined}
data-reflow-container={enabled || !preventOverflow ? true : undefined}
/>
</Portal>
)
Expand Down

0 comments on commit b1950f5

Please sign in to comment.