Toast notification styling
This commit is contained in:
@ -6,6 +6,22 @@ export const theme = {
|
||||
syne: ['Syne', '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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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 = {}
|
@ -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()
|
||||
})
|
||||
})
|
60
web/src/components/ToastNotification/ToastNotification.tsx
Normal file
60
web/src/components/ToastNotification/ToastNotification.tsx
Normal 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
|
@ -2,8 +2,10 @@ import { mdiMenu } from '@mdi/js'
|
||||
import Icon from '@mdi/react'
|
||||
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
import { Toaster } from '@redwoodjs/web/toast'
|
||||
|
||||
import ThemeToggle from 'src/components/ThemeToggle/ThemeToggle'
|
||||
import ToastNotification from 'src/components/ToastNotification/ToastNotification'
|
||||
|
||||
interface NavbarRoute {
|
||||
name: string
|
||||
@ -32,6 +34,22 @@ const NavbarLayout = ({ children }: NavbarLayoutProps) => {
|
||||
|
||||
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="navbar rounded-xl bg-base-300 shadow-xl">
|
||||
<div className="navbar-start space-x-2 lg:first:space-x-0">
|
||||
|
@ -1,19 +1,53 @@
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
import { Metadata } from '@redwoodjs/web'
|
||||
import { toast } from '@redwoodjs/web/toast'
|
||||
|
||||
const HomePage = () => {
|
||||
return (
|
||||
<>
|
||||
<Metadata title="Home" description="Home page" />
|
||||
|
||||
<h1>HomePage</h1>
|
||||
<p>
|
||||
Find me in <code>./web/src/pages/HomePage/HomePage.tsx</code>
|
||||
</p>
|
||||
<p>
|
||||
My default route is named <code>home</code>, link to me with `
|
||||
<Link to={routes.home()}>Home</Link>`
|
||||
</p>
|
||||
<button className="btn" onClick={() => toast.loading('Loading...')}>
|
||||
Infinite loading
|
||||
</button>
|
||||
<button
|
||||
className="btn"
|
||||
onClick={() =>
|
||||
toast.promise(
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user