diff --git a/.changeset/blue-fireants-behave.md b/.changeset/blue-fireants-behave.md
new file mode 100644
index 00000000000..7f763372b41
--- /dev/null
+++ b/.changeset/blue-fireants-behave.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': minor
+---
+
+PageHeader: Draft implementation
diff --git a/src/PageHeader/PageHeader.stories.tsx b/src/PageHeader/PageHeader.stories.tsx
new file mode 100644
index 00000000000..c781473185d
--- /dev/null
+++ b/src/PageHeader/PageHeader.stories.tsx
@@ -0,0 +1,284 @@
+import React from 'react'
+import {Meta, Story} from '@storybook/react'
+import {Button, IconButton, Breadcrumbs, Link, Text, StateLabel, BranchName, Box} from '..'
+import {UnderlineNav} from '../UnderlineNav2'
+import Label from '../Label'
+import {
+ KebabHorizontalIcon,
+ GitBranchIcon,
+ CodeIcon,
+ GitPullRequestIcon,
+ PeopleIcon,
+ PencilIcon,
+ CommentDiscussionIcon,
+ CommitIcon,
+ ChecklistIcon,
+ FileDiffIcon,
+ ArrowRightIcon,
+ SidebarExpandIcon,
+} from '@primer/octicons-react'
+import {OcticonArgType} from '../utils/story-helpers'
+
+import {PageHeader} from './PageHeader'
+import Hidden from '../Hidden'
+
+const meta: Meta = {
+ title: 'Drafts/Components/PageHeader',
+ parameters: {
+ layout: 'fullscreen',
+ controls: {expanded: true},
+ },
+ args: {
+ hasContextArea: false,
+ hasParentLink: true,
+ ParentLink: 'Previous page',
+ hasContextBar: false,
+ hasContextAreaAction: true,
+ hasLeadingAction: false,
+ hasTitle: true,
+ Title: 'Branches',
+ 'Title.as': 'h2',
+ 'Title.variant': 'medium',
+ hasLeadingVisual: false,
+ LeadingVisual: GitBranchIcon,
+ hasTrailingVisual: false,
+ hasTrailingAction: false,
+ hasActions: false,
+ hasDescription: false,
+ hasNavigation: false,
+ },
+ argTypes: {
+ hasContextArea: {
+ type: 'boolean',
+ table: {
+ category: 'ContextArea Slot',
+ type: {summary: 'string'},
+ },
+ description:
+ 'ContextArea is only visible on narrow viewports by default to provide user context of where they are at their journey.',
+ },
+ ParentLink: {
+ type: 'string',
+ if: {arg: 'hasContextArea'},
+ table: {
+ category: 'ContextArea Slot',
+ },
+ description: 'The default way to let users navigate up in the hierarchy on Narrow viewports.',
+ },
+ hasParentLink: {
+ type: 'boolean',
+ if: {arg: 'hasContextArea'},
+ table: {
+ category: 'ContextArea Slot',
+ },
+ description: 'Parent ',
+ },
+ hasContextBar: {
+ type: 'boolean',
+ if: {arg: 'hasContextArea'},
+ table: {
+ category: 'ContextArea Slot',
+ },
+ description:
+ 'ContextBar is generic slot for any component above the title region. Use it for custom breadcrumbs and other navigation elements instead of ParentLink.',
+ },
+ hasContextAreaAction: {
+ type: 'boolean',
+ if: {arg: 'hasContextArea'},
+ table: {
+ category: 'ContextArea Slot',
+ },
+ },
+ hasLeadingAction: {
+ type: 'boolean',
+ table: {
+ category: 'TitleArea Slot',
+ },
+ description:
+ 'A back button can be used as a leading action for local navigation. On Narrow viewports, use parentLink instead.',
+ },
+ hasTitle: {
+ type: 'boolean',
+ table: {
+ category: 'TitleArea Slot',
+ },
+ description:
+ 'ContextBar is generic slot for any component above the title region. Use it for custom breadcrumbs and other navigation elements instead of ParentLink.',
+ },
+ Title: {
+ type: 'string',
+ table: {
+ category: 'TitleArea Slot',
+ },
+ },
+ 'Title.as': {
+ control: {
+ type: 'select',
+ },
+ options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
+ table: {
+ category: 'TitleArea Slot',
+ },
+ },
+ 'Title.variant': {
+ control: {
+ type: 'radio',
+ },
+ options: ['large', 'medium', 'subtitle'],
+ table: {
+ category: 'TitleArea Slot',
+ },
+ description:
+ '`medium` is the most common page title size. Use for static titles in most situations. `large` for for user-generated content such as issues, pull requests, or discussions. `subtitle` when a PageHeader.Title is already present in the page, such as in a SplitPageLayout.',
+ },
+ hasLeadingVisual: {
+ type: 'boolean',
+ table: {
+ category: 'TitleArea Slot',
+ },
+ },
+ LeadingVisual: {
+ ...OcticonArgType([CodeIcon, GitPullRequestIcon, PeopleIcon]),
+ table: {
+ category: 'TitleArea Slot',
+ },
+ description:
+ 'Leading visualLeading visuals are optional and appear at the start of the title. They can be octicons, avatars, and other custom visuals that fit a small area.',
+ },
+ hasTrailingVisual: {
+ type: 'boolean',
+ table: {
+ category: 'TitleArea Slot',
+ },
+ description:
+ 'Trailing visualTrailing visual and trailing text can display auxiliary information. They are placed at the right of the item, and can denote status, privacy details, etc.',
+ },
+ hasTrailingAction: {
+ type: 'boolean',
+ table: {
+ category: 'TitleArea Slot',
+ },
+ },
+ hasActions: {
+ type: 'boolean',
+ table: {
+ category: 'TitleArea Slot',
+ },
+ description: 'Description region/slot',
+ },
+ hasDescription: {
+ type: 'boolean',
+ table: {
+ category: 'Other Slots',
+ },
+ description: 'Description region/slot',
+ },
+ hasNavigation: {
+ type: 'boolean',
+ table: {
+ category: 'Other Slots',
+ },
+ description: 'Description region/slot',
+ },
+ },
+}
+
+const Template: Story = args => (
+
+
+
+
+ {args.ParentLink}
+
+
+
+
+ ...
+ primer
+ react
+ src
+ PageHeader
+ PageHeader.tsx
+
+
+
+
+
+
+
+
+
+
+ {' '}
+
+ {}
+
+ {args.Title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Open
+
+
+
+ broccolinisoup
+ {' '}
+ wants to merge 3 commits into main from{' '}
+ broccolinisoup/switch-to-new-underlineNav
+
+
+
+
+ main
+
+ page-header-initial
+
+
+
+
+
+
+ Conversation
+
+
+ Commits
+
+
+ Checks
+
+
+ Files Changes
+
+
+
+
+
+)
+
+export const Playground = Template.bind({})
+
+export default meta
diff --git a/src/PageHeader/PageHeader.test.tsx b/src/PageHeader/PageHeader.test.tsx
new file mode 100644
index 00000000000..81d726637a2
--- /dev/null
+++ b/src/PageHeader/PageHeader.test.tsx
@@ -0,0 +1,159 @@
+import React from 'react'
+import '@testing-library/jest-dom/extend-expect'
+import {render} from '@testing-library/react'
+import {PageHeader} from '.'
+import MatchMediaMock from 'jest-matchmedia-mock'
+import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
+import {act} from 'react-test-renderer'
+import {viewportRanges} from '../hooks/useResponsiveValue'
+import {IconButton} from '../Button'
+import {ChevronLeftIcon, GitBranchIcon, PencilIcon, SidebarExpandIcon} from '@primer/octicons-react'
+
+let matchmedia: MatchMediaMock
+describe('PageHeader', () => {
+ beforeAll(() => {
+ matchmedia = new MatchMediaMock()
+ })
+ afterAll(() => {
+ matchmedia.clear()
+ })
+ behavesAsComponent({
+ Component: PageHeader,
+ options: {skipAs: true, skipSx: true},
+ toRender: () => (
+
+
+
+
+
+
+ ),
+ })
+ checkExports('PageHeader', {
+ default: undefined,
+ PageHeader,
+ })
+ it('renders default layout', () => {
+ const {container} = render(
+
+ ContextArea
+ TitleArea
+ Description
+ Navigation
+ ,
+ )
+ expect(container).toMatchSnapshot()
+ })
+ it('does not render ContextArea in wide viewport as default', () => {
+ act(() => {
+ matchmedia.useMediaQuery(viewportRanges.wide)
+ })
+
+ const {getByText} = render(
+
+ ContextArea
+ TitleArea
+ Description
+ Navigation
+ ,
+ )
+ expect(getByText('ContextArea')).not.toBeVisible()
+ })
+ it('respects the hidden prop of ContextArea and renders accordingly', () => {
+ act(() => {
+ matchmedia.useMediaQuery(viewportRanges.regular)
+ })
+
+ const {getByText} = render(
+
+
+ ContextArea
+
+ TitleArea
+ Description
+ Navigation
+ ,
+ )
+ expect(getByText('ContextArea')).toBeVisible()
+ })
+ it('respects default visibility of LeadingAction and TrailingAction and renders accordingly', () => {
+ act(() => {
+ matchmedia.useMediaQuery(viewportRanges.narrow)
+ })
+ const {getByTestId} = render(
+
+ ContextArea
+
+
+
+
+ Title
+
+
+
+
+
+
+ ,
+ )
+ expect(getByTestId('LeadingAction')).not.toBeVisible()
+ expect(getByTestId('TrailingAction')).not.toBeVisible()
+ })
+ it('respects the title variant prop', () => {
+ act(() => {
+ matchmedia.useMediaQuery(viewportRanges.narrow)
+ })
+ const {getByText} = render(
+
+ ContextArea
+
+ Title
+
+ ,
+ )
+ expect(getByText('Title')).toHaveStyle('font-size: 2rem')
+ })
+ it("respects the title variant prop and updates the children components' container height accordingly", () => {
+ act(() => {
+ matchmedia.useMediaQuery(viewportRanges.narrow)
+ })
+ const {getByText} = render(
+
+ ContextArea
+
+
+ Leading Action
+
+
+
+ Leading Visual
+
+
+ Title
+
+ Trailing Action
+
+
+
+ Trailing Visual
+
+
+
+ ,
+ )
+
+ expect(getByText('Leading Visual')).toHaveStyle('height: 3rem')
+ expect(getByText('Trailing Visual')).toHaveStyle('height: 3rem')
+ expect(getByText('Leading Action')).toHaveStyle('height: 3rem')
+ expect(getByText('Trailing Action')).toHaveStyle('height: 3rem')
+ // add actions here
+ })
+})
+
+checkStoriesForAxeViolations('examples', '../PageHeader/')
diff --git a/src/PageHeader/PageHeader.tsx b/src/PageHeader/PageHeader.tsx
new file mode 100644
index 00000000000..4fdc2d705b6
--- /dev/null
+++ b/src/PageHeader/PageHeader.tsx
@@ -0,0 +1,393 @@
+import React from 'react'
+import {Box} from '..'
+import {useResponsiveValue, ResponsiveValue} from '../hooks/useResponsiveValue'
+import {SxProp, merge, BetterSystemStyleObject} from '../sx'
+import Heading from '../Heading'
+import {ArrowLeftIcon} from '@primer/octicons-react'
+import Link from '../Link'
+import {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
+const REGION_ORDER = {
+ ContextArea: 0,
+ TitleArea: 1,
+ Description: 2,
+ Navigation: 3,
+}
+
+// Types that are shared between sub components
+export type sharedPropTypes = {
+ hidden?: boolean | ResponsiveValue
+} & SxProp
+
+// Default state for the `visible` prop when a sub component is only visible on narrow viewport
+const hiddenOnRegularAndWide = {
+ narrow: false,
+ regular: true,
+ wide: true,
+}
+
+// Default state for the `visible` prop when a sub component is visible on regular and wide viewport
+const hiddenOnNarrow = {
+ narrow: true,
+ regular: false,
+ wide: false,
+}
+
+// Root
+// -----------------------------------------------------------------------------
+export type PageHeaderProps = {
+ 'aria-label'?: React.AriaAttributes['aria-label']
+ as?: React.ElementType | 'header' | 'div'
+} & sharedPropTypes
+
+const Root: React.FC> = ({children, sx = {}, as = 'div'}) => {
+ const rootStyles = {
+ display: 'flex',
+ flexDirection: 'column',
+ // TODO: We used hard-coded values for the spacing and font size in this component. Update them to use new design tokens when they are ready to use.
+ gap: '0.5rem',
+ }
+ return (
+ (rootStyles, sx)}>
+ {children}
+
+ )
+}
+
+// PageHeader.ContextArea : Only visible on narrow viewports by default to provide user context of where they are at their journey. `visible` prop available
+// to manage their custom visibility but consumers should be careful if they choose to hide this on narrow viewports.
+// PageHeader.ContextArea Sub Components: PageHeader.ParentLink, PageHeader.ContextBar, PageHeader.ContextAreaActions
+// ---------------------------------------------------------------------
+
+const ContextArea: React.FC> = ({
+ children,
+ hidden = hiddenOnRegularAndWide,
+ sx = {},
+}) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ const contentNavStyles = {
+ display: isHidden ? 'none' : 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: '0.5rem',
+ order: REGION_ORDER.ContextArea,
+ }
+ return (contentNavStyles, sx)}>{children}
+}
+type LinkProps = Pick<
+ React.AnchorHTMLAttributes,
+ 'download' | 'href' | 'hrefLang' | 'media' | 'ping' | 'rel' | 'target' | 'type' | 'referrerPolicy'
+>
+export type ParentLinkProps = PageHeaderProps & LinkProps
+
+const ParentLink = React.forwardRef(
+ (
+ {
+ children,
+ sx = {},
+ href,
+ 'aria-label': ariaLabel = `Back to ${children}`,
+ as = 'a',
+ hidden = hiddenOnRegularAndWide,
+ },
+ ref,
+ ) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ return (
+ <>
+ (
+ {
+ display: isHidden ? 'none' : 'flex',
+ alignItems: 'center',
+ gap: '0.5rem',
+ },
+ sx,
+ )}
+ href={href}
+ >
+
+ {children}
+
+ >
+ )
+ },
+) as PolymorphicForwardRefComponent<'a', ParentLinkProps>
+
+// ContextBar
+// Generic slot for any component above the title region. Use it for custom breadcrumbs and other navigation elements instead of ParentLink.
+// ---------------------------------------------------------------------
+
+const ContextBar: React.FC> = ({
+ children,
+ sx = {},
+ hidden = hiddenOnRegularAndWide,
+}) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ return ({display: isHidden ? 'none' : 'flex'}, sx)}>{children}
+}
+
+// ContextAreaActions
+// ---------------------------------------------------------------------
+const ContextAreaActions: React.FC> = ({
+ children,
+ sx = {},
+ hidden = hiddenOnRegularAndWide,
+}) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ return (
+ (
+ {
+ display: isHidden ? 'none' : 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: '0.5rem',
+ flexGrow: '1',
+ justifyContent: 'right',
+ },
+ sx,
+ )}
+ >
+ {children}
+
+ )
+}
+
+const MEDIUM_TITLE_HEIGHT = '2rem'
+const LARGE_TITLE_HEIGHT = '3rem'
+
+const TitleAreaContext = React.createContext<{
+ titleVariant: 'subtitle' | 'medium' | 'large'
+ titleAreaHeight?: string | number
+}>({
+ titleVariant: 'medium',
+ titleAreaHeight: MEDIUM_TITLE_HEIGHT,
+})
+
+type TitleAreaProps = {
+ variant?: 'subtitle' | 'medium' | 'large' | ResponsiveValue<'subtitle' | 'medium' | 'large'>
+} & PageHeaderProps
+// PageHeader.TitleArea: The main title area of the page. Visible on all viewports.
+// PageHeader.TitleArea Sub Components: PageHeader.LeadingAction, PageHeader.LeadingVisual, PageHeader.Title, PageTitle.TrailingVisual, PageHeader.TrailingAction, PageHeader.Actions
+// PageHeader.LeadingAction and PageHeader.TrailingAction are only visible on regular viewports therefore they come as visible on narrow viewports and their visibility can be managed by their exposed `visible` prop
+// ---------------------------------------------------------------------
+
+const TitleArea: React.FC> = ({
+ children,
+ sx = {},
+ hidden = false,
+ variant = 'medium',
+}) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ const currentVariant = useResponsiveValue(variant, 'medium')
+ const height = currentVariant === 'large' ? LARGE_TITLE_HEIGHT : MEDIUM_TITLE_HEIGHT
+ return (
+
+ (
+ {gap: '0.5rem', display: isHidden ? 'none' : 'flex', flexDirection: 'row', alignItems: 'flex-start'},
+ sx,
+ )}
+ >
+ {children}
+
+
+ )
+}
+
+const LeadingAction: React.FC> = ({
+ children,
+ sx = {},
+ hidden = hiddenOnNarrow,
+}) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ const {titleAreaHeight} = React.useContext(TitleAreaContext)
+
+ return (
+ (
+ {display: isHidden ? 'none' : 'flex', alignItems: 'center', height: titleAreaHeight},
+ sx,
+ )}
+ >
+ {children}
+
+ )
+}
+
+const LeadingVisual: React.FC> = ({children, sx = {}, hidden = false}) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ const {titleAreaHeight} = React.useContext(TitleAreaContext)
+ return (
+ (
+ {
+ display: isHidden ? 'none' : 'flex',
+ alignItems: 'center',
+ height: titleAreaHeight,
+ },
+ sx,
+ )}
+ >
+ {children}
+
+ )
+}
+
+export type TitleProps = {
+ // Check if we need responsive values for heading is so should we update as prop's type for Heading component?
+ as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
+} & PageHeaderProps
+
+const Title: React.FC> = ({children, sx = {}, hidden = false, as = 'h3'}) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ const {titleVariant} = React.useContext(TitleAreaContext)
+ return (
+ (
+ {
+ fontSize: {
+ large: '2rem',
+ medium: '1.25rem',
+ subtitle: '1.25rem',
+ }[titleVariant],
+ // line-height is calculated with calc(height/font-size) and the below numbers are from @primer/primitives
+ lineHeight: {
+ large: 1.5, // calc(48/32)
+ medium: 1.6, // calc(32/20)
+ subtitle: 1.6, // calc(32/20)
+ }[titleVariant],
+ fontWeight: {
+ large: '400',
+ medium: '600',
+ subtitle: '400',
+ }[titleVariant],
+ display: isHidden ? 'none' : 'flex',
+ },
+ sx,
+ )}
+ >
+ {children}
+
+ )
+}
+const TrailingVisual: React.FC> = ({children, sx = {}, hidden = false}) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ const {titleAreaHeight} = React.useContext(TitleAreaContext)
+
+ return (
+ (
+ {
+ display: isHidden ? 'none' : 'flex',
+ alignItems: 'center',
+ height: titleAreaHeight,
+ },
+ sx,
+ )}
+ >
+ {children}
+
+ )
+}
+
+const TrailingAction: React.FC> = ({
+ children,
+ sx = {},
+ hidden = hiddenOnNarrow,
+}) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ const {titleAreaHeight} = React.useContext(TitleAreaContext)
+
+ return (
+ (
+ {display: isHidden ? 'none' : 'flex', alignItems: 'center', height: titleAreaHeight},
+ sx,
+ )}
+ >
+ {children}
+
+ )
+}
+
+const Actions: React.FC> = ({children, sx = {}, hidden = false}) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ const {titleAreaHeight} = React.useContext(TitleAreaContext)
+ return (
+ (
+ {
+ display: isHidden ? 'none' : 'flex',
+ flexDirection: 'row',
+ gap: '0.5rem',
+ flexGrow: '1',
+ justifyContent: 'right',
+ height: titleAreaHeight,
+ alignItems: 'center',
+ },
+ sx,
+ )}
+ >
+ {children}
+
+ )
+}
+
+// PageHeader.Description: The description area of the header. Visible on all viewports
+const Description: React.FC> = ({children, sx = {}, hidden = false}) => {
+ const isHidden = useResponsiveValue(hidden, true)
+ return (
+ (
+ {
+ display: isHidden ? 'none' : 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: '0.5rem',
+ },
+ sx,
+ )}
+ >
+ {children}
+
+ )
+}
+
+// PageHeader.Navigation: The local navigation area of the header. Visible on all viewports
+const Navigation: React.FC> = ({children, sx = {}, hidden = false}) => {
+ const isHidden = useResponsiveValue(hidden, false)
+ return (
+ (
+ {
+ display: isHidden ? 'none' : 'block',
+ },
+ sx,
+ )}
+ >
+ {children}
+
+ )
+}
+
+export const PageHeader = Object.assign(Root, {
+ ContextArea,
+ ParentLink,
+ ContextBar,
+ ContextAreaActions,
+ TitleArea,
+ LeadingAction,
+ LeadingVisual,
+ Title,
+ TrailingVisual,
+ TrailingAction,
+ Actions,
+ Description,
+ Navigation,
+})
diff --git a/src/PageHeader/__snapshots__/PageHeader.test.tsx.snap b/src/PageHeader/__snapshots__/PageHeader.test.tsx.snap
new file mode 100644
index 00000000000..c21d48ac2da
--- /dev/null
+++ b/src/PageHeader/__snapshots__/PageHeader.test.tsx.snap
@@ -0,0 +1,175 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PageHeader renders consistently 1`] = `
+.c0 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.c1 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ gap: 0.5rem;
+ -webkit-order: 0;
+ -ms-flex-order: 0;
+ order: 0;
+}
+
+.c2 {
+ gap: 0.5rem;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+ -webkit-align-items: flex-start;
+ -webkit-box-align: flex-start;
+ -ms-flex-align: flex-start;
+ align-items: flex-start;
+}
+
+.c3 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.c4 {
+ display: block;
+}
+
+
+`;
+
+exports[`PageHeader renders default layout 1`] = `
+.c0 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.c1 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ gap: 0.5rem;
+ -webkit-order: 0;
+ -ms-flex-order: 0;
+ order: 0;
+}
+
+.c2 {
+ gap: 0.5rem;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+ -webkit-align-items: flex-start;
+ -webkit-box-align: flex-start;
+ -ms-flex-align: flex-start;
+ align-items: flex-start;
+}
+
+.c3 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.c4 {
+ display: block;
+}
+
+
+
+
+ ContextArea
+
+
+ TitleArea
+
+
+ Description
+
+
+ Navigation
+
+
+
+`;
diff --git a/src/PageHeader/examples.stories.tsx b/src/PageHeader/examples.stories.tsx
new file mode 100644
index 00000000000..f84411de157
--- /dev/null
+++ b/src/PageHeader/examples.stories.tsx
@@ -0,0 +1,211 @@
+import React from 'react'
+import {Meta} from '@storybook/react'
+import {Button, IconButton, Breadcrumbs, Link, Text, StateLabel, BranchName, Box} from '..'
+import {
+ KebabHorizontalIcon,
+ GitBranchIcon,
+ CodeIcon,
+ CommentDiscussionIcon,
+ CommitIcon,
+ ChecklistIcon,
+ FileDiffIcon,
+ ArrowRightIcon,
+} from '@primer/octicons-react'
+
+import {PageHeader} from './PageHeader'
+import Hidden from '../Hidden'
+import {UnderlineNav} from '../UnderlineNav2'
+
+const meta: Meta = {
+ title: 'Drafts/Components/PageHeader/Examples',
+ parameters: {
+ layout: 'fullscreen',
+ controls: {expanded: true},
+ },
+ args: {},
+}
+
+// See if there is an interest to take this into global params
+
+const PrimerViewports = {
+ xsmall: {
+ name: 'Xsmall',
+ styles: {
+ width: '320px',
+ height: '100%',
+ },
+ },
+ small: {
+ name: 'Small',
+ styles: {
+ width: '540px',
+ height: '100%',
+ },
+ },
+ medium: {
+ name: 'Medium',
+ styles: {
+ width: '768px',
+ height: '100%',
+ },
+ },
+ large: {
+ name: 'Large',
+ styles: {
+ width: '1012px',
+ height: '100%',
+ },
+ },
+ xlarge: {
+ name: 'Xlarge',
+ styles: {
+ width: '1280px',
+ height: '100%',
+ },
+ },
+ xxlarge: {
+ name: 'XXlarge',
+ styles: {
+ width: '1400px',
+ height: '100%',
+ },
+ },
+}
+
+const setViewportParamToNarrow = {
+ viewport: {
+ viewports: {
+ ...PrimerViewports,
+ },
+ defaultViewport: 'small',
+ },
+}
+export const Webhooks = () => (
+
+
+
+ Repository settings
+
+
+ Webhooks
+
+
+
+
+
+
+
+
+
+
+
+
+
+)
+
+export const WebhooksOnNarrowViewport = () => {
+ return
+}
+
+WebhooksOnNarrowViewport.parameters = setViewportParamToNarrow
+
+export const PullRequestPage = () => (
+
+
+
+ Pull requests
+
+
+
+ PageHeader component initial layout explorations extra long pull request title
+
+
+
+
+ {/* 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
+
+
+
+
+
+)
+
+export const PullRequestPageOnNarrowViewport = () => {
+ return
+}
+
+PullRequestPageOnNarrowViewport.parameters = setViewportParamToNarrow
+
+export const FilesPage = () => (
+
+
+
+ Files
+
+
+
+
+
+
+
+ ...
+ primer
+ react
+ src
+ PageHeader
+ PageHeader.tsx
+
+
+
+
+)
+
+export const FilesPageOnNarrowViewport = () => {
+ return
+}
+
+FilesPageOnNarrowViewport.parameters = setViewportParamToNarrow
+
+export default meta
diff --git a/src/PageHeader/features.stories.tsx b/src/PageHeader/features.stories.tsx
new file mode 100644
index 00000000000..ccd9c89a351
--- /dev/null
+++ b/src/PageHeader/features.stories.tsx
@@ -0,0 +1,137 @@
+import React from 'react'
+import {Meta} from '@storybook/react'
+import {IconButton, ActionMenu, ActionList, Box} from '..'
+import {PencilIcon, KebabHorizontalIcon, ArchiveIcon, TrashIcon} from '@primer/octicons-react'
+
+import {PageHeader} from './PageHeader'
+
+const meta: Meta = {
+ title: 'Drafts/Components/PageHeader/Features',
+ parameters: {
+ layout: 'fullscreen',
+ controls: {expanded: true},
+ },
+ args: {},
+}
+
+const PrimerViewports = {
+ xsmall: {
+ name: 'Xsmall',
+ styles: {
+ width: '320px',
+ height: '100%',
+ },
+ },
+ small: {
+ name: 'Small',
+ styles: {
+ width: '540px',
+ height: '100%',
+ },
+ },
+ medium: {
+ name: 'Medium',
+ styles: {
+ width: '768px',
+ height: '100%',
+ },
+ },
+ large: {
+ name: 'Large',
+ styles: {
+ width: '1012px',
+ height: '100%',
+ },
+ },
+ xlarge: {
+ name: 'Xlarge',
+ styles: {
+ width: '1280px',
+ height: '100%',
+ },
+ },
+ xxlarge: {
+ name: 'XXlarge',
+ styles: {
+ width: '1400px',
+ height: '100%',
+ },
+ },
+}
+
+export const TitleWithTrailingAction = () => (
+
+
+
+ Projects
+
+
+ Primer Backlog
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rename Title
+
+
+
+
+
+ Archive all cards
+
+
+
+
+
+ Delete
+
+
+
+
+
+
+
+
+)
+
+TitleWithTrailingAction.parameters = {
+ viewport: {
+ viewports: {
+ ...PrimerViewports,
+ },
+ defaultViewport: 'medium',
+ },
+}
+
+export const TitleWithTrailingActionOnNarrowViewports = () => {
+ return
+}
+
+TitleWithTrailingActionOnNarrowViewports.parameters = {
+ viewport: {
+ viewports: {
+ ...PrimerViewports,
+ },
+ defaultViewport: 'small',
+ },
+}
+
+export default meta
diff --git a/src/PageHeader/index.ts b/src/PageHeader/index.ts
new file mode 100644
index 00000000000..3e3ee9ffef7
--- /dev/null
+++ b/src/PageHeader/index.ts
@@ -0,0 +1 @@
+export * from './PageHeader'