82 lines
3.0 KiB
TypeScript
82 lines
3.0 KiB
TypeScript
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
||
|
||
export interface OriginPost {
|
||
title: string;
|
||
url: string;
|
||
imageUrl?: string | null;
|
||
subreddit?: string;
|
||
}
|
||
|
||
export function OriginPostCard({ post }: { post: OriginPost }) {
|
||
const [isImageRevealed, setIsImageRevealed] = useState(false);
|
||
|
||
return (
|
||
<div className="origin-post-card">
|
||
{post.imageUrl ? (
|
||
<div className="origin-post-image">
|
||
<img alt={`Source post: ${post.title}`} className={isImageRevealed ? undefined : 'is-blurred'} loading="lazy" src={post.imageUrl} />
|
||
<button aria-pressed={isImageRevealed} onClick={() => setIsImageRevealed((current) => !current)} type="button">
|
||
{isImageRevealed ? 'Blur original image' : 'Reveal original image'}
|
||
</button>
|
||
</div>
|
||
) : null}
|
||
<a aria-label={post.title} className="origin-post-copy" href={post.url} rel="noreferrer" target="_blank">
|
||
<small>Started from {post.subreddit ? `r/${post.subreddit}` : 'Reddit'}</small>
|
||
<strong>{post.title}</strong>
|
||
<span>Open original post</span>
|
||
</a>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function OriginPostButton({ children, post }: { children: ReactNode; post: OriginPost }) {
|
||
const [isOpen, setIsOpen] = useState(false);
|
||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||
const closeRef = useRef<HTMLButtonElement>(null);
|
||
|
||
useEffect(() => {
|
||
if (!isOpen) return;
|
||
closeRef.current?.focus();
|
||
const closeOnEscape = (event: KeyboardEvent) => {
|
||
if (event.key === 'Escape') setIsOpen(false);
|
||
};
|
||
document.addEventListener('keydown', closeOnEscape);
|
||
return () => document.removeEventListener('keydown', closeOnEscape);
|
||
}, [isOpen]);
|
||
|
||
function close() {
|
||
setIsOpen(false);
|
||
queueMicrotask(() => triggerRef.current?.focus());
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<button className="origin-post-trigger" onClick={() => setIsOpen(true)} ref={triggerRef} type="button">
|
||
{children}
|
||
</button>
|
||
{isOpen ? (
|
||
<div
|
||
aria-labelledby="origin-post-dialog-title"
|
||
aria-modal="true"
|
||
className="origin-post-backdrop"
|
||
onMouseDown={(event) => { if (event.target === event.currentTarget) close(); }}
|
||
role="dialog"
|
||
>
|
||
<article className="origin-post-dialog">
|
||
<div className="origin-post-dialog-heading">
|
||
<span>Conversation origin</span>
|
||
<button aria-label="Close source post" onClick={close} ref={closeRef} type="button">×</button>
|
||
</div>
|
||
{post.imageUrl ? <img alt={`Source post: ${post.title}`} loading="lazy" src={post.imageUrl} /> : null}
|
||
<div className="origin-post-dialog-copy">
|
||
{post.subreddit ? <small>r/{post.subreddit}</small> : null}
|
||
<h2 id="origin-post-dialog-title">{post.title}</h2>
|
||
<a className="button primary" href={post.url} rel="noreferrer" target="_blank">Open Reddit post</a>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
) : null}
|
||
</>
|
||
);
|
||
}
|