diff --git a/.changeset/happy-snails-learn.md b/.changeset/happy-snails-learn.md new file mode 100644 index 00000000000..80f96117fef --- /dev/null +++ b/.changeset/happy-snails-learn.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +PageHeader: Add visual ordering for layout enforcement diff --git a/src/PageHeader/PageHeader.tsx b/src/PageHeader/PageHeader.tsx index f43aa9a8d40..b702359816e 100644 --- a/src/PageHeader/PageHeader.tsx +++ b/src/PageHeader/PageHeader.tsx @@ -14,6 +14,21 @@ const REGION_ORDER = { Navigation: 3, } +const CONTEXT_AREA_REGION_ORDER = { + ParentLink: 0, + ContextBar: 1, + ContextAreaActions: 2, +} + +const TITLE_AREA_REGION_ORDER = { + LeadingAction: 0, + LeadingVisual: 1, + Title: 2, + TrailingVisual: 3, + TrailingAction: 4, + Actions: 5, +} + // Types that are shared between sub components export type sharedPropTypes = { hidden?: boolean | ResponsiveValue @@ -105,6 +120,7 @@ const ParentLink = React.forwardRef( { display: 'flex', alignItems: 'center', + order: CONTEXT_AREA_REGION_ORDER.ParentLink, gap: '0.5rem', ...getBreakpointDeclarations(hidden, 'display', value => { return value ? 'none' : 'flex' @@ -136,6 +152,7 @@ const ContextBar: React.FC> = ({ sx={merge( { display: 'flex', + order: CONTEXT_AREA_REGION_ORDER.ContextBar, ...getBreakpointDeclarations(hidden, 'display', value => { return value ? 'none' : 'flex' }), @@ -161,6 +178,7 @@ const ContextAreaActions: React.FC> = ( { display: 'flex', flexDirection: 'row', + order: CONTEXT_AREA_REGION_ORDER.ContextAreaActions, alignItems: 'center', gap: '0.5rem', flexGrow: '1', @@ -211,6 +229,7 @@ const TitleArea: React.FC> = ({ { display: 'flex', gap: '0.5rem', + order: REGION_ORDER.TitleArea, ...getBreakpointDeclarations(hidden, 'display', value => { return value ? 'none' : 'flex' }), @@ -238,6 +257,7 @@ const LeadingAction: React.FC> = ({ sx={merge( { display: 'flex', + order: TITLE_AREA_REGION_ORDER.LeadingAction, ...getBreakpointDeclarations(hidden, 'display', value => { return value ? 'none' : 'flex' }), @@ -259,6 +279,7 @@ const LeadingVisual: React.FC> = ({chil sx={merge( { display: 'flex', + order: TITLE_AREA_REGION_ORDER.LeadingVisual, ...getBreakpointDeclarations(hidden, 'display', value => { return value ? 'none' : 'flex' }), @@ -302,6 +323,7 @@ const Title: React.FC> = ({children, sx = {} subtitle: '400', }[titleVariant], display: 'flex', + order: TITLE_AREA_REGION_ORDER.Title, ...getBreakpointDeclarations(hidden, 'display', value => { return value ? 'none' : 'flex' }), @@ -321,6 +343,7 @@ const TrailingVisual: React.FC> = ({chi sx={merge( { display: 'flex', + order: TITLE_AREA_REGION_ORDER.TrailingVisual, ...getBreakpointDeclarations(hidden, 'display', value => { return value ? 'none' : 'flex' }), @@ -347,6 +370,7 @@ const TrailingAction: React.FC> = ({ sx={merge( { display: 'flex', + order: TITLE_AREA_REGION_ORDER.TrailingAction, ...getBreakpointDeclarations(hidden, 'display', value => { return value ? 'none' : 'flex' }), @@ -368,6 +392,7 @@ const Actions: React.FC> = ({children, sx={merge( { display: 'flex', + order: TITLE_AREA_REGION_ORDER.Actions, ...getBreakpointDeclarations(hidden, 'display', value => { return value ? 'none' : 'flex' }), @@ -393,6 +418,7 @@ const Description: React.FC> = ({childr sx={merge( { display: 'flex', + order: REGION_ORDER.Description, ...getBreakpointDeclarations(hidden, 'display', value => { return value ? 'none' : 'flex' }), @@ -415,6 +441,7 @@ const Navigation: React.FC> = ({childre sx={merge( { display: 'flex', + order: REGION_ORDER.Navigation, ...getBreakpointDeclarations(hidden, 'display', value => { return value ? 'none' : 'block' }), diff --git a/src/PageHeader/__snapshots__/PageHeader.test.tsx.snap b/src/PageHeader/__snapshots__/PageHeader.test.tsx.snap index 25f75b6d9b1..3ed3eac0cd7 100644 --- a/src/PageHeader/__snapshots__/PageHeader.test.tsx.snap +++ b/src/PageHeader/__snapshots__/PageHeader.test.tsx.snap @@ -36,6 +36,9 @@ exports[`PageHeader renders consistently 1`] = ` display: -ms-flexbox; display: flex; gap: 0.5rem; + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -50,6 +53,9 @@ exports[`PageHeader renders consistently 1`] = ` display: -webkit-flex; display: -ms-flexbox; display: flex; + -webkit-order: 2; + -ms-flex-order: 2; + order: 2; -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -62,6 +68,9 @@ exports[`PageHeader renders consistently 1`] = ` .c4 { display: block; + -webkit-order: 3; + -ms-flex-order: 3; + order: 3; } @media screen and (max-width:calc(768px - 0.02px)) { @@ -133,6 +142,9 @@ exports[`PageHeader renders default layout 1`] = ` display: -ms-flexbox; display: flex; gap: 0.5rem; + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -147,6 +159,9 @@ exports[`PageHeader renders default layout 1`] = ` display: -webkit-flex; display: -ms-flexbox; display: flex; + -webkit-order: 2; + -ms-flex-order: 2; + order: 2; -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -159,6 +174,9 @@ exports[`PageHeader renders default layout 1`] = ` .c4 { display: block; + -webkit-order: 3; + -ms-flex-order: 3; + order: 3; } @media screen and (max-width:calc(768px - 0.02px)) { diff --git a/src/PageHeader/examples.stories.tsx b/src/PageHeader/examples.stories.tsx index 92eec4312e6..7b2c8dbfd6d 100644 --- a/src/PageHeader/examples.stories.tsx +++ b/src/PageHeader/examples.stories.tsx @@ -1,6 +1,6 @@ import React from 'react' import {Meta} from '@storybook/react' -import {Button, IconButton, Breadcrumbs, Link, Text, StateLabel, BranchName, Box} from '..' +import {Button, IconButton, Breadcrumbs, Link, Text, StateLabel, BranchName, Box, PageLayout} from '..' import { KebabHorizontalIcon, GitBranchIcon, @@ -158,6 +158,110 @@ export const FilesPageOnNarrowViewport = () => { return } +export const WithPageLayout = () => { + return ( + + + + + Pull requests + + + + PageHeader component initial layout explorations extra long pull request title   + #1831 + + + + + {/* Pop up actions */} + + + + + + + + + + + + Open + + + + broccolinisoup + {' '} + wants to merge 3 commits into main from{' '} + broccolinisoup/switch-to-new-underlineNav + + + + + main + + page-header-initial + + + + + + + Conversation + + + Commits + + + Checks + + + Files Changes + + + + + + + + + This box has really long content. If it is too long, it will cause x overflow and should show a scrollbar. + When this overflows, it should not break to overall page layout! + + + + + + Assignees + + No one –{' '} + + assign yourself + + + + + + Labels + None yet + + + + + ) +} + FilesPageOnNarrowViewport.parameters = setViewportParamToNarrow export default meta diff --git a/src/utils/testing.tsx b/src/utils/testing.tsx index a84e0053dc4..ef5433002bb 100644 --- a/src/utils/testing.tsx +++ b/src/utils/testing.tsx @@ -247,8 +247,25 @@ export function checkStoriesForAxeViolations(name: string, storyDir?: string) { if (typeof Story !== 'function') return const {storyName, name: StoryFunctionName} = Story as StoryType + + beforeEach(() => { + // IntersectionObserver isn't available in test environment + const mockIntersectionObserver = jest.fn() + mockIntersectionObserver.mockReturnValue({ + observe: () => null, + unobserve: () => null, + disconnect: () => null, + }) + window.IntersectionObserver = mockIntersectionObserver + }) + it(`story ${storyName || StoryFunctionName} should have no axe violations`, async () => { - const {container} = HTMLRender() + const {container} = HTMLRender( + + + , + ) + const results = await axe(container) expect(results).toHaveNoViolations() })