Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: messagebar: add message bar component #622

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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