import { feature } from 'bun:bundle' import * as React from 'react' import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js' import { useTerminalSize } from '../hooks/useTerminalSize.js' import { Box, Text, usePreviewTheme, useTheme, useThemeSetting } from '@anthropic/ink' import { useRegisterKeybindingContext } from '../keybindings/KeybindingContext.js' import { useKeybinding } from '../keybindings/useKeybinding.js' import { useShortcutDisplay } from '../keybindings/useShortcutDisplay.js' import { useAppState, useSetAppState } from '../state/AppState.js' import { gracefulShutdown } from '../utils/gracefulShutdown.js' import { updateSettingsForSource } from '../utils/settings/settings.js' import type { ThemeSetting } from '../utils/theme.js' import { Select } from './CustomSelect/index.js' import { Byline, KeyboardShortcutHint } from '@anthropic/ink' import { getColorModuleUnavailableReason, getSyntaxTheme, } from './StructuredDiff/colorDiff.js' import { StructuredDiff } from './StructuredDiff.js' export type ThemePickerProps = { onThemeSelect: (setting: ThemeSetting) => void showIntroText?: boolean helpText?: string showHelpTextBelow?: boolean hideEscToCancel?: boolean /** Skip exit handling when running in a context that already has it (e.g., onboarding) */ skipExitHandling?: boolean /** Called when the user cancels (presses Escape). If skipExitHandling is true and this is provided, it will be called instead of just saving the preview. */ onCancel?: () => void } export function ThemePicker({ onThemeSelect, showIntroText = false, helpText = '', showHelpTextBelow = false, hideEscToCancel = false, skipExitHandling = false, onCancel: onCancelProp, }: ThemePickerProps): React.ReactNode { const [theme] = useTheme() const themeSetting = useThemeSetting() const { columns } = useTerminalSize() const colorModuleUnavailableReason = getColorModuleUnavailableReason() const syntaxTheme = colorModuleUnavailableReason === null ? getSyntaxTheme(theme) : null const { setPreviewTheme, savePreview, cancelPreview } = usePreviewTheme() const syntaxHighlightingDisabled = useAppState(s => s.settings.syntaxHighlightingDisabled) ?? false const setAppState = useSetAppState() // Register ThemePicker context so its keybindings take precedence over Global useRegisterKeybindingContext('ThemePicker') const syntaxToggleShortcut = useShortcutDisplay( 'theme:toggleSyntaxHighlighting', 'ThemePicker', 'ctrl+t', ) useKeybinding( 'theme:toggleSyntaxHighlighting', () => { if (colorModuleUnavailableReason === null) { const newValue = !syntaxHighlightingDisabled updateSettingsForSource('userSettings', { syntaxHighlightingDisabled: newValue, }) setAppState(prev => ({ ...prev, settings: { ...prev.settings, syntaxHighlightingDisabled: newValue }, })) } }, { context: 'ThemePicker' }, ) // Always call the hook to follow React rules, but conditionally assign the exit handler const exitState = useExitOnCtrlCDWithKeybindings( skipExitHandling ? () => {} : undefined, ) const themeOptions: { label: string; value: ThemeSetting }[] = [ ...(feature('AUTO_THEME') ? [{ label: 'Auto (match terminal)', value: 'auto' as const }] : []), { label: 'Dark mode', value: 'dark' }, { label: 'Light mode', value: 'light' }, { label: 'Dark mode (colorblind-friendly)', value: 'dark-daltonized', }, { label: 'Light mode (colorblind-friendly)', value: 'light-daltonized', }, { label: 'Dark mode (ANSI colors only)', value: 'dark-ansi', }, { label: 'Light mode (ANSI colors only)', value: 'light-ansi', }, ] const content = ( {showIntroText ? ( Let's get started. ) : ( Theme )} Choose the text style that looks best with your terminal {helpText && !showHelpTextBelow && {helpText}}