SkateSpots/src/components/LazyZapDialog.tsx

72 lines
1.8 KiB
TypeScript

import {
cloneElement,
isValidElement,
lazy,
Suspense,
useState,
type MouseEvent,
type ReactElement,
type ReactNode,
} from 'react';
import type { Event } from 'nostr-tools';
/**
* Lightweight zap trigger.
*
* The full ZapDialog pulls in the Cashu wallet stack (`useCashu`, mint repos,
* token operations) — several hundred KB that used to land in the initial
* bundle because every SpotCard rendered a ZapButton. This wrapper keeps only
* the trigger in the entry chunk and loads the actual dialog on first tap.
*/
const LoadedZapDialog = lazy(() =>
import('@/components/ZapDialog').then((m) => ({ default: m.ZapDialog })),
);
interface LazyZapDialogProps {
target: Event;
children: ReactNode;
className?: string;
}
export function LazyZapDialog({ target, children, className }: LazyZapDialogProps) {
const [open, setOpen] = useState(false);
const handleClick = (event: MouseEvent) => {
if (isValidElement(children)) {
const child = children as ReactElement<{ onClick?: (event: MouseEvent) => void }>;
child.props.onClick?.(event);
}
setOpen(true);
};
const trigger = isValidElement(children) ? (
cloneElement(
children as ReactElement<{ onClick?: (event: MouseEvent) => void; className?: string }>,
{
onClick: handleClick,
className: [
(children as ReactElement<{ className?: string }>).props.className,
'cursor-pointer',
].filter(Boolean).join(' '),
},
)
) : (
<div className={className} onClick={handleClick}>{children}</div>
);
return (
<>
{trigger}
{open && (
<Suspense fallback={null}>
<LoadedZapDialog
target={target}
open={open}
onOpenChange={setOpen}
hideTrigger
/>
</Suspense>
)}
</>
);
}