diff --git a/.changeset/responsive-anchored-overlay.md b/.changeset/responsive-anchored-overlay.md new file mode 100644 index 00000000000..dd3590df18b --- /dev/null +++ b/.changeset/responsive-anchored-overlay.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +AnchoredOverlay: Add prop to set responsive variant. Example: `variant: {regular: 'anchored', narrow: 'anchored'}` diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Default-responsive-width-light-modern-action-list--true-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Default-responsive-width-light-modern-action-list--true-linux.png new file mode 100644 index 00000000000..1b994c3baf1 Binary files /dev/null and b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Default-responsive-width-light-modern-action-list--true-linux.png differ diff --git a/e2e/components/SelectPanel.test.ts b/e2e/components/SelectPanel.test.ts index 09904ec1816..49198328841 100644 --- a/e2e/components/SelectPanel.test.ts +++ b/e2e/components/SelectPanel.test.ts @@ -108,4 +108,24 @@ test.describe('SelectPanel', () => { `SelectPanel-Default-forced-colors-dark-modern-action-list--true.png`, ) }) + + test(`Default @vrt responsive width .modern-action-list--true`, async ({page}) => { + await visit(page, { + id: 'components-selectpanel--default', + globals: {featureFlags: {primer_react_select_panel_with_modern_action_list: true}}, + }) + + await page.setViewportSize({width: 767, height: 767}) + + // Open select panel + const isPanelOpen = await page.isVisible('[role="listbox"]') + if (!isPanelOpen) { + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') + } + + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `SelectPanel-Default-responsive-width-light-modern-action-list--true.png`, + ) + }) }) diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.docs.json b/packages/react/src/AnchoredOverlay/AnchoredOverlay.docs.json index 4ecfb62eb2a..8d52c6955ef 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.docs.json +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.docs.json @@ -147,12 +147,20 @@ "required": false, "description": "", "defaultValue": "" - }, { + }, + { "name": "pinPosition", "type": "boolean", "required": false, "description": "If true, the overlay will attempt to prevent position shifting when sitting at the top of the anchor.", "defaultValue": "false" + }, + { + "name": "variant", + "type": "{ regular?: 'anchored', narrow?: 'anchored' | 'fullscreen' }", + "required": false, + "description": "Optional prop to set variant for narrow screen sizes", + "defaultValue": "{ regular: 'anchored', narrow: 'anchored' }" } ] } diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index 5f69fcb168f..cb4de3af3a3 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -8,6 +8,7 @@ import {useFocusZone} from '../hooks/useFocusZone' import {useAnchoredPosition, useProvidedRefOrCreate, useRenderForcingRef} from '../hooks' import {useId} from '../hooks/useId' import type {PositionSettings} from '@primer/behaviors' +import {useResponsiveValue, type ResponsiveValue} from '../hooks/useResponsiveValue' interface AnchoredOverlayPropsWithAnchor { /** @@ -93,6 +94,10 @@ interface AnchoredOverlayBaseProps extends Pick } export type AnchoredOverlayProps = AnchoredOverlayBaseProps & @@ -122,6 +127,7 @@ export const AnchoredOverlay: React.FC { const anchorRef = useProvidedRefOrCreate(externalAnchorRef) @@ -183,6 +189,8 @@ export const AnchoredOverlay: React.FC {renderAnchor && @@ -206,8 +214,9 @@ export const AnchoredOverlay: React.FC, 'confirmButtonType'>) => { const [isOpen, setIsOpen] = useState(false) diff --git a/packages/react/src/Overlay/Overlay.module.css b/packages/react/src/Overlay/Overlay.module.css index 113d82b8bc5..5aab9024d0a 100644 --- a/packages/react/src/Overlay/Overlay.module.css +++ b/packages/react/src/Overlay/Overlay.module.css @@ -156,4 +156,13 @@ &:where([data-visibility-hidden]) { visibility: hidden; } + + &:where([data-variant='fullscreen']) { + top: 0; + left: 0; + width: 100vw; + height: 100vh; + margin: 0; + border-radius: unset; + } } diff --git a/packages/react/src/Overlay/Overlay.test.tsx b/packages/react/src/Overlay/Overlay.test.tsx index f52ebaba446..66cbb7982b2 100644 --- a/packages/react/src/Overlay/Overlay.test.tsx +++ b/packages/react/src/Overlay/Overlay.test.tsx @@ -8,6 +8,9 @@ import BaseStyles from '../BaseStyles' import {ThemeProvider} from '../ThemeProvider' import {NestedOverlays, MemexNestedOverlays, MemexIssueOverlay, PositionedOverlays} from './Overlay.features.stories' import {FeatureFlags} from '../FeatureFlags' +import {setupMatchMedia} from '../utils/test-helpers' + +setupMatchMedia() type TestComponentSettings = { initialFocus?: 'button' diff --git a/packages/react/src/Overlay/Overlay.tsx b/packages/react/src/Overlay/Overlay.tsx index d3bedcfeb21..519be0d24f4 100644 --- a/packages/react/src/Overlay/Overlay.tsx +++ b/packages/react/src/Overlay/Overlay.tsx @@ -103,6 +103,15 @@ const StyledOverlay = toggleStyledComponent( max-width: calc(100vw - 2rem); } + &:where([data-variant='fullscreen']) { + top: 0; + left: 0; + width: 100vw; + height: 100vh; + margin: 0; + border-radius: unset; + } + ${sx}; `, ) diff --git a/packages/react/src/SelectPanel/SelectPanel.test.tsx b/packages/react/src/SelectPanel/SelectPanel.test.tsx index 8e7cb169127..877fa43ba19 100644 --- a/packages/react/src/SelectPanel/SelectPanel.test.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.test.tsx @@ -10,6 +10,9 @@ import {getLiveRegion} from '../utils/testing' import {IconButton} from '../Button' import {ArrowLeftIcon} from '@primer/octicons-react' import Box from '../Box' +import {setupMatchMedia} from '../utils/test-helpers' + +setupMatchMedia() const renderWithFlag = (children: React.ReactNode, flag: boolean) => { return render( diff --git a/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx b/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx index e832be1ec9c..c019df7c042 100644 --- a/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx +++ b/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx @@ -6,6 +6,9 @@ import {render as HTMLRender} from '@testing-library/react' import theme from '../../theme' import {Button, IconButton, ActionMenu, ActionList, ThemeProvider, BaseStyles, ButtonGroup} from '../..' import {XIcon} from '@primer/octicons-react' +import {setupMatchMedia} from '../../utils/test-helpers' + +setupMatchMedia() const TooltipComponent = (props: Omit & {text?: string}) => ( diff --git a/packages/react/src/__tests__/ActionMenu.test.tsx b/packages/react/src/__tests__/ActionMenu.test.tsx index 29b4e165dea..64b488b2679 100644 --- a/packages/react/src/__tests__/ActionMenu.test.tsx +++ b/packages/react/src/__tests__/ActionMenu.test.tsx @@ -10,6 +10,9 @@ import {behavesAsComponent, checkExports} from '../utils/testing' import {SingleSelect} from '../ActionMenu/ActionMenu.features.stories' import {MixedSelection} from '../ActionMenu/ActionMenu.examples.stories' import {SearchIcon, KebabHorizontalIcon} from '@primer/octicons-react' +import {setupMatchMedia} from '../utils/test-helpers' + +setupMatchMedia() function Example(): JSX.Element { return ( diff --git a/packages/react/src/__tests__/AnchoredOverlay.test.tsx b/packages/react/src/__tests__/AnchoredOverlay.test.tsx index 761a1597390..e79514a04ef 100644 --- a/packages/react/src/__tests__/AnchoredOverlay.test.tsx +++ b/packages/react/src/__tests__/AnchoredOverlay.test.tsx @@ -7,6 +7,9 @@ import {Button} from '../Button' import theme from '../theme' import BaseStyles from '../BaseStyles' import {ThemeProvider} from '../ThemeProvider' +import {setupMatchMedia} from '../utils/test-helpers' + +setupMatchMedia() type TestComponentSettings = { initiallyOpen?: boolean diff --git a/packages/react/src/__tests__/LabelGroup.test.tsx b/packages/react/src/__tests__/LabelGroup.test.tsx index 77a89f65581..dc48c8fe1d3 100644 --- a/packages/react/src/__tests__/LabelGroup.test.tsx +++ b/packages/react/src/__tests__/LabelGroup.test.tsx @@ -5,6 +5,9 @@ import {LabelGroup, Label, ThemeProvider, BaseStyles} from '..' import {behavesAsComponent, checkExports} from '../utils/testing' import theme from '../theme' import userEvent from '@testing-library/user-event' +import {setupMatchMedia} from '../utils/test-helpers' + +setupMatchMedia() const ThemeAndStyleContainer: React.FC = ({children}) => ( diff --git a/packages/react/src/__tests__/__snapshots__/AnchoredOverlay.test.tsx.snap b/packages/react/src/__tests__/__snapshots__/AnchoredOverlay.test.tsx.snap index 169fa86efef..ce4d66ef15a 100644 --- a/packages/react/src/__tests__/__snapshots__/AnchoredOverlay.test.tsx.snap +++ b/packages/react/src/__tests__/__snapshots__/AnchoredOverlay.test.tsx.snap @@ -30,6 +30,15 @@ exports[`AnchoredOverlay should render consistently when open 1`] = ` max-width: calc(100vw - 2rem); } +.c1:where([data-variant='fullscreen']) { + top: 0; + left: 0; + width: 100vw; + height: 100vh; + margin: 0; + border-radius: unset; +} + @media (forced-colors:active) { .c1 { outline: solid 1px transparent; @@ -82,6 +91,7 @@ exports[`AnchoredOverlay should render consistently when open 1`] = `
{ + // window.matchMedia() is not implemented by JSDOM so we have to create a mock: + // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }) +}