[🐴] Finalize web message screen (#3868)

* add `onStartReached` to web list

* fix `rootMargin`

* Add `contain`, handle scroll events

* improve types, fix typo

* simplify

* adjust `scrollToTop` and `scrollToOffset` to support `contain`, add `scrollToEnd`

* rename `handleWindowScroll` to `handleScroll`

* support basic `maintainVisibleContentPosition`

* rename `contain` to `containWeb`

* remove unnecessary `flex: 1`

* add missing props

* add root prop to `Visibility`

* add root prop to `Visibility`

* revert adding `maintainVisibleContentPosition`

* remove unnecessary wrapper

* add style

* oops

* maintain position for web

* always apply `flex: 1` to styles when contained

* add a contained list to storybook

* make `onScroll` a worklet in storybook

* revert test code

* remove unnecessary `flex: 1`
This commit is contained in:
Hailey 2024-05-06 08:48:08 -07:00 committed by GitHub
parent bc07019911
commit ae7626ce6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 47 additions and 35 deletions

View file

@ -94,6 +94,9 @@ export function MessagesList() {
// the bottom. // the bottom.
const isAtBottom = useSharedValue(true) const isAtBottom = useSharedValue(true)
// This will be used on web to assist in determing if we need to maintain the content offset
const isAtTop = useSharedValue(true)
// Used to keep track of the current content height. We'll need this in `onScroll` so we know when to start allowing // Used to keep track of the current content height. We'll need this in `onScroll` so we know when to start allowing
// onStartReached to fire. // onStartReached to fire.
const contentHeight = useSharedValue(0) const contentHeight = useSharedValue(0)
@ -116,6 +119,15 @@ export function MessagesList() {
// we will not scroll whenever new items get prepended to the top. // we will not scroll whenever new items get prepended to the top.
const onContentSizeChange = useCallback( const onContentSizeChange = useCallback(
(_: number, height: number) => { (_: number, height: number) => {
// Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the
// previous offset whenever we add new content to the previous offset whenever we add new content to the list.
if (isWeb && isAtTop.value && hasInitiallyScrolled) {
flatListRef.current?.scrollToOffset({
animated: false,
offset: height - contentHeight.value,
})
}
contentHeight.value = height contentHeight.value = height
// This number _must_ be the height of the MaybeLoader component // This number _must_ be the height of the MaybeLoader component
@ -133,6 +145,7 @@ export function MessagesList() {
contentHeight, contentHeight,
hasInitiallyScrolled, hasInitiallyScrolled,
isAtBottom.value, isAtBottom.value,
isAtTop.value,
isMomentumScrolling, isMomentumScrolling,
], ],
) )
@ -164,6 +177,7 @@ export function MessagesList() {
// Most apps have a little bit of space the user can scroll past while still automatically scrolling ot the bottom // Most apps have a little bit of space the user can scroll past while still automatically scrolling ot the bottom
// when a new message is added, hence the 100 pixel offset // when a new message is added, hence the 100 pixel offset
isAtBottom.value = e.contentSize.height - 100 < bottomOffset isAtBottom.value = e.contentSize.height - 100 < bottomOffset
isAtTop.value = e.contentOffset.y <= 1
// This number _must_ be the height of the MaybeLoader component. // This number _must_ be the height of the MaybeLoader component.
// We don't check for zero, because the `MaybeLoader` component is always present, even when not visible, which // We don't check for zero, because the `MaybeLoader` component is always present, even when not visible, which
@ -172,7 +186,7 @@ export function MessagesList() {
runOnJS(setHasInitiallyScrolled)(true) runOnJS(setHasInitiallyScrolled)(true)
} }
}, },
[contentHeight.value, hasInitiallyScrolled, isAtBottom], [contentHeight.value, hasInitiallyScrolled, isAtBottom, isAtTop],
) )
const onMomentumEnd = React.useCallback(() => { const onMomentumEnd = React.useCallback(() => {
@ -211,9 +225,6 @@ export function MessagesList() {
keyboardVerticalOffset={isIOS ? topInset : 0} keyboardVerticalOffset={isIOS ? topInset : 0}
behavior="padding" behavior="padding"
contentContainerStyle={a.flex_1}> contentContainerStyle={a.flex_1}>
{/* This view keeps the scroll bar and content within the CenterView on web, otherwise the entire window would scroll */}
{/* @ts-expect-error web only */}
<View style={[a.flex_1, isWeb && {'overflow-y': 'scroll'}]}>
{/* Custom scroll provider so that we can use the `onScroll` event in our custom List implementation */} {/* Custom scroll provider so that we can use the `onScroll` event in our custom List implementation */}
<ScrollProvider onScroll={onScroll} onMomentumEnd={onMomentumEnd}> <ScrollProvider onScroll={onScroll} onMomentumEnd={onMomentumEnd}>
<List <List
@ -229,6 +240,7 @@ export function MessagesList() {
maintainVisibleContentPosition={{ maintainVisibleContentPosition={{
minIndexForVisible: 1, minIndexForVisible: 1,
}} }}
containWeb={true}
contentContainerStyle={{paddingHorizontal: 10}} contentContainerStyle={{paddingHorizontal: 10}}
removeClippedSubviews={false} removeClippedSubviews={false}
onContentSizeChange={onContentSizeChange} onContentSizeChange={onContentSizeChange}
@ -244,7 +256,6 @@ export function MessagesList() {
} }
/> />
</ScrollProvider> </ScrollProvider>
</View>
<MessageInput onSendMessage={onSendMessage} scrollToEnd={scrollToEnd} /> <MessageInput onSendMessage={onSendMessage} scrollToEnd={scrollToEnd} />
</KeyboardAvoidingView> </KeyboardAvoidingView>
) )

View file

@ -143,6 +143,7 @@ function ListImpl<ItemT>(
scrollToTop() { scrollToTop() {
getScrollableNode()?.scrollTo({top: 0}) getScrollableNode()?.scrollTo({top: 0})
}, },
scrollToOffset({ scrollToOffset({
animated, animated,
offset, offset,