72 lines
1.8 KiB
TypeScript
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>
|
|
)}
|
|
</>
|
|
);
|
|
}
|