1import { BookmarkIcon } from "lucide-react";
2import { memo } from "react";
3
4import { cn } from "~/lib/utils";
5import { ComposerBanner } from "./ComposerBanner";
6
7/**
8 * Bookmark tab that shows the stash count beside the composer's other attachments
9 * and opens the stash menu.
10 *
11 * On save the badge gives one quiet acknowledgement: it lifts to full
12 * opacity and the count ticks over. `pulseKey` changes per stash, remounting
13 * the count so the transition replays without a continuous animation.
14 */
15export const ComposerStashBadge = memo(function ComposerStashBadge(props: {
16 count: number;
17 menuOpen: boolean;
18 pulseKey: number;
19 pulsing: boolean;
20 onToggleMenu: () => void;
21}) {
22 if (props.count === 0) return null;
23 const count = (
24 <ComposerBanner.Count
25 key={props.pulseKey}
26 className={cn(
27 props.pulsing
28 ? "text-primary transition-[opacity,translate] duration-180 ease-out starting:translate-y-0.5 starting:opacity-0 motion-reduce:transition-none"
29 : "text-muted-foreground",
30 )}
31 >
32 {props.count}
33 </ComposerBanner.Count>
34 );
35
36 return (
37 <ComposerBanner.Root
38 density="comfortable"
39 width="content"
40 data-composer-shoulder-tab
41 className="ml-auto"
42 >
43 <ComposerBanner.Row
44 render={<button type="button" />}
45 data-prompt-stash-badge="true"
46 aria-label={`Stashed prompts: ${props.count}. Open stash.`}
47 aria-expanded={props.menuOpen}
48 className={cn(
49 "transition-colors duration-200",
50 props.menuOpen && "pointer-events-none",
51 props.menuOpen || props.pulsing
52 ? "text-foreground"
53 : "text-muted-foreground hover:text-foreground",
54 )}
55 onPointerDown={(event) => event.preventDefault()}
56 onClick={props.onToggleMenu}
57 >
58 <ComposerBanner.Icon>
59 <BookmarkIcon />
60 </ComposerBanner.Icon>
61 <ComposerBanner.Content>Stash</ComposerBanner.Content>
62 <ComposerBanner.Actions>{count}</ComposerBanner.Actions>
63 </ComposerBanner.Row>
64 </ComposerBanner.Root>
65 );
66});