Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/lemon-islands-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@primer/react': patch
---

Banner: Improve button layout wrapping behavior with CSS container queries

- Banners with titles now always wrap action buttons for better readability
- Banners without titles or dismiss buttons use container queries for responsive wrapping based on content length
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 7 additions & 2 deletions e2e/components/Banner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ const stories: Array<{title: string; id: string; viewports?: Array<keyof typeof
id: 'components-banner-features--with-hidden-title',
},
{
title: 'WithHiddenTitleAndActions',
id: 'components-banner-features--with-hidden-title-and-actions',
title: 'WithHiddenTitleAndActionsLong',
id: 'components-banner-features--with-hidden-title-and-actions-long',
viewports: ['primer.breakpoint.xs', 'primer.breakpoint.sm'],
},
{
Expand All @@ -70,6 +70,11 @@ const stories: Array<{title: string; id: string; viewports?: Array<keyof typeof
id: 'components-banner-examples--multiline',
viewports: ['primer.breakpoint.xs', 'primer.breakpoint.sm'],
},
{
title: 'WithHiddenTitleAndActionsShort',
id: 'components-banner-features--with-hidden-title-and-actions-short',
viewports: ['primer.breakpoint.xs', 'primer.breakpoint.sm'],
},
]

test.describe('Banner', () => {
Expand Down
5 changes: 4 additions & 1 deletion packages/react/src/Banner/Banner.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
"id": "components-banner-features--with-hidden-title"
},
{
"id": "components-banner-features--with-hidden-title-and-actions"
"id": "components-banner-features--with-hidden-title-and-actions-short"
},
{
"id": "components-banner-features--with-hidden-title-and-actions-long"
},
{
"id": "components-banner-features--dismissible-with-hidden-title-and-actions"
Expand Down
14 changes: 13 additions & 1 deletion packages/react/src/Banner/Banner.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export const WithHiddenTitle = () => {
)
}

export const WithHiddenTitleAndActions = () => {
export const WithHiddenTitleAndActionsLong = () => {
return (
<Banner
title="Warning"
Expand Down Expand Up @@ -205,6 +205,18 @@ export const DismissibleWithHiddenTitleAndActions = () => {
)
}

export const WithHiddenTitleAndActionsShort = () => {
return (
<Banner
title="Warning"
hideTitle
description={<>A short message that does not wrap.</>}
variant="warning"
primaryAction={<Banner.PrimaryAction>Button</Banner.PrimaryAction>}
/>
)
}

export const DismissibleWithHiddenTitleAndSecondaryAction = () => {
return (
<Banner
Expand Down
120 changes: 58 additions & 62 deletions packages/react/src/Banner/Banner.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@
grid-template-rows: auto;
}

/* stylelint-disable-next-line selector-max-specificity */
.Banner:not([data-dismissible]):not([data-title-hidden]):has(.BannerTitle) .BannerContainer {
display: grid;
grid-template-columns: auto;
grid-template-rows: auto;
}

/* BannerContent ---------------------------------------------------------- */

.BannerContent {
Expand All @@ -83,12 +90,6 @@
margin-block: var(--base-size-6);
}

@media screen and (min-width: 544px) {
.BannerContent {
flex: 1 1 0%;
}
}

.BannerTitle {
margin: 0;
font-size: inherit;
Expand Down Expand Up @@ -142,78 +143,73 @@
column-gap: var(--base-size-12);
align-items: center;
margin-block: var(--base-size-2);
min-height: var(--base-size-32);
}

.BannerActions :where([data-primary-action='trailing']) {
.BannerActions [data-primary-action='trailing'] {
display: none;
}

@media screen and (--viewportRange-regular) {
.BannerActions :where([data-primary-action='trailing']) {
display: flex;
}
.BannerActions [data-primary-action='leading'] {
display: flex;
}

.BannerActions :where([data-primary-action='leading']) {
display: none;
}
.Banner:has(.BannerTitle):not([data-title-hidden]) .BannerContainer {
display: grid;
grid-template-columns: auto;
grid-template-rows: auto;
}

.Banner[data-dismissible]:not([data-title-hidden]) .BannerActions {
.Banner:has(.BannerTitle):not([data-title-hidden]) .BannerActions {
margin-block-end: var(--base-size-6);
}

/* stylelint-disable-next-line selector-max-specificity */
.Banner[data-dismissible]:not([data-title-hidden]) .BannerActionsContainer[data-primary-action='trailing'] {
.Banner:has(.BannerTitle):not([data-title-hidden]) [data-primary-action='trailing'] {
display: none;
}

/* stylelint-disable-next-line selector-max-specificity */
.Banner[data-dismissible]:not([data-title-hidden]) .BannerActionsContainer[data-primary-action='leading'] {
.Banner:has(.BannerTitle):not([data-title-hidden]) [data-primary-action='leading'] {
display: flex;
}

/* Layout ------------------------------------------------------------------- */

@container banner (max-width: 500px) {
.BannerContainer {
display: grid;
}

.BannerContainer:has(.BannerActionsContainer) {
grid-template-rows: auto auto;
}

.BannerActions {
margin-block-end: var(--base-size-6);
}

.BannerActions [data-primary-action='trailing'] {
display: none;
}

.BannerActions [data-primary-action='leading'] {
display: flex;
}
}

@container banner (min-width: 500px) {
.BannerContainer {
display: grid;
grid-template-columns: auto auto;
}

:where(.Banner):not([data-dismissible])
:where(.BannerActionsContainer[data-primary-action='trailing'])
:where([data-variant='link']:only-child) {
padding-inline: 0 var(--base-size-12);
}

.BannerActions [data-primary-action='trailing'] {
display: flex;
min-height: var(--base-size-32, 2rem);
}

.BannerActions [data-primary-action='leading'] {
display: none;
@container banner (min-width: 544px) {
.Banner[data-content-length='long']:not(:has(.BannerTitle)):not([data-title-hidden]),
.Banner[data-content-length='long'][data-title-hidden] {
/* stylelint-disable-next-line selector-max-specificity */
.BannerContainer {
display: grid;
grid-template-columns: auto auto;
}

/* stylelint-disable-next-line selector-max-specificity */
[data-primary-action='trailing'] {
display: flex;
}

/* stylelint-disable-next-line selector-max-specificity */
[data-primary-action='leading'] {
display: none;
}
}
}

@container banner (min-width: 400px) {
.Banner[data-content-length='short']:not(:has(.BannerTitle)):not([data-title-hidden]),
.Banner[data-content-length='short'][data-title-hidden] {
/* stylelint-disable-next-line selector-max-specificity */
.BannerContainer {
display: grid;
grid-template-columns: auto auto;
}

/* stylelint-disable-next-line selector-max-specificity */
[data-primary-action='trailing'] {
display: flex;
}

/* stylelint-disable-next-line selector-max-specificity */
[data-primary-action='leading'] {
display: none;
}
}
}
21 changes: 21 additions & 0 deletions packages/react/src/Banner/Banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ const labels: Record<BannerVariant, string> = {
warning: 'Warning',
}

const BANNER_CONTENT_LENGTH_THRESHOLD = {
LONG_CONTENT_THRESHOLD: 50,
NON_STRING_CONTENT_ESTIMATE: 80,
} as const

export const Banner = React.forwardRef<HTMLElement, BannerProps>(function Banner(
{
'aria-label': label,
Expand Down Expand Up @@ -134,6 +139,21 @@ export const Banner = React.forwardRef<HTMLElement, BannerProps>(function Banner
}, [title])
}

const getContentLength = (title?: string, description?: React.ReactNode): 'short' | 'long' => {
const titleLength = title?.length || 0
const descriptionLength =
typeof description === 'string'
? description.length
: description
? BANNER_CONTENT_LENGTH_THRESHOLD.NON_STRING_CONTENT_ESTIMATE
: 0

const totalLength = titleLength + descriptionLength

if (totalLength > BANNER_CONTENT_LENGTH_THRESHOLD.LONG_CONTENT_THRESHOLD) return 'long'
return 'short'
}

return (
<section
{...rest}
Expand All @@ -142,6 +162,7 @@ export const Banner = React.forwardRef<HTMLElement, BannerProps>(function Banner
className={clsx(className, classes.Banner)}
data-dismissible={onDismiss ? '' : undefined}
data-title-hidden={hideTitle ? '' : undefined}
data-content-length={getContentLength(title, description)}
data-variant={variant}
tabIndex={-1}
ref={ref}
Expand Down
Loading