import { useEffect, useMemo, useState } from "react"; import { Box, Text } from "../../ui"; import { type PromptContext, useDialogKeyboard } from "../../ui/dialog"; import { useThemeColors } from "../../theme/theme-context"; import { t } from "../../i18n"; import { DialogFrame } from "./frame"; import { ListView, listCursorMove, type ListViewItem } from "./list-view"; export interface ChoiceDialogChoice { id: string; label: string; description?: string; detail?: string; disabled?: boolean; } export interface ChoiceDialogProps extends PromptContext { title: string; choices: ChoiceDialogChoice[]; selectedChoiceId?: string; footer?: string; bgColor?: string; } const MAX_VISIBLE_CHOICE_ROWS = 12; function clampChoiceIndex(index: number, length: number): number { if (length <= 0) return -1; return Math.max(0, Math.min(length - 1, index)); } function getChoiceDescription(choice: ChoiceDialogChoice | undefined): string { return choice?.description ?? ""; } function getInitialChoiceIndex(choices: ChoiceDialogChoice[], selectedChoiceId: string | undefined): number { if (!selectedChoiceId) return choices.findIndex((choice) => !choice.disabled); const selectedIndex = choices.findIndex((choice) => choice.id === selectedChoiceId); return selectedIndex >= 0 ? selectedIndex : 0; } function choiceDialogWidth(title: string, choices: ChoiceDialogChoice[]): number { const contentWidth = Math.max( title.length, ...choices.map((choice) => choice.label.length + (choice.detail ? choice.detail.length + 4 : 0)), ...choices.map((choice) => getChoiceDescription(choice).length), ); return Math.max(34, Math.min(76, contentWidth + 4)); } export function ChoiceDialog({ resolve, title, choices, selectedChoiceId, footer, bgColor, }: ChoiceDialogProps) { const colors = useThemeColors(); const [index, setIndex] = useState(() => clampChoiceIndex(getInitialChoiceIndex(choices, selectedChoiceId), choices.length) ); const selectedIndex = clampChoiceIndex(index, choices.length); const selectedChoice = selectedIndex >= 0 ? choices[selectedIndex] : undefined; const items = useMemo(() => choices.map((choice) => ({ id: choice.id, label: choice.label, description: getChoiceDescription(choice), detail: choice.detail, disabled: choice.disabled, })), [choices]); const width = useMemo(() => choiceDialogWidth(title, choices), [choices, title]); useEffect(() => { setIndex((current) => clampChoiceIndex(current, choices.length)); }, [choices.length]); const activateChoice = (choice: ChoiceDialogChoice | undefined) => { if (!choice || choice.disabled) return; resolve(choice.id); }; useDialogKeyboard((event) => { event.stopPropagation(); const move = listCursorMove(event, MAX_VISIBLE_CHOICE_ROWS - 1); if (move) { setIndex((current) => move(choices, current)); } else if (event.name === "enter" || event.name === "return") { activateChoice(selectedChoice); } else if (event.name === "escape") { resolve(""); } }); return ( MAX_VISIBLE_CHOICE_ROWS} selectOnHover onSelect={setIndex} onActivate={(_, nextIndex) => activateChoice(choices[nextIndex])} /> {getChoiceDescription(selectedChoice)} ); }