Support app.relay.emoji_confetti on jumbo emoji clicks with canvas physics, per-emoji particle profiles, and a fixed overlay that does not affect scroll. Also add a Vite playground for live component previews and theme hover preview.
60 lines
1.6 KiB
TypeScript
60 lines
1.6 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
|
|
/**
|
|
* Dynamically remounts the Vite virtual live module when the harness changes.
|
|
*/
|
|
export function LivePreview() {
|
|
const [revision, setRevision] = useState(0);
|
|
const [Comp, setComp] = useState<React.ComponentType | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
const onUpdate = () => setRevision((n) => n + 1);
|
|
const handler = () => onUpdate();
|
|
|
|
// Vite custom event from liveTsxPlugin
|
|
if (import.meta.hot) {
|
|
import.meta.hot.on('playground:live-updated', handler);
|
|
}
|
|
|
|
return () => {
|
|
if (import.meta.hot) {
|
|
import.meta.hot.off('playground:live-updated', handler);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
setError(null);
|
|
setComp(null);
|
|
|
|
import(/* @vite-ignore */ `/@playground/live.tsx?t=${revision}`)
|
|
.then((mod) => {
|
|
if (cancelled) return;
|
|
const candidate = mod.default;
|
|
if (typeof candidate !== 'function') {
|
|
setError('Live module must `export default` a React component.');
|
|
return;
|
|
}
|
|
setComp(() => candidate);
|
|
})
|
|
.catch((err: unknown) => {
|
|
if (cancelled) return;
|
|
const message =
|
|
err instanceof Error
|
|
? `${err.message}\n\n${err.stack ?? ''}`
|
|
: String(err);
|
|
setError(message);
|
|
});
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [revision]);
|
|
|
|
if (error) return <pre className="error-block">{error}</pre>;
|
|
if (!Comp) return <div className="muted">Compiling harness…</div>;
|
|
return <Comp />;
|
|
}
|