1- import { useEffect , useState } from 'react'
1+ import { useEffect , useState , useRef } from 'react'
22import { Link , useNavigate } from 'react-router-dom'
33import useAuth from '../../store/useAuth'
44import usePages from '../../store/usePages'
55import useBookmarks from '../../store/useBookmarks'
6- import useTheme from '../../store/useTheme'
6+ import useTheme , { themes } from '../../store/useTheme'
77import Sidebar from './Sidebar'
88import KeyboardShortcuts from '../../hooks/useKeyboard'
99import SearchModal from '../Search/SearchModal'
1010
11+ function applyThemePreview ( themeId ) {
12+ const t = themes [ themeId ] || themes . light
13+ document . documentElement . setAttribute ( 'data-theme' , themeId )
14+ document . documentElement . classList . toggle ( 'dark' , t . dark )
15+ }
16+
17+ function ThemePicker ( { theme, setTheme } ) {
18+ const [ open , setOpen ] = useState ( false )
19+ const ref = useRef ( null )
20+ const savedTheme = useRef ( theme )
21+
22+ useEffect ( ( ) => {
23+ const handler = ( e ) => {
24+ if ( ref . current && ! ref . current . contains ( e . target ) ) {
25+ applyThemePreview ( savedTheme . current )
26+ setOpen ( false )
27+ }
28+ }
29+ document . addEventListener ( 'mousedown' , handler )
30+ return ( ) => document . removeEventListener ( 'mousedown' , handler )
31+ } , [ ] )
32+
33+ const handleOpen = ( ) => {
34+ savedTheme . current = theme
35+ setOpen ( ! open )
36+ }
37+
38+ const handleHover = ( id ) => {
39+ applyThemePreview ( id )
40+ }
41+
42+ const handleLeave = ( ) => {
43+ applyThemePreview ( savedTheme . current )
44+ }
45+
46+ const handleSelect = ( id ) => {
47+ savedTheme . current = id
48+ setTheme ( id )
49+ setOpen ( false )
50+ }
51+
52+ return (
53+ < div className = "relative mr-2" ref = { ref } >
54+ < button
55+ onClick = { handleOpen }
56+ className = "p-1.5 rounded hover:bg-surface-hover text-text-secondary flex items-center gap-1"
57+ title = "Change theme"
58+ >
59+ < svg className = "w-4 h-4" fill = "none" stroke = "currentColor" strokeWidth = "2" viewBox = "0 0 24 24" >
60+ < path d = "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z" />
61+ < path d = "M12 2c3 2.5 4.5 6 4.5 10s-1.5 7.5-4.5 10" />
62+ < path d = "M12 2c-3 2.5-4.5 6-4.5 10s1.5 7.5 4.5 10" />
63+ < path d = "M2 12h20" />
64+ </ svg >
65+ </ button >
66+ { open && (
67+ < div
68+ className = "absolute right-0 top-full mt-1 bg-surface border border-border rounded-xl shadow-lg p-2 z-50 w-44"
69+ onMouseLeave = { handleLeave }
70+ >
71+ < div className = "text-xs font-semibold text-text-secondary uppercase tracking-wider px-2 py-1 mb-1" > Theme</ div >
72+ { Object . entries ( themes ) . map ( ( [ id , t ] ) => (
73+ < button
74+ key = { id }
75+ onMouseEnter = { ( ) => handleHover ( id ) }
76+ onClick = { ( ) => handleSelect ( id ) }
77+ className = { `w-full flex items-center gap-2.5 px-2 py-1.5 rounded-lg text-sm text-left transition-colors ${
78+ theme === id
79+ ? 'bg-surface-hover font-medium text-text'
80+ : 'text-text-secondary hover:bg-surface-hover'
81+ } `}
82+ >
83+ < span className = "flex gap-0.5 shrink-0" >
84+ { t . preview . map ( ( c , i ) => (
85+ < span
86+ key = { i }
87+ className = "w-3 h-3 rounded-full border border-border"
88+ style = { { background : c } }
89+ />
90+ ) ) }
91+ </ span >
92+ < span > { t . name } </ span >
93+ { theme === id && < span className = "ml-auto text-primary text-xs" > ✓</ span > }
94+ </ button >
95+ ) ) }
96+ </ div >
97+ ) }
98+ </ div >
99+ )
100+ }
101+
11102export default function Layout ( { children } ) {
12103 const { user, logout } = useAuth ( )
13104 const { fetchTree } = usePages ( )
14105 const { fetchBookmarks } = useBookmarks ( )
15- const { dark , toggle : toggleTheme , init : initTheme } = useTheme ( )
106+ const { theme , setTheme , init : initTheme , dark } = useTheme ( )
16107 const navigate = useNavigate ( )
17108 const [ sidebarOpen , setSidebarOpen ] = useState ( window . innerWidth > 768 )
18109 const [ searchOpen , setSearchOpen ] = useState ( false )
@@ -29,69 +120,54 @@ export default function Layout({ children }) {
29120 }
30121
31122 return (
32- < div className = "h-screen flex flex-col bg-white dark:bg-gray-900 text-gray-900 dark: text-gray-100 " >
123+ < div className = "h-screen flex flex-col bg-bg text-text" >
33124 < KeyboardShortcuts onOpenSearch = { ( ) => setSearchOpen ( true ) } />
34125 < SearchModal isOpen = { searchOpen } onClose = { ( ) => setSearchOpen ( false ) } />
35126
36127 { /* Navbar */ }
37- < nav className = "h-12 bg-white dark:bg-gray-800 border-b border-gray-200 dark: border-gray-700 flex items-center px-4 shrink-0" >
128+ < nav className = "h-12 bg-surface border-b border-border flex items-center px-4 shrink-0" >
38129 < button
39130 onClick = { ( ) => setSidebarOpen ( ! sidebarOpen ) }
40- className = "p-1.5 rounded hover:bg-gray-100 mr-2 text-gray-500 "
131+ className = "p-1.5 rounded hover:bg-surface-hover mr-2 text-text-secondary "
41132 title = "Toggle sidebar"
42133 >
43134 < svg width = "18" height = "18" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" >
44135 < path d = "M3 12h18M3 6h18M3 18h18" />
45136 </ svg >
46137 </ button >
47- < Link to = "/" className = "font-bold text-lg text-gray-800 dark: text-gray-100 mr-4" > JustWiki</ Link >
138+ < Link to = "/" className = "font-bold text-lg text-text mr-4" > JustWiki</ Link >
48139 < div className = "flex-1" />
49140
50141 { /* Search button */ }
51142 < button
52143 onClick = { ( ) => setSearchOpen ( true ) }
53- className = "flex items-center gap-2 text-sm px-3 py-1.5 bg-gray-100 text-gray-500 rounded-lg hover:bg-gray-200 mr-3"
144+ className = "flex items-center gap-2 text-sm px-3 py-1.5 bg-surface-hover text-text-secondary rounded-lg hover:brightness-95 mr-3"
54145 title = "Search (Ctrl+K)"
55146 >
56147 < svg className = "w-4 h-4" fill = "none" stroke = "currentColor" strokeWidth = "2" viewBox = "0 0 24 24" >
57148 < circle cx = "11" cy = "11" r = "8" />
58149 < path d = "m21 21-4.35-4.35" />
59150 </ svg >
60151 < span className = "hidden sm:inline" > Search</ span >
61- < kbd className = "text-xs text-gray-400 border border-gray-300 rounded px-1" > ⌘K</ kbd >
152+ < kbd className = "text-xs text-text-secondary border border-border rounded px-1" > ⌘K</ kbd >
62153 </ button >
63154
64155 < button
65156 onClick = { ( ) => navigate ( '/new' ) }
66- className = "text-sm px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 mr-3"
157+ className = "text-sm px-3 py-1.5 bg-primary text-primary-text rounded-lg hover:bg-primary-hover mr-3"
67158 title = "New page (Ctrl+N)"
68159 >
69160 + New
70161 </ button >
71- < button
72- onClick = { toggleTheme }
73- className = "p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 mr-2 text-gray-500 dark:text-gray-400"
74- title = { dark ? 'Light mode' : 'Dark mode' }
75- >
76- { dark ? (
77- < svg className = "w-4 h-4" fill = "none" stroke = "currentColor" strokeWidth = "2" viewBox = "0 0 24 24" >
78- < circle cx = "12" cy = "12" r = "5" />
79- < path d = "M12 1v2m0 18v2M4.22 4.22l1.42 1.42m12.72 12.72l1.42 1.42M1 12h2m18 0h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
80- </ svg >
81- ) : (
82- < svg className = "w-4 h-4" fill = "none" stroke = "currentColor" strokeWidth = "2" viewBox = "0 0 24 24" >
83- < path d = "M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
84- </ svg >
85- ) }
86- </ button >
162+ < ThemePicker theme = { theme } setTheme = { setTheme } />
87163 < Link
88164 to = "/profile"
89- className = "text-sm text-gray-500 dark: text-gray-400 hover:text-gray-700 dark:hover: text-gray-200 mr-3"
165+ className = "text-sm text-text-secondary hover:text-text mr-3"
90166 title = "Profile"
91167 >
92168 { user ?. username }
93169 </ Link >
94- < button onClick = { handleLogout } className = "text-sm text-gray-500 hover: text-gray-700 dark:text-gray-400 dark: hover:text-gray-200 " >
170+ < button onClick = { handleLogout } className = "text-sm text-text-secondary hover:text-text " >
95171 Logout
96172 </ button >
97173 </ nav >
@@ -112,7 +188,7 @@ export default function Layout({ children }) {
112188 >
113189 < Sidebar />
114190 </ div >
115- < main className = "flex-1 overflow-auto bg-gray-50 dark:bg-gray-900 p-4 sm:p-6" >
191+ < main className = "flex-1 overflow-auto bg-bg p-4 sm:p-6" >
116192 { children }
117193 </ main >
118194 </ div >
0 commit comments