From a685597b70242eb4c6b38d30c5356ad79418176d Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 8 May 2025 16:00:55 -0700 Subject: UI Polish for theme selector (#294) --- .../cli/src/ui/components/SuggestionsDisplay.tsx | 7 +- packages/cli/src/ui/components/ThemeDialog.tsx | 86 ++++++++++-------- .../ui/components/messages/ToolGroupMessage.tsx | 2 +- .../src/ui/components/shared/RadioButtonSelect.tsx | 101 ++++++++++++++------- 4 files changed, 123 insertions(+), 73 deletions(-) (limited to 'packages/cli/src/ui/components') diff --git a/packages/cli/src/ui/components/SuggestionsDisplay.tsx b/packages/cli/src/ui/components/SuggestionsDisplay.tsx index f0626fa9..ba25f2b6 100644 --- a/packages/cli/src/ui/components/SuggestionsDisplay.tsx +++ b/packages/cli/src/ui/components/SuggestionsDisplay.tsx @@ -5,6 +5,7 @@ */ import { Box, Text } from 'ink'; +import { Colors } from '../colors.js'; export interface Suggestion { label: string; value: string; @@ -48,7 +49,7 @@ export function SuggestionsDisplay({ return ( - {scrollOffset > 0 && } + {scrollOffset > 0 && } {visibleSuggestions.map((suggestion, index) => { const originalIndex = startIndex + index; @@ -56,8 +57,8 @@ export function SuggestionsDisplay({ return ( {suggestion.label} diff --git a/packages/cli/src/ui/components/ThemeDialog.tsx b/packages/cli/src/ui/components/ThemeDialog.tsx index 7e8c5afd..20686040 100644 --- a/packages/cli/src/ui/components/ThemeDialog.tsx +++ b/packages/cli/src/ui/components/ThemeDialog.tsx @@ -32,16 +32,22 @@ export function ThemeDialog({ SettingScope.User, ); - const themeItems = themeManager.getAvailableThemes().map((theme) => ({ - label: theme.active ? `${theme.name} (Active)` : theme.name, - value: theme.name, - })); + // Generate theme items + const themeItems = themeManager.getAvailableThemes().map((theme) => { + const typeString = theme.type.charAt(0).toUpperCase() + theme.type.slice(1); + return { + label: theme.name, + value: theme.name, + themeNameDisplay: theme.name, + themeTypeDisplay: typeString, + }; + }); const [selectInputKey, setSelectInputKey] = useState(Date.now()); + // Determine which radio button should be initially selected in the theme list + // This should reflect the theme *saved* for the selected scope, or the default const initialThemeIndex = themeItems.findIndex( - (item) => - item.value === - (settings.forScope(selectedScope).settings.theme || DEFAULT_THEME.name), + (item) => item.value === (settings.merged.theme || DEFAULT_THEME.name), ); const scopeItems = [ @@ -88,45 +94,49 @@ export function ThemeDialog({ return ( - - {focusedSection === 'theme' ? '> ' : ' '}Select Theme{' '} - {otherScopeModifiedMessage} - - - - {/* Scope Selection */} - - - {focusedSection === 'scope' ? '> ' : ' '}Apply To + {/* Left Column: Selection */} + + + {focusedSection === 'theme' ? '> ' : ' '}Select Theme{' '} + {otherScopeModifiedMessage} - - - - (Use ↑/↓ arrows and Enter to select, Tab to change focus) - + {/* Scope Selection */} + + + {focusedSection === 'scope' ? '> ' : ' '}Apply To + + + + + + + (Use ↑/↓ arrows and Enter to select, Tab to change focus) + + - + {/* Right Column: Preview */} + Preview = ({ const hasPending = !toolCalls.every( (t) => t.status === ToolCallStatus.Success, ); - const borderColor = hasPending ? Colors.AccentYellow : Colors.AccentCyan; + const borderColor = hasPending ? Colors.AccentYellow : Colors.AccentPurple; return ( { */ export interface RadioButtonSelectProps { /** An array of items to display as radio options. */ - items: Array>; + items: Array< + RadioSelectItem & { + themeNameDisplay?: string; + themeTypeDisplay?: string; + } + >; /** The initial index selected */ initialIndex?: number; @@ -42,33 +47,6 @@ export interface RadioButtonSelectProps { isFocused?: boolean; } -/** - * Custom indicator component displaying radio button style (◉/○). - */ -function RadioIndicator({ - isSelected = false, -}: InkSelectIndicatorProps): React.JSX.Element { - return ( - - - {isSelected ? '◉' : '○'} - - - ); -} - -/** - * Custom item component for displaying the label with appropriate color. - */ -function RadioItem({ - isSelected = false, - label, -}: InkSelectItemProps): React.JSX.Element { - return ( - {label} - ); -} - /** * A specialized SelectInput component styled to look like radio buttons. * It uses '◉' for selected and '○' for unselected items. @@ -80,7 +58,7 @@ export function RadioButtonSelect({ initialIndex, onSelect, onHighlight, - isFocused, + isFocused, // This prop indicates if the current RadioButtonSelect group is focused }: RadioButtonSelectProps): React.JSX.Element { const handleSelect = (item: RadioSelectItem) => { onSelect(item.value); @@ -90,11 +68,72 @@ export function RadioButtonSelect({ 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 { + let indicatorColor = Colors.Foreground; // Default for not selected + if (isSelected) { + if (isFocused) { + // Group is focused, selected item is AccentGreen + indicatorColor = Colors.AccentGreen; + } else { + // Group is NOT focused, selected item is Foreground + indicatorColor = Colors.Foreground; + } + } + return ( + + {isSelected ? '●' : '○'} + + ); + } + + /** + * 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; + }; + + let textColor = Colors.Foreground; + if (isSelected) { + textColor = isFocused ? Colors.AccentGreen : Colors.Foreground; + } + + if ( + itemWithThemeProps.themeNameDisplay && + itemWithThemeProps.themeTypeDisplay + ) { + return ( + + {itemWithThemeProps.themeNameDisplay}{' '} + + {itemWithThemeProps.themeTypeDisplay} + + + ); + } + + return {label}; + } + initialIndex = initialIndex ?? 0; return (