refactor: deprecate SidebarBodyWrapper

This commit is contained in:
EnixCoda 2022-07-16 00:32:56 +08:00
parent 0e96aeb26b
commit 8fb1eb924c
11 changed files with 127 additions and 140 deletions

View file

@ -2,7 +2,7 @@ import { GrabberIcon } from '@primer/octicons-react'
import { Icon } from 'components/Icon'
import * as React from 'react'
import { ResizeState, useResizeHandler } from '../utils/hooks/useResizeHandler'
import { Size2D } from './SideBarBodyWrapper'
import { Size2D } from "./Size"
type Props = {
size: Size2D

View file

@ -4,16 +4,20 @@ import { FileExplorer } from 'components/FileExplorer'
import { Footer } from 'components/Footer'
import { MetaBar } from 'components/MetaBar'
import { Portal } from 'components/Portal'
import { SideBarBodyWrapper } from 'components/SideBarBodyWrapper'
import { ToggleShowButton } from 'components/ToggleShowButton'
import { useConfigs } from 'containers/ConfigsContext'
import { platform } from 'platforms'
import * as React from 'react'
import { IIFC } from 'react-iifc'
import { useWindowSize } from 'react-use'
import { cx } from 'utils/cx'
import * as DOMHelper from 'utils/DOMHelper'
import * as features from 'utils/features'
import { detectBrowser } from 'utils/general'
import { useConditionalHook } from 'utils/hooks/useConditionalHook'
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { useOnPJAXDone, usePJAX } from 'utils/hooks/usePJAX'
import { ResizeState } from 'utils/hooks/useResizeHandler'
import * as keyHelper from 'utils/keyHelper'
import { SideBarErrorContext } from '../containers/ErrorContext'
import { SideBarStateContext } from '../containers/SideBarState'
@ -21,17 +25,33 @@ import { Theme } from '../containers/Theme'
import { LoadingIndicator } from './LoadingIndicator'
import { RoundIconButton } from './RoundIconButton'
import { SettingsBarContent } from './settings/SettingsBar'
import { SideBarResizeHandler } from './SideBarResizeHandler'
export function SideBar() {
usePJAX()
platform.usePlatformHooks?.()
useMarkGitakoReadyState()
const error = useLoadedContext(SideBarErrorContext).value
const [shouldExpand, setShouldExpand, toggleShowSideBar] = useShouldExpand()
const error = useLoadedContext(SideBarErrorContext).value
const configContext = useConfigs()
const blockLeaveRef = React.useRef(false)
const { sidebarToggleMode } = configContext.value
const onMouseLeaveSideBar: React.MouseEventHandler<HTMLElement> = React.useCallback(() => {
if (blockLeaveRef.current) return
if (sidebarToggleMode === 'float') setShouldExpand(false)
}, [sidebarToggleMode, setShouldExpand])
const onResizeStateChange = React.useCallback((state: ResizeState) => {
blockLeaveRef.current = state === 'resizing'
}, [])
const heightForSafari = useConditionalHook(
() => detectBrowser() === 'Safari',
() => useWindowSize().height, // eslint-disable-line react-hooks/rules-of-hooks
)
return (
<Theme>
@ -53,16 +73,17 @@ export function SideBar() {
}}
</IIFC>
<div className={'gitako-side-bar'}>
<SideBarBodyWrapper
className={cx(`toggle-mode-${sidebarToggleMode}`, {
<div
className={cx('gitako-side-bar-body-wrapper', `toggle-mode-${sidebarToggleMode}`, {
collapsed: error || !shouldExpand,
})}
onLeave={sidebarToggleMode === 'float' ? () => setShouldExpand(false) : undefined}
style={{ height: heightForSafari }}
onMouseLeave={onMouseLeaveSideBar}
>
<div className={'gitako-side-bar-body'}>
<div className={'gitako-side-bar-content'}>
<div className={'header'}>
<div className={'close-side-bar-button-position'}>
<div className={'side-bar-position-controls'}>
{sidebarToggleMode === 'persistent' && (
<RoundIconButton
icon={TabIcon}
@ -126,7 +147,8 @@ export function SideBar() {
}}
</IIFC>
</div>
</SideBarBodyWrapper>
{features.resize && <SideBarResizeHandler onResizeStateChange={onResizeStateChange} />}
</div>
</div>
</Theme>
)

View file

@ -1,127 +0,0 @@
import { ResizeHandler } from 'components/ResizeHandler'
import { useConfigs } from 'containers/ConfigsContext'
import * as React from 'react'
import { useDebounce, useWindowSize } from 'react-use'
import { getDefaultConfigs } from 'utils/config/helper'
import { cx } from 'utils/cx'
import * as DOMHelper from 'utils/DOMHelper'
import { setCSSVariable } from 'utils/DOMHelper'
import * as features from 'utils/features'
import { detectBrowser } from 'utils/general'
import { useOnPJAXDone } from 'utils/hooks/usePJAX'
import { ResizeState } from 'utils/hooks/useResizeHandler'
import { useConditionalHook } from '../utils/hooks/useConditionalHook'
type Size = number
export type Size2D = [Size, Size]
type Props = {
className?: string
onLeave?: React.HTMLAttributes<HTMLElement>['onMouseLeave']
}
const MINIMAL_CONTENT_VIEWPORT_WIDTH = 100
const MINIMAL_WIDTH = 240
function getSafeSize(size: number, width: number) {
if (size > width - MINIMAL_CONTENT_VIEWPORT_WIDTH) return width - MINIMAL_CONTENT_VIEWPORT_WIDTH
if (size < MINIMAL_WIDTH) return MINIMAL_WIDTH
return size
}
const sizeVariableMountPoint = DOMHelper.gitakoDescriptionTarget
export function SideBarBodyWrapper({
className,
children,
onLeave,
}: React.PropsWithChildren<Props>) {
const configContext = useConfigs()
const { sideBarWidth: baseSize } = configContext.value
const [size, setSize] = React.useState(baseSize)
// TODO: fix sidebar flash on first load
// TODO: verify if it is required
// React.useEffect(() => {
// setSize(baseSize)
// }, [baseSize])
const heightForSafari = useConditionalHook(
() => detectBrowser() === 'Safari',
() => useWindowSize().height, // eslint-disable-line react-hooks/rules-of-hooks
)
const { width } = useWindowSize()
React.useEffect(() => {
const safeSize = getSafeSize(size, width)
if (safeSize !== size) setSize(safeSize)
}, [width, size])
useDebounce(() => configContext.onChange({ sideBarWidth: size }), 100, [size])
const applySizeToCSSVariables = React.useCallback((size: number) => {
setCSSVariable('--gitako-width', `${size}px`, sizeVariableMountPoint)
}, [])
// Update size using useEffect would cause delay
const onResize = React.useMemo(() => {
let sizeToApply: number
let applied = true
return ([size]: number[]) => {
// do NOT merge this with the above similar effect, side bar will jump otherwise
sizeToApply = getSafeSize(size, width)
setSize(sizeToApply)
if (applied) {
applied = false
requestAnimationFrame(() => {
applied = true
applySizeToCSSVariables(sizeToApply)
})
}
}
}, [width, applySizeToCSSVariables])
const applyLatestSizeToCSSVariables = React.useCallback(
() => applySizeToCSSVariables(size),
[applySizeToCSSVariables, size],
)
React.useLayoutEffect(applyLatestSizeToCSSVariables, [applyLatestSizeToCSSVariables])
useOnPJAXDone(applyLatestSizeToCSSVariables)
const blockLeaveRef = React.useRef(false)
const onMouseLeave = React.useCallback(
<E extends HTMLElement>(e: React.MouseEvent<E>) => {
if (blockLeaveRef.current) return
onLeave?.(e)
},
[onLeave],
)
const onResizeStateChange = React.useCallback((state: ResizeState) => {
blockLeaveRef.current = state === 'resizing'
}, [])
const dummySize: [number, number] = React.useMemo(() => [size, size], [size])
const defaultSideBarWidth = React.useMemo(() => getDefaultConfigs().sideBarWidth, [])
return (
<div
className={cx('gitako-side-bar-body-wrapper', className)}
style={{ height: heightForSafari }}
onMouseLeave={onMouseLeave}
>
{children}
{features.resize && (
<ResizeHandler
onResize={onResize}
onResetSize={() => {
setSize(defaultSideBarWidth)
applySizeToCSSVariables(defaultSideBarWidth)
}}
onResizeStateChange={onResizeStateChange}
size={dummySize}
/>
)}
</div>
)
}

View file

@ -0,0 +1,85 @@
import { useConfigs } from 'containers/ConfigsContext'
import * as React from 'react'
import { useDebounce, useWindowSize } from 'react-use'
import { getDefaultConfigs } from 'utils/config/helper'
import * as DOMHelper from 'utils/DOMHelper'
import { useOnPJAXDone } from 'utils/hooks/usePJAX'
import { ResizeHandler } from './ResizeHandler'
import { Size, Size2D } from './Size'
const MINIMAL_CONTENT_VIEWPORT_WIDTH = 100
const MINIMAL_WIDTH = 240
function getSafeWidth(width: Size, windowWidth: number) {
if (width > windowWidth - MINIMAL_CONTENT_VIEWPORT_WIDTH)
return windowWidth - MINIMAL_CONTENT_VIEWPORT_WIDTH
if (width < MINIMAL_WIDTH) return MINIMAL_WIDTH
return width
}
function useSidebarWidth() {
const configContext = useConfigs()
// size data flow:
// windowSize.width => width
// width => config.sideBarWidth
// width => --gitako-width // layout effect
// resize event => width
// resize event => --gitako-width // rAF
const [width, setWidth] = React.useState(configContext.value.sideBarWidth)
const { width: windowWidth } = useWindowSize()
React.useEffect(() => {
const safeSize = getSafeWidth(width, windowWidth)
if (safeSize !== width) setWidth(safeSize)
}, [windowWidth, width])
useDebounce(() => configContext.onChange({ sideBarWidth: width }), 100, [width])
React.useLayoutEffect(() => DOMHelper.setGitakoWidthCSSVariable(width), [width])
// Keep variable when directing from PR to repo home via meta bar
useOnPJAXDone(React.useCallback(() => DOMHelper.setGitakoWidthCSSVariable(width), [width]))
return [width, setWidth] as const
}
export function SideBarResizeHandler({
onResizeStateChange,
}: Pick<React.ComponentProps<typeof ResizeHandler>, 'onResizeStateChange'>) {
const [width, setWidth] = useSidebarWidth()
const { width: windowWidth } = useWindowSize()
const onResize = React.useMemo(() => {
let widthToApply: Size
let pending = false
return ([width]: Size2D) => {
// do NOT merge this with the above similar effect
widthToApply = width
if (!pending) {
pending = true
// Update size using useEffect would cause delay
requestAnimationFrame(() => {
pending = false
widthToApply = getSafeWidth(widthToApply, windowWidth)
DOMHelper.setGitakoWidthCSSVariable(widthToApply)
setWidth(widthToApply)
})
}
}
}, [windowWidth, setWidth])
const onResetSize = React.useCallback(
() => setWidth(getDefaultConfigs().sideBarWidth),
[setWidth],
)
const dummySize: Size2D = React.useMemo(() => [width, 0], [width])
return (
<ResizeHandler
onResize={onResize}
onResetSize={onResetSize}
onResizeStateChange={onResizeStateChange}
size={dummySize}
/>
)
}

2
src/components/Size.tsx Normal file
View file

@ -0,0 +1,2 @@
export type Size = number;
export type Size2D = [Size, Size];

View file

@ -41,7 +41,7 @@ export function ToggleShowButton({ error, className, onClick, onHover }: Props)
)
// reposition on window height change, but ignores distance change
React.useEffect(() => {
React.useLayoutEffect(() => {
if (ref.current) {
ref.current.style.top = distance + 'px'
}

View file

@ -467,7 +467,7 @@ html[data-with-gitako-spacing='true'] {
}
}
.close-side-bar-button-position {
.side-bar-position-controls {
float: right;
z-index: 1; // prevent being covered by following elements
}

View file

@ -166,6 +166,10 @@ export function setCSSVariable(name: string, value: string | undefined, element:
else element.style.setProperty(name, value)
}
export const setGitakoWidthCSSVariable = (size: number) => {
setCSSVariable('--gitako-width', `${size}px`, gitakoDescriptionTarget)
}
export function formatID(id: string) {
return `#${id}`
}

View file

@ -1,4 +1,5 @@
import { useLayoutEffect } from 'react'
import { setCSSVariable } from 'utils/DOMHelper'
export function useCSSVariable(
name: string,
@ -6,6 +7,6 @@ export function useCSSVariable(
element: HTMLElement = document.documentElement,
) {
useLayoutEffect(() => {
element.style.setProperty(name, value)
setCSSVariable(name, value, element)
}, [name, value, element])
}

View file

@ -1,6 +1,6 @@
import * as React from 'react'
import * as features from 'utils/features'
import { Size2D } from '../../components/SideBarBodyWrapper'
import { Size2D } from "../../components/Size"
export function useElementSize<E extends HTMLElement>() {
const ref = React.useRef<E | null>(null)

View file

@ -1,5 +1,5 @@
import * as React from 'react'
import { Size2D } from '../../components/SideBarBodyWrapper'
import { Size2D } from "../../components/Size"
export type ResizeState = 'idle' | 'resizing'