Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Banner): add banner component #4335

Merged
merged 50 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
6f53c42
feat(Banner): add banner component
joshblack Mar 1, 2024
e7ecf41
chore: check-in work
joshblack Mar 4, 2024
9afaad9
feat: add styling for actions to Banner
joshblack Mar 5, 2024
73542f6
fix: justify actions to end for wider viewports
joshblack Mar 5, 2024
017ba4e
Merge branch 'main' into feat/add-banner-exploration
joshblack Mar 5, 2024
8405bcf
Merge branch 'main' of github.com:primer/react into feat/add-banner-e…
joshblack Mar 6, 2024
6136b81
chore: remove title prop
joshblack Mar 6, 2024
860eea1
docs(Banner): add examples for banner scenarios
joshblack Mar 7, 2024
8140698
Merge branch 'main' of github.com:primer/react into feat/add-banner-e…
joshblack Mar 11, 2024
ea248f8
Merge branch 'main' of github.com:primer/react into feat/add-banner-e…
joshblack Mar 13, 2024
49f0fb1
refactor(Banner): update to include top-level slot props based on API…
joshblack Mar 13, 2024
7fca41e
Merge branch 'main' of github.com:primer/react into feat/add-banner-e…
joshblack Apr 10, 2024
1fc43e9
chore: run format
joshblack Apr 10, 2024
107f0ee
test(Banner): update tests for Banner
joshblack Apr 10, 2024
c418441
chore: update docs.json and fix eslint warning
joshblack Apr 11, 2024
3952918
chore: add changeset
joshblack Apr 11, 2024
e627264
fix(Banner): change alignment of buttons to only apply in larger brea…
joshblack Apr 11, 2024
beeaf2f
Merge branch 'main' into feat/add-banner-exploration
joshblack Apr 11, 2024
fd2d938
test(vrt): update snapshots
joshblack Apr 11, 2024
b4797a8
use CSS vars with fallbacks
langermank Apr 11, 2024
6f39e0b
test(vrt): update snapshots
joshblack Apr 11, 2024
b838530
Update packages/react/src/Banner/Banner.tsx
joshblack Apr 12, 2024
b0cb281
Merge branch 'main' of github.com:primer/react into feat/add-banner-e…
joshblack Apr 12, 2024
f38a562
refactor(Banner): update done to use upsell, when available
joshblack Apr 12, 2024
e7ff35a
test(vrt): update snapshots
joshblack Apr 12, 2024
d139a86
docs(Banner): add additional controls to playground
joshblack Apr 12, 2024
c82781e
fix(Banner): update upsell colors to be applied to intended properties
joshblack Apr 12, 2024
8f547e3
Merge branch 'feat/add-banner-exploration' of github.com:primer/react…
joshblack Apr 12, 2024
a555ba2
Merge branch 'main' into feat/add-banner-exploration
joshblack Apr 12, 2024
17c0384
test(vrt): update snapshots
joshblack Apr 12, 2024
21b5854
Merge branch 'main' of github.com:primer/react into feat/add-banner-e…
joshblack Apr 17, 2024
a219f7e
refactor(Banner): add support for container queries and flexbox fallback
joshblack Apr 17, 2024
17893b2
test(vrt): update snapshots
joshblack Apr 17, 2024
36f9cea
fix(Banner): update flexbox fallback to not wrap early
joshblack Apr 17, 2024
3dbbeb6
test(vrt): update snapshots
joshblack Apr 17, 2024
6c54463
test: update tests to ignore jsdom cssparser error
joshblack Apr 17, 2024
f0a4d14
Merge branch 'feat/add-banner-exploration' of github.com:primer/react…
joshblack Apr 17, 2024
fe93241
fix: re-enable container query
joshblack Apr 17, 2024
b628857
test(vrt): update snapshots
joshblack Apr 17, 2024
2f753ba
test(vrt): update snapshots
joshblack Apr 17, 2024
59299d7
chore: fix eslint and axe violations
joshblack Apr 17, 2024
2f520a7
Merge branch 'feat/add-banner-exploration' of github.com:primer/react…
joshblack Apr 17, 2024
068b0da
fix: update grid for container usage
joshblack Apr 17, 2024
b3c8e93
test(vrt): update snapshots
joshblack Apr 17, 2024
0e9f8e1
docs: add sidebar example to playground
joshblack Apr 19, 2024
3f91ca0
fix(Banner): update stories and address style issues
joshblack Apr 19, 2024
42fe2bf
Merge branch 'feat/add-banner-exploration' of github.com:primer/react…
joshblack Apr 19, 2024
54a70f4
Merge branch 'main' into feat/add-banner-exploration
joshblack Apr 19, 2024
167c43b
test(e2e): update Banner e2e test to include new stories
joshblack Apr 19, 2024
fe5b3ed
test(vrt): update snapshots
joshblack Apr 19, 2024
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
5 changes: 5 additions & 0 deletions .changeset/shaggy-kids-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Add support for experimental Banner component
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.
112 changes: 112 additions & 0 deletions e2e/components/Banner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {test, expect} from '@playwright/test'
joshblack marked this conversation as resolved.
Show resolved Hide resolved
import {visit} from '../test-helpers/storybook'
import {themes} from '../test-helpers/themes'
import {viewports} from '../test-helpers/viewports'

