-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(page-level-banner): add component
- Loading branch information
Showing
7 changed files
with
868 additions
and
0 deletions.
There are no files selected for viewing
114 changes: 114 additions & 0 deletions
114
src/components/PageLevelBanner/PageLeveBanner.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
@import '../../design-tokens/mixins.css'; | ||
|
||
/*------------------------------------*\ | ||
# PAGE LEVEL BANNER | ||
\*------------------------------------*/ | ||
|
||
/** | ||
* 1) Message of information, success, caution, or warning to the user | ||
* 2) Default styles are for mobile view; desktop view is further down | ||
*/ | ||
.banner { | ||
display: grid; | ||
gap: var(--eds-size-2); | ||
position: relative; | ||
background-color: var(--eds-theme-color-neutral-subtle-background); | ||
border-bottom: var(--eds-border-width-md) solid var(--messaging-border-color); | ||
|
||
/* 2 */ | ||
padding: var(--eds-size-4); | ||
text-align: center; | ||
justify-items: center; | ||
} | ||
|
||
/** | ||
* PageLeveBanner informational icon | ||
* 1) Icon color matches the thick bottom border | ||
*/ | ||
.banner__icon { | ||
color: var(--messaging-icon-color); /* 1 */ | ||
--icon-size-default: var(--eds-size-5); | ||
} | ||
|
||
.banner__textContent { | ||
display: flex; | ||
flex-direction: column; | ||
justify-content: space-between; | ||
} | ||
|
||
/** | ||
* PageLeveBanner close button | ||
* 1) Button used to dismiss dismissable banners | ||
*/ | ||
.banner__close-btn.banner__close-btn { | ||
position: absolute; | ||
top: var(--eds-size-half); | ||
right: var(--eds-size-half); | ||
} | ||
|
||
/*------------------------------------*\ | ||
# DESKTOP VIEW | ||
\*------------------------------------*/ | ||
|
||
@media screen and (min-width: $eds-bp-md) { | ||
/** | ||
* PageLeveBanner desktop view | ||
* 1) Everything is left-aligned | ||
* 2) Padding is reduced | ||
* 3) Icon is smaller to match the heading line height | ||
*/ | ||
.banner { | ||
grid-template-columns: min-content 1fr; | ||
text-align: left; /* 1 */ | ||
justify-items: flex-start; /* 1 */ | ||
padding: var(--eds-size-2) var(--eds-size-4); /* 2 */ | ||
|
||
.banner__icon { | ||
--icon-size-default: var(--eds-size-3); /* 3 */ | ||
} | ||
} | ||
|
||
/** | ||
* Dismissable banner desktop view | ||
* 1) Add extra right padding to account for close button | ||
*/ | ||
.banner--dismissable { | ||
padding-right: var(--eds-size-9); /* 1 */ | ||
} | ||
} | ||
|
||
/*------------------------------------*\ | ||
# VARIANTS | ||
\*------------------------------------*/ | ||
|
||
/** | ||
* PageLeveBanner success | ||
*/ | ||
.banner--success { | ||
@mixin messagingSuccess; | ||
border-color: var(--eds-theme-color-border-utility-success-strong); | ||
} | ||
|
||
/** | ||
* PageLeveBanner warning | ||
*/ | ||
.banner--warning { | ||
@mixin messagingWarning; | ||
border-color: var(--eds-theme-color-border-utility-warning-strong); | ||
} | ||
|
||
/** | ||
* PageLeveBanner error | ||
*/ | ||
.banner--error { | ||
@mixin messagingError; | ||
border-color: var(--eds-theme-color-border-utility-error-strong); | ||
} | ||
|
||
/** | ||
* PageLeveBanner brand | ||
*/ | ||
.banner--brand { | ||
@mixin messagingBrand; | ||
border-color: var(--eds-theme-color-border-brand-primary-strong); | ||
} |
119 changes: 119 additions & 0 deletions
119
src/components/PageLevelBanner/PageLeveBanner.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import type { StoryObj } from '@storybook/react'; | ||
import React from 'react'; | ||
|
||
import { PageLeveBanner, Variant } from './PageLeveBanner'; | ||
import Button from '../Button'; | ||
|
||
export default { | ||
title: 'Molecules/Messaging/PageLeveBanner', | ||
component: PageLeveBanner, | ||
args: { | ||
title: | ||
'New curriculum updates are available for one or more of your courses.', | ||
}, | ||
}; | ||
|
||
type Args = React.ComponentProps<typeof PageLeveBanner>; | ||
|
||
const getDescription = (status?: Variant) => ( | ||
<> | ||
Summit Learning has a full-time team dedicated to constantly improving our | ||
curriculum. To see the updates,{' '} | ||
<Button | ||
onClick={(event: any) => event.preventDefault()} | ||
status={status} | ||
variant="link" | ||
> | ||
click into the course | ||
</Button> | ||
. | ||
</> | ||
); | ||
|
||
const dismissMethod = () => { | ||
console.log('dismissing~'); | ||
}; | ||
|
||
export const Brand: StoryObj<Args> = { | ||
render: ({ variant, ...other }) => { | ||
return ( | ||
<PageLeveBanner | ||
description={getDescription(variant)} | ||
variant={variant} | ||
{...other} | ||
/> | ||
); | ||
}, | ||
}; | ||
|
||
export const Success: StoryObj<Args> = { | ||
...Brand, | ||
args: { | ||
variant: 'success', | ||
}, | ||
parameters: { | ||
chromatic: { viewports: [560, 960, 1200] }, | ||
}, | ||
}; | ||
|
||
export const Warning: StoryObj<Args> = { | ||
...Brand, | ||
args: { | ||
variant: 'warning', | ||
}, | ||
}; | ||
|
||
export const Error: StoryObj<Args> = { | ||
...Brand, | ||
args: { | ||
variant: 'error', | ||
}, | ||
}; | ||
|
||
export const NoDescription: StoryObj<Args> = { | ||
...Brand, | ||
args: { | ||
description: undefined, | ||
}, | ||
}; | ||
|
||
export const NoTitle: StoryObj<Args> = { | ||
...Brand, | ||
args: { | ||
title: undefined, | ||
}, | ||
}; | ||
|
||
export const BrandDismissable: StoryObj<Args> = { | ||
...Brand, | ||
args: { | ||
onDismiss: dismissMethod, | ||
}, | ||
parameters: { | ||
chromatic: { viewports: [560, 960, 1200] }, | ||
}, | ||
}; | ||
|
||
export const SuccessDismissable: StoryObj<Args> = { | ||
...Brand, | ||
args: { | ||
variant: 'success', | ||
onDismiss: dismissMethod, | ||
}, | ||
}; | ||
|
||
export const WarningDismissable: StoryObj<Args> = { | ||
...Brand, | ||
args: { | ||
variant: 'warning', | ||
onDismiss: dismissMethod, | ||
}, | ||
}; | ||
|
||
export const ErrorDismissable: StoryObj<Args> = { | ||
...Brand, | ||
args: { | ||
variant: 'error', | ||
onDismiss: dismissMethod, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { generateSnapshots } from '@chanzuckerberg/story-utils'; | ||
import * as PageLeveBannerStoryFile from './PageLeveBanner.stories'; | ||
|
||
describe('<PageLeveBanner />', () => { | ||
generateSnapshots(PageLeveBannerStoryFile); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import clsx from 'clsx'; | ||
import React, { ReactNode } from 'react'; | ||
import styles from './PageLeveBanner.module.css'; | ||
import Button from '../Button'; | ||
import Heading, { HeadingElement } from '../Heading'; | ||
import Icon from '../Icon'; | ||
import Text from '../Text'; | ||
|
||
export type Variant = 'brand' | 'success' | 'warning' | 'error'; | ||
|
||
export type PageLeveBannerProps = { | ||
/** | ||
* CSS class names that can be appended to the component. | ||
*/ | ||
className?: string; | ||
/** | ||
* The description/body text of the banner | ||
*/ | ||
description?: ReactNode; | ||
/** | ||
* The element the description renders as | ||
*/ | ||
descriptionAs?: 'p' | 'span'; | ||
/** | ||
* Callback when banner is dismissed. When passed in, renders banner with a close icon in the top right. | ||
*/ | ||
onDismiss?: () => void; | ||
/** | ||
* The title/heading of the banner | ||
*/ | ||
title?: ReactNode; | ||
/** | ||
* The element the title renders as | ||
*/ | ||
titleAs?: HeadingElement; | ||
/** | ||
* Stylistic variations for the banner type. | ||
* - **brand** - results in a purple banner | ||
* - **success** - results in a green banner | ||
* - **warning** - results in a yellow banner | ||
* - **error** - results in a red banner | ||
*/ | ||
variant?: Variant; | ||
}; | ||
|
||
const variantToIconAssetsMap: { | ||
[key: string]: { | ||
name: 'notifications' | 'forum' | 'check-circle' | 'warning' | 'dangerous'; | ||
title: string; | ||
}; | ||
} = { | ||
brand: { | ||
name: 'notifications', | ||
title: 'attention', | ||
}, | ||
success: { | ||
name: 'check-circle', | ||
title: 'success', | ||
}, | ||
warning: { | ||
name: 'warning', | ||
title: 'warning', | ||
}, | ||
error: { | ||
name: 'dangerous', | ||
title: 'error', | ||
}, | ||
}; | ||
|
||
/** | ||
* ```ts | ||
* import {PageLeveBanner} from "@chanzuckerberg/eds"; | ||
* ``` | ||
* | ||
* A banner placed at the top of the page with important information. | ||
* | ||
* Example usage: | ||
* | ||
* ```tsx | ||
* <PageLeveBanner | ||
* onDismiss={handleDismiss} | ||
* title="Some Title" | ||
* description={<>Some description, possibly with a <Link href="https://go.czi.team/eds">link to some other resource</Link>.</>} | ||
* /> | ||
* ``` | ||
*/ | ||
export const PageLeveBanner = ({ | ||
className, | ||
description, | ||
descriptionAs = 'p', | ||
onDismiss, | ||
variant = 'brand', | ||
title, | ||
titleAs = 'h3', | ||
}: PageLeveBannerProps) => { | ||
const componentClassName = clsx( | ||
// Base styles | ||
styles['banner'], | ||
className, | ||
// Variants | ||
variant === 'brand' && styles['banner--brand'], | ||
variant === 'error' && styles['banner--error'], | ||
variant === 'warning' && styles['banner--warning'], | ||
variant === 'success' && styles['banner--success'], | ||
// Other options | ||
onDismiss && styles['banner--dismissable'], | ||
); | ||
|
||
return ( | ||
<article className={componentClassName}> | ||
{onDismiss && ( | ||
<Button | ||
className={styles['banner__close-btn']} | ||
onClick={onDismiss} | ||
status="neutral" | ||
variant="icon" | ||
> | ||
<Icon name={'close'} purpose="informative" title={'dismiss module'} /> | ||
</Button> | ||
)} | ||
|
||
<Icon | ||
className={styles['banner__icon']} | ||
name={variantToIconAssetsMap[variant].name} | ||
purpose="informative" | ||
title={variantToIconAssetsMap[variant].title} | ||
/> | ||
<div> | ||
{title && ( | ||
<Heading as={titleAs} size="title-md" variant={variant}> | ||
{title} | ||
</Heading> | ||
)} | ||
{description && ( | ||
<Text as={descriptionAs} size="sm" variant="neutral"> | ||
{description} | ||
</Text> | ||
)} | ||
</div> | ||
</article> | ||
); | ||
}; | ||
PageLeveBanner.displayName = 'PageLeveBanner'; |
Oops, something went wrong.