Skip to content

Commit

Permalink
feat(Banner): add banner component (#4335)
Browse files Browse the repository at this point in the history
* feat(Banner): add banner component

* chore: check-in work

* feat: add styling for actions to Banner

* fix: justify actions to end for wider viewports

* chore: remove title prop

* docs(Banner): add examples for banner scenarios

* refactor(Banner): update to include top-level slot props based on API doc

* chore: run format

* test(Banner): update tests for Banner

* chore: update docs.json and fix eslint warning

* chore: add changeset

* fix(Banner): change alignment of buttons to only apply in larger breakpoints

* test(vrt): update snapshots

* use CSS vars with fallbacks

* test(vrt): update snapshots

* Update packages/react/src/Banner/Banner.tsx

Co-authored-by: Katie Langerman <18661030+langermank@users.noreply.github.com>

* refactor(Banner): update done to use upsell, when available

* test(vrt): update snapshots

* docs(Banner): add additional controls to playground

* fix(Banner): update upsell colors to be applied to intended properties

* test(vrt): update snapshots

* refactor(Banner): add support for container queries and flexbox fallback

* test(vrt): update snapshots

* fix(Banner): update flexbox fallback to not wrap early

* test(vrt): update snapshots

* test: update tests to ignore jsdom cssparser error

* fix: re-enable container query

* test(vrt): update snapshots

* test(vrt): update snapshots

* chore: fix eslint and axe violations

* fix: update grid for container usage

* test(vrt): update snapshots

* docs: add sidebar example to playground

* fix(Banner): update stories and address style issues

* test(e2e): update Banner e2e test to include new stories

* test(vrt): update snapshots

---------

Co-authored-by: Josh Black <joshblack@users.noreply.github.com>
Co-authored-by: langermank <18661030+langermank@users.noreply.github.com>
  • Loading branch information
3 people committed Apr 24, 2024
1 parent 1c847bd commit 8b4d838
Show file tree
Hide file tree
Showing 148 changed files with 1,337 additions and 2 deletions.
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
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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'
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>}
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>}
/>
)
}

0 comments on commit 8b4d838

Please sign in to comment.