diff --git a/.changeset/pink-windows-add.md b/.changeset/pink-windows-add.md
new file mode 100644
index 00000000000..96bb3e319f4
--- /dev/null
+++ b/.changeset/pink-windows-add.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": minor
+---
+
+Adds `hasBorder` prop to PageHeader to allow a bottom border
diff --git a/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-colorblind-linux.png b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-colorblind-linux.png
new file mode 100644
index 00000000000..36e53264c8a
Binary files /dev/null and b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-colorblind-linux.png differ
diff --git a/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-dimmed-linux.png b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-dimmed-linux.png
new file mode 100644
index 00000000000..40e9d7f882e
Binary files /dev/null and b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-dimmed-linux.png differ
diff --git a/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-high-contrast-linux.png b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-high-contrast-linux.png
new file mode 100644
index 00000000000..9e147048a42
Binary files /dev/null and b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-high-contrast-linux.png differ
diff --git a/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-linux.png b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-linux.png
new file mode 100644
index 00000000000..36e53264c8a
Binary files /dev/null and b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-linux.png differ
diff --git a/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-tritanopia-linux.png b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-tritanopia-linux.png
new file mode 100644
index 00000000000..36e53264c8a
Binary files /dev/null and b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-dark-tritanopia-linux.png differ
diff --git a/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-colorblind-linux.png b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-colorblind-linux.png
new file mode 100644
index 00000000000..7f4a3d0c02f
Binary files /dev/null and b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-colorblind-linux.png differ
diff --git a/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-high-contrast-linux.png b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-high-contrast-linux.png
new file mode 100644
index 00000000000..3847974402c
Binary files /dev/null and b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-high-contrast-linux.png differ
diff --git a/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-linux.png b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-linux.png
new file mode 100644
index 00000000000..7f4a3d0c02f
Binary files /dev/null and b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-linux.png differ
diff --git a/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-tritanopia-linux.png b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-tritanopia-linux.png
new file mode 100644
index 00000000000..7f4a3d0c02f
Binary files /dev/null and b/.playwright/snapshots/components/PageHeader.test.ts-snapshots/PageHeader-Has-Border-light-tritanopia-linux.png differ
diff --git a/e2e/components/PageHeader.test.ts b/e2e/components/PageHeader.test.ts
index c183cb247fe..f641d5f1af1 100644
--- a/e2e/components/PageHeader.test.ts
+++ b/e2e/components/PageHeader.test.ts
@@ -185,6 +185,24 @@ test.describe('PageHeader', () => {
}
})
+ test.describe('Has Border', () => {
+ for (const theme of themes) {
+ test.describe(theme, () => {
+ test('default @vrt', async ({page}) => {
+ await visit(page, {
+ id: 'components-pageheader-features--has-bottom-border',
+ globals: {
+ colorScheme: theme,
+ },
+ })
+
+ // Default state
+ expect(await page.screenshot()).toMatchSnapshot(`PageHeader.Has Border.${theme}.png`)
+ })
+ })
+ }
+ })
+
test.describe('Has Large Title', () => {
for (const theme of themes) {
test.describe(theme, () => {
diff --git a/packages/react/src/PageHeader/PageHeader.docs.json b/packages/react/src/PageHeader/PageHeader.docs.json
index 5a599706f2b..e2d05a1cc5e 100644
--- a/packages/react/src/PageHeader/PageHeader.docs.json
+++ b/packages/react/src/PageHeader/PageHeader.docs.json
@@ -77,6 +77,11 @@
"name": "as",
"type": "React.ElementType",
"defaultValue": "\"div\""
+ },
+ {
+ "name": "hasBorder",
+ "type": "boolean",
+ "description": "Whether to render a border below the PageHeader. This border will NOT be rendered if the PageHeader has a `PageHeader.Navigation` child that is not hidden at the current breakpoint."
}
],
"subcomponents": [
diff --git a/packages/react/src/PageHeader/PageHeader.features.stories.tsx b/packages/react/src/PageHeader/PageHeader.features.stories.tsx
index 5df0ce6caae..7c21f166b9b 100644
--- a/packages/react/src/PageHeader/PageHeader.features.stories.tsx
+++ b/packages/react/src/PageHeader/PageHeader.features.stories.tsx
@@ -266,4 +266,14 @@ export const WithActionsThatHaveResponsiveContent = () => (
)
+export const HasBottomBorder = () => (
+
+
+
+ Title
+
+
+
+)
+
export default meta
diff --git a/packages/react/src/PageHeader/PageHeader.module.css b/packages/react/src/PageHeader/PageHeader.module.css
index 1d84ab75981..55fa99714c5 100644
--- a/packages/react/src/PageHeader/PageHeader.module.css
+++ b/packages/react/src/PageHeader/PageHeader.module.css
@@ -58,6 +58,33 @@
--title-line-height: var(--custom-line-height, var(--text-title-lineHeight-medium, 1.6));
}
+ &[data-has-border='true']:has([data-component='PH_Navigation'][data-hidden-all]),
+ &[data-has-border='true']:not(:has([data-component='PH_Navigation'])) {
+ border-block-end: var(--borderWidth-thin) solid var(--borderColor-default);
+ padding-block-end: var(--base-size-8);
+ }
+
+ @media screen and (max-width: 768px) {
+ &[data-has-border='true']:has([data-component='PH_Navigation'][data-hidden-narrow]) {
+ border-block-end: var(--borderWidth-thin) solid var(--borderColor-default);
+ padding-block-end: var(--base-size-8);
+ }
+ }
+
+ @media screen and (min-width: 768px) {
+ &[data-has-border='true']:has([data-component='PH_Navigation'][data-hidden-regular]) {
+ border-block-end: var(--borderWidth-thin) solid var(--borderColor-default);
+ padding-block-end: var(--base-size-8);
+ }
+ }
+
+ @media screen and (min-width: 1440px) {
+ &[data-has-border='true']:has([data-component='PH_Navigation'][data-hidden-wide]) {
+ border-block-end: var(--borderWidth-thin) solid var(--borderColor-default);
+ padding-block-end: var(--base-size-8);
+ }
+ }
+
& [data-component='PH_LeadingAction'],
& [data-component='PH_TrailingAction'],
& [data-component='PH_Actions'],
diff --git a/packages/react/src/PageHeader/PageHeader.stories.tsx b/packages/react/src/PageHeader/PageHeader.stories.tsx
index 7341e7d9ec2..65d7400bab8 100644
--- a/packages/react/src/PageHeader/PageHeader.stories.tsx
+++ b/packages/react/src/PageHeader/PageHeader.stories.tsx
@@ -59,6 +59,9 @@ const meta: Meta = {
description:
'ContextArea is only visible on narrow viewports by default to provide user context of where they are at their journey.',
},
+ hasBorder: {
+ type: 'boolean',
+ },
ParentLink: {
type: 'string',
if: {arg: 'hasContextArea'},
@@ -189,7 +192,7 @@ export default meta
export const Playground: StoryFn = args => (
-
+
>(
- ({children, className, sx = {}, as = 'div', 'aria-label': ariaLabel, role}, forwardedRef) => {
+ ({children, className, sx = {}, as = 'div', 'aria-label': ariaLabel, role, hasBorder}, forwardedRef) => {
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)
const rootStyles = {
@@ -116,6 +117,29 @@ const Root = React.forwardRef(forwardedRef as React.RefObject)
@@ -176,6 +200,7 @@ const Root = React.forwardRef(rootStyles, sx)}
aria-label={ariaLabel}
role={role}
@@ -754,6 +779,7 @@ const Navigation: React.FC> = ({
aria-label={as === 'nav' ? ariaLabel : undefined}
aria-labelledby={as === 'nav' ? ariaLabelledBy : undefined}
className={clsx(enabled && classes.Navigation, className)}
+ data-component="PH_Navigation"
sx={
enabled
? sx
@@ -773,7 +799,9 @@ const Navigation: React.FC> = ({
sx,
)
}
- {...getHiddenDataAttributes(enabled, hidden)}
+ // passing `true` always get the data attributes for the hidden prop,
+ // not just for CSS modules
+ {...getHiddenDataAttributes(true, hidden)}
>
{children}