fix(frontend): 'Other — specify' opens a text field and emits control:freetext
This commit is contained in:
@@ -53,7 +53,7 @@ export function ArtifactGateWidget({ descriptor, onRespond }: WidgetProps) {
|
|||||||
</div>
|
</div>
|
||||||
<ReservedControls
|
<ReservedControls
|
||||||
reserved={descriptor.reserved}
|
reserved={descriptor.reserved}
|
||||||
onControl={(c) => onRespond({ id: descriptor.id, control: c })}
|
onControl={(c, t) => onRespond({ id: descriptor.id, control: c, ...(t !== undefined ? { text: t } : {}) })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function MultiselectWidget({ descriptor, onRespond }: WidgetProps) {
|
|||||||
</button>
|
</button>
|
||||||
<ReservedControls
|
<ReservedControls
|
||||||
reserved={descriptor.reserved}
|
reserved={descriptor.reserved}
|
||||||
onControl={(c) => onRespond({ id: descriptor.id, control: c })}
|
onControl={(c, t) => onRespond({ id: descriptor.id, control: c, ...(t !== undefined ? { text: t } : {}) })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { ReservedControls } from "./ReservedControls";
|
||||||
|
|
||||||
|
test("back and exit fire immediately with no text", async () => {
|
||||||
|
const onControl = vi.fn();
|
||||||
|
render(<ReservedControls reserved={["back", "exit"]} onControl={onControl} />);
|
||||||
|
await userEvent.click(screen.getByRole("button", { name: /go back/i }));
|
||||||
|
expect(onControl).toHaveBeenCalledWith("back");
|
||||||
|
await userEvent.click(screen.getByRole("button", { name: /^exit$/i }));
|
||||||
|
expect(onControl).toHaveBeenCalledWith("exit");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("other reveals a textarea and emits freetext with the typed text", async () => {
|
||||||
|
const onControl = vi.fn();
|
||||||
|
render(<ReservedControls reserved={["other"]} onControl={onControl} />);
|
||||||
|
await userEvent.click(screen.getByRole("button", { name: /other — specify/i }));
|
||||||
|
// clicking Other does NOT emit a control yet — it reveals the input
|
||||||
|
expect(onControl).not.toHaveBeenCalled();
|
||||||
|
await userEvent.type(screen.getByRole("textbox"), "usa la tabella X");
|
||||||
|
await userEvent.click(screen.getByRole("button", { name: /send/i }));
|
||||||
|
expect(onControl).toHaveBeenCalledWith("freetext", "usa la tabella X");
|
||||||
|
});
|
||||||
@@ -1,7 +1,42 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
const LABELS: Record<string, string> = { back: "Go back", exit: "Exit", other: "Other — specify" };
|
const LABELS: Record<string, string> = { back: "Go back", exit: "Exit", other: "Other — specify" };
|
||||||
export function ReservedControls({ reserved, onControl }: { reserved?: string[]; onControl: (c: string) => void }) {
|
|
||||||
|
export function ReservedControls({
|
||||||
|
reserved,
|
||||||
|
onControl,
|
||||||
|
}: {
|
||||||
|
reserved?: string[];
|
||||||
|
onControl: (c: string, text?: string) => void;
|
||||||
|
}) {
|
||||||
|
const [otherOpen, setOtherOpen] = useState(false);
|
||||||
|
const [text, setText] = useState("");
|
||||||
if (!reserved?.length) return null;
|
if (!reserved?.length) return null;
|
||||||
return <div className="flex gap-2 pt-2">{reserved.map((c) => (
|
return (
|
||||||
<button key={c} className="text-sm border rounded px-2 py-1" onClick={() => onControl(c)}>{LABELS[c] ?? c}</button>
|
<div className="flex flex-col gap-2 pt-2">
|
||||||
))}</div>;
|
<div className="flex gap-2">
|
||||||
|
{reserved.map((c) => (
|
||||||
|
<button
|
||||||
|
key={c}
|
||||||
|
className="text-sm border rounded px-2 py-1"
|
||||||
|
onClick={() => (c === "other" ? setOtherOpen(true) : onControl(c))}
|
||||||
|
>
|
||||||
|
{LABELS[c] ?? c}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{otherOpen && (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<textarea
|
||||||
|
className="w-full border rounded p-2"
|
||||||
|
value={text}
|
||||||
|
onChange={(e) => setText(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button className="text-sm border rounded px-2 py-1 self-start" onClick={() => onControl("freetext", text)}>
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function SelectWidget({ descriptor, onRespond }: WidgetProps) {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<ReservedControls reserved={descriptor.reserved} onControl={(c) => onRespond({ id: descriptor.id, control: c })} />
|
<ReservedControls reserved={descriptor.reserved} onControl={(c, t) => onRespond({ id: descriptor.id, control: c, ...(t !== undefined ? { text: t } : {}) })} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user