diff --git a/ui/components/component-library/banner-base/__snapshots__/banner-base.test.js.snap b/ui/components/component-library/banner-base/__snapshots__/banner-base.test.js.snap index 13440fa40..bb950921f 100644 --- a/ui/components/component-library/banner-base/__snapshots__/banner-base.test.js.snap +++ b/ui/components/component-library/banner-base/__snapshots__/banner-base.test.js.snap @@ -3,7 +3,7 @@ exports[`BannerBase should render bannerbase element correctly 1`] = `
diff --git a/ui/components/component-library/banner-base/banner-base.js b/ui/components/component-library/banner-base/banner-base.js index 7141d3650..14a9e76b3 100644 --- a/ui/components/component-library/banner-base/banner-base.js +++ b/ui/components/component-library/banner-base/banner-base.js @@ -29,11 +29,12 @@ export const BannerBase = ({ return ( {startAccessory && <>{startAccessory}} @@ -68,6 +69,7 @@ export const BannerBase = ({ {onClose && ( + + + +## Props + +The `Banner` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props + + + +The `Banner` accepts all `BannerBase` component props below + + + +### Severity + +Use the `severity` prop and the `SEVERITIES` object from `./ui/helpers/constants/design-system.js` to change the context of `Banner`. + +Optional: `BANNER_SEVERITIES` from `./banner` object can be used instead of `SEVERITIES`. + +Possible options: + +- `SEVERITIES.INFO` Default +- `SEVERITIES.WARNING` +- `SEVERITIES.DANGER` +- `SEVERITIES.SUCCESS` + + + + + +```jsx +import { Banner } from '../../component-library'; +import { SEVERITIES } from '../../../helpers/constants/design-system'; + + + This is a demo of severity Info. + + + This is a demo of severity Warning. + + + This is a demo of severity Danger. + + + This is a demo of severity Success. + +``` + +### Title + +Use the `title` prop to pass a string that is sentence case no period. Use the `titleProps` prop to pass additional props to the `Text` component. + + + + + +```jsx +import { Banner } from '../../component-library'; + + + Pass only a string through the title prop +; +``` + +### Children + +The `children` is the description area of the `Banner` that can be a text or react node. Description shouldn't repeat title and only 1-3 lines. + + + + + +```jsx +import { SIZES } from '../../../helpers/constants/design-system'; +import { Banner } from '../../component-library'; + + + {`Description shouldn't repeat title. 1-3 lines. Can contain a `} + + hyperlink. + +; +``` + +### Action Button Label, onClick, & Props + +Use the `actionButtonLabel` prop to pass text, `actionButtonOnClick` prop to pass an onClick handler, and `actionButtonProps` prop to pass an object of [ButtonLink props](/docs/components-componentlibrary-buttonlink--default-story) for the action + + + + + +```jsx +import { Banner, ICON_NAMES } from '../../component-library'; + + console.log('ButtonLink actionButtonOnClick demo')} +> + Use actionButtonLabel for action text, actionButtonOnClick for the onClick + handler, and actionButtonProps to pass any ButtonLink prop types such as + iconName +; +``` + +### On Close + +Use the `onClose` prop to pass a function to the close button. The close button will appear when this prop is used. + +Additional props can be passed to the close button with `closeButtonProps` + + + + + +```jsx +import { Banner } from '../../component-library'; + + console.log('close button clicked')} +> + Click the close button icon to hide this notifcation +; +``` diff --git a/ui/components/component-library/banner/__snapshots__/banner.test.js.snap b/ui/components/component-library/banner/__snapshots__/banner.test.js.snap new file mode 100644 index 000000000..3d991a687 --- /dev/null +++ b/ui/components/component-library/banner/__snapshots__/banner.test.js.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Banner should render banner element correctly 1`] = ` +
+
+
+
+
+ Banner test +
+

+ should render banner element correctly +

+
+
+
+`; diff --git a/ui/components/component-library/banner/banner.constants.js b/ui/components/component-library/banner/banner.constants.js new file mode 100644 index 000000000..88f653d51 --- /dev/null +++ b/ui/components/component-library/banner/banner.constants.js @@ -0,0 +1,8 @@ +import { SEVERITIES } from '../../../helpers/constants/design-system'; + +export const BANNER_SEVERITIES = { + DANGER: SEVERITIES.DANGER, + INFO: SEVERITIES.INFO, + SUCCESS: SEVERITIES.SUCCESS, + WARNING: SEVERITIES.WARNING, +}; diff --git a/ui/components/component-library/banner/banner.js b/ui/components/component-library/banner/banner.js new file mode 100644 index 000000000..80083d97c --- /dev/null +++ b/ui/components/component-library/banner/banner.js @@ -0,0 +1,93 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; + +import { BannerBase, Icon, ICON_NAMES } from '..'; + +import { + COLORS, + SEVERITIES, + SIZES, +} from '../../../helpers/constants/design-system'; +import { BANNER_SEVERITIES } from './banner.constants'; + +export const Banner = ({ + children, + className, + severity = SEVERITIES.INFO, + ...props +}) => { + const severityIcon = () => { + switch (severity) { + case SEVERITIES.DANGER: + return { + name: ICON_NAMES.DANGER, + color: COLORS.ERROR_DEFAULT, + }; + case SEVERITIES.WARNING: + return { + name: ICON_NAMES.WARNING, + color: COLORS.WARNING_DEFAULT, + }; + case SEVERITIES.SUCCESS: + return { + name: ICON_NAMES.CONFIRMATION, + color: COLORS.SUCCESS_DEFAULT, + }; + // Defaults to SEVERITIES.INFO + default: + return { + name: ICON_NAMES.INFO, + color: COLORS.PRIMARY_DEFAULT, + }; + } + }; + + const severityBackground = () => { + switch (severity) { + case SEVERITIES.DANGER: + return COLORS.ERROR_MUTED; + case SEVERITIES.WARNING: + return COLORS.WARNING_MUTED; + case SEVERITIES.SUCCESS: + return COLORS.SUCCESS_MUTED; + // Defaults to SEVERITIES.INFO + default: + return COLORS.PRIMARY_MUTED; + } + }; + + return ( + } + backgroundColor={severityBackground()} + className={classnames( + 'mm-banner', + { + [`mm-banner--severity-${severity}`]: + Object.values(BANNER_SEVERITIES).includes(severity), + }, + className, + )} + {...props} + > + {children} + + ); +}; + +Banner.propTypes = { + /** + * An additional className to apply to the Banner + */ + className: PropTypes.string, + /** + * Use the `severity` prop and the `SEVERITIES` object from `./ui/helpers/constants/design-system.js` to change the context of `Banner`. + * Possible options: `SEVERITIES.INFO`(Default), `SEVERITIES.WARNING`, `SEVERITIES.DANGER`, `SEVERITIES.SUCCESS` + */ + severity: PropTypes.oneOf(Object.values(BANNER_SEVERITIES)), + /** + * Banner accepts all the props from BannerBase + */ + ...BannerBase.propTypes, +}; diff --git a/ui/components/component-library/banner/banner.scss b/ui/components/component-library/banner/banner.scss new file mode 100644 index 000000000..5def11e64 --- /dev/null +++ b/ui/components/component-library/banner/banner.scss @@ -0,0 +1,16 @@ +.mm-banner { + border-left-color: var(--color-primary-default); + + &--severity-danger { + border-left-color: var(--color-error-default); + } + + &--severity-warning { + border-left-color: var(--color-warning-default); + } + + &--severity-success { + border-left-color: var(--color-success-default); + } +} + diff --git a/ui/components/component-library/banner/banner.stories.js b/ui/components/component-library/banner/banner.stories.js new file mode 100644 index 000000000..8c674cde5 --- /dev/null +++ b/ui/components/component-library/banner/banner.stories.js @@ -0,0 +1,183 @@ +import React from 'react'; +import { useState } from '@storybook/addons'; +import { + DISPLAY, + FLEX_DIRECTION, + SEVERITIES, + SIZES, +} from '../../../helpers/constants/design-system'; +import Box from '../../ui/box/box'; +import { ICON_NAMES, ButtonLink, ButtonPrimary } from '..'; +import README from './README.mdx'; +import { Banner, BANNER_SEVERITIES } from '.'; + +const marginSizeControlOptions = [ + undefined, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 'auto', +]; + +export default { + title: 'Components/ComponentLibrary/Banner', + component: Banner, + parameters: { + docs: { + page: README, + }, + backgrounds: { default: 'alternative' }, + }, + argTypes: { + severity: { + options: Object.values(BANNER_SEVERITIES), + control: 'select', + }, + className: { + control: 'text', + }, + title: { + control: 'text', + }, + children: { + control: 'text', + }, + action: { + control: 'func', + }, + actionButtonLabel: { + control: 'text', + }, + actionButtonOnClick: { + control: 'func', + }, + actionButtonProps: { + control: 'object', + }, + onClose: { + action: 'onClose', + }, + marginTop: { + options: marginSizeControlOptions, + control: 'select', + table: { category: 'box props' }, + }, + marginRight: { + options: marginSizeControlOptions, + control: 'select', + table: { category: 'box props' }, + }, + marginBottom: { + options: marginSizeControlOptions, + control: 'select', + table: { category: 'box props' }, + }, + marginLeft: { + options: marginSizeControlOptions, + control: 'select', + table: { category: 'box props' }, + }, + }, +}; + +export const DefaultStory = (args) => { + const onClose = () => console.log('Banner onClose trigger'); + return ; +}; + +DefaultStory.args = { + title: 'Title is sentence case no period', + children: "Description shouldn't repeat title. 1-3 lines.", + actionButtonLabel: 'Action', +}; + +DefaultStory.storyName = 'Default'; + +export const Severity = (args) => { + return ( + + + This is a demo of severity Info. + + + This is a demo of severity Warning. + + + This is a demo of severity Danger. + + + This is a demo of severity Success. + + + ); +}; + +export const Title = (args) => { + return ; +}; + +Title.args = { + title: 'Title is sentence case no period', + children: 'Pass only a string through the title prop', +}; + +export const Children = (args) => { + return ( + + {`Description shouldn't repeat title. 1-3 lines. Can contain a `} + + hyperlink. + + + ); +}; + +export const ActionButton = (args) => { + return ; +}; + +ActionButton.args = { + title: 'Action prop demo', + actionButtonLabel: 'Action', + actionButtonOnClick: () => console.log('ButtonLink actionButtonOnClick demo'), + actionButtonProps: { + iconName: ICON_NAMES.ARROW_2_RIGHT, + iconPositionRight: true, + }, + children: + 'Use actionButtonLabel for action text, actionButtonOnClick for the onClick handler, and actionButtonProps to pass any ButtonLink prop types such as iconName', +}; + +export const OnClose = (args) => { + const [isShown, setShown] = useState(true); + const bannerToggle = () => { + if (isShown) { + console.log('close button clicked'); + } + setShown(!isShown); + }; + return ( + <> + {isShown ? ( + + ) : ( + View Banner + )} + + ); +}; + +OnClose.args = { + title: 'onClose demo', + children: 'Click the close button icon to hide this notifcation', +}; diff --git a/ui/components/component-library/banner/banner.test.js b/ui/components/component-library/banner/banner.test.js new file mode 100644 index 000000000..9f92450b0 --- /dev/null +++ b/ui/components/component-library/banner/banner.test.js @@ -0,0 +1,111 @@ +/* eslint-disable jest/require-top-level-describe */ +import { render } from '@testing-library/react'; +import React from 'react'; + +import { renderWithUserEvent } from '../../../../test/lib/render-helpers'; + +import { Banner, BANNER_SEVERITIES } from '.'; + +describe('Banner', () => { + it('should render banner element correctly', () => { + const { getByTestId, container } = render( + + should render banner element correctly + , + ); + expect(getByTestId('banner')).toHaveClass('mm-banner'); + expect(container).toMatchSnapshot(); + }); + + it('should render with added classname', () => { + const { getByTestId } = render( + + should render banner element correctly + , + ); + expect(getByTestId('banner')).toHaveClass('mm-banner--test'); + }); + + it('should render with different severity classnames', () => { + const { getByTestId } = render( + <> + + This is a demo of severity Info. + + + This is a demo of severity Warning. + + + This is a demo of severity Danger. + + + This is a demo of severity Success. + + , + ); + expect(getByTestId('info')).toHaveClass('mm-banner--severity-info'); + expect(getByTestId('warning')).toHaveClass('mm-banner--severity-warning'); + expect(getByTestId('danger')).toHaveClass('mm-banner--severity-danger'); + expect(getByTestId('success')).toHaveClass('mm-banner--severity-success'); + }); + + it('should render banner title', () => { + const { getByText } = render(); + expect(getByText('Banner title test')).toHaveClass('mm-banner-base__title'); + }); + + it('should render banner description', () => { + const { getByText } = render(Banner description test); + expect(getByText('Banner description test')).toBeDefined(); + }); + + it('should render banner action button', () => { + const { getByTestId } = render( + + console.log('ButtonLink actionButtonOnClick demo') + } + > + Use actionButtonLabel for action text, actionButtonOnClick for the + onClick handler, and actionButtonProps to pass any ButtonLink prop types + such as iconName + , + ); + expect(getByTestId('action')).toHaveClass('mm-banner-base__action'); + }); + + it('should render and fire onClose event', async () => { + const onClose = jest.fn(); + const { user, getByTestId } = renderWithUserEvent( + , + ); + await user.click(getByTestId('close-button')); + expect(onClose).toHaveBeenCalledTimes(1); + }); +}); diff --git a/ui/components/component-library/banner/index.js b/ui/components/component-library/banner/index.js new file mode 100644 index 000000000..0e9e3ed86 --- /dev/null +++ b/ui/components/component-library/banner/index.js @@ -0,0 +1,2 @@ +export { Banner } from './banner'; +export { BANNER_SEVERITIES } from './banner.constants'; diff --git a/ui/components/component-library/component-library-components.scss b/ui/components/component-library/component-library-components.scss index 96a05a086..d56a616cc 100644 --- a/ui/components/component-library/component-library-components.scss +++ b/ui/components/component-library/component-library-components.scss @@ -27,3 +27,4 @@ @import 'text-field-search/text-field-search'; @import 'form-text-field/form-text-field'; @import 'banner-base/banner-base'; +@import 'banner/banner'; diff --git a/ui/components/component-library/index.js b/ui/components/component-library/index.js index c40fc7dea..28338b1fb 100644 --- a/ui/components/component-library/index.js +++ b/ui/components/component-library/index.js @@ -37,3 +37,4 @@ export { TextFieldSearch } from './text-field-search'; // Molecules export { BannerBase } from './banner-base'; +export { Banner, BANNER_SEVERITIES } from './banner';