Save workspace changes
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
/**
|
||||
* ShareToast — a minimal, auto-dismissing toast notification.
|
||||
*
|
||||
* Props:
|
||||
* message – text to display
|
||||
* visible – whether the toast is currently shown
|
||||
* onHide – callback when the toast finishes (auto-hidden after ~2 s)
|
||||
* duration – ms before auto-dismiss (default 2000)
|
||||
* variant – success or error tone (default success)
|
||||
*/
|
||||
export default function ShareToast({ message = 'Link copied!', visible = false, onHide, duration = 2000, variant = 'success' }) {
|
||||
const [show, setShow] = useState(false)
|
||||
const config = variant === 'error'
|
||||
? {
|
||||
border: 'border-rose-300/25',
|
||||
background: 'bg-rose-950/90',
|
||||
text: 'text-rose-50',
|
||||
icon: 'text-rose-300',
|
||||
role: 'alert',
|
||||
live: 'assertive',
|
||||
iconPath: 'M12 9v3.75m0 3.75h.007v.008H12v-.008ZM10.29 3.86 1.82 18a1.875 1.875 0 0 0 1.606 2.813h16.148A1.875 1.875 0 0 0 21.18 18L12.71 3.86a1.875 1.875 0 0 0-3.42 0Z',
|
||||
}
|
||||
: {
|
||||
border: 'border-white/[0.10]',
|
||||
background: 'bg-nova-800/90',
|
||||
text: 'text-white',
|
||||
icon: 'text-emerald-400',
|
||||
role: 'status',
|
||||
live: 'polite',
|
||||
iconPath: 'M16.704 4.153a.75.75 0 0 1 .143 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 0 1 1.05-.143Z',
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
// Small delay so the enter transition plays
|
||||
const enterTimer = requestAnimationFrame(() => setShow(true))
|
||||
const hideTimer = setTimeout(() => {
|
||||
setShow(false)
|
||||
setTimeout(() => onHide?.(), 200) // let exit transition finish
|
||||
}, duration)
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(enterTimer)
|
||||
clearTimeout(hideTimer)
|
||||
}
|
||||
} else {
|
||||
setShow(false)
|
||||
}
|
||||
}, [visible, duration, onHide])
|
||||
|
||||
if (!visible) return null
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
role={config.role}
|
||||
aria-live={config.live}
|
||||
className={[
|
||||
'fixed bottom-24 left-1/2 z-[10001] -translate-x-1/2 rounded-full border px-5 py-2.5 text-sm font-medium shadow-xl backdrop-blur-md transition-all duration-200',
|
||||
config.border,
|
||||
config.background,
|
||||
config.text,
|
||||
show ? 'translate-y-0 opacity-100' : 'translate-y-3 opacity-0',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className={`h-4 w-4 ${config.icon}`}>
|
||||
<path fillRule="evenodd" d={config.iconPath} clipRule="evenodd" />
|
||||
</svg>
|
||||
{message}
|
||||
</span>
|
||||
</div>,
|
||||
document.body,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user