1+ import { useState , useRef , useEffect } from 'react'
12import { useStore } from '@/store/useStore'
23import { Button } from '@/components/ui/button'
3- import type { LeftPanel } from '@/types'
4+ import { Sun , Moon , Monitor } from 'lucide-react'
5+ import type { LeftPanel , ThemeMode } from '@/types'
46
57const PANELS : { value : NonNullable < LeftPanel > ; label : string } [ ] = [
68 { value : 'library' , label : 'Library' } ,
79 { value : 'templates' , label : 'Templates' } ,
810 { value : 'creations' , label : 'Creations' } ,
911]
1012
13+ const THEME_OPTIONS : { value : ThemeMode ; label : string ; icon : typeof Sun } [ ] = [
14+ { value : 'light' , label : 'Light' , icon : Sun } ,
15+ { value : 'dark' , label : 'Dark' , icon : Moon } ,
16+ { value : 'system' , label : 'System' , icon : Monitor } ,
17+ ]
18+
1119export function LeftModeButtons ( ) {
1220 const leftPanel = useStore ( ( s ) => s . leftPanel )
1321 const setLeftPanel = useStore ( ( s ) => s . setLeftPanel )
1422 const themeMode = useStore ( ( s ) => s . themeMode )
1523 const setThemeMode = useStore ( ( s ) => s . setThemeMode )
24+ const [ open , setOpen ] = useState ( false )
25+ const menuRef = useRef < HTMLDivElement > ( null )
26+
27+ useEffect ( ( ) => {
28+ if ( ! open ) return
29+ const onClickOutside = ( e : MouseEvent ) => {
30+ if ( menuRef . current && ! menuRef . current . contains ( e . target as Node ) ) setOpen ( false )
31+ }
32+ document . addEventListener ( 'mousedown' , onClickOutside )
33+ return ( ) => document . removeEventListener ( 'mousedown' , onClickOutside )
34+ } , [ open ] )
35+
36+ const ActiveIcon = THEME_OPTIONS . find ( ( o ) => o . value === themeMode ) ?. icon ?? Monitor
1637
1738 return (
1839 < div className = "absolute top-3 left-3 z-10 flex items-center gap-1.5" >
@@ -28,15 +49,37 @@ export function LeftModeButtons() {
2849 { p . label }
2950 </ Button >
3051 ) ) }
31- < Button
32- variant = "ghost"
33- size = "xs"
34- className = "cursor-crosshair"
35- onClick = { ( ) => setThemeMode ( themeMode === 'dark' ? 'light' : 'dark' ) }
36- title = { `Switch to ${ themeMode === 'dark' ? 'light' : 'dark' } mode` }
37- >
38- { themeMode === 'dark' ? '\u2600' : '\u263E' }
39- </ Button >
52+
53+ < div className = "relative" ref = { menuRef } >
54+ < Button
55+ variant = "ghost"
56+ size = "xs"
57+ className = "cursor-crosshair"
58+ onClick = { ( ) => setOpen ( ! open ) }
59+ title = "Theme"
60+ >
61+ < ActiveIcon className = "h-3.5 w-3.5" />
62+ </ Button >
63+
64+ { open && (
65+ < div className = "absolute top-full left-0 mt-1 rounded-md border border-border bg-popover p-1 shadow-md min-w-[120px]" >
66+ { THEME_OPTIONS . map ( ( opt ) => (
67+ < button
68+ key = { opt . value }
69+ className = { `flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-xs cursor-crosshair transition-colors ${
70+ themeMode === opt . value
71+ ? 'bg-accent text-accent-foreground'
72+ : 'text-popover-foreground hover:bg-muted'
73+ } `}
74+ onClick = { ( ) => { setThemeMode ( opt . value ) ; setOpen ( false ) } }
75+ >
76+ < opt . icon className = "h-3.5 w-3.5" />
77+ { opt . label }
78+ </ button >
79+ ) ) }
80+ </ div >
81+ ) }
82+ </ div >
4083 </ div >
4184 )
4285}
0 commit comments