about summary refs log tree commit diff
path: root/src/view/com/pager/TabBar.tsx
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-02-27 22:55:25 +0000
committerGitHub <noreply@github.com>2024-02-27 14:55:25 -0800
commitac726497a475f7492ee0269851979817b17d98c2 (patch)
tree7f35ea2ae36e171814bf483456ef975f31f6c5de /src/view/com/pager/TabBar.tsx
parent978bcc1ba9cb426c8da34a970a79a023936e3dbc (diff)
downloadvoidsky-ac726497a475f7492ee0269851979817b17d98c2.tar.zst
Show tab bar on desktop web (#2998)
* Show tabbar on desktop

* Make bottom border always 1px

* Don't hide/show navbar when switching tabs

* two rows WIP

* Top bar tweaks

* Make scroll adjustement native-only

* Add new web scroll behavior
Diffstat (limited to 'src/view/com/pager/TabBar.tsx')
-rw-r--r--src/view/com/pager/TabBar.tsx78
1 files changed, 73 insertions, 5 deletions
diff --git a/src/view/com/pager/TabBar.tsx b/src/view/com/pager/TabBar.tsx
index 3204bb23e..ff8acd60c 100644
--- a/src/view/com/pager/TabBar.tsx
+++ b/src/view/com/pager/TabBar.tsx
@@ -5,6 +5,7 @@ import {PressableWithHover} from '../util/PressableWithHover'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {DraggableScrollView} from './DraggableScrollView'
+import {isNative} from '#/platform/detection'
 
 export interface TabBarProps {
   testID?: string
@@ -15,6 +16,10 @@ export interface TabBarProps {
   onPressSelected?: (index: number) => void
 }
 
+// How much of the previous/next item we're showing
+// to give the user a hint there's more to scroll.
+const OFFSCREEN_ITEM_WIDTH = 20
+
 export function TabBar({
   testID,
   selectedPage,
@@ -25,6 +30,7 @@ export function TabBar({
 }: TabBarProps) {
   const pal = usePalette('default')
   const scrollElRef = useRef<ScrollView>(null)
+  const itemRefs = useRef<Array<Element>>([])
   const [itemXs, setItemXs] = useState<number[]>([])
   const indicatorStyle = useMemo(
     () => ({borderBottomColor: indicatorColor || pal.colors.link}),
@@ -33,12 +39,58 @@ export function TabBar({
   const {isDesktop, isTablet} = useWebMediaQueries()
   const styles = isDesktop || isTablet ? desktopStyles : mobileStyles
 
-  // scrolls to the selected item when the page changes
   useEffect(() => {
-    scrollElRef.current?.scrollTo({
-      x:
-        (itemXs[selectedPage] || 0) - styles.contentContainer.paddingHorizontal,
-    })
+    if (isNative) {
+      // On native, the primary interaction is swiping.
+      // We adjust the scroll little by little on every tab change.
+      // Scroll into view but keep the end of the previous item visible.
+      let x = itemXs[selectedPage] || 0
+      x = Math.max(0, x - OFFSCREEN_ITEM_WIDTH)
+      scrollElRef.current?.scrollTo({x})
+    } else {
+      // On the web, the primary interaction is tapping.
+      // Scrolling under tap feels disorienting so only adjust the scroll offset
+      // when tapping on an item out of view--and we adjust by almost an entire page.
+      const parent = scrollElRef?.current?.getScrollableNode?.()
+      if (!parent) {
+        return
+      }
+      const parentRect = parent.getBoundingClientRect()
+      if (!parentRect) {
+        return
+      }
+      const {
+        left: parentLeft,
+        right: parentRight,
+        width: parentWidth,
+      } = parentRect
+      const child = itemRefs.current[selectedPage]
+      if (!child) {
+        return
+      }
+      const childRect = child.getBoundingClientRect?.()
+      if (!childRect) {
+        return
+      }
+      const {left: childLeft, right: childRight, width: childWidth} = childRect
+      let dx = 0
+      if (childRight >= parentRight) {
+        dx += childRight - parentRight
+        dx += parentWidth - childWidth - OFFSCREEN_ITEM_WIDTH
+      } else if (childLeft <= parentLeft) {
+        dx -= parentLeft - childLeft
+        dx -= parentWidth - childWidth - OFFSCREEN_ITEM_WIDTH
+      }
+      let x = parent.scrollLeft + dx
+      x = Math.max(0, x)
+      x = Math.min(x, parent.scrollWidth - parentWidth)
+      if (dx !== 0) {
+        parent.scroll({
+          left: x,
+          behavior: 'smooth',
+        })
+      }
+    }
   }, [scrollElRef, itemXs, selectedPage, styles])
 
   const onPressItem = useCallback(
@@ -78,6 +130,7 @@ export function TabBar({
             <PressableWithHover
               testID={`${testID}-selector-${i}`}
               key={`${item}-${i}`}
+              ref={node => (itemRefs.current[i] = node)}
               onLayout={e => onItemLayout(e, i)}
               style={styles.item}
               hoverStyle={pal.viewLight}
@@ -94,6 +147,7 @@ export function TabBar({
           )
         })}
       </DraggableScrollView>
+      <View style={[pal.border, styles.outerBottomBorder]} />
     </View>
   )
 }
@@ -117,6 +171,13 @@ const desktopStyles = StyleSheet.create({
     borderBottomWidth: 3,
     borderBottomColor: 'transparent',
   },
+  outerBottomBorder: {
+    position: 'absolute',
+    left: 0,
+    right: 0,
+    bottom: -1,
+    borderBottomWidth: 1,
+  },
 })
 
 const mobileStyles = StyleSheet.create({
@@ -137,4 +198,11 @@ const mobileStyles = StyleSheet.create({
     borderBottomWidth: 3,
     borderBottomColor: 'transparent',
   },
+  outerBottomBorder: {
+    position: 'absolute',
+    left: 0,
+    right: 0,
+    bottom: -1,
+    borderBottomWidth: 1,
+  },
 })