1- import React , { useState } from 'react' ;
1+ import React , { useState , useRef , useEffect } from 'react' ;
22import { Outlet } from 'react-router-dom' ;
3- import { Menu , X } from 'lucide-react' ;
3+ import { Menu , X , Search as SearchIcon } from 'lucide-react' ;
44import styles from './Layout.module.css' ;
55import Sidebar from './Sidebar.jsx' ;
6+ import Search from './Search.jsx' ;
67
78const Layout = ( ) => {
89 const [ isSidebarOpen , setIsSidebarOpen ] = useState ( false ) ;
10+ const [ isSearchOpen , setIsSearchOpen ] = useState ( false ) ;
11+ const [ progress , setProgress ] = useState ( 0 ) ;
12+ const contentRef = useRef ( null ) ;
913
1014 const toggleSidebar = ( ) => {
1115 setIsSidebarOpen ( ! isSidebarOpen ) ;
@@ -15,8 +19,35 @@ const Layout = () => {
1519 setIsSidebarOpen ( false ) ;
1620 } ;
1721
22+ const handleScroll = ( ) => {
23+ const el = contentRef . current ;
24+ if ( ! el ) return ;
25+ const pct = el . scrollTop / ( el . scrollHeight - el . clientHeight ) * 100 ;
26+ setProgress ( Math . min ( 100 , Math . max ( 0 , pct ) ) ) ;
27+ } ;
28+
29+ useEffect ( ( ) => {
30+ const handler = ( e ) => {
31+ if ( e . key === '/' && ! isSearchOpen &&
32+ e . target . tagName !== 'INPUT' &&
33+ e . target . tagName !== 'TEXTAREA' &&
34+ ! e . target . isContentEditable ) {
35+ e . preventDefault ( ) ;
36+ setIsSearchOpen ( true ) ;
37+ }
38+ } ;
39+ window . addEventListener ( 'keydown' , handler ) ;
40+ return ( ) => window . removeEventListener ( 'keydown' , handler ) ;
41+ } , [ isSearchOpen ] ) ;
42+
1843 return (
1944 < div className = { styles . container } >
45+ < div
46+ className = { styles . progressBar }
47+ style = { { width : `${ progress } %` } }
48+ aria-hidden = "true"
49+ />
50+
2051 < header className = { styles . header } >
2152 < button className = { styles . menuButton } onClick = { toggleSidebar } aria-label = "Toggle Menu" >
2253 { isSidebarOpen ? < X size = { 24 } /> : < Menu size = { 24 } /> }
@@ -25,18 +56,28 @@ const Layout = () => {
2556 < img src = "/logo.png" alt = "Transient Wiki Logo" className = { styles . logo } />
2657 < h1 className = { styles . title } > Transient Wiki</ h1 >
2758 </ div >
28- < div style = { { width : 24 } } > </ div >
59+ < button
60+ className = { styles . searchButton }
61+ onClick = { ( ) => setIsSearchOpen ( true ) }
62+ aria-label = "Suchen"
63+ >
64+ < SearchIcon size = { 15 } />
65+ < span className = { styles . searchLabel } > Suchen</ span >
66+ < kbd className = { styles . searchKbd } > /</ kbd >
67+ </ button >
2968 </ header >
3069
3170 < div className = { styles . main } >
3271 < Sidebar isOpen = { isSidebarOpen } onClose = { closeSidebar } />
3372
34- < main className = { styles . content } >
73+ < main className = { styles . content } ref = { contentRef } onScroll = { handleScroll } >
3574 < div className = { styles . contentInner } >
3675 < Outlet />
3776 </ div >
3877 </ main >
3978 </ div >
79+
80+ { isSearchOpen && < Search onClose = { ( ) => setIsSearchOpen ( false ) } /> }
4081 </ div >
4182 ) ;
4283} ;
0 commit comments