Files
eranos/src/components/FloatingComposeButton.tsx
T
Alex Gleason ecc3284a94 Improve code splitting: reduce index chunk from 1021K to 494K
- Lazy-load BlobbiCompanionLayer (~450K blobbi code off critical path)
- Split BlobbiActionsProvider into lightweight file to avoid pulling
  heavy blobbi action system into the index chunk
- Fix HomePage eagerly importing all 18 page components; use lazy()
  so only the configured homepage's chunk is loaded
- Lazy-load ReplyComposeModal in AppRouter and FloatingComposeButton
  to defer emoji-mart (~620K) until compose is opened
- Fix barrel import in App.tsx pulling BlobbiDevEditor into index;
  use direct import from EmotionDevContext instead
2026-03-27 20:56:36 -05:00

93 lines
3.1 KiB
TypeScript

import { lazy, Suspense, useState } from 'react';
import { Plus, Construction } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogTitle,
} from '@/components/ui/dialog';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { FabButton } from '@/components/FabButton';
// Lazy-load the compose modal (pulls in emoji-mart ~620K)
const ReplyComposeModal = lazy(() => import('@/components/ReplyComposeModal').then(m => ({ default: m.ReplyComposeModal })));
interface FloatingComposeButtonProps {
/** The Nostr event kind this FAB creates. kind=1 opens compose; others show "Coming soon". */
kind?: number;
/** If set, the FAB navigates to this URL instead of opening a dialog. */
href?: string;
/** If set, overrides the default FAB click behavior. */
onFabClick?: () => void;
/** If set, overrides the default Plus icon. */
icon?: React.ReactNode;
}
export function FloatingComposeButton({ kind = 1, href, onFabClick, icon }: FloatingComposeButtonProps) {
const { user, isLoading } = useCurrentUser();
const navigate = useNavigate();
const [composeOpen, setComposeOpen] = useState(false);
const [comingSoonOpen, setComingSoonOpen] = useState(false);
// Hide until user metadata is resolved so the shape mask is immediately
// correct — avoids a brief flash of the default circle fallback.
if (!user || isLoading) {
return null;
}
const handleClick = () => {
if (onFabClick) {
onFabClick();
} else if (href) {
navigate(href);
} else if (kind === 1) {
setComposeOpen(true);
} else {
setComingSoonOpen(true);
}
};
return (
<>
<FabButton
onClick={handleClick}
icon={icon ?? <Plus strokeWidth={4} size={16} />}
/>
{/* Kind 1: Compose modal (lazy-loaded) */}
{kind === 1 && composeOpen && (
<Suspense fallback={null}>
<ReplyComposeModal open={composeOpen} onOpenChange={setComposeOpen} />
</Suspense>
)}
{/* Other kinds: Coming soon dialog */}
{kind !== 1 && (
<Dialog open={comingSoonOpen} onOpenChange={setComingSoonOpen}>
<DialogContent className="max-w-[360px] rounded-2xl text-center">
<div className="flex flex-col items-center gap-4 py-4">
<div className="size-16 rounded-full bg-muted flex items-center justify-center">
<Construction className="size-8 text-muted-foreground" />
</div>
<DialogTitle className="text-lg font-semibold">Coming soon</DialogTitle>
<p className="text-sm text-muted-foreground leading-relaxed max-w-[260px]">
Creating this type of content isn't available yet. Stay tuned!
</p>
<Button
variant="outline"
className="rounded-full mt-2"
onClick={() => setComingSoonOpen(false)}
>
Got it
</Button>
</div>
</DialogContent>
</Dialog>
)}
</>
);
}