From 76b935d598b895240b9bc2b182eb9f1e1b24be0d Mon Sep 17 00:00:00 2001
From: Ali Al Jufairi <20195330@stu.uob.edu.bh>
Date: Sun, 20 Jul 2025 16:51:18 +0900
Subject: Feature custom themes logic  (#2639)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
---
 packages/cli/src/ui/components/ThemeDialog.tsx | 125 +++++++++++++++----------
 1 file changed, 78 insertions(+), 47 deletions(-)

(limited to 'packages/cli/src/ui/components/ThemeDialog.tsx')

diff --git a/packages/cli/src/ui/components/ThemeDialog.tsx b/packages/cli/src/ui/components/ThemeDialog.tsx
index be8c52a1..41c39b63 100644
--- a/packages/cli/src/ui/components/ThemeDialog.tsx
+++ b/packages/cli/src/ui/components/ThemeDialog.tsx
@@ -36,22 +36,45 @@ export function ThemeDialog({
     SettingScope.User,
   );
 
-  const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
+  // Track the currently highlighted theme name
+  const [highlightedThemeName, setHighlightedThemeName] = useState<
+    string | undefined
+  >(settings.merged.theme || DEFAULT_THEME.name);
 
+  // Generate theme items filtered by selected scope
+  const customThemes =
+    selectedScope === SettingScope.User
+      ? settings.user.settings.customThemes || {}
+      : settings.merged.customThemes || {};
+  const builtInThemes = themeManager
+    .getAvailableThemes()
+    .filter((theme) => theme.type !== 'custom');
+  const customThemeNames = Object.keys(customThemes);
+  const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
   // Generate theme items
-  const themeItems = themeManager.getAvailableThemes().map((theme) => ({
-    label: theme.name,
-    value: theme.name,
-    themeNameDisplay: theme.name,
-    themeTypeDisplay: capitalize(theme.type),
-  }));
+  const themeItems = [
+    ...builtInThemes.map((theme) => ({
+      label: theme.name,
+      value: theme.name,
+      themeNameDisplay: theme.name,
+      themeTypeDisplay: capitalize(theme.type),
+    })),
+    ...customThemeNames.map((name) => ({
+      label: name,
+      value: name,
+      themeNameDisplay: name,
+      themeTypeDisplay: 'Custom',
+    })),
+  ];
   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
+  // Find the index of the selected theme, but only if it exists in the list
+  const selectedThemeName = settings.merged.theme || DEFAULT_THEME.name;
   const initialThemeIndex = themeItems.findIndex(
-    (item) => item.value === (settings.merged.theme || DEFAULT_THEME.name),
+    (item) => item.value === selectedThemeName,
   );
+  // If not found, fallback to the first theme
+  const safeInitialThemeIndex = initialThemeIndex >= 0 ? initialThemeIndex : 0;
 
   const scopeItems = [
     { label: 'User Settings', value: SettingScope.User },
@@ -66,6 +89,11 @@ export function ThemeDialog({
     [onSelect, selectedScope],
   );
 
+  const handleThemeHighlight = (themeName: string) => {
+    setHighlightedThemeName(themeName);
+    onHighlight(themeName);
+  };
+
   const handleScopeHighlight = useCallback((scope: SettingScope) => {
     setSelectedScope(scope);
     setSelectInputKey(Date.now());
@@ -182,7 +210,6 @@ export function ThemeDialog({
   // The code block is slightly longer than the diff, so give it more space.
   const codeBlockHeight = Math.ceil(availableHeightForPanes * 0.6);
   const diffHeight = Math.floor(availableHeightForPanes * 0.4);
-  const themeType = capitalize(themeManager.getActiveTheme().type);
   return (
     <Box
       borderStyle="round"
@@ -204,9 +231,9 @@ export function ThemeDialog({
           <RadioButtonSelect
             key={selectInputKey}
             items={themeItems}
-            initialIndex={initialThemeIndex}
+            initialIndex={safeInitialThemeIndex}
             onSelect={handleThemeSelect}
-            onHighlight={onHighlight}
+            onHighlight={handleThemeHighlight}
             isFocused={currenFocusedSection === 'theme'}
             maxItemsToShow={8}
             showScrollArrows={true}
@@ -233,40 +260,44 @@ export function ThemeDialog({
 
         {/* Right Column: Preview */}
         <Box flexDirection="column" width="55%" paddingLeft={2}>
-          <Text bold>{themeType} Theme Preview</Text>
-          <Box
-            borderStyle="single"
-            borderColor={Colors.Gray}
-            paddingTop={includePadding ? 1 : 0}
-            paddingBottom={includePadding ? 1 : 0}
-            paddingLeft={1}
-            paddingRight={1}
-            flexDirection="column"
-          >
-            {colorizeCode(
-              `# python function
-def fibonacci(n):
-    a, b = 0, 1
-    for _ in range(n):
-        a, b = b, a + b
-    return a`,
-              'python',
-              codeBlockHeight,
-              colorizeCodeWidth,
-            )}
-            <Box marginTop={1} />
-            <DiffRenderer
-              diffContent={`--- a/util.py
-+++ b/util.py
-@@ -1,3 +1,3 @@
- def greet(name):
--    print("Hello, " + name)
-+    print(f"Hello, {name}!")
-`}
-              availableTerminalHeight={diffHeight}
-              terminalWidth={colorizeCodeWidth}
-            />
-          </Box>
+          <Text bold>Preview</Text>
+          {/* Get the Theme object for the highlighted theme, fallback to default if not found */}
+          {(() => {
+            const previewTheme =
+              themeManager.getTheme(
+                highlightedThemeName || DEFAULT_THEME.name,
+              ) || DEFAULT_THEME;
+            return (
+              <Box
+                borderStyle="single"
+                borderColor={Colors.Gray}
+                paddingTop={includePadding ? 1 : 0}
+                paddingBottom={includePadding ? 1 : 0}
+                paddingLeft={1}
+                paddingRight={1}
+                flexDirection="column"
+              >
+                {colorizeCode(
+                  `# function
+-def fibonacci(n):
+-    a, b = 0, 1
+-    for _ in range(n):
+-        a, b = b, a + b
+-    return a`,
+                  'python',
+                  codeBlockHeight,
+                  colorizeCodeWidth,
+                )}
+                <Box marginTop={1} />
+                <DiffRenderer
+                  diffContent={`--- a/old_file.txt\n+++ b/new_file.txt\n@@ -1,6 +1,7 @@\n # function\n-def fibonacci(n):\n-    a, b = 0, 1\n-    for _ in range(n):\n-        a, b = b, a + b\n-    return a\n+def fibonacci(n):\n+    a, b = 0, 1\n+    for _ in range(n):\n+        a, b = b, a + b\n+    return a\n+\n+print(fibonacci(10))\n`}
+                  availableTerminalHeight={diffHeight}
+                  terminalWidth={colorizeCodeWidth}
+                  theme={previewTheme}
+                />
+              </Box>
+            );
+          })()}
         </Box>
       </Box>
       <Box marginTop={1}>
-- 
cgit v1.2.3

