UNPKG

buroventures-harald-code

Version:

Harald Code - AI-powered coding assistant CLI

119 lines 6.13 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { useEffect, useState, useRef } from 'react'; import { Text, Box, useInput } from 'ink'; import { Colors } from '../../colors.js'; /** * A custom component that displays a list of items with radio buttons, * supporting scrolling and keyboard navigation. * * @template T The type of the value associated with each radio item. */ export function RadioButtonSelect({ items, initialIndex = 0, onSelect, onHighlight, isFocused, showScrollArrows = false, maxItemsToShow = 10, showNumbers = true, }) { const [activeIndex, setActiveIndex] = useState(initialIndex); const [scrollOffset, setScrollOffset] = useState(0); const [numberInput, setNumberInput] = useState(''); const numberInputTimer = useRef(null); useEffect(() => { const newScrollOffset = Math.max(0, Math.min(activeIndex - maxItemsToShow + 1, items.length - maxItemsToShow)); if (activeIndex < scrollOffset) { setScrollOffset(activeIndex); } else if (activeIndex >= scrollOffset + maxItemsToShow) { setScrollOffset(newScrollOffset); } }, [activeIndex, items.length, scrollOffset, maxItemsToShow]); useEffect(() => () => { if (numberInputTimer.current) { clearTimeout(numberInputTimer.current); } }, []); useInput((input, key) => { const isNumeric = showNumbers && /^[0-9]$/.test(input); // Any key press that is not a digit should clear the number input buffer. if (!isNumeric && numberInputTimer.current) { clearTimeout(numberInputTimer.current); setNumberInput(''); } if (input === 'k' || key.upArrow) { const newIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1; setActiveIndex(newIndex); onHighlight?.(items[newIndex].value); return; } if (input === 'j' || key.downArrow) { const newIndex = activeIndex < items.length - 1 ? activeIndex + 1 : 0; setActiveIndex(newIndex); onHighlight?.(items[newIndex].value); return; } if (key.return) { onSelect(items[activeIndex].value); return; } // Handle numeric input for selection. if (isNumeric) { if (numberInputTimer.current) { clearTimeout(numberInputTimer.current); } const newNumberInput = numberInput + input; setNumberInput(newNumberInput); const targetIndex = Number.parseInt(newNumberInput, 10) - 1; // A single '0' is not a valid selection since items are 1-indexed. if (newNumberInput === '0') { numberInputTimer.current = setTimeout(() => setNumberInput(''), 350); return; } if (targetIndex >= 0 && targetIndex < items.length) { const targetItem = items[targetIndex]; setActiveIndex(targetIndex); onHighlight?.(targetItem.value); // If the typed number can't be a prefix for another valid number, // select it immediately. Otherwise, wait for more input. const potentialNextNumber = Number.parseInt(newNumberInput + '0', 10); if (potentialNextNumber > items.length) { onSelect(targetItem.value); setNumberInput(''); } else { numberInputTimer.current = setTimeout(() => { onSelect(targetItem.value); setNumberInput(''); }, 350); // Debounce time for multi-digit input. } } else { // The typed number is out of bounds, clear the buffer setNumberInput(''); } } }, { isActive: isFocused && items.length > 0 }); const visibleItems = items.slice(scrollOffset, scrollOffset + maxItemsToShow); return (_jsxs(Box, { flexDirection: "column", children: [showScrollArrows && (_jsx(Text, { color: scrollOffset > 0 ? Colors.Foreground : Colors.Gray, children: "\u25B2" })), visibleItems.map((item, index) => { const itemIndex = scrollOffset + index; const isSelected = activeIndex === itemIndex; let textColor = Colors.Foreground; let numberColor = Colors.Foreground; if (isSelected) { textColor = Colors.AccentGreen; numberColor = Colors.AccentGreen; } else if (item.disabled) { textColor = Colors.Gray; numberColor = Colors.Gray; } if (!showNumbers) { numberColor = Colors.Gray; } const numberColumnWidth = String(items.length).length; const itemNumberText = `${String(itemIndex + 1).padStart(numberColumnWidth)}.`; return (_jsxs(Box, { alignItems: "center", children: [_jsx(Box, { minWidth: 2, flexShrink: 0, children: _jsx(Text, { color: isSelected ? Colors.AccentGreen : Colors.Foreground, children: isSelected ? '●' : ' ' }) }), _jsx(Box, { marginRight: 1, flexShrink: 0, minWidth: itemNumberText.length, children: _jsx(Text, { color: numberColor, children: itemNumberText }) }), item.themeNameDisplay && item.themeTypeDisplay ? (_jsxs(Text, { color: textColor, wrap: "truncate", children: [item.themeNameDisplay, ' ', _jsx(Text, { color: Colors.Gray, children: item.themeTypeDisplay })] })) : (_jsx(Text, { color: textColor, wrap: "truncate", children: item.label }))] }, item.label)); }), showScrollArrows && (_jsx(Text, { color: scrollOffset + maxItemsToShow < items.length ? Colors.Foreground : Colors.Gray, children: "\u25BC" }))] })); } //# sourceMappingURL=RadioButtonSelect.js.map