From 5790a5d7cf85deda92bf2e58477558b4a4ebc726 Mon Sep 17 00:00:00 2001 From: Jacob Richman Date: Thu, 24 Apr 2025 11:36:34 -0700 Subject: [PATCH] Add a theme preview and update the theme when highlight changes. (#151) --- packages/cli/src/ui/App.tsx | 13 +++++-- .../cli/src/ui/components/ThemeDialog.tsx | 38 ++++++++++++++++++- .../components/shared/RadioButtonSelect.tsx | 10 +++++ packages/cli/src/ui/hooks/useThemeCommand.ts | 14 ++++++- 4 files changed, 70 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index d6813387..cd8295f7 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -39,8 +39,12 @@ export const App = ({ config, cliVersion }: AppProps) => { const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator(streamingState); - const { isThemeDialogOpen, openThemeDialog, handleThemeSelect } = - useThemeCommand(); + const { + isThemeDialogOpen, + openThemeDialog, + handleThemeSelect, + handleThemeHighlight, + } = useThemeCommand(); useStartupWarnings(setStartupWarnings); useInitializationErrorEffect(initError, history, setHistory); @@ -134,7 +138,10 @@ export const App = ({ config, cliVersion }: AppProps) => { )} {isThemeDialogOpen ? ( - + ) : ( <> diff --git a/packages/cli/src/ui/components/ThemeDialog.tsx b/packages/cli/src/ui/components/ThemeDialog.tsx index b3e4f063..62ede336 100644 --- a/packages/cli/src/ui/components/ThemeDialog.tsx +++ b/packages/cli/src/ui/components/ThemeDialog.tsx @@ -9,13 +9,21 @@ import { Box, Text } from 'ink'; import { Colors } from '../colors.js'; import { themeManager } from '../themes/theme-manager.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; +import { DiffRenderer } from './messages/DiffRenderer.js'; +import { colorizeCode } from '../utils/CodeColorizer.js'; interface ThemeDialogProps { /** Callback function when a theme is selected */ onSelect: (themeName: string) => void; + + /** Callback function when a theme is highlighted */ + onHighlight: (themeName: string) => void; } -export function ThemeDialog({ onSelect }: ThemeDialogProps): React.JSX.Element { +export function ThemeDialog({ + onSelect, + onHighlight, +}: ThemeDialogProps): React.JSX.Element { const themeItems = themeManager.getAvailableThemes().map((theme) => ({ label: theme.active ? `${theme.name} (Active)` : theme.name, value: theme.name, @@ -38,12 +46,40 @@ export function ThemeDialog({ onSelect }: ThemeDialogProps): React.JSX.Element { items={themeItems} initialIndex={initialIndex} onSelect={onSelect} + onHighlight={onHighlight} /> (Use ↑/↓ arrows and Enter to select) + + + Preview + + {colorizeCode( + `# Source code +print("Hello, World!") +`, + 'python', + )} + + + + ); } diff --git a/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx b/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx index 7fef19c6..bda56014 100644 --- a/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx +++ b/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx @@ -34,6 +34,9 @@ export interface RadioButtonSelectProps { /** 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; } /** @@ -73,10 +76,16 @@ export function RadioButtonSelect({ items, initialIndex, onSelect, + onHighlight, }: RadioButtonSelectProps): React.JSX.Element { const handleSelect = (item: RadioSelectItem) => { onSelect(item.value); }; + const handleHighlight = (item: RadioSelectItem) => { + if (onHighlight) { + onHighlight(item.value); + } + }; initialIndex = initialIndex ?? 0; return ( ({ items={items} initialIndex={initialIndex} onSelect={handleSelect} + onHighlight={handleHighlight} /> ); } diff --git a/packages/cli/src/ui/hooks/useThemeCommand.ts b/packages/cli/src/ui/hooks/useThemeCommand.ts index 85bd4906..66ec9eda 100644 --- a/packages/cli/src/ui/hooks/useThemeCommand.ts +++ b/packages/cli/src/ui/hooks/useThemeCommand.ts @@ -11,6 +11,7 @@ interface UseThemeCommandReturn { isThemeDialogOpen: boolean; openThemeDialog: () => void; handleThemeSelect: (themeName: string) => void; + handleThemeHighlight: (themeName: string) => void; } export const useThemeCommand = (): UseThemeCommandReturn => { @@ -21,12 +22,22 @@ export const useThemeCommand = (): UseThemeCommandReturn => { setIsThemeDialogOpen(true); }, []); - const handleThemeSelect = useCallback((themeName: string) => { + function applyTheme(themeName: string) { try { themeManager.setActiveTheme(themeName); setForceRender((v) => v + 1); // Trigger potential re-render } catch (error) { console.error(`Error setting theme: ${error}`); + } + } + + const handleThemeHighlight = useCallback((themeName: string) => { + applyTheme(themeName); + }, []); + + const handleThemeSelect = useCallback((themeName: string) => { + try { + applyTheme(themeName); } finally { setIsThemeDialogOpen(false); // Close the dialog } @@ -36,5 +47,6 @@ export const useThemeCommand = (): UseThemeCommandReturn => { isThemeDialogOpen, openThemeDialog, handleThemeSelect, + handleThemeHighlight, }; };