1"use client";
2
3
18import { useState } from "react";
19import { Check, Languages, ListChecks, Sparkles, Wand2 } from "lucide-react";
20import { cn } from "@/lib/utils";
21import { WRITING_LANGUAGES, type WritingAction } from "@/lib/ai";
22
23const ACTIONS: { action: WritingAction; label: string; icon: typeof Sparkles }[] = [
24 { action: "summarize", label: "Summarize", icon: ListChecks },
25 { action: "rewrite", label: "Rewrite", icon: Wand2 },
26 { action: "improve", label: "Improve writing", icon: Sparkles },
27 { action: "translate", label: "Translate…", icon: Languages }
28];
29
30export interface WritingActionsListProps {
31 onPick: (action: WritingAction, language?: string) => void;
32}
33
34export function WritingActionsList({ onPick }: WritingActionsListProps) {
35 const [langOpen, setLangOpen] = useState(false);
36 const [otherOpen, setOtherOpen] = useState(false);
37 const [otherDraft, setOtherDraft] = useState("");
38
39 const pick = (action: WritingAction, language?: string) => {
40 setLangOpen(false);
41 setOtherOpen(false);
42 setOtherDraft("");
43 onPick(action, language);
44 };
45
46 return (
47 <>
48 {ACTIONS.map(({ action, label, icon: Icon }) => {
49 const isTranslate = action === "translate";
50 return (
51 <div key={action} className="relative">
52 <button
53 type="button"
54 role="menuitem"
55 data-testid={`writing-action-${action}`}
56 onClick={() => {
57 if (isTranslate) setLangOpen((v) => !v);
58 else pick(action);
59 }}
60 className={cn(
61 "flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm hover:bg-accent",
62 isTranslate && langOpen && "bg-accent"
63 )}
64 aria-haspopup={isTranslate ? "menu" : undefined}
65 aria-expanded={isTranslate ? langOpen : undefined}
66 >
67 <Icon className="size-4 shrink-0 text-muted-foreground" aria-hidden />
68 {label}
69 </button>
70
71 {isTranslate && langOpen ? (
72 <div
73 className="absolute left-full top-0 z-50 ml-1 w-44 rounded-md border border-border bg-popover p-1 shadow-lg"
74 role="menu"
75 aria-label="Translate to"
76 >
77 <p className="px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">Translate to</p>
78 {WRITING_LANGUAGES.map((l) => (
79 <button
80 key={l.id}
81 type="button"
82 role="menuitem"
83 data-testid={`writing-language-${l.id}`}
84 onClick={() => pick("translate", l.id)}
85 className="flex w-full items-center gap-2 rounded px-2 py-1 text-left text-sm hover:bg-accent"
86 >
87 <span className="size-3.5 rounded-full border border-border" aria-hidden />
88 {l.label}
89 </button>
90 ))}
91 {otherOpen ? (
92 <input
93 autoFocus
94 data-testid="writing-language-other"
95 value={otherDraft}
96 onChange={(e) => setOtherDraft(e.target.value)}
97 onKeyDown={(e) => {
98 if (e.key === "Enter" && otherDraft.trim()) pick("translate", otherDraft.trim().slice(0, 64));
99 }}
100 placeholder="Other language…"
101 aria-label="Other language"
102 maxLength={64}
103 className="mx-1 my-1 block w-full rounded-md border border-primary bg-background px-2 py-1 text-xs outline-none"
104 />
105 ) : (
106 <button
107 type="button"
108 role="menuitem"
109 data-testid="writing-language-other"
110 onClick={() => setOtherOpen(true)}
111 className="flex w-full items-center gap-2 rounded px-2 py-1 text-left text-sm hover:bg-accent"
112 >
113 <Check className="size-4 shrink-0 text-muted-foreground" aria-hidden />
114 Other…
115 </button>
116 )}
117 </div>
118 ) : null}
119 </div>
120 );
121 })}
122 </>
123 );
124}
125