Skip to content

Commit

Permalink
feat(page-level-banner): add component
Browse files Browse the repository at this point in the history
  • Loading branch information
dierat committed Jun 15, 2022
1 parent f3b00f4 commit 5c7809e
Show file tree
Hide file tree
Showing 7 changed files with 868 additions and 0 deletions.
114 changes: 114 additions & 0 deletions src/components/PageLevelBanner/PageLeveBanner.module.css
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 src/components/PageLevelBanner/PageLeveBanner.stories.tsx
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,
},
};
6 changes: 6 additions & 0 deletions src/components/PageLevelBanner/PageLeveBanner.test.tsx
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);
});
143 changes: 143 additions & 0 deletions src/components/PageLevelBanner/PageLeveBanner.tsx
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';
Loading

0 comments on commit 5c7809e

Please sign in to comment.