Files
SkinbaseNova/resources/js/components/ui/ShareToast.jsx
Gregor Klevze 90f244f264 feat: artwork share system with modal, native Web Share API, and tracking
- Add ArtworkShareModal with glassmorphism UI (Facebook, X, Pinterest, Email, Copy Link, Embed Code)
- Add ArtworkShareButton with lazy-loaded modal and native share fallback
- Add useWebShare hook abstracting navigator.share with AbortError handling
- Add ShareToast auto-dismissing notification component
- Add share() endpoint to ArtworkInteractionController (POST /api/artworks/{id}/share)
- Add artwork_shares migration for Phase 2 share tracking
- Refactor ArtworkActionBar to use new ArtworkShareButton component
2026-02-28 15:29:45 +01:00

56 lines
1.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
*/
export default function ShareToast({ message = 'Link copied!', visible = false, onHide, duration = 2000 }) {
const [show, setShow] = useState(false)
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="status"
aria-live="polite"
className={[
'fixed bottom-24 left-1/2 z-[10001] -translate-x-1/2 rounded-full border border-white/[0.10] bg-nova-800/90 px-5 py-2.5 text-sm font-medium text-white shadow-xl backdrop-blur-md transition-all duration-200',
show ? 'translate-y-0 opacity-100' : 'translate-y-3 opacity-0',
].join(' ')}
>
<span className="flex items-center gap-2">
{/* Check icon */}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 text-emerald-400">
<path fillRule="evenodd" d="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" clipRule="evenodd" />
</svg>
{message}
</span>
</div>,
document.body,
)
}