- 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
78 lines
2.8 KiB
JavaScript
78 lines
2.8 KiB
JavaScript
import React, { lazy, Suspense, useCallback, useState } from 'react'
|
||
import useWebShare from '../../hooks/useWebShare'
|
||
|
||
const ArtworkShareModal = lazy(() => import('./ArtworkShareModal'))
|
||
|
||
/* ── Share icon (lucide-style) ───────────────────────────────────────────── */
|
||
function ShareIcon() {
|
||
return (
|
||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="h-5 w-5">
|
||
<path strokeLinecap="round" strokeLinejoin="round" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 1 1 0-2.684m0 2.684 6.632 3.316m-6.632-6 6.632-3.316m0 0a3 3 0 1 0 5.367-2.684 3 3 0 0 0-5.367 2.684Zm0 9.316a3 3 0 1 0 5.368 2.684 3 3 0 0 0-5.368-2.684Z" />
|
||
</svg>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* ArtworkShareButton – renders the Share pill and manages modal / native share.
|
||
*
|
||
* Props:
|
||
* artwork – artwork object
|
||
* shareUrl – canonical URL to share
|
||
* size – 'default' | 'small' (for mobile bar)
|
||
*/
|
||
export default function ArtworkShareButton({ artwork, shareUrl, size = 'default' }) {
|
||
const [modalOpen, setModalOpen] = useState(false)
|
||
|
||
const openModal = useCallback(
|
||
() => setModalOpen(true),
|
||
[],
|
||
)
|
||
const closeModal = useCallback(
|
||
() => setModalOpen(false),
|
||
[],
|
||
)
|
||
|
||
const { share } = useWebShare({ onFallback: openModal })
|
||
|
||
const handleClick = () => {
|
||
share({
|
||
title: artwork?.title || 'Artwork',
|
||
text: artwork?.description?.substring(0, 120) || '',
|
||
url: shareUrl || artwork?.canonical_url || window.location.href,
|
||
})
|
||
}
|
||
|
||
const isSmall = size === 'small'
|
||
|
||
return (
|
||
<>
|
||
<button
|
||
type="button"
|
||
aria-label="Share artwork"
|
||
onClick={handleClick}
|
||
className={
|
||
isSmall
|
||
? 'inline-flex items-center gap-1.5 rounded-full border border-white/[0.08] bg-white/[0.04] px-3.5 py-2 text-xs font-medium text-white/70 transition-all hover:border-white/[0.15] hover:bg-white/[0.07] hover:text-white'
|
||
: 'group inline-flex items-center gap-2 rounded-full border border-white/[0.08] bg-white/[0.04] px-5 py-2.5 text-sm font-medium text-white/70 transition-all duration-200 hover:border-white/[0.15] hover:bg-white/[0.07] hover:text-white hover:shadow-lg hover:shadow-white/[0.03]'
|
||
}
|
||
title="Share"
|
||
>
|
||
<ShareIcon />
|
||
{!isSmall && <span>Share</span>}
|
||
</button>
|
||
|
||
{/* Lazy-loaded modal – only rendered when opened */}
|
||
{modalOpen && (
|
||
<Suspense fallback={null}>
|
||
<ArtworkShareModal
|
||
open={modalOpen}
|
||
onClose={closeModal}
|
||
artwork={artwork}
|
||
shareUrl={shareUrl}
|
||
/>
|
||
</Suspense>
|
||
)}
|
||
</>
|
||
)
|
||
}
|