File size: 3,948 Bytes
40e575e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import { Text, Box } from 'ink';
import SelectInput, {
type ItemProps as InkSelectItemProps,
type IndicatorProps as InkSelectIndicatorProps,
} from 'ink-select-input';
import { Colors } from '../../colors.js';
/**
* Represents a single option for the RadioButtonSelect.
* Requires a label for display and a value to be returned on selection.
*/
export interface RadioSelectItem<T> {
label: string;
value: T;
disabled?: boolean;
}
/**
* Props for the RadioButtonSelect component.
* @template T The type of the value associated with each radio item.
*/
export interface RadioButtonSelectProps<T> {
/** An array of items to display as radio options. */
items: Array<
RadioSelectItem<T> & {
themeNameDisplay?: string;
themeTypeDisplay?: string;
}
>;
/** The initial index selected */
initialIndex?: number;
/** Function called when an item is selected. Receives the `value` of the selected item. */
onSelect: (value: T) => void;
/** Function called when an item is highlighted. Receives the `value` of the selected item. */
onHighlight?: (value: T) => void;
/** Whether this select input is currently focused and should respond to input. */
isFocused?: boolean;
}
/**
* A specialized SelectInput component styled to look like radio buttons.
* It uses '◉' for selected and '○' for unselected items.
*
* @template T The type of the value associated with each radio item.
*/
export function RadioButtonSelect<T>({
items,
initialIndex,
onSelect,
onHighlight,
isFocused, // This prop indicates if the current RadioButtonSelect group is focused
}: RadioButtonSelectProps<T>): React.JSX.Element {
const handleSelect = (item: RadioSelectItem<T>) => {
onSelect(item.value);
};
const handleHighlight = (item: RadioSelectItem<T>) => {
if (onHighlight) {
onHighlight(item.value);
}
};
/**
* Custom indicator component displaying radio button style (◉/○).
* Color changes based on whether the item is selected and if its group is focused.
*/
function DynamicRadioIndicator({
isSelected = false,
}: InkSelectIndicatorProps): React.JSX.Element {
return (
<Box minWidth={2} flexShrink={0}>
<Text color={isSelected ? Colors.AccentGreen : Colors.Foreground}>
{isSelected ? '●' : '○'}
</Text>
</Box>
);
}
/**
* Custom item component for displaying the label.
* Color changes based on whether the item is selected and if its group is focused.
* Now also handles displaying theme type with custom color.
*/
function CustomThemeItemComponent(
props: InkSelectItemProps,
): React.JSX.Element {
const { isSelected = false, label } = props;
const itemWithThemeProps = props as typeof props & {
themeNameDisplay?: string;
themeTypeDisplay?: string;
disabled?: boolean;
};
let textColor = Colors.Foreground;
if (isSelected) {
textColor = Colors.AccentGreen;
} else if (itemWithThemeProps.disabled === true) {
textColor = Colors.Gray;
}
if (
itemWithThemeProps.themeNameDisplay &&
itemWithThemeProps.themeTypeDisplay
) {
return (
<Text color={textColor} wrap="truncate">
{itemWithThemeProps.themeNameDisplay}{' '}
<Text color={Colors.Gray}>{itemWithThemeProps.themeTypeDisplay}</Text>
</Text>
);
}
return (
<Text color={textColor} wrap="truncate">
{label}
</Text>
);
}
initialIndex = initialIndex ?? 0;
return (
<SelectInput
indicatorComponent={DynamicRadioIndicator}
itemComponent={CustomThemeItemComponent}
items={items}
initialIndex={initialIndex}
onSelect={handleSelect}
onHighlight={handleHighlight}
isFocused={isFocused}
/>
);
}
|