const stories: Array<{title: string; id: string; viewports?: Array<keyof typeof viewports>}> = [
{
title: 'Default',
id: 'drafts-components-banner--default',
viewports: ['primer.breakpoint.xs', 'primer.breakpoint.sm'],
},
{
title: 'Critical',
id: 'drafts-components-banner-features--critical',
},
{
title: 'Dismiss',
id: 'drafts-components-banner-features--dismiss',
},
{
title: 'Dismiss With Actions',
id: 'drafts-components-banner-features--dismiss-with-actions',
},
{
title: 'Info',
id: 'drafts-components-banner-features--info',
},
{
title: 'Success',
id: 'drafts-components-banner-features--success',
},
{
title: 'Upsell',
id: 'drafts-components-banner-features--upsell',
},
{
title: 'Warning',
id: 'drafts-components-banner-features--warning',
},
{
title: 'WithActions',
id: 'drafts-components-banner-features--with-actions',
viewports: ['primer.breakpoint.xs', 'primer.breakpoint.sm'],
},
{
title: 'WithHiddenTitle',
id: 'drafts-components-banner-features--with-hidden-title',
},
{
title: 'WithHiddenTitleAndActions',
id: 'drafts-components-banner-features--with-hidden-title-and-actions',
viewports: ['primer.breakpoint.xs', 'primer.breakpoint.sm'],
},
{
title: 'InSidebar',
id: 'drafts-components-banner-examples--in-sidebar',
},
{
title: 'Multiline',
id: 'drafts-components-banner-examples--multiline',
viewports: ['primer.breakpoint.xs', 'primer.breakpoint.sm'],
},
]

test.describe('Banner', () => {
for (const story of stories) {
test.describe(story.title, () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: story.id,
globals: {
colorScheme: theme,
},
})

// Default state
expect(await page.screenshot()).toMatchSnapshot(`Banner.${story.title}.${theme}.png`)
})

test('axe @aat', async ({page}) => {
await visit(page, {
id: story.id,
globals: {
colorScheme: theme,
},
})
await expect(page).toHaveNoViolations()
})
})
}

if (story.viewports) {
for (const name of story.viewports) {
test(`${name} @vrt`, async ({page}) => {
await visit(page, {
id: story.id,
})
const width = viewports[name]

await page.setViewportSize({
width,
height: 667,
})
expect(await page.screenshot()).toMatchSnapshot(`Banner.${story.title}.${name}.png`)
})
}
}
})
}
})
4 changes: 2 additions & 2 deletions e2e/test-helpers/viewports.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// TODO: Import PrimerBreakpoints from src/utils/layout/breakpoints.ts and refactor the usage of this object
export const viewports: {[key: string]: number} = {
export const viewports = {
'primer.breakpoint.xs': 544,
'primer.breakpoint.sm': 768,
'primer.breakpoint.md': 1012,
'primer.breakpoint.lg': 1280,
'primer.breakpoint.xl': 1400,
}
} as const
68 changes: 68 additions & 0 deletions packages/react/src/Banner/Banner.docs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"id": "banner",
"name": "Banner",
"status": "alpha",
"a11yReviewed": false,
"importPath": "@primer/react/experimental",
"stories": [],
"props": [
{
"name": "description",
"type": "React.ReactNode",
"description": "Provide an optional description for the Banner. This should provide supplemental information about the Banner"
},
{
"name": "icon",
"type": "React.ReactNode",
"description": "Provide an icon for the banner"
},
{
"name": "onDismiss",
"type": "() => void",
"description": "Optionally provide a handler to be called when the banner is dismissed. Providing this prop will show a dismiss button"
},
{
"name": "primaryAction",
"type": "React.ReactNode",
"description": ""
},
{
"name": "secondaryAction",
"type": "React.ReactNode",
"description": ""
},
{
"name": "title",
"type": "React.ReactNode",
"description": "The title for the Banner. This will be used as the accessible name and is required unless `Banner.Title` is used as a child"
},
{
"name": "variant",
"type": "'critical' | 'info' | 'success' | 'upsell' | 'warning'",
"description": ""
}
],
"subcomponents": [
{
"name": "Banner.Title",
"props": [
{
"name": "as",
"type": "'h2' | 'h3' | 'h4' | 'h5' | 'h6'"
}
]
},
{
"name": "Banner.Description",
"props": []
},
{
"name": "Banner.PrimaryAction",
"props": []
},
{
"name": "Banner.SecondaryAction",
"props": []
}
]
}
152 changes: 152 additions & 0 deletions packages/react/src/Banner/Banner.examples.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import {Banner} from '../Banner'
import {action} from '@storybook/addon-actions'
import Link from '../Link'
import type {Meta} from '@storybook/react'
import {Status} from '../internal/components/Status'
import {Alert} from '../internal/components/Alert'
import FormControl from '../FormControl'
import RadioGroup from '../RadioGroup'
import Radio from '../Radio'
import {Button} from '../Button'
import React from 'react'
import {useFocus} from '../internal/hooks/useFocus'
import {PageLayout} from '../PageLayout'

