diff --git a/.changeset/css-layers-page-layout.md b/.changeset/css-layers-page-layout.md new file mode 100644 index 00000000000..0a5650cbb44 --- /dev/null +++ b/.changeset/css-layers-page-layout.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +PageLayout: Add CSS layer support for component styles diff --git a/packages/react/src/PageLayout/PageLayout.module.css b/packages/react/src/PageLayout/PageLayout.module.css index 526fa6d8ee5..35fef4ee279 100644 --- a/packages/react/src/PageLayout/PageLayout.module.css +++ b/packages/react/src/PageLayout/PageLayout.module.css @@ -1,200 +1,130 @@ -/* Exported values for JavaScript consumption */ -:export { - /* Breakpoint where --pane-max-width-diff changes (used in usePaneWidth.ts) */ - paneMaxWidthDiffBreakpoint: 1280; - /* Default value for --pane-max-width-diff below the breakpoint */ - paneMaxWidthDiffDefault: 511; - /* Default value for --sidebar-max-width-diff (constant across all viewports) */ - sidebarMaxWidthDiffDefault: 256; - /* Value for --pane-max-width-diff at/above the breakpoint */ - paneMaxWidthDiffWide: 959; -} - -.PageLayoutRoot { - /* Region Order */ - --region-order-header: 0; - --region-order-pane-start: 1; - --region-order-content: 2; - --region-order-pane-end: 3; - --region-order-footer: 4; - - /* Spacing Values */ - --spacing-none: 0; - --spacing-condensed: var(--base-size-16); - --spacing-normal: var(--base-size-16); - - @media screen and (min-width: 1012px) { - --spacing-normal: var(--base-size-24); - } - - /* Pane Width Values */ - --pane-width-small: 100%; - --pane-width-medium: 100%; - --pane-width-large: 100%; - /* NOTE: This value is exported via :export for use in usePaneWidth.ts */ - --pane-max-width-diff: 511px; - /* Sidebar uses a smaller diff since it doesn't need to reserve space for a file tree pane */ - --sidebar-max-width-diff: 256px; - - @media screen and (min-width: 768px) { - --pane-width-small: 240px; - --pane-width-medium: 256px; - --pane-width-large: 256px; - } - - @media screen and (min-width: 1012px) { - --pane-width-small: 256px; - --pane-width-medium: 296px; - --pane-width-large: 320px; - } - - /* NOTE: This breakpoint value is exported via :export for use in usePaneWidth.ts */ - @media screen and (min-width: 1280px) { - --pane-max-width-diff: 959px; - } - - /* These following CSS variables are dynamic values that get overridden by styles passed in via props. */ - --spacing: 0; - --spacing-row: 0; - --spacing-column: 0; - --spacing-divider: 0; - --offset-header: 0; - --pane-width: 0; - --pane-min-width: 0; - --pane-max-width: 0; - --pane-width-custom: 0; - --pane-width-size: 0; - - /* stylelint-disable-next-line primer/spacing */ - padding: var(--spacing); -} - -.PageLayoutWrapper { - display: flex; - margin-right: auto; - margin-left: auto; - flex-wrap: wrap; - /* the wrapper should match the Root's dimensions by default */ - width: 100%; - height: 100%; - - &:where([data-width='medium']) { - max-width: 768px; - } - - &:where([data-width='large']) { - max-width: 1012px; - } - - &:where([data-width='full']) { - max-width: 100%; - } - - &:where([data-width='xlarge']) { - max-width: 1280px; - } -} +@layer primer.components.PageLayout { + /* Exported values for JavaScript consumption */ + :export { + /* Breakpoint where --pane-max-width-diff changes (used in usePaneWidth.ts) */ + paneMaxWidthDiffBreakpoint: 1280; + /* Default value for --pane-max-width-diff below the breakpoint */ + paneMaxWidthDiffDefault: 511; + /* Default value for --sidebar-max-width-diff (constant across all viewports) */ + sidebarMaxWidthDiffDefault: 256; + /* Value for --pane-max-width-diff at/above the breakpoint */ + paneMaxWidthDiffWide: 959; + } + + .PageLayoutRoot { + /* Region Order */ + --region-order-header: 0; + --region-order-pane-start: 1; + --region-order-content: 2; + --region-order-pane-end: 3; + --region-order-footer: 4; + + /* Spacing Values */ + --spacing-none: 0; + --spacing-condensed: var(--base-size-16); + --spacing-normal: var(--base-size-16); + + @media screen and (min-width: 1012px) { + --spacing-normal: var(--base-size-24); + } + + /* Pane Width Values */ + --pane-width-small: 100%; + --pane-width-medium: 100%; + --pane-width-large: 100%; + /* NOTE: This value is exported via :export for use in usePaneWidth.ts */ + --pane-max-width-diff: 511px; + /* Sidebar uses a smaller diff since it doesn't need to reserve space for a file tree pane */ + --sidebar-max-width-diff: 256px; -.PageLayoutContent { - display: flex; - flex: 1 1 100%; - flex-wrap: wrap; - max-width: 100%; + @media screen and (min-width: 768px) { + --pane-width-small: 240px; + --pane-width-medium: 256px; + --pane-width-large: 256px; + } - @media (--viewportRange-narrow) { - flex-direction: column; - } -} + @media screen and (min-width: 1012px) { + --pane-width-small: 256px; + --pane-width-medium: 296px; + --pane-width-large: 320px; + } -.HorizontalDivider { - /* stylelint-disable-next-line primer/spacing */ - margin-right: calc(-1 * var(--spacing-divider)); - /* stylelint-disable-next-line primer/spacing */ - margin-left: calc(-1 * var(--spacing-divider)); + /* NOTE: This breakpoint value is exported via :export for use in usePaneWidth.ts */ + @media screen and (min-width: 1280px) { + --pane-max-width-diff: 959px; + } - &:where([data-variant='none']) { - display: none; - } + /* These following CSS variables are dynamic values that get overridden by styles passed in via props. */ + --spacing: 0; + --spacing-row: 0; + --spacing-column: 0; + --spacing-divider: 0; + --offset-header: 0; + --pane-width: 0; + --pane-min-width: 0; + --pane-max-width: 0; + --pane-width-custom: 0; + --pane-width-size: 0; - &:where([data-variant='line']) { - display: block; - height: 1px; - /* stylelint-disable-next-line primer/colors */ - background-color: var(--borderColor-default); + /* stylelint-disable-next-line primer/spacing */ + padding: var(--spacing); } - &:where([data-variant='filled']) { - display: block; - height: var(--base-size-8); - background-color: var(--bgColor-inset); - box-shadow: - /* stylelint-disable-next-line primer/box-shadow */ - inset 0 -1px 0 0 var(--borderColor-default), - inset 0 1px 0 0 var(--borderColor-default); - } + .PageLayoutWrapper { + display: flex; + margin-right: auto; + margin-left: auto; + flex-wrap: wrap; + /* the wrapper should match the Root's dimensions by default */ + width: 100%; + height: 100%; - /* Narrow viewport */ - @media (--viewportRange-narrow) { - &:where([data-variant-narrow='none']) { - display: none; + &:where([data-width='medium']) { + max-width: 768px; } - &:where([data-variant-narrow='line']) { - display: block; - height: 1px; - /* stylelint-disable-next-line primer/colors */ - background-color: var(--borderColor-default); + &:where([data-width='large']) { + max-width: 1012px; } - &:where([data-variant-narrow='filled']) { - display: block; - height: var(--base-size-8); - background-color: var(--bgColor-inset); - box-shadow: - /* stylelint-disable-next-line primer/box-shadow */ - inset 0 -1px 0 0 var(--borderColor-default), - inset 0 1px 0 0 var(--borderColor-default); + &:where([data-width='full']) { + max-width: 100%; } - } - /* Regular viewport */ - @media (--viewportRange-regular) { - &:where([data-variant-regular='none']) { - display: none; + &:where([data-width='xlarge']) { + max-width: 1280px; } + } - &:where([data-variant-regular='line']) { - display: block; - height: 1px; - /* stylelint-disable-next-line primer/colors */ - background-color: var(--borderColor-default); - } + .PageLayoutContent { + display: flex; + flex: 1 1 100%; + flex-wrap: wrap; + max-width: 100%; - &:where([data-variant-regular='filled']) { - display: block; - height: var(--base-size-8); - background-color: var(--bgColor-inset); - box-shadow: - /* stylelint-disable-next-line primer/box-shadow */ - inset 0 -1px 0 0 var(--borderColor-default), - inset 0 1px 0 0 var(--borderColor-default); + @media (--viewportRange-narrow) { + flex-direction: column; } } - /* Wide viewport */ - @media (--viewportRange-wide) { - &:where([data-variant-wide='none']) { + .HorizontalDivider { + /* stylelint-disable-next-line primer/spacing */ + margin-right: calc(-1 * var(--spacing-divider)); + /* stylelint-disable-next-line primer/spacing */ + margin-left: calc(-1 * var(--spacing-divider)); + + &:where([data-variant='none']) { display: none; } - &:where([data-variant-wide='line']) { + &:where([data-variant='line']) { display: block; height: 1px; /* stylelint-disable-next-line primer/colors */ background-color: var(--borderColor-default); } - &:where([data-variant-wide='filled']) { + &:where([data-variant='filled']) { display: block; height: var(--base-size-8); background-color: var(--bgColor-inset); @@ -203,101 +133,101 @@ inset 0 -1px 0 0 var(--borderColor-default), inset 0 1px 0 0 var(--borderColor-default); } - } - - @media screen and (min-width: 768px) { - margin-right: 0 !important; - margin-left: 0 !important; - } -} - -.VerticalDivider { - position: relative; - height: 100%; - - &:where([data-variant='none']) { - display: none; - } - - &:where([data-variant='line']) { - display: block; - width: 1px; - /* stylelint-disable-next-line primer/colors */ - background-color: var(--borderColor-default); - } - - &:where([data-variant='filled']) { - display: block; - width: var(--base-size-8); - background-color: var(--bgColor-inset); - box-shadow: - /* stylelint-disable-next-line primer/box-shadow */ - inset -1px 0 0 0 var(--borderColor-default), - inset 1px 0 0 0 var(--borderColor-default); - } - - /* Narrow viewport */ - @media (--viewportRange-narrow) { - &:where([data-variant-narrow='none']) { - display: none; - } - &:where([data-variant-narrow='line']) { - display: block; - width: 1px; - /* stylelint-disable-next-line primer/colors */ - background-color: var(--borderColor-default); + /* Narrow viewport */ + @media (--viewportRange-narrow) { + &:where([data-variant-narrow='none']) { + display: none; + } + + &:where([data-variant-narrow='line']) { + display: block; + height: 1px; + /* stylelint-disable-next-line primer/colors */ + background-color: var(--borderColor-default); + } + + &:where([data-variant-narrow='filled']) { + display: block; + height: var(--base-size-8); + background-color: var(--bgColor-inset); + box-shadow: + /* stylelint-disable-next-line primer/box-shadow */ + inset 0 -1px 0 0 var(--borderColor-default), + inset 0 1px 0 0 var(--borderColor-default); + } + } + + /* Regular viewport */ + @media (--viewportRange-regular) { + &:where([data-variant-regular='none']) { + display: none; + } + + &:where([data-variant-regular='line']) { + display: block; + height: 1px; + /* stylelint-disable-next-line primer/colors */ + background-color: var(--borderColor-default); + } + + &:where([data-variant-regular='filled']) { + display: block; + height: var(--base-size-8); + background-color: var(--bgColor-inset); + box-shadow: + /* stylelint-disable-next-line primer/box-shadow */ + inset 0 -1px 0 0 var(--borderColor-default), + inset 0 1px 0 0 var(--borderColor-default); + } + } + + /* Wide viewport */ + @media (--viewportRange-wide) { + &:where([data-variant-wide='none']) { + display: none; + } + + &:where([data-variant-wide='line']) { + display: block; + height: 1px; + /* stylelint-disable-next-line primer/colors */ + background-color: var(--borderColor-default); + } + + &:where([data-variant-wide='filled']) { + display: block; + height: var(--base-size-8); + background-color: var(--bgColor-inset); + box-shadow: + /* stylelint-disable-next-line primer/box-shadow */ + inset 0 -1px 0 0 var(--borderColor-default), + inset 0 1px 0 0 var(--borderColor-default); + } } - &:where([data-variant-narrow='filled']) { - display: block; - width: var(--base-size-8); - background-color: var(--bgColor-inset); - box-shadow: - /* stylelint-disable-next-line primer/box-shadow */ - inset -1px 0 0 0 var(--borderColor-default), - inset 1px 0 0 0 var(--borderColor-default); + @media screen and (min-width: 768px) { + margin-right: 0 !important; + margin-left: 0 !important; } } - /* Regular viewport */ - @media (--viewportRange-regular) { - &:where([data-variant-regular='none']) { - display: none; - } - - &:where([data-variant-regular='line']) { - display: block; - width: 1px; - /* stylelint-disable-next-line primer/colors */ - background-color: var(--borderColor-default); - } - - &:where([data-variant-regular='filled']) { - display: block; - width: var(--base-size-8); - background-color: var(--bgColor-inset); - box-shadow: - /* stylelint-disable-next-line primer/box-shadow */ - inset -1px 0 0 0 var(--borderColor-default), - inset 1px 0 0 0 var(--borderColor-default); - } - } + .VerticalDivider { + position: relative; + height: 100%; - /* Wide viewport */ - @media (--viewportRange-wide) { - &:where([data-variant-wide='none']) { + &:where([data-variant='none']) { display: none; } - &:where([data-variant-wide='line']) { + &:where([data-variant='line']) { display: block; width: 1px; /* stylelint-disable-next-line primer/colors */ background-color: var(--borderColor-default); } - &:where([data-variant-wide='filled']) { + &:where([data-variant='filled']) { display: block; width: var(--base-size-8); background-color: var(--bgColor-inset); @@ -306,583 +236,655 @@ inset -1px 0 0 0 var(--borderColor-default), inset 1px 0 0 0 var(--borderColor-default); } - } -} - -.Header { - width: 100%; - /* stylelint-disable-next-line primer/spacing */ - margin-bottom: var(--spacing); - - &:where([data-hidden='true']) { - display: none; - } - /* Narrow viewport */ - @media (--viewportRange-narrow) { - &:where([data-hidden-narrow='true']) { - display: none; - } - } - - /* Regular viewport */ - @media (--viewportRange-regular) { - &:where([data-hidden-regular='true']) { - display: none; - } - } + /* Narrow viewport */ + @media (--viewportRange-narrow) { + &:where([data-variant-narrow='none']) { + display: none; + } + + &:where([data-variant-narrow='line']) { + display: block; + width: 1px; + /* stylelint-disable-next-line primer/colors */ + background-color: var(--borderColor-default); + } + + &:where([data-variant-narrow='filled']) { + display: block; + width: var(--base-size-8); + background-color: var(--bgColor-inset); + box-shadow: + /* stylelint-disable-next-line primer/box-shadow */ + inset -1px 0 0 0 var(--borderColor-default), + inset 1px 0 0 0 var(--borderColor-default); + } + } + + /* Regular viewport */ + @media (--viewportRange-regular) { + &:where([data-variant-regular='none']) { + display: none; + } + + &:where([data-variant-regular='line']) { + display: block; + width: 1px; + /* stylelint-disable-next-line primer/colors */ + background-color: var(--borderColor-default); + } + + &:where([data-variant-regular='filled']) { + display: block; + width: var(--base-size-8); + background-color: var(--bgColor-inset); + box-shadow: + /* stylelint-disable-next-line primer/box-shadow */ + inset -1px 0 0 0 var(--borderColor-default), + inset 1px 0 0 0 var(--borderColor-default); + } + } + + /* Wide viewport */ + @media (--viewportRange-wide) { + &:where([data-variant-wide='none']) { + display: none; + } + + &:where([data-variant-wide='line']) { + display: block; + width: 1px; + /* stylelint-disable-next-line primer/colors */ + background-color: var(--borderColor-default); + } + + &:where([data-variant-wide='filled']) { + display: block; + width: var(--base-size-8); + background-color: var(--bgColor-inset); + box-shadow: + /* stylelint-disable-next-line primer/box-shadow */ + inset -1px 0 0 0 var(--borderColor-default), + inset 1px 0 0 0 var(--borderColor-default); + } + } + } + + .Header { + width: 100%; + /* stylelint-disable-next-line primer/spacing */ + margin-bottom: var(--spacing); - /* Wide viewport */ - @media (--viewportRange-wide) { - &:where([data-hidden-wide='true']) { + &:where([data-hidden='true']) { display: none; } - } -} - -.HeaderContent { - /* stylelint-disable-next-line primer/spacing */ - padding: var(--spacing); -} - -.HeaderHorizontalDivider { - /* stylelint-disable-next-line primer/spacing */ - margin-top: var(--spacing); -} - -.ContentWrapper { - display: flex; - width: 100%; - - /* Hack to prevent overflowing content from pushing the pane region to the next line */ - min-width: 1px; - flex-direction: column; - order: var(--region-order-content); - - /* Set flex-basis to 0% to allow flex-grow to control the width of the content region. - Without this, the content region could wrap onto a different line - than the pane region on wide viewports if its contents are too wide. */ - flex-basis: 0; - flex-grow: 1; - flex-shrink: 1; - - &:where([data-is-hidden='true']) { - display: none; - } - /* Narrow viewport */ - @media (--viewportRange-narrow) { - &:where([data-is-hidden-narrow='true']) { - display: none; + /* Narrow viewport */ + @media (--viewportRange-narrow) { + &:where([data-hidden-narrow='true']) { + display: none; + } } - } - /* Regular viewport */ - @media (--viewportRange-regular) { - &:where([data-is-hidden-regular='true']) { - display: none; + /* Regular viewport */ + @media (--viewportRange-regular) { + &:where([data-hidden-regular='true']) { + display: none; + } } - } - /* Wide viewport */ - @media (--viewportRange-wide) { - &:where([data-is-hidden-wide='true']) { - display: none; + /* Wide viewport */ + @media (--viewportRange-wide) { + &:where([data-hidden-wide='true']) { + display: none; + } } } -} - -.Content { - width: 100%; - - /* stylelint-disable-next-line primer/spacing */ - padding: var(--spacing); - margin-right: auto; - margin-left: auto; - flex-grow: 1; - - &:where([data-width='medium']) { - max-width: 768px; - } - &:where([data-width='large']) { - max-width: 1012px; + .HeaderContent { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--spacing); } - &:where([data-width='full']) { - max-width: 100%; + .HeaderHorizontalDivider { + /* stylelint-disable-next-line primer/spacing */ + margin-top: var(--spacing); } - &:where([data-width='xlarge']) { - max-width: 1280px; - } -} + .ContentWrapper { + display: flex; + width: 100%; -.PaneWrapper { - display: flex; - width: 100%; - margin-right: 0; - margin-left: 0; + /* Hack to prevent overflowing content from pushing the pane region to the next line */ + min-width: 1px; + flex-direction: column; + order: var(--region-order-content); - &:where([data-is-hidden='true']) { - display: none; - } + /* Set flex-basis to 0% to allow flex-grow to control the width of the content region. + Without this, the content region could wrap onto a different line + than the pane region on wide viewports if its contents are too wide. */ + flex-basis: 0; + flex-grow: 1; + flex-shrink: 1; - /* Narrow viewport */ - @media (--viewportRange-narrow) { - &:where([data-is-hidden-narrow='true']) { + &:where([data-is-hidden='true']) { display: none; } - &:where([data-position-narrow='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-top: var(--spacing-row); - flex-direction: column; - order: var(--region-order-pane-end); - } - - &:where([data-position-narrow='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-bottom: var(--spacing-row); - flex-direction: column-reverse; - order: var(--region-order-pane-start); + /* Narrow viewport */ + @media (--viewportRange-narrow) { + &:where([data-is-hidden-narrow='true']) { + display: none; + } } - } - /* Base position (non-responsive) - applies at narrow viewports only */ - @media (--viewportRange-narrow) { - &:where([data-position='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-top: var(--spacing-row); - flex-direction: column; - order: var(--region-order-pane-end); + /* Regular viewport */ + @media (--viewportRange-regular) { + &:where([data-is-hidden-regular='true']) { + display: none; + } } - &:where([data-position='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-bottom: var(--spacing-row); - flex-direction: column-reverse; - order: var(--region-order-pane-start); + /* Wide viewport */ + @media (--viewportRange-wide) { + &:where([data-is-hidden-wide='true']) { + display: none; + } } } - @media screen and (min-width: 768px) { - &:where([data-is-hidden-regular='true']) { - display: none; - } - - width: auto; - margin-top: 0 !important; - margin-bottom: 0 !important; + .Content { + width: 100%; - &:where([data-sticky]) { - position: sticky; - /* stylelint-disable-next-line primer/spacing */ - top: var(--offset-header); - max-height: 100vh; - } + /* stylelint-disable-next-line primer/spacing */ + padding: var(--spacing); + margin-right: auto; + margin-left: auto; + flex-grow: 1; - /* Base position (non-responsive) - applies at regular+ viewports */ - &:where([data-position='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-left: var(--spacing-column); - flex-direction: row-reverse; - order: var(--region-order-pane-end); + &:where([data-width='medium']) { + max-width: 768px; } - &:where([data-position='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-right: var(--spacing-column); - flex-direction: row; - order: var(--region-order-pane-start); + &:where([data-width='large']) { + max-width: 1012px; } - &:where([data-position-regular='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-left: var(--spacing-column); - flex-direction: row-reverse; - order: var(--region-order-pane-end); + &:where([data-width='full']) { + max-width: 100%; } - &:where([data-position-regular='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-right: var(--spacing-column); - flex-direction: row; - order: var(--region-order-pane-start); + &:where([data-width='xlarge']) { + max-width: 1280px; } } - @media (--viewportRange-wide) { - &:where([data-is-hidden-wide='true']) { - display: none; - } + .PaneWrapper { + display: flex; + width: 100%; + margin-right: 0; + margin-left: 0; - &:where([data-position-wide='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-left: var(--spacing-column); - flex-direction: row-reverse; - order: var(--region-order-pane-end); + &:where([data-is-hidden='true']) { + display: none; } - &:where([data-position-wide='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-right: var(--spacing-column); - flex-direction: row; - order: var(--region-order-pane-start); + /* Narrow viewport */ + @media (--viewportRange-narrow) { + &:where([data-is-hidden-narrow='true']) { + display: none; + } + + &:where([data-position-narrow='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-top: var(--spacing-row); + flex-direction: column; + order: var(--region-order-pane-end); + } + + &:where([data-position-narrow='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-bottom: var(--spacing-row); + flex-direction: column-reverse; + order: var(--region-order-pane-start); + } + } + + /* Base position (non-responsive) - applies at narrow viewports only */ + @media (--viewportRange-narrow) { + &:where([data-position='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-top: var(--spacing-row); + flex-direction: column; + order: var(--region-order-pane-end); + } + + &:where([data-position='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-bottom: var(--spacing-row); + flex-direction: column-reverse; + order: var(--region-order-pane-start); + } } - } -} - -.PaneVerticalDivider { - /* Base position (non-responsive) */ - &:where([data-position='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-left: var(--spacing); - } - - &:where([data-position='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-right: var(--spacing); - } - /* Responsive position */ - @media (--viewportRange-narrow) { - &:where([data-position-narrow='start']) { + @media screen and (min-width: 768px) { + &:where([data-is-hidden-regular='true']) { + display: none; + } + + width: auto; + margin-top: 0 !important; + margin-bottom: 0 !important; + + &:where([data-sticky]) { + position: sticky; + /* stylelint-disable-next-line primer/spacing */ + top: var(--offset-header); + max-height: 100vh; + } + + /* Base position (non-responsive) - applies at regular+ viewports */ + &:where([data-position='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-left: var(--spacing-column); + flex-direction: row-reverse; + order: var(--region-order-pane-end); + } + + &:where([data-position='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-right: var(--spacing-column); + flex-direction: row; + order: var(--region-order-pane-start); + } + + &:where([data-position-regular='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-left: var(--spacing-column); + flex-direction: row-reverse; + order: var(--region-order-pane-end); + } + + &:where([data-position-regular='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-right: var(--spacing-column); + flex-direction: row; + order: var(--region-order-pane-start); + } + } + + @media (--viewportRange-wide) { + &:where([data-is-hidden-wide='true']) { + display: none; + } + + &:where([data-position-wide='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-left: var(--spacing-column); + flex-direction: row-reverse; + order: var(--region-order-pane-end); + } + + &:where([data-position-wide='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-right: var(--spacing-column); + flex-direction: row; + order: var(--region-order-pane-start); + } + } + } + + .PaneVerticalDivider { + /* Base position (non-responsive) */ + &:where([data-position='start']) { /* stylelint-disable-next-line primer/spacing */ margin-left: var(--spacing); - margin-right: 0; } - &:where([data-position-narrow='end']) { + &:where([data-position='end']) { /* stylelint-disable-next-line primer/spacing */ margin-right: var(--spacing); - margin-left: 0; } - } - @media (--viewportRange-regular) { - &:where([data-position-regular='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-left: var(--spacing); - margin-right: 0; - } + /* Responsive position */ + @media (--viewportRange-narrow) { + &:where([data-position-narrow='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-left: var(--spacing); + margin-right: 0; + } - &:where([data-position-regular='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-right: var(--spacing); - margin-left: 0; + &:where([data-position-narrow='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-right: var(--spacing); + margin-left: 0; + } } - } - @media (--viewportRange-wide) { - &:where([data-position-wide='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-left: var(--spacing); - margin-right: 0; - } + @media (--viewportRange-regular) { + &:where([data-position-regular='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-left: var(--spacing); + margin-right: 0; + } - &:where([data-position-wide='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-right: var(--spacing); - margin-left: 0; + &:where([data-position-regular='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-right: var(--spacing); + margin-left: 0; + } } - } -} -.Pane { - width: var(--pane-width-size); - /* stylelint-disable-next-line primer/spacing */ - padding: var(--spacing); + @media (--viewportRange-wide) { + &:where([data-position-wide='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-left: var(--spacing); + margin-right: 0; + } - @media screen and (min-width: 768px) { - overflow: auto; + &:where([data-position-wide='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-right: var(--spacing); + margin-left: 0; + } + } } - &:where([data-resizable]) { - width: 100%; + .Pane { + width: var(--pane-width-size); + /* stylelint-disable-next-line primer/spacing */ + padding: var(--spacing); @media screen and (min-width: 768px) { - /* - * --pane-max-width is set by JS on mount and updated on resize (debounced). - * JS calculates viewport - margin to avoid scrollbar discrepancy with 100vw. - */ - width: clamp(var(--pane-min-width), var(--pane-width), var(--pane-max-width)); + overflow: auto; } - } -} -.PaneHorizontalDivider { - &:where([data-position='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-top: var(--spacing); - } + &:where([data-resizable]) { + width: 100%; - &:where([data-position='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-bottom: var(--spacing); + @media screen and (min-width: 768px) { + /* + * --pane-max-width is set by JS on mount and updated on resize (debounced). + * JS calculates viewport - margin to avoid scrollbar discrepancy with 100vw. + */ + width: clamp(var(--pane-min-width), var(--pane-width), var(--pane-max-width)); + } + } } - /* Responsive position */ - @media (--viewportRange-narrow) { - &:where([data-position-narrow='start']) { + .PaneHorizontalDivider { + &:where([data-position='start']) { /* stylelint-disable-next-line primer/spacing */ margin-top: var(--spacing); - margin-bottom: 0; } - &:where([data-position-narrow='end']) { + &:where([data-position='end']) { /* stylelint-disable-next-line primer/spacing */ margin-bottom: var(--spacing); - margin-top: 0; } - } - @media (--viewportRange-regular) { - &:where([data-position-regular='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-top: var(--spacing); - margin-bottom: 0; - } + /* Responsive position */ + @media (--viewportRange-narrow) { + &:where([data-position-narrow='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-top: var(--spacing); + margin-bottom: 0; + } - &:where([data-position-regular='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-bottom: var(--spacing); - margin-top: 0; + &:where([data-position-narrow='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-bottom: var(--spacing); + margin-top: 0; + } } - } - @media (--viewportRange-wide) { - &:where([data-position-wide='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-top: var(--spacing); - margin-bottom: 0; + @media (--viewportRange-regular) { + &:where([data-position-regular='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-top: var(--spacing); + margin-bottom: 0; + } + + &:where([data-position-regular='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-bottom: var(--spacing); + margin-top: 0; + } } - &:where([data-position-wide='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-bottom: var(--spacing); - margin-top: 0; + @media (--viewportRange-wide) { + &:where([data-position-wide='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-top: var(--spacing); + margin-bottom: 0; + } + + &:where([data-position-wide='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-bottom: var(--spacing); + margin-top: 0; + } } } -} - -.FooterWrapper { - width: 100%; - order: var(--region-order-footer); - /* stylelint-disable-next-line primer/spacing */ - margin-top: var(--spacing); + .FooterWrapper { + width: 100%; + order: var(--region-order-footer); - &:where([data-hidden='true']) { - display: none; - } + /* stylelint-disable-next-line primer/spacing */ + margin-top: var(--spacing); - /* Narrow viewport */ - @media (--viewportRange-narrow) { - &:where([data-hidden-narrow='true']) { + &:where([data-hidden='true']) { display: none; } - } - /* Regular viewport */ - @media (--viewportRange-regular) { - &:where([data-hidden-regular='true']) { - display: none; + /* Narrow viewport */ + @media (--viewportRange-narrow) { + &:where([data-hidden-narrow='true']) { + display: none; + } } - } - /* Wide viewport */ - @media (--viewportRange-wide) { - &:where([data-hidden-wide='true']) { - display: none; + /* Regular viewport */ + @media (--viewportRange-regular) { + &:where([data-hidden-regular='true']) { + display: none; + } } - } -} - -.FooterHorizontalDivider { - /* stylelint-disable-next-line primer/spacing */ - margin-bottom: var(--spacing); -} -.FooterContent { - /* stylelint-disable-next-line primer/spacing */ - padding: var(--spacing); -} - -/** - * DraggableHandle - Interactive resize handle - */ -.DraggableHandle { - position: absolute; - inset: 0 -2px; - cursor: col-resize; - - /* Prevent touch scrolling and text selection during drag */ - touch-action: none; - user-select: none; -} - -.DraggableHandle::before { - content: ''; - position: absolute; - inset: 0; - /* stylelint-disable-next-line primer/colors */ - background-color: var(--draggable-handle--bg-color, var(--bgColor-neutral-muted)); - opacity: var(--draggable-handle--drag-opacity, 0); - transition: var(--draggable-handle--transition, opacity 150ms ease); /* compositor-friendly, disabled during drag */ - border-radius: inherit; /* optional if you need rounded corners */ -} - -/* Hover effect */ -.DraggableHandle:hover::before { - opacity: 1; -} - -/** - * OPTIMIZATION: CSS containment during drag/resize - * Direct attribute selectors are O(1) - only the attributed element is invalidated - * (Unlike descendant selectors which require O(n) traversal) - */ -.Pane[data-dragging='true'], -.ContentWrapper[data-dragging='true'] { - contain: layout style paint; - pointer-events: none; -} - -/* Sidebar */ -.PageLayoutRoot:where([data-has-sidebar]) { - /* Note: Sidebar styles are only applied when the PageLayout has a Sidebar child - via `[data-has-sidebar`]` on the root element - */ - display: flex; - /* - Current layout structure for Sidebar support: - -- [Sidebar] | [Header + Content + Footer] | [Sidebar] -- - */ - flex-direction: row; -} - -:where([data-has-sidebar]) > .PageLayoutWrapper { - flex-shrink: 1; - min-width: 0; -} - -.SidebarWrapper { - /* Current layout structure: - -- [Sidebar] | [Resizable Divider] -- - */ - display: flex; - flex-direction: row; - flex-shrink: 0; - height: 100%; - - &:where([data-is-hidden='true']) { - display: none; + /* Wide viewport */ + @media (--viewportRange-wide) { + &:where([data-hidden-wide='true']) { + display: none; + } + } } - /* Position: start (left side) */ - &:where([data-position='start']) { - order: -1; + .FooterHorizontalDivider { /* stylelint-disable-next-line primer/spacing */ - margin-right: var(--spacing-column); + margin-bottom: var(--spacing); } - /* Position: end (right side) */ - &:where([data-position='end']) { - order: 1; + .FooterContent { /* stylelint-disable-next-line primer/spacing */ - margin-left: var(--spacing-column); + padding: var(--spacing); } - /* Sticky sidebar */ - &:where([data-sticky]) { - position: sticky; - top: 0; - height: 100vh; + /** + * DraggableHandle - Interactive resize handle + */ + .DraggableHandle { + position: absolute; + inset: 0 -2px; + cursor: col-resize; + + /* Prevent touch scrolling and text selection during drag */ + touch-action: none; + user-select: none; } - /* Narrow viewport */ - @media (--viewportRange-narrow) { - &:where([data-is-hidden-narrow='true']) { + .DraggableHandle::before { + content: ''; + position: absolute; + inset: 0; + /* stylelint-disable-next-line primer/colors */ + background-color: var(--draggable-handle--bg-color, var(--bgColor-neutral-muted)); + opacity: var(--draggable-handle--drag-opacity, 0); + transition: var(--draggable-handle--transition, opacity 150ms ease); /* compositor-friendly, disabled during drag */ + border-radius: inherit; /* optional if you need rounded corners */ + } + + /* Hover effect */ + .DraggableHandle:hover::before { + opacity: 1; + } + + /** + * OPTIMIZATION: CSS containment during drag/resize + * Direct attribute selectors are O(1) - only the attributed element is invalidated + * (Unlike descendant selectors which require O(n) traversal) + */ + .Pane[data-dragging='true'], + .ContentWrapper[data-dragging='true'] { + contain: layout style paint; + pointer-events: none; + } + + /* Sidebar */ + .PageLayoutRoot:where([data-has-sidebar]) { + /* Note: Sidebar styles are only applied when the PageLayout has a Sidebar child + via `[data-has-sidebar`]` on the root element + */ + display: flex; + /* + Current layout structure for Sidebar support: + -- [Sidebar] | [Header + Content + Footer] | [Sidebar] -- + */ + flex-direction: row; + } + + :where([data-has-sidebar]) > .PageLayoutWrapper { + flex-shrink: 1; + min-width: 0; + } + + .SidebarWrapper { + /* Current layout structure: + -- [Sidebar] | [Resizable Divider] -- + */ + display: flex; + flex-direction: row; + flex-shrink: 0; + height: 100%; + + &:where([data-is-hidden='true']) { display: none; } - /* Fullscreen mode at narrow viewport */ - &:where([data-responsive-variant='fullscreen']) { - position: fixed; + /* Position: start (left side) */ + &:where([data-position='start']) { + order: -1; + /* stylelint-disable-next-line primer/spacing */ + margin-right: var(--spacing-column); + } + + /* Position: end (right side) */ + &:where([data-position='end']) { + order: 1; + /* stylelint-disable-next-line primer/spacing */ + margin-left: var(--spacing-column); + } + + /* Sticky sidebar */ + &:where([data-sticky]) { + position: sticky; top: 0; - left: 0; - width: 100vw; height: 100vh; - min-width: unset; - max-width: unset; - border-radius: 0; - z-index: 999; - background-color: var(--bgColor-default); } - } - /* Regular viewport */ - @media (--viewportRange-regular) { - &:where([data-is-hidden-regular='true']) { - display: none; + /* Narrow viewport */ + @media (--viewportRange-narrow) { + &:where([data-is-hidden-narrow='true']) { + display: none; + } + + /* Fullscreen mode at narrow viewport */ + &:where([data-responsive-variant='fullscreen']) { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + min-width: unset; + max-width: unset; + border-radius: 0; + z-index: 999; + background-color: var(--bgColor-default); + } } - } - /* Wide viewport */ - @media (--viewportRange-wide) { - &:where([data-is-hidden-wide='true']) { - display: none; + /* Regular viewport */ + @media (--viewportRange-regular) { + &:where([data-is-hidden-regular='true']) { + display: none; + } + } + + /* Wide viewport */ + @media (--viewportRange-wide) { + &:where([data-is-hidden-wide='true']) { + display: none; + } } } -} -.Sidebar { - width: var(--pane-width-size); - height: 100%; - overflow: auto; + .Sidebar { + width: var(--pane-width-size); + height: 100%; + overflow: auto; - &:where([data-resizable]) { - width: 100%; + &:where([data-resizable]) { + width: 100%; - @media screen and (min-width: 768px) { - width: clamp(var(--pane-min-width), var(--pane-width), var(--pane-max-width)); + @media screen and (min-width: 768px) { + width: clamp(var(--pane-min-width), var(--pane-width), var(--pane-max-width)); + } } - } - /* In fullscreen mode at narrow, sidebar takes full available space */ - @media (--viewportRange-narrow) { - :where([data-responsive-variant='fullscreen']) > & { - width: 100%; - height: 100%; - min-width: unset; - max-width: unset; + /* In fullscreen mode at narrow, sidebar takes full available space */ + @media (--viewportRange-narrow) { + :where([data-responsive-variant='fullscreen']) > & { + width: 100%; + height: 100%; + min-width: unset; + max-width: unset; + } } } -} -.SidebarVerticalDivider { - height: auto; - /* Base position (non-responsive) */ - &:where([data-position='start']) { - /* stylelint-disable-next-line primer/spacing */ - margin-left: var(--spacing-column); - } + .SidebarVerticalDivider { + height: auto; + /* Base position (non-responsive) */ + &:where([data-position='start']) { + /* stylelint-disable-next-line primer/spacing */ + margin-left: var(--spacing-column); + } - &:where([data-position='end']) { - /* stylelint-disable-next-line primer/spacing */ - margin-right: var(--spacing-column); - } + &:where([data-position='end']) { + /* stylelint-disable-next-line primer/spacing */ + margin-right: var(--spacing-column); + } - /* Hide divider in fullscreen mode at narrow viewport */ - @media (--viewportRange-narrow) { - :where([data-responsive-variant='fullscreen']) > & { - display: none; + /* Hide divider in fullscreen mode at narrow viewport */ + @media (--viewportRange-narrow) { + :where([data-responsive-variant='fullscreen']) > & { + display: none; + } } } -} -.Sidebar[data-dragging='true'] { - contain: layout style paint; - pointer-events: none; + .Sidebar[data-dragging='true'] { + contain: layout style paint; + pointer-events: none; + } } diff --git a/packages/react/src/__tests__/css-layers.test.ts b/packages/react/src/__tests__/css-layers.test.ts index b29c1e6c2f4..6fa172f0af8 100644 --- a/packages/react/src/__tests__/css-layers.test.ts +++ b/packages/react/src/__tests__/css-layers.test.ts @@ -44,6 +44,7 @@ const allowlist = new Set([ path.resolve(import.meta.dirname, '../Popover/Popover.module.css'), path.resolve(import.meta.dirname, '../ProgressBar/ProgressBar.module.css'), path.resolve(import.meta.dirname, '../Radio/Radio.module.css'), + path.resolve(import.meta.dirname, '../PageLayout/PageLayout.module.css'), ]) const files = Array.from(allowlist).map(file => { return [path.relative(path.resolve(import.meta.dirname, '..'), file), file]