Toggle minimal shell on any scroll for web (#2499)

zio/stable
dan 2024-01-11 18:44:56 +00:00 committed by GitHub
parent 3662259c5b
commit b147f7ae8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 47 additions and 42 deletions

View File

@ -3,9 +3,11 @@ import {ScrollProvider} from '#/lib/ScrollContext'
import {NativeScrollEvent} from 'react-native' import {NativeScrollEvent} from 'react-native'
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
import {useShellLayout} from '#/state/shell/shell-layout' import {useShellLayout} from '#/state/shell/shell-layout'
import {isWeb} from 'platform/detection' import {isNative} from 'platform/detection'
import {useSharedValue, interpolate} from 'react-native-reanimated' import {useSharedValue, interpolate} from 'react-native-reanimated'
const WEB_HIDE_SHELL_THRESHOLD = 200
function clamp(num: number, min: number, max: number) { function clamp(num: number, min: number, max: number) {
'worklet' 'worklet'
return Math.min(Math.max(num, min), max) return Math.min(Math.max(num, min), max)
@ -21,8 +23,10 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
const onBeginDrag = useCallback( const onBeginDrag = useCallback(
(e: NativeScrollEvent) => { (e: NativeScrollEvent) => {
'worklet' 'worklet'
startDragOffset.value = e.contentOffset.y if (isNative) {
startMode.value = mode.value startDragOffset.value = e.contentOffset.y
startMode.value = mode.value
}
}, },
[mode, startDragOffset, startMode], [mode, startDragOffset, startMode],
) )
@ -30,14 +34,16 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
const onEndDrag = useCallback( const onEndDrag = useCallback(
(e: NativeScrollEvent) => { (e: NativeScrollEvent) => {
'worklet' 'worklet'
startDragOffset.value = null if (isNative) {
startMode.value = null startDragOffset.value = null
if (e.contentOffset.y < headerHeight.value / 2) { startMode.value = null
// If we're close to the top, show the shell. if (e.contentOffset.y < headerHeight.value / 2) {
setMode(false) // If we're close to the top, show the shell.
} else { setMode(false)
// Snap to whichever state is the closest. } else {
setMode(Math.round(mode.value) === 1) // Snap to whichever state is the closest.
setMode(Math.round(mode.value) === 1)
}
} }
}, },
[startDragOffset, startMode, setMode, mode, headerHeight], [startDragOffset, startMode, setMode, mode, headerHeight],
@ -46,41 +52,40 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
const onScroll = useCallback( const onScroll = useCallback(
(e: NativeScrollEvent) => { (e: NativeScrollEvent) => {
'worklet' 'worklet'
if (startDragOffset.value === null || startMode.value === null) { if (isNative) {
if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) { if (startDragOffset.value === null || startMode.value === null) {
// If we're close enough to the top, always show the shell. if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) {
// Even if we're not dragging. // If we're close enough to the top, always show the shell.
setMode(false) // Even if we're not dragging.
setMode(false)
}
return return
} }
if (isWeb) {
// On the web, there is no concept of "starting" the drag.
// When we get the first scroll event, we consider that the start.
startDragOffset.value = e.contentOffset.y
startMode.value = mode.value
}
return
}
// The "mode" value is always between 0 and 1. // The "mode" value is always between 0 and 1.
// Figure out how much to move it based on the current dragged distance. // Figure out how much to move it based on the current dragged distance.
const dy = e.contentOffset.y - startDragOffset.value const dy = e.contentOffset.y - startDragOffset.value
const dProgress = interpolate( const dProgress = interpolate(
dy, dy,
[-headerHeight.value, headerHeight.value], [-headerHeight.value, headerHeight.value],
[-1, 1], [-1, 1],
) )
const newValue = clamp(startMode.value + dProgress, 0, 1) const newValue = clamp(startMode.value + dProgress, 0, 1)
if (newValue !== mode.value) { if (newValue !== mode.value) {
// Manually adjust the value. This won't be (and shouldn't be) animated. // Manually adjust the value. This won't be (and shouldn't be) animated.
mode.value = newValue mode.value = newValue
} }
if (isWeb) { } else {
// On the web, there is no concept of "starting" the drag, // On the web, we don't try to follow the drag because we don't know when it ends.
// so we don't have any specific anchor point to calculate the distance. // Instead, show/hide immediately based on whether we're scrolling up or down.
// Instead, update it continuosly along the way and diff with the last event. const dy = e.contentOffset.y - (startDragOffset.value ?? 0)
startDragOffset.value = e.contentOffset.y startDragOffset.value = e.contentOffset.y
startMode.value = mode.value
if (dy < 0 || e.contentOffset.y < WEB_HIDE_SHELL_THRESHOLD) {
setMode(false)
} else if (dy > 0) {
setMode(true)
}
} }
}, },
[headerHeight, mode, setMode, startDragOffset, startMode], [headerHeight, mode, setMode, startDragOffset, startMode],