86 行
2.9 KiB
TypeScript
86 行
2.9 KiB
TypeScript
import { motion } from 'framer-motion'
|
|
import { useEffect, useRef } from 'react'
|
|
import { useLocation } from 'react-router-dom'
|
|
|
|
import { clientAnimationTransition } from '@/lib/clientAnimation'
|
|
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
|
|
import { useVerticalPreview } from '@/lib/useLocale'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
import type { FC, ReactNode } from 'react'
|
|
|
|
type Props = {
|
|
children: ReactNode
|
|
className?: string
|
|
animateLayout?: boolean
|
|
verticalContent?: boolean }
|
|
|
|
|
|
const MainArea: FC<Props> = (
|
|
{ children, className, animateLayout = true, verticalContent = true },
|
|
) => {
|
|
const behaviourSettings = useClientBehaviourSettings ()
|
|
const verticalPreview = useVerticalPreview ()
|
|
const horizontal = verticalPreview && verticalContent
|
|
const { pathname, search } = useLocation ()
|
|
const rootRef = useRef<HTMLElement | null> (null)
|
|
const animationMode = behaviourSettings.animation ?? 'normal'
|
|
const layoutTransition = clientAnimationTransition (
|
|
animationMode,
|
|
{ normal: { duration: .2, ease: 'easeOut' as const } })
|
|
|
|
useEffect (() => {
|
|
if (horizontal && rootRef.current != null)
|
|
rootRef.current.scrollLeft = 0
|
|
}, [horizontal, pathname, search])
|
|
|
|
useEffect (() => {
|
|
const root = rootRef.current
|
|
if (!horizontal || root == null)
|
|
return
|
|
|
|
const handleWheel = (event: WheelEvent) => {
|
|
if (event.ctrlKey || event.metaKey || event.shiftKey
|
|
|| event.deltaX !== 0 || event.deltaY === 0 || event.defaultPrevented)
|
|
return
|
|
|
|
// Nested tables, forms and code blocks retain their own scrolling.
|
|
let node = event.target instanceof HTMLElement ? event.target : null
|
|
while (node != null && node !== root)
|
|
{
|
|
if (node.matches ('input, select, textarea, [contenteditable="true"]'))
|
|
return
|
|
const style = getComputedStyle (node)
|
|
if ((/auto|scroll/.test (style.overflowY) && node.scrollHeight > node.clientHeight)
|
|
|| (/auto|scroll/.test (style.overflowX) && node.scrollWidth > node.clientWidth))
|
|
return
|
|
node = node.parentElement
|
|
}
|
|
|
|
const unit = event.deltaMode === 1 ? 16 : (event.deltaMode === 2 ? root.clientWidth : 1)
|
|
const delta = event.deltaY * unit
|
|
if ((delta < 0 && root.scrollLeft <= 0)
|
|
|| (delta > 0 && root.scrollLeft + root.clientWidth >= root.scrollWidth))
|
|
return
|
|
event.preventDefault ()
|
|
root.scrollLeft += delta
|
|
}
|
|
|
|
root.addEventListener ('wheel', handleWheel, { passive: false })
|
|
return () => root.removeEventListener ('wheel', handleWheel)
|
|
}, [horizontal])
|
|
|
|
return (
|
|
<motion.main
|
|
ref={rootRef}
|
|
data-vertical-content={horizontal || undefined}
|
|
tabIndex={horizontal ? 0 : undefined}
|
|
transition={{ layout: layoutTransition }}
|
|
className={cn ('main-area flex-1 overflow-y-auto p-4', className)}
|
|
layout={(animationMode === 'off' || !animateLayout) ? false : 'position'}>
|
|
{children}
|
|
</motion.main>)
|
|
}
|
|
|
|
export default MainArea
|