diff --git a/.changeset/grumpy-walls-cry.md b/.changeset/grumpy-walls-cry.md new file mode 100644 index 00000000000..438c66eb601 --- /dev/null +++ b/.changeset/grumpy-walls-cry.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Add new `Banner` `actionsLayout` prop to handle actions layout edge cases diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-colorblind-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-colorblind-linux.png new file mode 100644 index 00000000000..6292dda31d1 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-dimmed-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-dimmed-linux.png new file mode 100644 index 00000000000..595a540a456 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-high-contrast-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-high-contrast-linux.png new file mode 100644 index 00000000000..f705a51066e Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-linux.png new file mode 100644 index 00000000000..6292dda31d1 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-tritanopia-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-tritanopia-linux.png new file mode 100644 index 00000000000..6292dda31d1 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-colorblind-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-colorblind-linux.png new file mode 100644 index 00000000000..df7aaf43cc8 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-high-contrast-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-high-contrast-linux.png new file mode 100644 index 00000000000..925139f62a9 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-linux.png new file mode 100644 index 00000000000..df7aaf43cc8 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-tritanopia-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-tritanopia-linux.png new file mode 100644 index 00000000000..df7aaf43cc8 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsInline-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-colorblind-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-colorblind-linux.png new file mode 100644 index 00000000000..3b188d90464 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-dimmed-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-dimmed-linux.png new file mode 100644 index 00000000000..fe281f9f37a Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-high-contrast-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-high-contrast-linux.png new file mode 100644 index 00000000000..3853da5b198 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-linux.png new file mode 100644 index 00000000000..3b188d90464 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-tritanopia-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-tritanopia-linux.png new file mode 100644 index 00000000000..3b188d90464 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-colorblind-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-colorblind-linux.png new file mode 100644 index 00000000000..9b936b81d17 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-high-contrast-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-high-contrast-linux.png new file mode 100644 index 00000000000..01eef51f4ee Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-linux.png new file mode 100644 index 00000000000..9b936b81d17 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-linux.png differ diff --git a/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-tritanopia-linux.png b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-tritanopia-linux.png new file mode 100644 index 00000000000..9b936b81d17 Binary files /dev/null and b/.playwright/snapshots/components/Banner.test.ts-snapshots/Banner-ActionsStacked-light-tritanopia-linux.png differ diff --git a/e2e/components/Banner.test.ts b/e2e/components/Banner.test.ts index 97f10fd7f50..c82a2feb2f9 100644 --- a/e2e/components/Banner.test.ts +++ b/e2e/components/Banner.test.ts @@ -70,6 +70,14 @@ const stories: Array<{title: string; id: string; viewports?: Array { diff --git a/packages/react/src/Banner/Banner.docs.json b/packages/react/src/Banner/Banner.docs.json index 11f6ab12bf5..3f13ed1f2a3 100644 --- a/packages/react/src/Banner/Banner.docs.json +++ b/packages/react/src/Banner/Banner.docs.json @@ -6,49 +6,49 @@ "importPath": "@primer/react", "stories": [ { - "id": "components-banner--default" + "id": "components-banner--default" }, { - "id": "components-banner-features--critical" + "id": "components-banner-features--critical" }, { - "id": "components-banner-features--info" + "id": "components-banner-features--info" }, { - "id": "components-banner-features--success" + "id": "components-banner-features--success" }, { - "id": "components-banner-features--upsell" + "id": "components-banner-features--upsell" }, { - "id": "components-banner-features--warning" + "id": "components-banner-features--warning" }, { - "id": "components-banner-features--dismiss" + "id": "components-banner-features--dismiss" }, { - "id": "components-banner-features--dismiss-with-actions" + "id": "components-banner-features--dismiss-with-actions" }, { - "id": "components-banner-features--with-hidden-title" + "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" }, { - "id": "components-banner-features--dismissible-with-hidden-title-and-actions" + "id": "components-banner-features--dismissible-with-hidden-title-and-actions" }, { - "id": "components-banner-features--dismissible-with-hidden-title-and-secondary-action" + "id": "components-banner-features--dismissible-with-hidden-title-and-secondary-action" }, { - "id": "components-banner-features--with-actions" + "id": "components-banner-features--with-actions" }, { - "id": "components-banner-features--custom-icon" + "id": "components-banner-features--custom-icon" }, { - "id": "components-banner-examples--with-announcement" + "id": "components-banner-examples--with-announcement" } ], "props": [ @@ -106,6 +106,11 @@ "name": "layout", "type": "'default' | 'compact'", "description": "Specify the layout of the Banner. Compact layout will reduce the padding." + }, + { + "name": "actionsLayout", + "type": "'default' | 'inline' | 'stacked'", + "description": "Override the responsive layout of the action buttons. 'inline' layout will display the buttons inline with the title and description, while 'stacked' layout will always render the buttons in a new row." } ], "subcomponents": [ diff --git a/packages/react/src/Banner/Banner.features.stories.tsx b/packages/react/src/Banner/Banner.features.stories.tsx index 3b9ffbe8829..97a435b635a 100644 --- a/packages/react/src/Banner/Banner.features.stories.tsx +++ b/packages/react/src/Banner/Banner.features.stories.tsx @@ -1,8 +1,12 @@ import {CopilotIcon, GitPullRequestIcon} from '@primer/octicons-react' import {action} from 'storybook/actions' import type {Meta} from '@storybook/react-vite' +import React from 'react' import {Banner} from '../Banner' import Link from '../Link' +import {Dialog} from '../Dialog/Dialog' +import {Stack} from '../Stack' +import Heading from '../Heading' const meta = { title: 'Components/Banner/Features', @@ -257,3 +261,138 @@ export const CustomIcon = () => { /> ) } + +export const InsideDialog = () => { + const onDialogClose = React.useCallback(() => {}, []) + + return ( + + Try again} + /> + + ) +} + +export const ActionsLayoutStacked = () => { + return ( + + + + Mobile (320px) + + Primary Action} + secondaryAction={ + Secondary Action + } + /> + Primary Action} + secondaryAction={ + Secondary Action + } + /> + + + + + Desktop (768px) + + Primary Action} + secondaryAction={ + Secondary Action + } + /> + Primary Action} + secondaryAction={ + Secondary Action + } + /> + + + ) +} + +export const ActionsLayoutInline = () => { + return ( + + + + Mobile (320px) + + Primary} + /> + Primary} + /> + + + + + Desktop (768px) + + Primary} + secondaryAction={Secondary} + /> + Primary} + secondaryAction={Secondary} + /> + + + ) +} diff --git a/packages/react/src/Banner/Banner.module.css b/packages/react/src/Banner/Banner.module.css index 7a37f3786dc..a0e3aac4ed0 100644 --- a/packages/react/src/Banner/Banner.module.css +++ b/packages/react/src/Banner/Banner.module.css @@ -9,8 +9,42 @@ grid-template-columns: auto minmax(0, 1fr) auto; align-items: start; - @supports (container-type: inline-size) { - container: banner / inline-size; + &[data-actions-layout='default'] { + @supports (container-type: inline-size) { + container: banner / inline-size; + } + } + + &[data-actions-layout='stacked'] { + & .BannerContainer { + flex-direction: column; + } + + & .BannerActions :where([data-primary-action='trailing']) { + display: none; + } + + & .BannerActions :where([data-primary-action='leading']) { + display: flex; + } + } + + &[data-actions-layout='inline'] { + & .BannerContainer { + flex-wrap: nowrap; + } + + & .BannerActions { + flex: 0 0 auto; + } + + & .BannerActions :where([data-primary-action='trailing']) { + display: flex; + } + + & .BannerActions :where([data-primary-action='leading']) { + display: none; + } } &[data-layout='compact'] { @@ -64,7 +98,7 @@ justify-content: space-between; } -.Banner[data-dismissible]:not([data-title-hidden='']) .BannerContainer { +.Banner[data-dismissible]:not([data-title-hidden], [data-actions-layout='inline']) .BannerContainer { display: grid; grid-template-columns: auto; grid-template-rows: auto; @@ -148,27 +182,19 @@ display: none; } -@media screen and (--viewportRange-regular) { - .BannerActions :where([data-primary-action='trailing']) { - display: flex; - } - - .BannerActions :where([data-primary-action='leading']) { - display: none; - } -} - -.Banner[data-dismissible]:not([data-title-hidden]) .BannerActions { +.Banner[data-dismissible]:not([data-title-hidden], [data-actions-layout='inline']) .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[data-dismissible]:not([data-title-hidden], [data-actions-layout='inline']) + .BannerActionsContainer[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[data-dismissible]:not([data-title-hidden], [data-actions-layout='inline']) + .BannerActionsContainer[data-primary-action='leading'] { display: flex; } diff --git a/packages/react/src/Banner/Banner.test.tsx b/packages/react/src/Banner/Banner.test.tsx index bf17f308c97..b08b0d52bc0 100644 --- a/packages/react/src/Banner/Banner.test.tsx +++ b/packages/react/src/Banner/Banner.test.tsx @@ -188,6 +188,21 @@ describe('Banner', () => { expect(screen.queryByTestId('icon')).toBe(null) }) + it('should render data-actions-layout attribute with inline value', () => { + const {container} = render() + expect(container.firstChild).toHaveAttribute('data-actions-layout', 'inline') + }) + + it('should render data-actions-layout attribute with stacked value', () => { + const {container} = render() + expect(container.firstChild).toHaveAttribute('data-actions-layout', 'stacked') + }) + + it('should render data-actions-layout attribute with default value when not specified', () => { + const {container} = render() + expect(container.firstChild).toHaveAttribute('data-actions-layout', 'default') + }) + describe('Banner.Title', () => { it('should render as a h2 element by default', () => { render( diff --git a/packages/react/src/Banner/Banner.tsx b/packages/react/src/Banner/Banner.tsx index 0ece441893d..81d04875cf6 100644 --- a/packages/react/src/Banner/Banner.tsx +++ b/packages/react/src/Banner/Banner.tsx @@ -69,6 +69,11 @@ export type BannerProps = React.ComponentPropsWithoutRef<'section'> & { * Specify the layout of the Banner. Compact layout will reduce the padding. */ layout?: 'default' | 'compact' + + /** + * Override the default actions layout behavior + */ + actionsLayout?: 'inline' | 'stacked' | 'default' } const iconForVariant: Record = { @@ -101,6 +106,7 @@ export const Banner = React.forwardRef(function Banner secondaryAction, title, variant = 'info', + actionsLayout = 'default', ...rest }, forwardRef, @@ -143,6 +149,7 @@ export const Banner = React.forwardRef(function Banner data-dismissible={onDismiss ? '' : undefined} data-title-hidden={hideTitle ? '' : undefined} data-variant={variant} + data-actions-layout={actionsLayout} tabIndex={-1} ref={ref} data-layout={rest.layout || 'default'}