diff --git a/src/packages/Toaster/index.tsx b/src/packages/Toaster/index.tsx new file mode 100644 index 0000000..6e87437 --- /dev/null +++ b/src/packages/Toaster/index.tsx @@ -0,0 +1,31 @@ +import React, { useEffect, useState } from 'react' +import Alert, { AlertProps } from '../Alert' + +export type ToasterProps = { + duration?: number | null + closable?: boolean +} & Omit // Omit the 'header' prop from AlertProps + +const Toaster = ({ duration, closable = true, ...props }: ToasterProps) => { + const [isVisible, setIsVisible] = useState(true) + + useEffect(() => { + if (typeof duration === 'number') { + const timer = setTimeout(() => { + setIsVisible(false) + }, duration) + + return () => { + clearTimeout(timer) + } + } + }, [duration]) + + if (!isVisible) { + return null + } + + return // Explicitly set the 'header' prop to null +} + +export default Toaster diff --git a/src/packages/Toaster/stories.tsx b/src/packages/Toaster/stories.tsx new file mode 100644 index 0000000..57d6b52 --- /dev/null +++ b/src/packages/Toaster/stories.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import { StoryFn, Meta } from '@storybook/react' +import Toaster, { ToasterProps } from './index' + +export default { + title: 'Toaster', + component: Toaster, + args: { + children: 'This is a toaster message', + severity: 'info', + showIcon: true, + closable: true + } +} as Meta + +export const Default: StoryFn = (args) => diff --git a/src/packages/Toaster/styles.ts b/src/packages/Toaster/styles.ts new file mode 100644 index 0000000..6437cef --- /dev/null +++ b/src/packages/Toaster/styles.ts @@ -0,0 +1,32 @@ +import styled, { css, DefaultTheme } from 'styled-components' +import { AlertProps } from '../Alert' + +const severityShape = { + error: (theme: DefaultTheme) => css` + background-color: ${theme.colors.base.error}1f; + `, + warning: (theme: DefaultTheme) => css` + background-color: ${theme.colors.base.warning}1f; + `, + info: (theme: DefaultTheme) => css` + background-color: ${theme.colors.base.info}1f; + `, + success: (theme: DefaultTheme) => css` + background-color: ${theme.colors.base.success}1f; + ` +} + +export const ToasterWrapper = styled.div>` + position: fixed; + bottom: 2vw; + right: 2vw; + z-index: 1000; + + ${({ theme, severity = 'info' }) => css` + animation-fill-mode: forwards; + border-radius: ${theme.border.radius}; + position: relative; + transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; + ${!!severity && severityShape[severity](theme)} + `} +`