mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
refactor: deprecate SidebarBodyWrapper
This commit is contained in:
parent
0e96aeb26b
commit
8fb1eb924c
11 changed files with 127 additions and 140 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
85
src/components/SideBarResizeHandler.tsx
Normal file
85
src/components/SideBarResizeHandler.tsx
Normal 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
2
src/components/Size.tsx
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export type Size = number;
|
||||
export type Size2D = [Size, Size];
|
||||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react'
|
||||
import { Size2D } from '../../components/SideBarBodyWrapper'
|
||||
import { Size2D } from "../../components/Size"
|
||||
|
||||
export type ResizeState = 'idle' | 'resizing'
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue