1- import { Monitor , Moon , Sun } from "lucide-react" ;
1+ import { Monitor , Moon , Palette , Sun , Upload } from "lucide-react" ;
22import type React from "react" ;
33import { useCallback , useEffect , useRef , useState } from "react" ;
4+ import { themeRegistry } from "../../../extensions/themes/theme-registry" ;
5+ import type { ThemeDefinition } from "../../../extensions/themes/types" ;
6+ import Button from "../../ui/button" ;
47import Command , {
58 CommandEmpty ,
69 CommandHeader ,
710 CommandInput ,
811 CommandItem ,
912 CommandList ,
10- } from "../ui/command" ;
11-
12- type Theme = "auto" | "athas-light" | "athas-dark" ;
13+ } from "../../ui/command" ;
1314
1415interface ThemeInfo {
15- id : Theme ;
16+ id : string ;
1617 name : string ;
1718 description : string ;
1819 category : "System" | "Light" | "Dark" | "Colorful" ;
@@ -22,50 +23,68 @@ interface ThemeInfo {
2223interface ThemeSelectorProps {
2324 isVisible : boolean ;
2425 onClose : ( ) => void ;
25- onThemeChange : ( theme : Theme ) => void ;
26- currentTheme ?: Theme ;
26+ onThemeChange : ( theme : string ) => void ;
27+ currentTheme ?: string ;
2728}
2829
29- // Dynamic theme definitions based on existing theme system
30- const THEME_DEFINITIONS : ThemeInfo [ ] = [
31- // System
32- {
33- id : "auto" ,
34- name : "Auto" ,
35- description : "Follow system preference" ,
36- category : "System" ,
37- icon : < Monitor size = { 14 } /> ,
38- } ,
39- // Light theme
40- {
41- id : "athas-light" ,
42- name : "Athas Light" ,
43- description : "Clean and bright theme" ,
44- category : "Light" ,
45- icon : < Sun size = { 14 } /> ,
46- } ,
47- // Dark theme
48- {
49- id : "athas-dark" ,
50- name : "Athas Dark" ,
51- description : "Modern dark theme" ,
52- category : "Dark" ,
53- icon : < Moon size = { 14 } /> ,
54- } ,
55- ] ;
30+ const getThemeIcon = ( category : string , _isDark ?: boolean ) : React . ReactNode => {
31+ switch ( category ) {
32+ case "System" :
33+ return < Monitor size = { 14 } /> ;
34+ case "Light" :
35+ return < Sun size = { 14 } /> ;
36+ case "Dark" :
37+ return < Moon size = { 14 } /> ;
38+ default :
39+ return < Palette size = { 14 } /> ;
40+ }
41+ } ;
5642
5743const ThemeSelector = ( { isVisible, onClose, onThemeChange, currentTheme } : ThemeSelectorProps ) => {
5844 const [ query , setQuery ] = useState ( "" ) ;
5945 const [ selectedIndex , setSelectedIndex ] = useState ( 0 ) ;
6046 const [ initialTheme , setInitialTheme ] = useState ( currentTheme ) ;
61- const [ previewTheme , setPreviewTheme ] = useState < Theme | null > ( null ) ;
47+ const [ previewTheme , setPreviewTheme ] = useState < string | null > ( null ) ;
48+ const [ themes , setThemes ] = useState < ThemeInfo [ ] > ( [ ] ) ;
6249 const inputRef = useRef < HTMLInputElement > ( null ) ;
6350 const resultsRef = useRef < HTMLDivElement > ( null ) ;
6451
65- // Focus is handled internally when the selector becomes visible
52+ // Load themes from theme registry
53+ useEffect ( ( ) => {
54+ const loadThemes = ( ) => {
55+ const registryThemes = themeRegistry . getAllThemes ( ) ;
56+ const themeInfos : ThemeInfo [ ] = [
57+ // System theme always first
58+ {
59+ id : "auto" ,
60+ name : "Auto" ,
61+ description : "Follow system preference" ,
62+ category : "System" ,
63+ icon : < Monitor size = { 14 } /> ,
64+ } ,
65+ // Convert registry themes to ThemeInfo
66+ ...registryThemes . map (
67+ ( theme : ThemeDefinition ) : ThemeInfo => ( {
68+ id : theme . id ,
69+ name : theme . name ,
70+ description : theme . description ,
71+ category : theme . category ,
72+ icon : getThemeIcon ( theme . category , theme . isDark ) ,
73+ } ) ,
74+ ) ,
75+ ] ;
76+ setThemes ( themeInfos ) ;
77+ } ;
78+
79+ loadThemes ( ) ;
80+
81+ // Listen for theme registry changes
82+ const unsubscribe = themeRegistry . onRegistryChange ( loadThemes ) ;
83+ return unsubscribe ;
84+ } , [ ] ) ;
6685
6786 // Filter themes based on query
68- const filteredThemes = THEME_DEFINITIONS . filter (
87+ const filteredThemes = themes . filter (
6988 ( theme ) =>
7089 theme . name . toLowerCase ( ) . includes ( query . toLowerCase ( ) ) ||
7190 theme . description ?. toLowerCase ( ) . includes ( query . toLowerCase ( ) ) ||
@@ -79,12 +98,12 @@ const ThemeSelector = ({ isVisible, onClose, onThemeChange, currentTheme }: Them
7998 setQuery ( "" ) ;
8099 setPreviewTheme ( null ) ;
81100
82- const initialIndex = THEME_DEFINITIONS . findIndex ( ( t ) => t . id === currentTheme ) ;
101+ const initialIndex = themes . findIndex ( ( t ) => t . id === currentTheme ) ;
83102 setSelectedIndex ( initialIndex >= 0 ? initialIndex : 0 ) ;
84103
85104 requestAnimationFrame ( ( ) => inputRef . current ?. focus ( ) ) ;
86105 }
87- } , [ isVisible ] ) ;
106+ } , [ isVisible , themes , currentTheme ] ) ;
88107
89108 const handleKeyDown = useCallback (
90109 ( e : KeyboardEvent ) => {
@@ -143,6 +162,31 @@ const ThemeSelector = ({ isVisible, onClose, onThemeChange, currentTheme }: Them
143162 selectedElement ?. scrollIntoView ( { block : "nearest" , behavior : "smooth" } ) ;
144163 } , [ selectedIndex ] ) ;
145164
165+ const handleUploadTheme = async ( ) => {
166+ // Create file input element
167+ const input = document . createElement ( "input" ) ;
168+ input . type = "file" ;
169+ input . accept = ".toml" ;
170+ input . onchange = async ( e ) => {
171+ const file = ( e . target as HTMLInputElement ) . files ?. [ 0 ] ;
172+ if ( file ) {
173+ const { uploadTheme } = await import ( "../../../utils/theme-upload" ) ;
174+ const result = await uploadTheme ( file ) ;
175+ if ( result . success ) {
176+ console . log ( "Theme uploaded successfully:" , result . theme ?. name ) ;
177+ // Optionally switch to the newly uploaded theme
178+ if ( result . theme ) {
179+ onThemeChange ( result . theme . id ) ;
180+ onClose ( ) ;
181+ }
182+ } else {
183+ console . error ( "Theme upload failed:" , result . error ) ;
184+ }
185+ }
186+ } ;
187+ input . click ( ) ;
188+ } ;
189+
146190 if ( ! isVisible ) return null ;
147191
148192 const handleClose = ( ) => {
@@ -155,12 +199,23 @@ const ThemeSelector = ({ isVisible, onClose, onThemeChange, currentTheme }: Them
155199 return (
156200 < Command isVisible = { isVisible } >
157201 < CommandHeader onClose = { handleClose } >
158- < CommandInput
159- ref = { inputRef }
160- value = { query }
161- onChange = { setQuery }
162- placeholder = "Search themes..."
163- />
202+ < div className = "flex w-full items-center gap-2" >
203+ < CommandInput
204+ ref = { inputRef }
205+ value = { query }
206+ onChange = { setQuery }
207+ placeholder = "Search themes..."
208+ className = "flex-1"
209+ />
210+ < Button
211+ onClick = { handleUploadTheme }
212+ variant = "ghost"
213+ size = "xs"
214+ className = "flex-shrink-0 gap-1 px-2"
215+ >
216+ < Upload size = { 12 } />
217+ </ Button >
218+ </ div >
164219 </ CommandHeader >
165220
166221 < CommandList ref = { resultsRef } >
0 commit comments