const meta = {
title: 'Drafts/Components/Banner/Examples',
component: Banner,
} satisfies Meta<typeof Banner>

export default meta

export const WithUserAction = () => {
const [hasError, setHasError] = React.useState(false)
const bannerRef = React.useRef<React.ElementRef<typeof Banner>>(null)
const focus = useFocus()

return (
<>
{hasError ? (
<Banner
ref={bannerRef}
title="Error"
description={<Alert>Something went wrong. Please try again later.</Alert>}
Comment on lines +32 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I understand that heading semantics won't be conveyed through a live region, I'm wondering whether it would make sense to encourage including it as part of the live region announcement since it may contain information that isn't part of the description. 🤔

What would that look like?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@khiga8 would something like this maybe work?

description={
  <Alert>
    <VisuallyHidden>Error:</VisuallyHidden> Something went wrong. Please try again later.
  </Alert>
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To reduce redundancy around the heading text appearing in the banner twice for screen reader users, would something like this be possible?

<Banner>
  <Status>
    <Banner.Title>Subscription renewed</Banner.Title>
    <Banner.Description>Your subscription has been successfully renewed until May 5, 2024. </Banner.description>
  </Status>
</Banner>

cc: @ichelsea if you have other thoughts!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@khiga8 I bet we could make it work, we could also announce the live region itself if that would be desirable (e.g. something like Banner as={Status}

variant="critical"
/>
) : null}
<Button
type="button"
onClick={() => {
setHasError(true)
focus(bannerRef)
}}
>
Update profile
</Button>
</>
)
}

export const WithDynamicContent = () => {
type Choice = 'one' | 'two' | 'three'
const messages: Map<Choice, string> = new Map([
['one', 'This is a message for choice one'],
['two', 'This is a message for choice two'],
['three', 'This is a message for choice three'],
])
const [selected, setSelected] = React.useState<Choice>('one')

return (
<>
<Banner
title="Info"
description={<Status>{messages.get(selected)}</Status>}
onDismiss={action('onDismiss')}
primaryAction={<Banner.PrimaryAction>Button</Banner.PrimaryAction>}
secondaryAction={<Banner.SecondaryAction>Button</Banner.SecondaryAction>}
/>
<RadioGroup
sx={{marginTop: 4}}
name="options"
onChange={selected => {
setSelected(selected as Choice)
}}
>
<RadioGroup.Label>Choices</RadioGroup.Label>
<FormControl>
<Radio value="one" defaultChecked />
<FormControl.Label>Choice one</FormControl.Label>
</FormControl>
<FormControl>
<Radio value="two" />
<FormControl.Label>Choice two</FormControl.Label>
</FormControl>
<FormControl>
<Radio value="three" />
<FormControl.Label>Choice three</FormControl.Label>
</FormControl>
</RadioGroup>
</>
)
}

export const WithCustomHeading = () => {
return (
<Banner
onDismiss={action('onDismiss')}
primaryAction={<Banner.PrimaryAction>Button</Banner.PrimaryAction>}
secondaryAction={<Banner.SecondaryAction>Button</Banner.SecondaryAction>}
>
<Banner.Title as="h3">Info</Banner.Title>
<Banner.Description>
GitHub users are{' '}
<Link inline underline href="#">
now required
</Link>{' '}
to enable two-factor authentication as an additional security measure.
</Banner.Description>
</Banner>
)
}

export const InSidebar = () => {
return (
<>
<PageLayout>
<PageLayout.Header divider="line">PageLayout Header</PageLayout.Header>
<PageLayout.Pane position="start" divider="line">
<h2>PageLayout Pane</h2>
<Banner
title="Info"
description={
<>
GitHub users are{' '}
<Link inline underline href="#">
now required
</Link>{' '}
to enable two-factor authentication as an additional security measure.
</>
}
primaryAction={<Banner.PrimaryAction>Button</Banner.PrimaryAction>}
secondaryAction={<Banner.SecondaryAction>Button</Banner.SecondaryAction>}
/>
</PageLayout.Pane>
<PageLayout.Content>
<h1>PageLayout Content</h1>
</PageLayout.Content>
</PageLayout>
</>
)
}

export const Multiline = () => {
return (
<Banner
onDismiss={action('onDismiss')}
title="Info"
description="GitHub users are now required to enable two-factor authentication as an additional security measure. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen bSed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"
primaryAction={<Banner.PrimaryAction>Button</Banner.PrimaryAction>}
secondaryAction={<Banner.SecondaryAction>Button</Banner.SecondaryAction>}
/>
)
}
Loading
Loading