feat(component): add Notification

Closes #20
This commit is contained in:
Eric Liu 2019-12-20 18:56:56 -08:00
commit 94dceae1fb
11 changed files with 309 additions and 2 deletions

View file

@ -28,9 +28,16 @@ Currently, the following components are supported:
- CopyButton
- DataTableSkeleton
- InlineLoading
- Loading
- Link
- ListItem
- Loading
- Notification
- ToastNotification
- InlineNotification
- NotificationActionButton
- NotificationButton
- NotificationIcon
- NotificationTextDetails
- OrderedList
- RadioButton
- RadioButtonSkeleton

View file

@ -0,0 +1,54 @@
<script>
let className = undefined;
export { className as class };
export let kind = 'error';
export let title = 'provide a title';
export let subtitle = ''; // TODO: support subtitle slot?
export let role = 'alert';
export let notificationType = 'inline';
export let iconDescription = 'closes notification';
export let hideCloseButton = false;
export let lowContrast = false;
export let style = undefined;
import { createEventDispatcher } from 'svelte';
import NotificationIcon from './NotificationIcon.svelte';
import NotificationTextDetails from './NotificationTextDetails.svelte';
import NotificationButton from './NotificationButton.svelte';
import { cx } from '../../lib';
const dispatch = createEventDispatcher();
const _class = cx(
'--inline-notification',
lowContrast && '--inline-notification--low-contrast',
kind && `--inline-notification--${kind}`,
hideCloseButton && '--inline-notification--hide-close-button',
className
);
let open = true;
$: if (!open) {
dispatch('close');
}
</script>
{#if open}
<div on:click on:mouseover on:mouseenter on:mouseleave class={_class} {style} {role} {kind}>
<div class={cx('--inline-notification__details')}>
<NotificationIcon {notificationType} {kind} {iconDescription} />
<NotificationTextDetails {title} {subtitle} {notificationType}>
<slot />
</NotificationTextDetails>
</div>
<slot name="actions" />
{#if !hideCloseButton}
<NotificationButton
{iconDescription}
{notificationType}
on:click={() => {
open = false;
}} />
{/if}
</div>
{/if}

View file

@ -0,0 +1,20 @@
<script>
export let story = undefined;
import Layout from '../../internal/ui/Layout.svelte';
import InlineNotification from './InlineNotification.svelte';
import ToastNotification from './ToastNotification.svelte';
import NotificationActionButton from './NotificationActionButton.svelte';
</script>
<Layout>
{#if story === 'inline'}
<InlineNotification {...$$props}>
<div slot="actions">
<NotificationActionButton>{$$props.action}</NotificationActionButton>
</div>
</InlineNotification>
{:else if story === 'toast'}
<ToastNotification {...$$props} style="min-width: 30rem; margin-bottom: .5rem" />
{/if}
</Layout>

View file

@ -0,0 +1,42 @@
import { withKnobs, select, boolean, text } from '@storybook/addon-knobs';
import Component from './Notification.Story.svelte';
export default { title: 'Notification', decorators: [withKnobs] };
const kinds = {
'Error (error)': 'error',
'Info (info)': 'info',
'Success (success)': 'success',
'Warning (warning)': 'warning'
};
export const Toast = () => ({
Component,
props: {
story: 'toast',
kind: select('The notification kind (kind)', kinds, 'info'),
lowContrast: boolean('Use low contrast variant (lowContrast)', false),
role: text('ARIA role (role)', 'alert'),
title: text('Title (title)', 'Notification title'),
subtitle: text('Subtitle (subtitle)', 'Subtitle text goes here.'),
caption: text('Caption (caption)', 'Time stamp [00:00:00]'),
iconDescription: text('Icon description (iconDescription)', 'describes the close button'),
hideCloseButton: boolean('Hide close button (hideCloseButton)', false)
}
});
export const Inline = () => ({
Component,
props: {
story: 'inline',
kind: select('The notification kind (kind)', kinds, 'info'),
lowContrast: boolean('Use low contrast variant (lowContrast)', false),
role: text('ARIA role (role)', 'alert'),
title: text('Title (title)', 'Notification title'),
subtitle: text('Subtitle (subtitle)', 'Subtitle text goes here.'),
caption: text('Caption (caption)', 'Time stamp [00:00:00]'),
iconDescription: text('Icon description (iconDescription)', 'describes the close button'),
hideCloseButton: boolean('Hide close button (hideCloseButton)', false),
action: text('Action (NotificationActionButton > $$slot#action)', 'Action')
}
});

View file

@ -0,0 +1,22 @@
<script>
let className = undefined;
export { className as class };
export let style = undefined;
import Button from '../Button';
import { cx } from '../../lib';
const _class = cx('--inline-notification__action-button', className);
</script>
<Button
kind="ghost"
size="small"
on:click
on:mouseover
on:mouseenter
on:mouseleave
class={_class}
{style}>
<slot />
</Button>

View file

@ -0,0 +1,36 @@
<script>
let className = undefined;
export { className as class };
export let notificationType = 'toast';
export let type = 'button';
export let iconDescription = 'close icon';
export let renderIcon = Close20;
export let title = undefined;
export let style = undefined;
import Close20 from 'carbon-icons-svelte/lib/Close20';
import { cx } from '../../lib';
const _class = cx(
notificationType === 'toast' && '--toast-notification__close-button',
notificationType === 'inline' && '--inline-notification__close-button',
className
);
const _iconClass = cx(
notificationType === 'toast' && '--toast-notification__close-icon',
notificationType === 'inline' && '--inline-notification__close-icon'
);
</script>
<button
on:click
on:mouseover
on:mouseenter
on:mouseleave
aria-label={iconDescription}
title={iconDescription}
class={_class}
{style}
{type}>
<svelte:component this={renderIcon} class={_iconClass} {title} />
</button>

View file

@ -0,0 +1,21 @@
<script>
export let kind = 'error';
export let notificationType = 'toast';
export let iconDescription = 'closes notification';
import ErrorFilled20 from 'carbon-icons-svelte/lib/ErrorFilled20';
import CheckmarkFilled20 from 'carbon-icons-svelte/lib/CheckmarkFilled20';
import WarningFilled20 from 'carbon-icons-svelte/lib/WarningFilled20';
import { cx } from '../../lib';
const icons = {
error: ErrorFilled20,
success: CheckmarkFilled20,
warning: WarningFilled20
};
</script>
<svelte:component
this={icons[kind]}
class={cx(`--${notificationType}-notification__icon`)}
title={iconDescription} />

View file

@ -0,0 +1,25 @@
<script>
export let title = 'title';
export let subtitle = '';
export let caption = 'caption';
export let notificationType = 'toast';
import { cx } from '../../lib';
</script>
{#if notificationType === 'toast'}
<div class={cx('--toast-notification__details')}>
<h3 class={cx('--toast-notification__title')}>{title}</h3>
<div class={cx('--toast-notification__subtitle')}>{subtitle}</div>
<div class={cx('--toast-notification__caption')}>{caption}</div>
<slot />
</div>
{/if}
{#if notificationType === 'inline'}
<div class={cx('--inline-notification__text-wrapper')}>
<p class={cx('--inline-notification__title')}>{title}</p>
<div class={cx('--inline-notification__subtitle')}>{subtitle}</div>
<slot />
</div>
{/if}

View file

@ -0,0 +1,60 @@
<script>
let className = undefined;
export { className as class };
export let kind = 'error';
export let title = 'provide a title';
export let subtitle = ''; // TODO: support subtitle slot?
export let caption = 'provide a caption';
export let role = 'alert';
export let notificationType = 'toast';
export let iconDescription = 'closes notification';
export let hideCloseButton = false;
export let lowContrast = false;
export let timeout = 0;
export let style = undefined;
import { createEventDispatcher, onMount } from 'svelte';
import NotificationIcon from './NotificationIcon.svelte';
import NotificationTextDetails from './NotificationTextDetails.svelte';
import NotificationButton from './NotificationButton.svelte';
import { cx } from '../../lib';
const dispatch = createEventDispatcher();
const _class = cx(
'--toast-notification',
lowContrast && '--toast-notification--low-contrast',
kind && `--toast-notification--${kind}`,
className
);
let open = true;
onMount(() => {
if (timeout) {
window.setTimeout(() => {
open = false;
}, timeout);
}
});
$: if (!open) {
dispatch('close');
}
</script>
{#if open}
<div on:click on:mouseover on:mouseenter on:mouseleave class={_class} {style} {role} {kind}>
<NotificationIcon {notificationType} {kind} {iconDescription} />
<NotificationTextDetails {title} {subtitle} {caption} {notificationType}>
<slot />
</NotificationTextDetails>
{#if !hideCloseButton}
<NotificationButton
{iconDescription}
{notificationType}
on:click={() => {
open = false;
}} />
{/if}
</div>
{/if}

View file

@ -0,0 +1,6 @@
export { default as ToastNotification } from './ToastNotification.svelte';
export { default as InlineNotification } from './InlineNotification.svelte';
export { default as NotificationActionButton } from './NotificationActionButton.svelte';
export { default as NotificationButton } from './NotificationButton.svelte';
export { default as NotificationIcon } from './NotificationIcon.svelte';
export { default as NotificationTextDetails } from './NotificationTextDetails.svelte';

View file

@ -8,9 +8,17 @@ import CopyButton from './components/CopyButton';
import CodeSnippet, { CodeSnippetSkeleton } from './components/CodeSnippet';
import DataTableSkeleton from './components/DataTableSkeleton';
import InlineLoading from './components/InlineLoading';
import Loading from './components/Loading';
import Link from './components/Link';
import ListItem from './components/ListItem';
import Loading from './components/Loading';
import {
ToastNotification,
InlineNotification,
NotificationActionButton,
NotificationButton,
NotificationIcon,
NotificationTextDetails
} from './components/Notification';
import OrderedList from './components/OrderedList';
import RadioButton, { RadioButtonSkeleton } from './components/RadioButton';
import Search, { SearchSkeleton } from './components/Search';
@ -53,9 +61,14 @@ export {
DataTableSkeleton,
ExpandableTile,
InlineLoading,
InlineNotification,
Link,
ListItem,
Loading,
NotificationActionButton,
NotificationButton,
NotificationIcon,
NotificationTextDetails,
OrderedList,
PasswordInput,
RadioButton,
@ -76,6 +89,7 @@ export {
TileAboveTheFoldContent,
TileBelowTheFoldContent,
TileGroup,
ToastNotification,
Toggle,
ToggleSkeleton,
ToggleSmall,