Skip to content

Commit

Permalink
feat: messagebar: add message bar component (#622)
Browse files Browse the repository at this point in the history
* feat: messagebar: add message bar component

* chore: messagebar: update icons per spec

* chore: messagebar: export component

* chore: messagebar: address pr feedback by adding a mapping
  • Loading branch information
dkilgore-eightfold authored May 24, 2023
1 parent 9a8d34a commit 421d785
Show file tree
Hide file tree
Showing 9 changed files with 679 additions and 0 deletions.
104 changes: 104 additions & 0 deletions src/components/MessageBar/MessageBar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import { Stories } from '@storybook/addon-docs';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { MessageBar, MessageBarType } from '.';
import { IconName } from '../Icon';

export default {
title: 'Message Bar',
parameters: {
docs: {
page: (): JSX.Element => (
<main>
<article>
<section>
<h1>Message Bar</h1>
<p>
These alret banners run across the whole viewport of the
interface. They are to be put at the top of the page to alert
the important message to the user. Depending on the severeity
and context of the alert, these banners can have close button on
the right and another CTA for the users to take action.
</p>
</section>
<section>
<Stories includePrimary title="" />
</section>
</article>
</main>
),
},
},
} as ComponentMeta<typeof MessageBar>;

const Neutral_Story: ComponentStory<typeof MessageBar> = (args) => (
<MessageBar {...args} />
);

export const Neutral = Neutral_Story.bind({});

const Positive_Story: ComponentStory<typeof MessageBar> = (args) => (
<MessageBar {...args} />
);

export const Positive = Positive_Story.bind({});

const Warning_Story: ComponentStory<typeof MessageBar> = (args) => (
<MessageBar {...args} />
);

export const Warning = Warning_Story.bind({});

const Disruptive_Story: ComponentStory<typeof MessageBar> = (args) => (
<MessageBar {...args} />
);

export const Disruptive = Disruptive_Story.bind({});

const messageBarArgs: Object = {
actionButtonProps: {
ariaLabel: 'Action',
classNames: 'my-action-btn-class',
'data-test-id': 'my-action-btn-test-id',
iconProps: null,
id: 'myActionButton',
text: 'Action',
},
closable: true,
header: 'Header 4 used in this MessageBar',
content:
'Body 2 which is at 16px font size is used here in the body section of the MessageBar. The body text can wrap to multiple lines and the buttons will be vertically centered.',
style: {},
classNames: 'my-message-bar-class',
closeButtonProps: {
classNames: 'my-close-btn-class',
'data-test-id': 'my-close-btn-test-id',
id: 'myCloseButton',
},
closeIcon: IconName.mdiClose,
icon: IconName.mdiCheckCircle,
role: 'alert',
type: MessageBarType.positive,
};

Neutral.args = {
...messageBarArgs,
icon: IconName.mdiInformation,
type: MessageBarType.neutral,
};

Positive.args = {
...messageBarArgs,
};

Warning.args = {
...messageBarArgs,
icon: IconName.mdiAlert,
type: MessageBarType.warning,
};

Disruptive.args = {
...messageBarArgs,
icon: IconName.mdiAlertCircle,
type: MessageBarType.disruptive,
};
118 changes: 118 additions & 0 deletions src/components/MessageBar/MessageBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React, { FC, Ref, useEffect, useState } from 'react';
import { MessageBarsProps, MessageBarType } from './MessageBar.types';
import { InfoBarLocale } from '../InfoBar/InfoBar.types';
import { Icon, IconName } from '../Icon';
import { mergeClasses } from '../../shared/utilities';
import { ButtonShape, NeutralButton } from '../Button';
import LocaleReceiver, {
useLocaleReceiver,
} from '../LocaleProvider/LocaleReceiver';
import enUS from '../InfoBar/Locale/en_US';

import styles from './messageBar.module.scss';

export const MessageBar: FC<MessageBarsProps> = React.forwardRef(
(props: MessageBarsProps, ref: Ref<HTMLDivElement>) => {
const {
actionButtonProps,
classNames,
closable,
closeButtonAriaLabelText: defaultCloseButtonAriaLabelText,
closeButtonProps,
closeIcon = IconName.mdiClose,
content,
header,
icon,
locale = enUS,
onClose,
role = 'alert',
style,
type = MessageBarType.neutral,
...rest
} = props;

// ============================ Strings ===========================
const [infoBarLocale] = useLocaleReceiver('InfoBar');
let mergedLocale: InfoBarLocale;

if (props.locale) {
mergedLocale = props.locale;
} else {
mergedLocale = infoBarLocale || props.locale;
}

const [closeButtonAriaLabelText, setCloseButtonAriaLabelText] =
useState<string>(defaultCloseButtonAriaLabelText);

// Locs: if the prop isn't provided use the loc defaults.
// If the mergedLocale is changed, update.
useEffect(() => {
setCloseButtonAriaLabelText(
props.closeButtonAriaLabelText
? props.closeButtonAriaLabelText
: mergedLocale.lang!.closeButtonAriaLabelText
);
}, [mergedLocale]);

const messageBarClassNames: string = mergeClasses([
styles.messageBar,
classNames,
{ [styles.neutral]: type === MessageBarType.neutral },
{ [styles.positive]: type === MessageBarType.positive },
{ [styles.warning]: type === MessageBarType.warning },
{ [styles.disruptive]: type === MessageBarType.disruptive },
]);

const messageBarTypeToIconNameMap = new Map<MessageBarType, IconName>([
[MessageBarType.disruptive, IconName.mdiAlertCircle],
[MessageBarType.neutral, IconName.mdiInformation],
[MessageBarType.positive, IconName.mdiCheckCircle],
[MessageBarType.warning, IconName.mdiAlert],
]);

const getIconName = (): IconName => {
if (icon) {
return icon;
}
return messageBarTypeToIconNameMap.get(type);
};

return (
<LocaleReceiver componentName={'InfoBar'} defaultLocale={enUS}>
{(_contextLocale: InfoBarLocale) => {
return (
<div
{...rest}
className={messageBarClassNames}
ref={ref}
style={style}
role={role}
>
<Icon path={getIconName()} classNames={styles.icon} />
<div className={styles.message}>
{header && <h4 className={styles.header}>{header}</h4>}
<div className={styles.innerMessage}>{content}</div>
</div>
{(actionButtonProps || closable) && (
<div className={styles.actions}>
{actionButtonProps && (
<NeutralButton {...actionButtonProps} />
)}
{closable && (
<NeutralButton
ariaLabel={closeButtonAriaLabelText}
iconProps={{ path: closeIcon }}
onClick={onClose}
shape={ButtonShape.Round}
{...closeButtonProps}
/>
)}
</div>
)}
</div>
);
}}
</LocaleReceiver>
);
}
);
21 changes: 21 additions & 0 deletions src/components/MessageBar/MessageBar.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { InfoBarsProps } from '../InfoBar';

export enum MessageBarType {
neutral = 'neutral',
positive = 'positive',
warning = 'warning',
disruptive = 'disruptive',
}

export interface MessageBarsProps extends Omit<InfoBarsProps, 'type'> {
/**
* Header of the MessageBar
*/
header?: React.ReactNode;
/**
* Type of the MessageBar
* @default MessageBarType.neutral
*/
type?: MessageBarType;
}
Loading

0 comments on commit 421d785

Please sign in to comment.