2023-03-26 06:50:59 +02:00
|
|
|
// Dependencies
|
|
|
|
import React, { useState, useEffect } from 'react'
|
|
|
|
import { useSwipeable } from 'react-swipeable'
|
|
|
|
import { useHotkeys } from 'react-hotkeys-hook'
|
|
|
|
// Hooks
|
|
|
|
import { useTheme } from 'shared/hooks/use-theme.mjs'
|
|
|
|
// Components
|
|
|
|
import { LayoutWrapper, ns as layoutNs } from 'site/components/wrappers/layout.mjs'
|
2023-03-28 16:47:07 +02:00
|
|
|
import { DocsLayout, ns as docsNs } from 'site/components/layouts/docs.mjs'
|
2023-03-26 06:50:59 +02:00
|
|
|
import { Feeds } from 'site/components/feeds.mjs'
|
|
|
|
|
2023-03-28 16:47:07 +02:00
|
|
|
export const ns = [...new Set([...layoutNs, ...docsNs])]
|
2023-03-26 06:50:59 +02:00
|
|
|
|
|
|
|
/* This component should wrap all page content */
|
|
|
|
export const PageWrapper = ({
|
|
|
|
noSearch = false,
|
|
|
|
app = false,
|
|
|
|
layout = DocsLayout,
|
|
|
|
footer = true,
|
|
|
|
children = [],
|
|
|
|
title = 'FIXME: No title set',
|
|
|
|
}) => {
|
|
|
|
/*
|
|
|
|
* This forces a re-render upon initial bootstrap of the app
|
|
|
|
* This is needed to avoid hydration errors because theme can't be set reliably in SSR
|
|
|
|
*/
|
|
|
|
const [theme, setTheme] = useTheme()
|
|
|
|
const [currentTheme, setCurrentTheme] = useState()
|
|
|
|
useEffect(() => setCurrentTheme(theme), [currentTheme, theme])
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Swipe handling for the entire site
|
|
|
|
*/
|
|
|
|
const swipeHandlers = useSwipeable({
|
2023-03-26 08:32:44 +02:00
|
|
|
onSwipedLeft: () => (app.state?.menu?.main ? app.updateState('menu.main', false) : null),
|
|
|
|
onSwipedRight: () => (app.state?.menu?.main ? null : app.updateState('menu.main', true)),
|
2023-03-26 06:50:59 +02:00
|
|
|
trackMouse: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Hotkeys (keyboard actions)
|
|
|
|
*/
|
|
|
|
// Trigger search with /
|
|
|
|
useHotkeys('/', (evt) => {
|
|
|
|
evt.preventDefault()
|
|
|
|
setSearch(true)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Always close modal when Escape key is hit
|
|
|
|
useHotkeys('esc', (evt) => {
|
|
|
|
evt.preventDefault()
|
2023-03-26 08:32:44 +02:00
|
|
|
app.updateState('modal', null)
|
2023-03-26 06:50:59 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
// Search state
|
|
|
|
const [search, setSearch] = useState(false)
|
|
|
|
|
|
|
|
// Helper object to pass props down (keeps things DRY)
|
|
|
|
const childProps = {
|
|
|
|
app: app,
|
|
|
|
footer,
|
|
|
|
search,
|
|
|
|
setSearch,
|
|
|
|
toggleSearch: () => setSearch(!search),
|
|
|
|
noSearch: noSearch,
|
2023-03-26 08:32:44 +02:00
|
|
|
title: app.state.title ? app.state.title : title,
|
2023-03-26 06:50:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make layout prop into a (uppercase) component
|
|
|
|
const Layout = layout
|
|
|
|
|
|
|
|
// Return wrapper
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
ref={swipeHandlers.ref}
|
|
|
|
onMouseDown={swipeHandlers.onMouseDown}
|
|
|
|
data-theme={currentTheme} // This facilitates CSS selectors
|
|
|
|
key={currentTheme} // This forces the data-theme update
|
|
|
|
>
|
|
|
|
<Feeds />
|
|
|
|
<LayoutWrapper {...childProps}>
|
|
|
|
{Layout ? <Layout {...childProps}>{children}</Layout> : children}
|
|
|
|
</LayoutWrapper>
|
2023-04-01 15:41:58 +02:00
|
|
|
{app.state.modal ? (
|
2023-03-26 06:50:59 +02:00
|
|
|
<div
|
|
|
|
className={`fixed top-0 left-0 m-0 p-0 shadow drop-shadow-lg w-full h-screen
|
2023-04-07 17:10:54 +02:00
|
|
|
bg-base-100 bg-opacity-95 z-50 hover:cursor-pointer
|
2023-04-09 15:57:25 +02:00
|
|
|
flex flex-row justify-center overflow-auto
|
2023-03-26 06:50:59 +02:00
|
|
|
`}
|
2023-03-26 08:32:44 +02:00
|
|
|
onClick={() => app.updateState('modal', false)}
|
2023-03-26 06:50:59 +02:00
|
|
|
>
|
2023-04-01 15:41:58 +02:00
|
|
|
{app.state.modal}
|
2023-03-26 06:50:59 +02:00
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|