brf/client/public/components/app.tsx

90 lines
2.1 KiB
TypeScript

import { h, type FunctionComponent } from 'preact'
import { useCallback, useEffect, useRef } from 'preact/hooks'
import { LocationProvider, Route, Router } from 'preact-iso'
import { get } from 'lowline'
import Head from './head.ts'
import Footer from './footer.tsx'
import Header from './header.tsx'
import ErrorPage from './error_page.tsx'
import routes from '../routes.ts'
import throttle from '../../shared/utils/throttle.ts'
import s from './app.module.scss'
type Props = {
error?: Error
title?: string
}
const scroll = () => {
const offset = get(window.history, 'state.scrollTop')
window.scrollTo(0, offset || 0)
}
const App: FunctionComponent<Props> = ({ error, title }) => {
const loadRef = useRef<boolean>(false)
useEffect(() => {
history.scrollRestoration = 'manual'
const remember = throttle(function remember() {
if (loadRef.current) return
window.history.replaceState(
{
...window.history.state,
scrollTop: window.scrollY,
},
'',
null,
)
}, 100)
addEventListener('scroll', remember)
return () => removeEventListener('scroll', remember)
}, [])
const onLoadStart = useCallback(() => {
loadRef.current = true
}, [])
const onLoadEnd = useCallback(() => {
scroll()
loadRef.current = false
}, [])
const onRouteChange = useCallback(() => {
if (!loadRef.current) scroll()
}, [])
return (
<LocationProvider>
<div id='app' className={s.base}>
<Head>
<title>{title || 'Untitled'}</title>
</Head>
<Header routes={routes} />
<main className={s.main}>
{error ? (
<ErrorPage error={error} />
) : (
<Router onLoadStart={onLoadStart} onLoadEnd={onLoadEnd} onRouteChange={onRouteChange}>
{routes.map((route) => (
<Route key={route.path} path={route.path} component={route.component} />
))}
</Router>
)}
</main>
<Footer />
</div>
</LocationProvider>
)
}
export default App