Skip to content

Commit bed2ebc

Browse files
authored
feat: Add children prop to <Banner> component to pass extra content (#985)
feat: Add `children` prop to `<Banner>` component to pass extra content (#985)
1 parent a3e8684 commit bed2ebc

File tree

4 files changed

+223
-49
lines changed

4 files changed

+223
-49
lines changed

src/banner/banner.module.css

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
.content {
3434
padding: var(--reactist-spacing-large);
35+
align-items: flex-start;
3536
}
3637

3738
.title,
@@ -74,24 +75,29 @@
7475

7576
.copy {
7677
padding: calc(var(--reactist-spacing-xsmall) / 2) 0;
78+
flex: 1 1 auto;
7779
}
7880
.copy .inlineLink:first-of-type {
7981
margin-left: var(--reactist-spacing-xsmall);
8082
}
83+
.actions {
84+
align-self: center;
85+
}
8186

8287
@container banner (width < 235px) {
83-
.content,
84-
.staticContent {
88+
.content {
8589
flex-direction: column;
86-
align-items: flex-start;
8790
}
88-
8991
.icon {
9092
display: flex;
9193
width: 100%;
9294
align-items: center;
9395
justify-content: space-between;
9496
}
97+
.topContent {
98+
flex-direction: column;
99+
align-items: stretch;
100+
}
95101
.icon:has(.closeButton:only-child) {
96102
display: flex;
97103
}
@@ -101,7 +107,9 @@
101107
.icon .closeButton:only-child {
102108
margin-left: auto;
103109
}
104-
110+
.actions {
111+
align-self: flex-start;
112+
}
105113
.actions .closeButton {
106114
display: none;
107115
}

src/banner/banner.stories.mdx

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { Stack } from '../stack'
55
import { Banner } from './banner'
66
import { Button } from '../button'
77
import { PromoImage } from './story-promo-image'
8+
import { Notice } from '../notice'
9+
import { CheckboxField } from '../checkbox-field'
810

911
<Meta
1012
title="Design system/Banner"
@@ -35,6 +37,10 @@ export function getButton(buttonText) {
3537
)
3638
}
3739

40+
export function getLongDescription() {
41+
return 'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Provident cumque recusandae quibusdam, veniam cum illo? Inventore, doloremque necessitatibus! Sequi porro alias mollitia, temporibus quidem, aut modi tempora placeat laborum eos sapiente necessitatibus autem ipsum officia rerum distinctio consectetur tenetur qui! Perspiciatis ab corporis, itaque alias ex optio voluptatum nulla consequatur aut explicabo dolorem rerum ratione magnam. Mollitia dignissimos et ad commodi quasi molestias fugiat repellendus, magni distinctio voluptate neque quos esse asperiores iure excepturi eligendi eaque veniam voluptas blanditiis temporibus, omnis laborum quidem autem totam. Iure, numquam. Totam facilis dolorum, consequatur, eligendi est dolores modi dolore maiores ipsum magnam a.'
42+
}
43+
3844
export function PlaygroundTemplate({ type, title, description, action }) {
3945
return (
4046
<Stack space="large" maxWidth="medium">
@@ -168,6 +174,29 @@ export function BannerActionExamples({ theme }) {
168174
}}
169175
onClose={() => ({})}
170176
/>
177+
<Banner
178+
type="neutral"
179+
icon={<ArchiveIcon />}
180+
title="This is a sample title"
181+
description={getLongDescription()}
182+
action={{
183+
type: 'button',
184+
label: 'Action',
185+
variant: 'primary',
186+
}}
187+
/>
188+
<Banner
189+
type="neutral"
190+
icon={<ArchiveIcon />}
191+
title="This is a sample title"
192+
description={getLongDescription()}
193+
action={{
194+
type: 'button',
195+
label: 'Action',
196+
variant: 'primary',
197+
}}
198+
onClose={() => ({})}
199+
/>
171200
</Stack>
172201
</Stack>
173202
)
@@ -230,6 +259,102 @@ export function BannerImageExamples({ theme }) {
230259
)
231260
}
232261

262+
export function BannerContentExamples({ theme }) {
263+
return (
264+
<Stack space="medium" align="start">
265+
<Stack space="large">
266+
<Banner
267+
type="neutral"
268+
title="This is a sample title"
269+
description="Here’s the message below the title, sometimes the copy spans over two lines."
270+
>
271+
<Text tone="secondary">Some extra content here</Text>
272+
</Banner>
273+
<Banner
274+
type="neutral"
275+
title="This is a sample title"
276+
description="Here’s the message below the title, sometimes the copy spans over two lines."
277+
action={getButton('Click me!')}
278+
>
279+
<Text tone="secondary">Some extra content here</Text>
280+
</Banner>
281+
<Banner
282+
type="neutral"
283+
title="This is a sample title"
284+
description="Here’s the message below the title, sometimes the copy spans over two lines."
285+
action={getButton('Click me!')}
286+
onClose={() => ({})}
287+
>
288+
<Text tone="secondary">Some extra content here</Text>
289+
</Banner>
290+
<Banner
291+
type="neutral"
292+
title="This is a sample title"
293+
description="Here’s the message below the title, sometimes the copy spans over two lines."
294+
icon={<ArchiveIcon />}
295+
>
296+
<Text tone="secondary">Some extra content here</Text>
297+
</Banner>
298+
<Banner
299+
type="neutral"
300+
title="This is a sample title"
301+
description="Here’s the message below the title, sometimes the copy spans over two lines."
302+
inlineLinks={[{ label: 'Learn more', href: '#' }]}
303+
icon={<ArchiveIcon />}
304+
action={getButton('Click me!')}
305+
>
306+
<Text tone="secondary">Some extra content here</Text>
307+
</Banner>
308+
<Banner
309+
type="neutral"
310+
title="This is a sample title"
311+
description="Here’s the message below the title, sometimes the copy spans over two lines."
312+
inlineLinks={[{ label: 'Learn more', href: '#' }]}
313+
icon={<ArchiveIcon />}
314+
action={getButton('Click me!')}
315+
>
316+
<Stack space="large">
317+
<Text tone="secondary">Some extra content here</Text>
318+
</Stack>
319+
</Banner>
320+
<Banner
321+
type="neutral"
322+
title="This is a sample title"
323+
description={getLongDescription()}
324+
inlineLinks={[{ label: 'Learn more', href: '#' }]}
325+
icon={<ArchiveIcon />}
326+
action={getButton('Click me!')}
327+
>
328+
<Stack space="large">
329+
<Text tone="secondary">{getLongDescription()}</Text>
330+
<Notice tone="info">
331+
<Text>Please check the box to continue.</Text>
332+
</Notice>
333+
<CheckboxField label="Check me!" />
334+
</Stack>
335+
</Banner>
336+
<Banner
337+
type="neutral"
338+
title="This is a sample title"
339+
description={getLongDescription()}
340+
inlineLinks={[{ label: 'Learn more', href: '#' }]}
341+
icon={<ArchiveIcon />}
342+
action={getButton('Click me!')}
343+
onClose={() => ({})}
344+
>
345+
<Stack space="large">
346+
<Text tone="secondary">{getLongDescription()}</Text>
347+
<Notice tone="info">
348+
<Text>Please check the box to continue.</Text>
349+
</Notice>
350+
<CheckboxField label="Check me!" />
351+
</Stack>
352+
</Banner>
353+
</Stack>
354+
</Stack>
355+
)
356+
}
357+
233358
# Banner
234359

235360
A simple banner component meant to be used to _inform_ the user of promotional content, disclaimers, warnings, as well as success and error states.
@@ -340,6 +465,22 @@ Image banners do not feature icons but can include body text or a combination of
340465
</Story>
341466
</Canvas>
342467

468+
### Content
469+
470+
Banners can include extra content.
471+
472+
<Canvas>
473+
<Story
474+
name="Content"
475+
parameters={{
476+
docs: { source: { type: 'code' } },
477+
chromatic: { disableSnapshot: false },
478+
}}
479+
>
480+
<BannerContentExamples theme="light" />
481+
</Story>
482+
</Canvas>
483+
343484
## Props
344485

345486
<ArgsTable of={Banner} />

src/banner/banner.test.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,13 @@ describe('Banner', () => {
245245
const results = await axe(container)
246246
expect(results).toHaveNoViolations()
247247
})
248+
249+
it('renders a banner with children', () => {
250+
render(
251+
<Banner type="info" description="Info">
252+
<p>Child content</p>
253+
</Banner>,
254+
)
255+
expect(screen.getByText('Child content')).toBeInTheDocument()
256+
})
248257
})

