Skip to content
57 linesCodeBlameRaw
1/**
2 * The top bar's Ask: the lavender pixel 1 beside its words. The panel it
3 * opens (components/ask-composer.tsx: the direct message with @g1t, its
4 * answers drawn with Markdown, code and tool steps) is loaded the first
5 * time it is wanted, starting when the pointer comes near, so no page
6 * carries it until then. Kept mounted once loaded, so the conversation
7 * stays while the panel is closed.
8 */
9import { Suspense, lazy, useState } from "react";
10
11import { MarkGlyph } from "./logo";
12import { Button } from "./ui/button";
13import { Hint } from "./ui/hint";
14import { Sheet, SheetContent, SheetTitle, SheetTrigger } from "./ui/sheet";
15import { Skeleton } from "./ui/skeleton";
16
17const load = () => import("./ask-composer");
18const AskSheet = lazy(() => load().then((module) => ({ default: module.AskSheet })));
19
20export function AskButton({ slug }: { slug: string }) {
21 const [open, setOpen] = useState(false);
22 const [wanted, setWanted] = useState(false);
23 const change = (next: boolean) => {
24 if (next) setWanted(true);
25 setOpen(next);
26 };
27 return (
28 <Sheet open={open} onOpenChange={change}>
29 <Hint label="Ask g1t about your work">
30 <SheetTrigger asChild>
31 <Button variant="soft" size="bar" aria-label="Ask g1t" className="gap-2 max-md:ring-0 lg:px-3" onPointerEnter={() => void load()} onFocus={() => void load()}>
32 <MarkGlyph className="h-3.5" />
33 <span className="max-lg:hidden">Ask</span>
34 </Button>
35 </SheetTrigger>
36 </Hint>
37 {wanted && (
38 <Suspense fallback={<Loading />}>
39 <AskSheet slug={slug} open={open} />
40 </Suspense>
41 )}
42 </Sheet>
43 );
44}
45
46/** The panel's frame while it loads. */
47function Loading() {
48 return (
49 <SheetContent side="right" className="sm:max-w-xl">
50 <SheetTitle className="sr-only">Ask g1t</SheetTitle>
51 <div className="flex flex-col gap-3 p-5" aria-busy="true">
52 <Skeleton className="h-5 w-32" />
53 <Skeleton className="h-4 w-56" />
54 </div>
55 </SheetContent>
56 );
57}