From bc48a16c45d67ff0d1654b2d4b29ee1b01d50e67 Mon Sep 17 00:00:00 2001 From: Tyler Jones Date: Mon, 8 Jun 2026 11:42:02 -0400 Subject: [PATCH] Remove the `primer_react_css_anchor_positioning` feature flag --- .../remove-css-anchor-positioning-flag.md | 5 + .github/skills/feature-flags/SKILL.md | 12 - e2e/components/ActionMenu.test.ts | 48 +- e2e/components/AnchoredOverlay.test.ts | 101 ++-- .../react/src/ActionMenu/ActionMenu.test.tsx | 187 ++----- .../AnchoredOverlay/AnchoredOverlay.test.tsx | 519 +++++++----------- .../src/AnchoredOverlay/AnchoredOverlay.tsx | 5 +- .../src/FeatureFlags/DefaultFeatureFlags.ts | 1 - packages/react/src/Overlay/Overlay.tsx | 4 +- 9 files changed, 323 insertions(+), 559 deletions(-) create mode 100644 .changeset/remove-css-anchor-positioning-flag.md diff --git a/.changeset/remove-css-anchor-positioning-flag.md b/.changeset/remove-css-anchor-positioning-flag.md new file mode 100644 index 00000000000..d8bc9884e42 --- /dev/null +++ b/.changeset/remove-css-anchor-positioning-flag.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +AnchoredOverlay: Remove the `primer_react_css_anchor_positioning` feature flag. CSS anchor positioning is now enabled by default when the browser supports it natively (and no `portalContainerName` is provided), so consumers no longer need to opt in via the feature flag. diff --git a/.github/skills/feature-flags/SKILL.md b/.github/skills/feature-flags/SKILL.md index 35367fc2545..d47004a0500 100644 --- a/.github/skills/feature-flags/SKILL.md +++ b/.github/skills/feature-flags/SKILL.md @@ -110,18 +110,6 @@ function MyOverlay() { ## Real Codebase Examples -### CSS anchor positioning (`packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx`) - -```tsx -const cssAnchorPositioning = useFeatureFlag('primer_react_css_anchor_positioning') -useEffect(() => { - if (cssAnchorPositioning && !hasLoadedAnchorPositioningPolyfill.current) { - applyAnchorPositioningPolyfill() - hasLoadedAnchorPositioningPolyfill.current = true - } -}, [open, overlayRef, updateOverlayRef, cssAnchorPositioning]) -``` - ### Breadcrumbs overflow (`packages/react/src/Breadcrumbs/Breadcrumbs.tsx`) ```tsx diff --git a/e2e/components/ActionMenu.test.ts b/e2e/components/ActionMenu.test.ts index d3d36df0555..a77efef6dc4 100644 --- a/e2e/components/ActionMenu.test.ts +++ b/e2e/components/ActionMenu.test.ts @@ -65,44 +65,34 @@ const stories: Array<{ }, ] as const -const featureFlagVariants = [ - {flagEnabled: false, suffix: ''}, - {flagEnabled: true, suffix: '.css-anchor-positioning'}, -] as const - test.describe('ActionMenu', () => { for (const story of stories) { test.describe(story.title, () => { for (const theme of themes) { test.describe(theme, () => { - for (const {flagEnabled, suffix} of featureFlagVariants) { - test(`default @vrt${suffix}`, async ({page}) => { - await visit(page, { - id: story.id, - globals: { - colorScheme: theme, - featureFlags: { - primer_react_css_anchor_positioning: flagEnabled, - }, - }, - }) + test(`default @vrt`, async ({page}) => { + await visit(page, { + id: story.id, + globals: { + colorScheme: theme, + }, + }) - const buttonNames = story.buttonNames ?? [story.buttonName ?? 'Open menu'] + const buttonNames = story.buttonNames ?? [story.buttonName ?? 'Open menu'] - // Default state - // Open state + // Default state + // Open state - if (!story.skipOpen) { - for (const buttonName of buttonNames) { - await page.locator('button', {hasText: buttonName}).waitFor() - await page.getByRole('button', {name: buttonName}).click() - } + if (!story.skipOpen) { + for (const buttonName of buttonNames) { + await page.locator('button', {hasText: buttonName}).waitFor() + await page.getByRole('button', {name: buttonName}).click() } - expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( - `ActionMenu.${story.title}.${theme}${suffix}.png`, - ) - }) - } + } + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `ActionMenu.${story.title}.${theme}.png`, + ) + }) }) } }) diff --git a/e2e/components/AnchoredOverlay.test.ts b/e2e/components/AnchoredOverlay.test.ts index 37bf305df1a..dbc047cc4f6 100644 --- a/e2e/components/AnchoredOverlay.test.ts +++ b/e2e/components/AnchoredOverlay.test.ts @@ -126,71 +126,62 @@ const theme = 'light' test.describe('AnchoredOverlay', () => { for (const story of stories) { test.describe(story.title, () => { - for (const withCSSAnchorPositioning of [false, true]) { - const namePostfix = withCSSAnchorPositioning ? '.css-anchor-positioning' : '' + test(`default @vrt`, async ({page}) => { + await visit(page, { + id: story.id, + globals: { + colorScheme: theme, + }, + }) - test(`default @vrt${namePostfix ? ` ${namePostfix}` : ''}`, async ({page}) => { - await visit(page, { - id: story.id, - globals: { - colorScheme: theme, - ...(withCSSAnchorPositioning && { - featureFlags: { - primer_react_css_anchor_positioning: true, - }, - }), - }, + if (story.viewport) { + await page.setViewportSize({ + width: viewports[story.viewport], + height: 667, }) + } - if (story.viewport) { - await page.setViewportSize({ - width: viewports[story.viewport], - height: 667, - }) - } - - // Open dialog if needed - if (story.openDialog) { - await page.getByRole('button', {name: 'Open Dialog'}).click() - } - - // Open nested dialog if needed - if (story.openNestedDialog) { - await page.getByRole('button', {name: 'Open Inner Dialog'}).click() - } - - // If the story has multiple overlays, screenshot each one individually - if (story.buttonNames) { - for (const name of story.buttonNames) { - await page.locator('button', {hasText: name}).first().waitFor() - const btn = page.getByRole('button', {name}).first() - await btn.click() - await waitForImages(page) - - expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( - `AnchoredOverlay.${story.title}.${name}.${theme}${namePostfix}.png`, - ) + // Open dialog if needed + if (story.openDialog) { + await page.getByRole('button', {name: 'Open Dialog'}).click() + } - // Close the overlay before opening the next one - await btn.click() - } - } else { - // Open the overlay - const buttonName = story.buttonName ?? 'Button' - await page.locator('button', {hasText: buttonName}).first().waitFor() - const overlayButton = page.getByRole('button', {name: buttonName}).first() - await overlayButton.click() + // Open nested dialog if needed + if (story.openNestedDialog) { + await page.getByRole('button', {name: 'Open Inner Dialog'}).click() + } - // for the dev stories, we intentionally change the content after the overlay is open to test that it repositions correctly - if (story.waitForText) await page.getByText(story.waitForText).waitFor() + // If the story has multiple overlays, screenshot each one individually + if (story.buttonNames) { + for (const name of story.buttonNames) { + await page.locator('button', {hasText: name}).first().waitFor() + const btn = page.getByRole('button', {name}).first() + await btn.click() await waitForImages(page) expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( - `AnchoredOverlay.${story.title}.${theme}${namePostfix}.png`, + `AnchoredOverlay.${story.title}.${name}.${theme}.png`, ) + + // Close the overlay before opening the next one + await btn.click() } - }) - } + } else { + // Open the overlay + const buttonName = story.buttonName ?? 'Button' + await page.locator('button', {hasText: buttonName}).first().waitFor() + const overlayButton = page.getByRole('button', {name: buttonName}).first() + await overlayButton.click() + + // for the dev stories, we intentionally change the content after the overlay is open to test that it repositions correctly + if (story.waitForText) await page.getByText(story.waitForText).waitFor() + await waitForImages(page) + + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `AnchoredOverlay.${story.title}.${theme}.png`, + ) + } + }) }) } }) diff --git a/packages/react/src/ActionMenu/ActionMenu.test.tsx b/packages/react/src/ActionMenu/ActionMenu.test.tsx index 09303b7923b..83bf6a98e64 100644 --- a/packages/react/src/ActionMenu/ActionMenu.test.tsx +++ b/packages/react/src/ActionMenu/ActionMenu.test.tsx @@ -15,7 +15,6 @@ import type {AnchorPosition} from '@primer/behaviors' import type {JSX} from 'react' import {implementsClassName} from '../utils/testing' -import {FeatureFlags} from '../FeatureFlags' // Mock getAnchoredPosition for feature flag tests vi.mock('@primer/behaviors', async () => { @@ -101,29 +100,27 @@ function ExampleWithReplaceableAnchor(): JSX.Element { const [anchorKey, setAnchorKey] = useState(0) return ( - - - - - - - { - // Prevent the menu from closing so the overlay stays mounted - event.preventDefault() - setAnchorKey(k => k + 1) - }} - > - Switch anchor - - Item one - - - - - + + + + + + { + // Prevent the menu from closing so the overlay stays mounted + event.preventDefault() + setAnchorKey(k => k + 1) + }} + > + Switch anchor + + Item one + + + + ) } @@ -655,89 +652,29 @@ describe('ActionMenu', () => { expect(baseAnchor).not.toHaveAttribute('aria-expanded', 'true') }) - it('supports className prop on ActionMenu.Anchor with css anchor positioning flag', async () => { - const component = HTMLRender( - - - - - - - - - New file - - Copy link - Edit file - event.preventDefault()}> - Delete file - - - GitHub - - - - - - , - ) - const anchor = component.getByRole('button', {name: 'Toggle Menu'}) - expect(anchor).toHaveClass('test-class') - }) - - it('supports className prop on ActionMenu.Button with css anchor positioning flag', async () => { - const component = HTMLRender( - - - - Toggle Menu - - - New file - - Copy link - Edit file - event.preventDefault()}> - Delete file - - - GitHub - - - - - - , - ) - const button = component.getByRole('button', {name: 'Toggle Menu'}) - expect(button).toHaveClass('test-class') - }) - it('supports className prop on ActionMenu.Anchor', async () => { const component = HTMLRender( - - - - - - - - - New file - - Copy link - Edit file - event.preventDefault()}> - Delete file - - - GitHub - - - - - - , + + + + + + + + New file + + Copy link + Edit file + event.preventDefault()}> + Delete file + + + GitHub + + + + + , ) const anchor = component.getByRole('button', {name: 'Toggle Menu'}) expect(anchor).toHaveClass('test-class') @@ -745,27 +682,25 @@ describe('ActionMenu', () => { it('supports className prop on ActionMenu.Button', async () => { const component = HTMLRender( - - - - Toggle Menu - - - New file - - Copy link - Edit file - event.preventDefault()}> - Delete file - - - Github - - - - - - , + + + Toggle Menu + + + New file + + Copy link + Edit file + event.preventDefault()}> + Delete file + + + Github + + + + + , ) const button = component.getByRole('button', {name: 'Toggle Menu'}) expect(button).toHaveClass('test-class') diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx index 0613dd97493..d2f30bbdce4 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx @@ -8,7 +8,6 @@ import BaseStyles from '../BaseStyles' import type {AnchorPosition} from '@primer/behaviors' import {implementsClassName} from '../utils/testing' import {createRenderCounter} from '../utils/testing/profiler' -import {FeatureFlags} from '../FeatureFlags' import {registerPortalRoot} from '../Portal' import overlayClasses from '../Overlay/Overlay.module.css' @@ -21,7 +20,6 @@ type TestComponentSettings = { onCloseCallback?: (gesture: string) => void onPositionChange?: ({position}: {position: AnchorPosition}) => void className?: string - withCSSAnchorPositioningFeatureFlag?: boolean overlayProps?: Pick renderAs?: 'portal' | 'popover' } @@ -32,7 +30,6 @@ const AnchoredOverlayTestComponent = ({ onCloseCallback, onPositionChange, className, - withCSSAnchorPositioningFeatureFlag, overlayProps, renderAs, }: TestComponentSettings = {}) => { @@ -69,171 +66,114 @@ const AnchoredOverlayTestComponent = ({ ) - if (withCSSAnchorPositioningFeatureFlag !== undefined) { - return ( - - {content} - - ) - } - return content } -describe.each([true, false])( - 'AnchoredOverlay (primer_react_css_anchor_positioning=%s)', - (withCSSAnchorPositioningFeatureFlag: boolean) => { - implementsClassName( - props => ( - - ), - overlayClasses.Overlay, - ) - - it('should call onOpen when the anchor is clicked', async () => { - const mockOpenCallback = vi.fn() - const mockCloseCallback = vi.fn() - const anchoredOverlay = render( - , - ) - const anchor = anchoredOverlay.baseElement.querySelector('[aria-haspopup="true"]')! - await act(async () => { - await userEvent.click(anchor) - }) +describe('AnchoredOverlay', () => { + implementsClassName(props => , overlayClasses.Overlay) - expect(mockOpenCallback).toHaveBeenCalledTimes(1) - expect(mockOpenCallback).toHaveBeenCalledWith('anchor-click') - expect(mockCloseCallback).toHaveBeenCalledTimes(0) + it('should call onOpen when the anchor is clicked', async () => { + const mockOpenCallback = vi.fn() + const mockCloseCallback = vi.fn() + const anchoredOverlay = render( + , + ) + const anchor = anchoredOverlay.baseElement.querySelector('[aria-haspopup="true"]')! + await act(async () => { + await userEvent.click(anchor) }) - it('should call onOpen when the anchor activated by a key press', async () => { - const mockOpenCallback = vi.fn() - const mockCloseCallback = vi.fn() - const anchoredOverlay = render( - , - ) - const anchor = anchoredOverlay.baseElement.querySelector('[aria-haspopup="true"]')! - await act(async () => { - await userEvent.type(anchor, '{Space}') - }) + expect(mockOpenCallback).toHaveBeenCalledTimes(1) + expect(mockOpenCallback).toHaveBeenCalledWith('anchor-click') + expect(mockCloseCallback).toHaveBeenCalledTimes(0) + }) - expect(mockOpenCallback).toHaveBeenCalledTimes(1) - expect(mockOpenCallback).toHaveBeenCalledWith('anchor-key-press') - expect(mockCloseCallback).toHaveBeenCalledTimes(0) + it('should call onOpen when the anchor activated by a key press', async () => { + const mockOpenCallback = vi.fn() + const mockCloseCallback = vi.fn() + const anchoredOverlay = render( + , + ) + const anchor = anchoredOverlay.baseElement.querySelector('[aria-haspopup="true"]')! + await act(async () => { + await userEvent.type(anchor, '{Space}') }) - it('should call onClose when the user clicks off of the overlay', async () => { - const mockOpenCallback = vi.fn() - const mockCloseCallback = vi.fn() - const anchoredOverlay = render( - , - ) - await act(async () => { - await userEvent.click(anchoredOverlay.baseElement) - }) + expect(mockOpenCallback).toHaveBeenCalledTimes(1) + expect(mockOpenCallback).toHaveBeenCalledWith('anchor-key-press') + expect(mockCloseCallback).toHaveBeenCalledTimes(0) + }) - expect(mockOpenCallback).toHaveBeenCalledTimes(0) - expect(mockCloseCallback).toHaveBeenCalledTimes(1) - expect(mockCloseCallback).toHaveBeenCalledWith('click-outside') + it('should call onClose when the user clicks off of the overlay', async () => { + const mockOpenCallback = vi.fn() + const mockCloseCallback = vi.fn() + const anchoredOverlay = render( + , + ) + await act(async () => { + await userEvent.click(anchoredOverlay.baseElement) }) - it('should call onClose when the escape key is pressed', async () => { - const mockOpenCallback = vi.fn() - const mockCloseCallback = vi.fn() - - render( - , - ) + expect(mockOpenCallback).toHaveBeenCalledTimes(0) + expect(mockCloseCallback).toHaveBeenCalledTimes(1) + expect(mockCloseCallback).toHaveBeenCalledWith('click-outside') + }) - await act(async () => { - await userEvent.keyboard('{Escape}') - }) + it('should call onClose when the escape key is pressed', async () => { + const mockOpenCallback = vi.fn() + const mockCloseCallback = vi.fn() - expect(mockOpenCallback).toHaveBeenCalledTimes(0) - expect(mockCloseCallback).toHaveBeenCalledTimes(1) - expect(mockCloseCallback).toHaveBeenCalledWith('escape') - }) - - it.skipIf(withCSSAnchorPositioningFeatureFlag)('should call onPositionChange when provided', async () => { - const mockPositionChangeCallback = vi.fn(({position}: {position: AnchorPosition}) => position) - render( - , - ) - - await act(async () => { - await userEvent.keyboard('{Escape}') - }) + render( + , + ) - expect(mockPositionChangeCallback).toHaveBeenCalled() - expect(mockPositionChangeCallback).toHaveBeenCalledWith({ - position: { - anchorAlign: 'start', - anchorSide: 'outside-bottom', - left: 0, - top: 36, - }, - }) + await act(async () => { + await userEvent.keyboard('{Escape}') }) - it('should support a `ref` through `overlayProps` on the overlay element', () => { - const ref = createRef() + expect(mockOpenCallback).toHaveBeenCalledTimes(0) + expect(mockCloseCallback).toHaveBeenCalledTimes(1) + expect(mockCloseCallback).toHaveBeenCalledWith('escape') + }) - function Test() { - const anchorRef = useRef(null) - return ( - - { - return ( - - ) - }} - > -
content
-
-
- ) - } + it('should support a `ref` through `overlayProps` on the overlay element', () => { + const ref = createRef() + + function Test() { + const anchorRef = useRef(null) + return ( + { + return ( + + ) + }} + > +
content
+
+ ) + } - render() + render() - expect(ref.current).toBeInstanceOf(HTMLDivElement) - expect(ref.current).toHaveAttribute('data-component', 'AnchoredOverlay') - }) - }, -) + expect(ref.current).toBeInstanceOf(HTMLDivElement) + expect(ref.current).toHaveAttribute('data-component', 'AnchoredOverlay') + }) +}) describe('AnchoredOverlay scroll/resize cascade', () => { it('does not re-render after close when window scrolls (closed-overlay listeners detached)', async () => { @@ -287,24 +227,18 @@ describe('AnchoredOverlay scroll/resize cascade', () => { }) }) -describe('AnchoredOverlay feature flag specific behavior', () => { - describe('with primer_react_css_anchor_positioning feature flag enabled', () => { - it('should render overlay as visible immediately when flag is enabled', () => { - const {baseElement} = render( - - - , - ) +describe('AnchoredOverlay CSS anchor positioning behavior', () => { + describe('when the browser supports native CSS anchor positioning', () => { + it('should render overlay as visible immediately', () => { + const {baseElement} = render() const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') expect(overlay).toHaveAttribute('data-visibility-visible', '') }) - it('should use portal when flag is enabled', () => { + it('should use portal by default', () => { const {baseElement} = render( - - - , + , ) const portalRoot = baseElement.querySelector('#__primerPortalRoot__') @@ -314,19 +248,17 @@ describe('AnchoredOverlay feature flag specific behavior', () => { it('should not use portal when _PrivateDisablePortal is passed via overlayProps', () => { const {baseElement, container} = render( - - - {}} - onClose={() => {}} - renderAnchor={props => } - overlayProps={{_PrivateDisablePortal: true}} - > - - - - , + + {}} + onClose={() => {}} + renderAnchor={props => } + overlayProps={{_PrivateDisablePortal: true}} + > + + + , ) // The overlay should not be inside the portal root @@ -339,45 +271,29 @@ describe('AnchoredOverlay feature flag specific behavior', () => { expect(overlayInContainer).toBeInTheDocument() }) - it('should apply AnchoredOverlay class to overlay when flag is enabled', () => { - const {baseElement} = render( - - - , - ) + it('should apply AnchoredOverlay class to overlay', () => { + const {baseElement} = render() const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') expect(overlay).toHaveClass(anchoredOverlayClasses.AnchoredOverlay) }) - it('should set data-anchor-position attribute when flag is enabled', () => { - const {baseElement} = render( - - - , - ) + it('should set data-anchor-position attribute', () => { + const {baseElement} = render() const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') expect(overlay).toHaveAttribute('data-anchor-position', 'true') }) it('should set popover="manual" on overlay when renderAs is "popover"', () => { - const {baseElement} = render( - - - , - ) + const {baseElement} = render() const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') expect(overlay).toHaveAttribute('popover', 'manual') }) it('should set popovertarget on anchor when renderAs is "popover"', () => { - const {baseElement} = render( - - - , - ) + const {baseElement} = render() const anchor = baseElement.querySelector('[aria-haspopup="true"]') const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') @@ -386,47 +302,37 @@ describe('AnchoredOverlay feature flag specific behavior', () => { }) it('should not set popover attribute on overlay when renderAs is "portal"', () => { - const {baseElement} = render( - - - , - ) + const {baseElement} = render() const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') expect(overlay).not.toHaveAttribute('popover') }) it('should not set popover attribute on overlay when renderAs defaults to "portal"', () => { - const {baseElement} = render( - - - , - ) + const {baseElement} = render() const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') expect(overlay).not.toHaveAttribute('popover') }) describe('when overlayProps.portalContainerName is provided', () => { - it('should fall back to JS positioning (data-anchor-position="false") even with the flag enabled', () => { + it('should fall back to JS positioning (data-anchor-position="false")', () => { const portalRoot = document.createElement('div') document.body.appendChild(portalRoot) registerPortalRoot(portalRoot, 'anchoredOverlayTestPortal') const {baseElement} = render( - - - {}} - onClose={() => {}} - renderAnchor={props => } - overlayProps={{portalContainerName: 'anchoredOverlayTestPortal'}} - > - - - - , + + {}} + onClose={() => {}} + renderAnchor={props => } + overlayProps={{portalContainerName: 'anchoredOverlayTestPortal'}} + > + + + , ) const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') @@ -442,20 +348,18 @@ describe('AnchoredOverlay feature flag specific behavior', () => { registerPortalRoot(portalRoot, 'anchoredOverlayTestPortalPopover') const {baseElement} = render( - - - {}} - onClose={() => {}} - renderAnchor={props => } - renderAs="popover" - overlayProps={{portalContainerName: 'anchoredOverlayTestPortalPopover'}} - > - - - - , + + {}} + onClose={() => {}} + renderAnchor={props => } + renderAs="popover" + overlayProps={{portalContainerName: 'anchoredOverlayTestPortalPopover'}} + > + + + , ) const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') @@ -468,43 +372,6 @@ describe('AnchoredOverlay feature flag specific behavior', () => { }) }) }) - - describe('with primer_react_css_anchor_positioning feature flag disabled', () => { - it('should use portal when flag is disabled', () => { - const {baseElement} = render( - - - , - ) - - // The overlay should be inside the portal root - const portalRoot = baseElement.querySelector('#__primerPortalRoot__') - const overlayInPortal = portalRoot?.querySelector('[data-component="AnchoredOverlay"]') - expect(overlayInPortal).toBeInTheDocument() - }) - - it('should set data-anchor-position to false when flag is disabled', () => { - const {baseElement} = render( - - - , - ) - - const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') - expect(overlay).toHaveAttribute('data-anchor-position', 'false') - }) - - it('should not set popover attribute on overlay when renderAs is "popover" but flag is disabled', () => { - const {baseElement} = render( - - - , - ) - - const overlay = baseElement.querySelector('[data-component="AnchoredOverlay"]') - expect(overlay).not.toHaveAttribute('popover') - }) - }) }) describe('AnchoredOverlay CSS anchor positioning viewport handling', () => { @@ -512,30 +379,28 @@ describe('AnchoredOverlay CSS anchor positioning viewport handling', () => { function TestComponent() { const ref = useRef(null) return ( - - - {/* Position anchor near the bottom so the overlay starts low and overflows */} - - {}} - onClose={() => {}} - renderAnchor={null} - anchorRef={ref} - side="outside-bottom" - > - {/* Content sized to fill the overlay's max-height */} -
tall content
-
-
-
+ + {/* Position anchor near the bottom so the overlay starts low and overflows */} + + {}} + onClose={() => {}} + renderAnchor={null} + anchorRef={ref} + side="outside-bottom" + > + {/* Content sized to fill the overlay's max-height */} +
tall content
+
+
) } @@ -560,16 +425,14 @@ describe('AnchoredOverlay CSS anchor positioning viewport handling', () => { function TestComponent() { const ref = useRef(null) return ( - - - - {}} onClose={() => {}} renderAnchor={null} anchorRef={ref}> -
short content
-
-
-
+ + + {}} onClose={() => {}} renderAnchor={null} anchorRef={ref}> +
short content
+
+
) } @@ -588,29 +451,27 @@ describe('AnchoredOverlay CSS anchor positioning viewport handling', () => { // Anchor pinned to the bottom-right corner so an outside-bottom + start // overlay would overflow both the right and bottom edges of the viewport. return ( - - - - {}} - onClose={() => {}} - renderAnchor={null} - anchorRef={ref} - side="outside-bottom" - width="medium" - > -
tall content
-
-
-
+ + + {}} + onClose={() => {}} + renderAnchor={null} + anchorRef={ref} + side="outside-bottom" + width="medium" + > +
tall content
+
+
) } @@ -637,7 +498,7 @@ describe('AnchoredOverlay anchor element replacement', () => { const [anchorKey, setAnchorKey] = useState(0) return ( - + <> @@ -656,7 +517,7 @@ describe('AnchoredOverlay anchor element replacement', () => { >
content
-
+ ) } diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index f532d590d81..eeea8a91f2d 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -14,7 +14,6 @@ import {IconButton, type IconButtonProps} from '../Button' import {XIcon} from '@primer/octicons-react' import classes from './AnchoredOverlay.module.css' import {clsx} from 'clsx' -import {useFeatureFlag} from '../FeatureFlags' import {widthMap} from '../Overlay/Overlay' interface AnchoredOverlayPropsWithAnchor { @@ -168,7 +167,6 @@ export const AnchoredOverlay: React.FC { - const cssAnchorPositioningFlag = useFeatureFlag('primer_react_css_anchor_positioning') // Lazy initial state so feature detection runs once per mount on the client. // Guarded for SSR where `document` is undefined. const [supportsNativeCSSAnchorPositioning] = useState( @@ -179,8 +177,7 @@ export const AnchoredOverlay: React.FC( const mergedOverlayRef = useMergedRefs(forwardedRef, overlayRef) const slideAnimationDistance = 8 // var(--base-size-8), hardcoded to do some math const slideAnimationEasing = 'cubic-bezier(0.33, 1, 0.68, 1)' - const cssAnchorPositioning = useFeatureFlag('primer_react_css_anchor_positioning') useOverlay({ overlayRef, @@ -252,7 +250,7 @@ const Overlay = React.forwardRef( // not strictly necessary. However, Portal can still be useful for // style isolation. Defaults to false (Portal enabled) for backwards // compatibility. - if (_PrivateDisablePortal && cssAnchorPositioning) { + if (_PrivateDisablePortal) { return overlayContent }