src/banner/banner.tsx

Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ type BaseBanner = {
6666
id?: string
6767
title?: React.ReactNode
6868
description: Exclude<React.ReactNode, null | undefined | boolean>
69+
children?: React.ReactNode
6970
action?: Action | React.ReactNode
7071
inlineLinks?: InlineLink[]
7172
} & CloseButton
@@ -112,6 +113,7 @@ const Banner = React.forwardRef<HTMLDivElement, BannerProps>(function Banner(
112113
type,
113114
title,
114115
description,
116+
children,
115117
action,
116118
icon,
117119
image,
@@ -153,57 +155,71 @@ const Banner = React.forwardRef<HTMLDivElement, BannerProps>(function Banner(
153155
>
154156
{image ? <Box className={styles.image}>{image}</Box> : null}
155157

156-
<Box className={styles.content} display="flex" gap="small" alignItems="center">
157-
<Box className={styles.staticContent} display="flex" gap="small" flexGrow={1}>
158-
<Box className={styles.icon}>
159-
{type === 'neutral' ? icon : <BannerIcon type={type} />}
160-
{closeButton}
161-
</Box>
158+
<Box className={styles.content} display="flex" gap="small">
159+
<Box className={styles.icon}>
160+
{type === 'neutral' ? icon : <BannerIcon type={type} />}
161+
{closeButton}
162+
</Box>
162163

163-
<Box className={styles.copy} display="flex" flexDirection="column">
164-
{title ? (
165-
<Box id={titleId} className={styles.title}>
166-
{title}
167-
</Box>
168-
) : null}
164+
<Box display="flex" flexDirection="column" gap="small" flexGrow={1}>
165+
<Box
166+
className={styles.topContent}
167+
display="flex"
168+
gap="small"
169+
alignItems="flexStart"
170+
>
169171
<Box
170-
id={descriptionId}
171-
className={[styles.description, title ? styles.secondary : null]}
172+
className={styles.copy}
173+
display="flex"
174+
flexDirection="column"
175+
flexGrow={1}
172176
>
173-
{description}
174-
{inlineLinks?.map(({ label, ...props }, index) => {
175-
return (
176-
<React.Fragment key={index}>
177-
<TextLink
178-
{...props}
179-
exceptionallySetClassName={styles.inlineLink}
180-
>
181-
{label}
182-
</TextLink>
183-
{index < inlineLinks.length - 1 ? <span> · </span> : ''}
184-
</React.Fragment>
185-
)
186-
})}
177+
{title ? (
178+
<Box id={titleId} className={styles.title}>
179+
{title}
180+
</Box>
181+
) : null}
182+
<Box
183+
id={descriptionId}
184+
className={[styles.description, title ? styles.secondary : null]}
185+
>
186+
{description}
187+
{inlineLinks?.map(({ label, ...props }, index) => {
188+
return (
189+
<React.Fragment key={index}>
190+
<TextLink
191+
{...props}
192+
exceptionallySetClassName={styles.inlineLink}
193+
>
194+
{label}
195+
</TextLink>
196+
{index < inlineLinks.length - 1 ? <span> · </span> : ''}
197+
</React.Fragment>
198+
)
199+
})}
200+
</Box>
187201
</Box>
188-
</Box>
189-
</Box>
190202

191-
{action || closeButton ? (
192-
<Box className={styles.actions} display="flex" gap="small">
193-
{action ? (
194-
isActionObject(action) ? (
195-
action.type === 'button' ? (
196-
<ActionButton {...action} />
197-
) : (
198-
<ActionLink {...action} />
199-
)
200-
) : (
201-
action
202-
)
203+
{action || closeButton ? (
204+
<Box className={styles.actions} display="flex" gap="small">
205+
{action ? (
206+
isActionObject(action) ? (
207+
action.type === 'button' ? (
208+
<ActionButton {...action} />
209+
) : (
210+
<ActionLink {...action} />
211+
)
212+
) : (
213+
action
214+
)
215+
) : null}
216+
{closeButton}
217+
</Box>
203218
) : null}
204-
{closeButton}
205219
</Box>
206-
) : null}
220+
221+
{children}
222+
</Box>
207223
</Box>
208224
</Box>
209225
)

0 commit comments

Comments
 (0)