diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.dev.stories.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.dev.stories.tsx index fc95111e9a3..dc9cb7331a6 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.dev.stories.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.dev.stories.tsx @@ -5,6 +5,11 @@ import {Button} from '../Button' import {AnchoredOverlay} from '.' import {Stack} from '../Stack' import {Dialog, Spinner, ActionList, ActionMenu} from '..' +import Octicon from '../Octicon' +import Avatar from '../Avatar' +import Link from '../Link' +import {LocationIcon, RepoIcon} from '@primer/octicons-react' +import classes from './AnchoredOverlay.features.stories.module.css' const meta = { title: 'Components/AnchoredOverlay/Dev', @@ -13,6 +18,34 @@ const meta = { export default meta +const hoverCard = ( + + + + + + + monalisa + + + Monalisa Octocat + + + + + Former beach cat and champion swimmer. Now your friendly octopus with a normal face. + + + + Interwebs + + + + Owns this repository + + +) + export const RepositionAfterContentGrows = () => { const [open, setOpen] = useState(false) const [loading, setLoading] = useState(true) @@ -345,3 +378,70 @@ export const ManyOverlays = () => { ) } + +const gridPositions = [ + {row: 'start', col: 'start'}, + {row: 'start', col: 'center'}, + {row: 'start', col: 'end'}, + {row: 'center', col: 'start'}, + {row: 'center', col: 'center'}, + {row: 'center', col: 'end'}, + {row: 'end', col: 'start'}, + {row: 'end', col: 'center'}, + {row: 'end', col: 'end'}, +] + +export const AnchorPositionGridFallbackDisabled = () => { + return +} + +const AnchorPositionGridFallback = ({ + cssAnchorPositioningSettings, +}: { + cssAnchorPositioningSettings: {fallbackStrategy: 'none' | 'opposite-side'} +}) => { + const [openCell, setOpenCell] = useState(null) + + return ( +
+
+
+ {gridPositions.map(({row, col}) => { + const key = `${row}-${col}` + const isCenter = row === 'center' && col === 'center' + + return ( +
+ + {row} / {col} + + {isCenter ? ( + setOpenCell(key)} + onClose={() => setOpenCell(null)} + renderAnchor={props => } + overlayProps={{ + role: 'dialog', + 'aria-modal': true, + 'aria-label': 'Anchor Position Grid Demo', + }} + focusZoneSettings={{disabled: true}} + preventOverflow={false} + cssAnchorPositioningSettings={cssAnchorPositioningSettings} + > +
{hoverCard}
+
+ ) : null} +
+ ) + })} +
+
+
+ ) +} + +export const AnchorPositionGridFallbackOppositeSide = () => { + return +} diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.features.stories.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.features.stories.tsx index 9656519d5a8..c5c41a22090 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.features.stories.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.features.stories.tsx @@ -402,6 +402,7 @@ export const CenteredOnPage = () => { 'aria-modal': true, 'aria-label': 'Centered Overlay Demo', }} + cssAnchorPositioningSettings={{fallbackStrategy: 'default'}} focusZoneSettings={{disabled: true}} preventOverflow={false} > diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx index 0613dd97493..929376ee06c 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx @@ -407,6 +407,50 @@ describe('AnchoredOverlay feature flag specific behavior', () => { expect(overlay).not.toHaveAttribute('popover') }) + it('should disable CSS side fallbacks when cssAnchorPositioningSettings.fallbackStrategy is "none"', () => { + const {baseElement} = render( + + + {}} + onClose={() => {}} + renderAnchor={props => } + side="outside-bottom" + cssAnchorPositioningSettings={{fallbackStrategy: 'none'}} + > + + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') as HTMLElement + expect(overlay.style.getPropertyValue('position-try-fallbacks')).toBe('none') + }) + + it('should only allow opposite-side CSS fallback when cssAnchorPositioningSettings.fallbackStrategy is "opposite-side"', () => { + const {baseElement} = render( + + + {}} + onClose={() => {}} + renderAnchor={props => } + side="outside-bottom" + cssAnchorPositioningSettings={{fallbackStrategy: 'opposite-side'}} + > + + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') as HTMLElement + expect(overlay.style.getPropertyValue('position-try-fallbacks')).toBe('flip-block') + }) + describe('when overlayProps.portalContainerName is provided', () => { it('should fall back to JS positioning (data-anchor-position="false") even with the flag enabled', () => { const portalRoot = document.createElement('div') @@ -504,6 +548,28 @@ describe('AnchoredOverlay feature flag specific behavior', () => { const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') expect(overlay).not.toHaveAttribute('popover') }) + + it('should ignore cssAnchorPositioningSettings when CSS anchor positioning is disabled', () => { + const {baseElement} = render( + + + {}} + onClose={() => {}} + renderAnchor={props => } + side="outside-bottom" + cssAnchorPositioningSettings={{fallbackStrategy: 'none'}} + > + + + + , + ) + + const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') as HTMLElement + expect(overlay.style.getPropertyValue('position-try-fallbacks')).toBe('') + }) }) }) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index f532d590d81..4e278da02d0 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -124,6 +124,18 @@ interface AnchoredOverlayBaseProps extends Pick { const cssAnchorPositioningFlag = useFeatureFlag('primer_react_css_anchor_positioning') // Lazy initial state so feature detection runs once per mount on the client. @@ -301,6 +314,17 @@ export const AnchoredOverlay: React.FC { @@ -344,11 +368,22 @@ export const AnchoredOverlay: React.FC( ref: React.MutableRefObject | ((instance: T | null) => void) | null | undefined, value: T | null,