Toast notification styling

This commit is contained in:
Ahmed Al-Taiar
2024-08-10 00:53:02 -04:00
parent dcb3012a01
commit 544cea9105
6 changed files with 177 additions and 9 deletions

View File

@ -6,6 +6,22 @@ export const theme = {
syne: ['Syne', 'sans-serif'], syne: ['Syne', 'sans-serif'],
inter: ['Inter', 'sans-serif'], inter: ['Inter', 'sans-serif'],
}, },
animation: {
enter: 'enter 200ms ease-out',
leave: 'leave 200ms ease-in forwards',
},
keyframes: {
enter: {
'0%': { transform: 'scale(0.75) translateY(-110%)', opacity: 0 },
'25%': { opacity: 0 },
'100%': { transform: 'scale(1) translateX(0)', opacity: 1 },
},
leave: {
'0%': { transform: 'scale(1) translateX(0)', opacity: 1 },
'75%': { opacity: 0 },
'100%': { transform: 'scale(0.75) translateY(-110%)', opacity: 0 },
},
},
}, },
} }

View File

@ -0,0 +1,26 @@
// Pass props to your component by passing an `args` object to your story
//
// ```tsx
// export const Primary: Story = {
// args: {
// propName: propValue
// }
// }
// ```
//
// See https://storybook.js.org/docs/react/writing-stories/args.
import type { Meta, StoryObj } from '@storybook/react'
import ToastNotification from './ToastNotification'
const meta: Meta<typeof ToastNotification> = {
component: ToastNotification,
tags: ['autodocs'],
}
export default meta
type Story = StoryObj<typeof ToastNotification>
export const Primary: Story = {}

View File

@ -0,0 +1,14 @@
import { render } from '@redwoodjs/testing/web'
import ToastNotification from './ToastNotification'
// Improve this test with help from the Redwood Testing Doc:
// https://redwoodjs.com/docs/testing#testing-components
describe('Toast', () => {
it('renders successfully', () => {
expect(() => {
render(<ToastNotification />)
}).not.toThrow()
})
})

View File

@ -0,0 +1,60 @@
import { mdiCloseCircle, mdiCheckCircle, mdiClose } from '@mdi/js'
import { Icon } from '@mdi/react'
import { Toast, toast, ToastType } from '@redwoodjs/web/toast'
interface Props {
t: Toast
type: ToastType
message: string
}
const ToastNotification = ({ t, type, message }: Props) => {
let iconElement: React.JSX.Element
if (type === 'blank' || type === 'custom') iconElement = <></>
else if (type === 'loading')
iconElement = <span className="loading loading-spinner loading-md" />
else {
let icon: string
let color: string
switch (type) {
case 'success':
icon = mdiCheckCircle
color = 'text-success'
break
case 'error':
icon = mdiCloseCircle
color = 'text-error'
break
}
iconElement = <Icon path={icon} className={`w-8 ${color}`} />
}
return (
<div
className={`${
t.visible ? 'animate-enter' : 'animate-leave'
} pointer-events-auto flex w-full max-w-56 items-center space-x-2 rounded-xl bg-base-200 p-2 shadow-xl ${
type === 'blank' ? 'first:space-x-0' : ''
}`}
>
{iconElement}
<p className="w-full font-inter">{message}</p>
{type !== 'loading' ? (
<button
onClick={() => toast.dismiss(t.id)}
className="btn btn-ghost btn-xs"
>
<Icon path={mdiClose} className="w-4" />
</button>
) : (
<></>
)}
</div>
)
}
export default ToastNotification

View File

@ -2,8 +2,10 @@ import { mdiMenu } from '@mdi/js'
import Icon from '@mdi/react' import Icon from '@mdi/react'
import { Link, routes } from '@redwoodjs/router' import { Link, routes } from '@redwoodjs/router'
import { Toaster } from '@redwoodjs/web/toast'
import ThemeToggle from 'src/components/ThemeToggle/ThemeToggle' import ThemeToggle from 'src/components/ThemeToggle/ThemeToggle'
import ToastNotification from 'src/components/ToastNotification/ToastNotification'
interface NavbarRoute { interface NavbarRoute {
name: string name: string
@ -32,6 +34,22 @@ const NavbarLayout = ({ children }: NavbarLayoutProps) => {
return ( return (
<> <>
<Toaster
position="top-right"
containerClassName="-mr-2 mt-16"
containerStyle={{
zIndex: 50, // "z-50" does not work
}}
gutter={8}
>
{(t) => (
<ToastNotification
t={t}
type={t.type}
message={t.message.toString()}
/>
)}
</Toaster>
<div className="sticky top-0 z-50 p-2"> <div className="sticky top-0 z-50 p-2">
<div className="navbar rounded-xl bg-base-300 shadow-xl"> <div className="navbar rounded-xl bg-base-300 shadow-xl">
<div className="navbar-start space-x-2 lg:first:space-x-0"> <div className="navbar-start space-x-2 lg:first:space-x-0">

View File

@ -1,19 +1,53 @@
import { Link, routes } from '@redwoodjs/router'
import { Metadata } from '@redwoodjs/web' import { Metadata } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/toast'
const HomePage = () => { const HomePage = () => {
return ( return (
<> <>
<Metadata title="Home" description="Home page" /> <Metadata title="Home" description="Home page" />
<h1>HomePage</h1> <button className="btn" onClick={() => toast.loading('Loading...')}>
<p> Infinite loading
Find me in <code>./web/src/pages/HomePage/HomePage.tsx</code> </button>
</p> <button
<p> className="btn"
My default route is named <code>home</code>, link to me with ` onClick={() =>
<Link to={routes.home()}>Home</Link>` toast.promise(
</p> new Promise<number>((resolve, reject) => {
setTimeout(() => {
const x = Math.random()
x >= 0.5 ? resolve(x) : reject(x)
}, 750)
}),
{
loading: 'Loading...',
success: (res) => `Success: ${res.toFixed(4)}`,
error: (err: number) => `Error: ${err.toFixed(4)}`,
}
)
}
>
Loading -{'>'} Random result
</button>
<button className="btn" onClick={() => toast('Blank')}>
Blank
</button>
<button className="btn" onClick={() => toast.error('Error')}>
Error
</button>
<button className="btn" onClick={() => toast.success('Success')}>
Success
</button>
<button
className="btn"
onClick={() =>
toast(
"I'm baby sriracha poutine hammock pour-over direct trade, bruh coloring book ascot gatekeep put a bird on it YOLO biodiesel. Lyft wayfarers cloud bread, la croix lo-fi pork belly synth williamsburg before they sold out."
)
}
>
A pretty big notification
</button>
</> </>
) )
} }