about summary refs log tree commit diff
path: root/src/view/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com')
-rw-r--r--src/view/com/feeds/FeedPage.tsx210
-rw-r--r--src/view/com/pager/FeedsTabBar.web.tsx28
-rw-r--r--src/view/com/pager/FeedsTabBarMobile.tsx36
-rw-r--r--src/view/com/pager/TabBar.tsx1
-rw-r--r--src/view/com/posts/Feed.tsx4
-rw-r--r--src/view/com/profile/ProfileHeaderSuggestedFollows.tsx4
-rw-r--r--src/view/com/util/ViewHeader.tsx34
-rw-r--r--src/view/com/util/fab/FABInner.tsx51
-rw-r--r--src/view/com/util/load-latest/LoadLatestBtn.tsx31
9 files changed, 275 insertions, 124 deletions
diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx
new file mode 100644
index 000000000..725106d59
--- /dev/null
+++ b/src/view/com/feeds/FeedPage.tsx
@@ -0,0 +1,210 @@
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
+import {useIsFocused} from '@react-navigation/native'
+import {useAnalytics} from '@segment/analytics-react-native'
+import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
+import {ComposeIcon2} from 'lib/icons'
+import {colors, s} from 'lib/styles'
+import {observer} from 'mobx-react-lite'
+import React from 'react'
+import {FlatList, View} from 'react-native'
+import {useStores} from 'state/index'
+import {PostsFeedModel} from 'state/models/feeds/posts'
+import {useHeaderOffset, POLL_FREQ} from 'view/screens/Home'
+import {Feed} from '../posts/Feed'
+import {TextLink} from '../util/Link'
+import {FAB} from '../util/fab/FAB'
+import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn'
+import useAppState from 'react-native-appstate-hook'
+
+export const FeedPage = observer(function FeedPageImpl({
+  testID,
+  isPageFocused,
+  feed,
+  renderEmptyState,
+  renderEndOfFeed,
+}: {
+  testID?: string
+  feed: PostsFeedModel
+  isPageFocused: boolean
+  renderEmptyState: () => JSX.Element
+  renderEndOfFeed?: () => JSX.Element
+}) {
+  const store = useStores()
+  const pal = usePalette('default')
+  const {isDesktop} = useWebMediaQueries()
+  const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll(store)
+  const {screen, track} = useAnalytics()
+  const headerOffset = useHeaderOffset()
+  const scrollElRef = React.useRef<FlatList>(null)
+  const {appState} = useAppState({
+    onForeground: () => doPoll(true),
+  })
+  const isScreenFocused = useIsFocused()
+  const hasNew = feed.hasNewLatest && !feed.isRefreshing
+
+  React.useEffect(() => {
+    // called on first load
+    if (!feed.hasLoaded && isPageFocused) {
+      feed.setup()
+    }
+  }, [isPageFocused, feed])
+
+  const doPoll = React.useCallback(
+    (knownActive = false) => {
+      if (
+        (!knownActive && appState !== 'active') ||
+        !isScreenFocused ||
+        !isPageFocused
+      ) {
+        return
+      }
+      if (feed.isLoading) {
+        return
+      }
+      store.log.debug('HomeScreen: Polling for new posts')
+      feed.checkForLatest()
+    },
+    [appState, isScreenFocused, isPageFocused, store, feed],
+  )
+
+  const scrollToTop = React.useCallback(() => {
+    scrollElRef.current?.scrollToOffset({offset: -headerOffset})
+    resetMainScroll()
+  }, [headerOffset, resetMainScroll])
+
+  const onSoftReset = React.useCallback(() => {
+    if (isPageFocused) {
+      scrollToTop()
+      feed.refresh()
+    }
+  }, [isPageFocused, scrollToTop, feed])
+
+  // fires when page within screen is activated/deactivated
+  // - check for latest
+  React.useEffect(() => {
+    if (!isPageFocused || !isScreenFocused) {
+      return
+    }
+
+    const softResetSub = store.onScreenSoftReset(onSoftReset)
+    const feedCleanup = feed.registerListeners()
+    const pollInterval = setInterval(doPoll, POLL_FREQ)
+
+    screen('Feed')
+    store.log.debug('HomeScreen: Updating feed')
+    feed.checkForLatest()
+
+    return () => {
+      clearInterval(pollInterval)
+      softResetSub.remove()
+      feedCleanup()
+    }
+  }, [store, doPoll, onSoftReset, screen, feed, isPageFocused, isScreenFocused])
+
+  const onPressCompose = React.useCallback(() => {
+    track('HomeScreen:PressCompose')
+    store.shell.openComposer({})
+  }, [store, track])
+
+  const onPressTryAgain = React.useCallback(() => {
+    feed.refresh()
+  }, [feed])
+
+  const onPressLoadLatest = React.useCallback(() => {
+    scrollToTop()
+    feed.refresh()
+  }, [feed, scrollToTop])
+
+  const ListHeaderComponent = React.useCallback(() => {
+    if (isDesktop) {
+      return (
+        <View
+          style={[
+            pal.view,
+            {
+              flexDirection: 'row',
+              alignItems: 'center',
+              justifyContent: 'space-between',
+              paddingHorizontal: 18,
+              paddingVertical: 12,
+            },
+          ]}>
+          <TextLink
+            type="title-lg"
+            href="/"
+            style={[pal.text, {fontWeight: 'bold'}]}
+            text={
+              <>
+                {store.session.isSandbox ? 'SANDBOX' : 'Bluesky'}{' '}
+                {hasNew && (
+                  <View
+                    style={{
+                      top: -8,
+                      backgroundColor: colors.blue3,
+                      width: 8,
+                      height: 8,
+                      borderRadius: 4,
+                    }}
+                  />
+                )}
+              </>
+            }
+            onPress={() => store.emitScreenSoftReset()}
+          />
+          <TextLink
+            type="title-lg"
+            href="/settings/home-feed"
+            style={{fontWeight: 'bold'}}
+            accessibilityLabel="Feed Preferences"
+            accessibilityHint=""
+            text={
+              <FontAwesomeIcon
+                icon="sliders"
+                style={pal.textLight as FontAwesomeIconStyle}
+              />
+            }
+          />
+        </View>
+      )
+    }
+    return <></>
+  }, [isDesktop, pal, store, hasNew])
+
+  return (
+    <View testID={testID} style={s.h100pct}>
+      <Feed
+        testID={testID ? `${testID}-feed` : undefined}
+        key="default"
+        feed={feed}
+        scrollElRef={scrollElRef}
+        onPressTryAgain={onPressTryAgain}
+        onScroll={onMainScroll}
+        scrollEventThrottle={100}
+        renderEmptyState={renderEmptyState}
+        renderEndOfFeed={renderEndOfFeed}
+        ListHeaderComponent={ListHeaderComponent}
+        headerOffset={headerOffset}
+      />
+      {(isScrolledDown || hasNew) && (
+        <LoadLatestBtn
+          onPress={onPressLoadLatest}
+          label="Load new posts"
+          showIndicator={hasNew}
+        />
+      )}
+      <FAB
+        testID="composeFAB"
+        onPress={onPressCompose}
+        icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
+        accessibilityRole="button"
+        accessibilityLabel="New post"
+        accessibilityHint=""
+      />
+    </View>
+  )
+})
diff --git a/src/view/com/pager/FeedsTabBar.web.tsx b/src/view/com/pager/FeedsTabBar.web.tsx
index 02aa623cc..dc91bd296 100644
--- a/src/view/com/pager/FeedsTabBar.web.tsx
+++ b/src/view/com/pager/FeedsTabBar.web.tsx
@@ -1,13 +1,14 @@
 import React, {useMemo} from 'react'
-import {Animated, StyleSheet} from 'react-native'
+import {StyleSheet} from 'react-native'
+import Animated from 'react-native-reanimated'
 import {observer} from 'mobx-react-lite'
 import {TabBar} from 'view/com/pager/TabBar'
 import {RenderTabBarFnProps} from 'view/com/pager/Pager'
 import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {FeedsTabBar as FeedsTabBarMobile} from './FeedsTabBarMobile'
+import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
 
 export const FeedsTabBar = observer(function FeedsTabBarImpl(
   props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
@@ -31,26 +32,12 @@ const FeedsTabBarTablet = observer(function FeedsTabBarTabletImpl(
     [store.me.savedFeeds.pinnedFeedNames],
   )
   const pal = usePalette('default')
-  const interp = useAnimatedValue(0)
-
-  React.useEffect(() => {
-    Animated.timing(interp, {
-      toValue: store.shell.minimalShellMode ? 1 : 0,
-      duration: 100,
-      useNativeDriver: true,
-      isInteraction: false,
-    }).start()
-  }, [interp, store.shell.minimalShellMode])
-  const transform = {
-    transform: [
-      {translateX: '-50%'},
-      {translateY: Animated.multiply(interp, -100)},
-    ],
-  }
+  const {headerMinimalShellTransform} = useMinimalShellMode()
 
   return (
     // @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
-    <Animated.View style={[pal.view, styles.tabBar, transform]}>
+    <Animated.View
+      style={[pal.view, styles.tabBar, headerMinimalShellTransform]}>
       <TabBar
         key={items.join(',')}
         {...props}
@@ -65,7 +52,8 @@ const styles = StyleSheet.create({
   tabBar: {
     position: 'absolute',
     zIndex: 1,
-    left: '50%',
+    // @ts-ignore Web only -prf
+    left: 'calc(50% - 299px)',
     width: 598,
     top: 0,
     flexDirection: 'row',
diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/pager/FeedsTabBarMobile.tsx
index 7924666e5..d8579badc 100644
--- a/src/view/com/pager/FeedsTabBarMobile.tsx
+++ b/src/view/com/pager/FeedsTabBarMobile.tsx
@@ -1,12 +1,10 @@
 import React, {useMemo} from 'react'
-import {Animated, StyleSheet, TouchableOpacity, View} from 'react-native'
+import {StyleSheet, TouchableOpacity, View} from 'react-native'
 import {observer} from 'mobx-react-lite'
-import {autorun} from 'mobx'
 import {TabBar} from 'view/com/pager/TabBar'
 import {RenderTabBarFnProps} from 'view/com/pager/Pager'
 import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
 import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
 import {Link} from '../util/Link'
 import {Text} from '../util/text/Text'
@@ -14,30 +12,17 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
 import {s} from 'lib/styles'
 import {HITSLOP_10} from 'lib/constants'
+import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
+import Animated from 'react-native-reanimated'
 
 export const FeedsTabBar = observer(function FeedsTabBarImpl(
   props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
 ) {
   const store = useStores()
   const pal = usePalette('default')
-  const interp = useAnimatedValue(0)
-
-  React.useEffect(() => {
-    return autorun(() => {
-      Animated.timing(interp, {
-        toValue: store.shell.minimalShellMode ? 1 : 0,
-        duration: 150,
-        useNativeDriver: true,
-        isInteraction: false,
-      }).start()
-    })
-  }, [interp, store])
-  const transform = {
-    opacity: Animated.subtract(1, interp),
-    transform: [{translateY: Animated.multiply(interp, -50)}],
-  }
 
   const brandBlue = useColorSchemeStyle(s.brandBlue, s.blue3)
+  const {headerMinimalShellTransform} = useMinimalShellMode()
 
   const onPressAvi = React.useCallback(() => {
     store.shell.openDrawer()
@@ -48,13 +33,17 @@ export const FeedsTabBar = observer(function FeedsTabBarImpl(
     [store.me.savedFeeds.pinnedFeedNames],
   )
 
+  const tabBarKey = useMemo(() => {
+    return items.join(',')
+  }, [items])
+
   return (
     <Animated.View
       style={[
         pal.view,
         pal.border,
         styles.tabBar,
-        transform,
+        headerMinimalShellTransform,
         store.shell.minimalShellMode && styles.disabled,
       ]}>
       <View style={[pal.view, styles.topBar]}>
@@ -92,8 +81,11 @@ export const FeedsTabBar = observer(function FeedsTabBarImpl(
         </View>
       </View>
       <TabBar
-        key={items.join(',')}
-        {...props}
+        key={tabBarKey}
+        onPressSelected={props.onPressSelected}
+        selectedPage={props.selectedPage}
+        onSelect={props.onSelect}
+        testID={props.testID}
         items={items}
         indicatorColor={pal.colors.link}
       />
diff --git a/src/view/com/pager/TabBar.tsx b/src/view/com/pager/TabBar.tsx
index 319d28f95..8614bdf64 100644
--- a/src/view/com/pager/TabBar.tsx
+++ b/src/view/com/pager/TabBar.tsx
@@ -64,6 +64,7 @@ export function TabBar({
   )
 
   const styles = isDesktop || isTablet ? desktopStyles : mobileStyles
+
   return (
     <View testID={testID} style={[pal.view, styles.outer]}>
       <DraggableScrollView
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index 0de769aab..74883f82a 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -96,7 +96,7 @@ export const Feed = observer(function Feed({
   }, [feed, track, setIsRefreshing])
 
   const onEndReached = React.useCallback(async () => {
-    if (!feed.hasLoaded) return
+    if (!feed.hasLoaded || !feed.hasMore) return
 
     track('Feed:onEndReached')
     try {
@@ -178,7 +178,7 @@ export const Feed = observer(function Feed({
         scrollEventThrottle={scrollEventThrottle}
         indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
         onEndReached={onEndReached}
-        onEndReachedThreshold={0.6}
+        onEndReachedThreshold={2}
         removeClippedSubviews={true}
         contentOffset={{x: 0, y: headerOffset * -1}}
         extraData={extraData}
diff --git a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx
index 41e4022d5..c5b187fb3 100644
--- a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx
+++ b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx
@@ -223,9 +223,9 @@ const SuggestedFollow = observer(function SuggestedFollowImpl({
 
   const onPress = React.useCallback(async () => {
     try {
-      const {following} = await toggle()
+      const {following: isFollowing} = await toggle()
 
-      if (following) {
+      if (isFollowing) {
         track('ProfileHeader:SuggestedFollowFollowed')
       }
     } catch (e: any) {
diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx
index 3a34777ab..ec459b4eb 100644
--- a/src/view/com/util/ViewHeader.tsx
+++ b/src/view/com/util/ViewHeader.tsx
@@ -1,17 +1,17 @@
 import React from 'react'
 import {observer} from 'mobx-react-lite'
-import {autorun} from 'mobx'
-import {Animated, StyleSheet, TouchableOpacity, View} from 'react-native'
+import {StyleSheet, TouchableOpacity, View} from 'react-native'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {useNavigation} from '@react-navigation/native'
 import {CenteredView} from './Views'
 import {Text} from './text/Text'
 import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {NavigationProp} from 'lib/routes/types'
+import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
+import Animated from 'react-native-reanimated'
 
 const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
 
@@ -150,32 +150,8 @@ const Container = observer(function ContainerImpl({
   hideOnScroll: boolean
   showBorder?: boolean
 }) {
-  const store = useStores()
   const pal = usePalette('default')
-  const interp = useAnimatedValue(0)
-
-  React.useEffect(() => {
-    return autorun(() => {
-      if (store.shell.minimalShellMode) {
-        Animated.timing(interp, {
-          toValue: 1,
-          duration: 100,
-          useNativeDriver: true,
-          isInteraction: false,
-        }).start()
-      } else {
-        Animated.timing(interp, {
-          toValue: 0,
-          duration: 100,
-          useNativeDriver: true,
-          isInteraction: false,
-        }).start()
-      }
-    })
-  }, [interp, store])
-  const transform = {
-    transform: [{translateY: Animated.multiply(interp, -100)}],
-  }
+  const {headerMinimalShellTransform} = useMinimalShellMode()
 
   if (!hideOnScroll) {
     return (
@@ -198,7 +174,7 @@ const Container = observer(function ContainerImpl({
         styles.headerFloating,
         pal.view,
         pal.border,
-        transform,
+        headerMinimalShellTransform,
         showBorder && styles.border,
       ]}>
       {children}
diff --git a/src/view/com/util/fab/FABInner.tsx b/src/view/com/util/fab/FABInner.tsx
index 97eeba358..5b1d5d888 100644
--- a/src/view/com/util/fab/FABInner.tsx
+++ b/src/view/com/util/fab/FABInner.tsx
@@ -1,14 +1,13 @@
 import React, {ComponentProps} from 'react'
 import {observer} from 'mobx-react-lite'
-import {autorun} from 'mobx'
-import {Animated, StyleSheet, TouchableWithoutFeedback} from 'react-native'
+import {StyleSheet, TouchableWithoutFeedback} from 'react-native'
 import LinearGradient from 'react-native-linear-gradient'
 import {gradients} from 'lib/styles'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
-import {useStores} from 'state/index'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import {clamp} from 'lib/numbers'
+import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
+import Animated from 'react-native-reanimated'
 
 export interface FABProps
   extends ComponentProps<typeof TouchableWithoutFeedback> {
@@ -22,30 +21,30 @@ export const FABInner = observer(function FABInnerImpl({
   ...props
 }: FABProps) {
   const insets = useSafeAreaInsets()
-  const {isTablet} = useWebMediaQueries()
-  const store = useStores()
-  const interp = useAnimatedValue(0)
-  React.useEffect(() => {
-    return autorun(() => {
-      Animated.timing(interp, {
-        toValue: store.shell.minimalShellMode ? 0 : 1,
-        duration: 100,
-        useNativeDriver: true,
-        isInteraction: false,
-      }).start()
-    })
-  }, [interp, store])
-  const transform = isTablet
-    ? undefined
-    : {
-        transform: [{translateY: Animated.multiply(interp, -44)}],
-      }
-  const size = isTablet ? styles.sizeLarge : styles.sizeRegular
-  const right = isTablet ? 50 : 24
-  const bottom = isTablet ? 50 : clamp(insets.bottom, 15, 60) + 15
+  const {isMobile, isTablet} = useWebMediaQueries()
+  const {fabMinimalShellTransform} = useMinimalShellMode()
+
+  const size = React.useMemo(() => {
+    return isTablet ? styles.sizeLarge : styles.sizeRegular
+  }, [isTablet])
+  const tabletSpacing = React.useMemo(() => {
+    return isTablet
+      ? {right: 50, bottom: 50}
+      : {
+          right: 24,
+          bottom: clamp(insets.bottom, 15, 60) + 15,
+        }
+  }, [insets.bottom, isTablet])
+
   return (
     <TouchableWithoutFeedback testID={testID} {...props}>
-      <Animated.View style={[styles.outer, size, {right, bottom}, transform]}>
+      <Animated.View
+        style={[
+          styles.outer,
+          size,
+          tabletSpacing,
+          isMobile && fabMinimalShellTransform,
+        ]}>
         <LinearGradient
           colors={[gradients.blueLight.start, gradients.blueLight.end]}
           start={{x: 0, y: 0}}
diff --git a/src/view/com/util/load-latest/LoadLatestBtn.tsx b/src/view/com/util/load-latest/LoadLatestBtn.tsx
index 57c3baa5b..b16a42396 100644
--- a/src/view/com/util/load-latest/LoadLatestBtn.tsx
+++ b/src/view/com/util/load-latest/LoadLatestBtn.tsx
@@ -2,16 +2,12 @@ import React from 'react'
 import {StyleSheet, TouchableOpacity, View} from 'react-native'
 import {observer} from 'mobx-react-lite'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {useSafeAreaInsets} from 'react-native-safe-area-context'
-import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {colors} from 'lib/styles'
 import {HITSLOP_20} from 'lib/constants'
-import {isWeb} from 'platform/detection'
-import {clamp} from 'lib/numbers'
-import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated'
-
+import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
+import Animated from 'react-native-reanimated'
 const AnimatedTouchableOpacity =
   Animated.createAnimatedComponent(TouchableOpacity)
 
@@ -23,20 +19,11 @@ export const LoadLatestBtn = observer(function LoadLatestBtnImpl({
   onPress: () => void
   label: string
   showIndicator: boolean
-  minimalShellMode?: boolean // NOTE not used on mobile -prf
 }) {
-  const store = useStores()
   const pal = usePalette('default')
-  const {isDesktop, isTablet} = useWebMediaQueries()
-  const safeAreaInsets = useSafeAreaInsets()
-  const minMode = store.shell.minimalShellMode
-  const bottom = isTablet
-    ? 50
-    : (minMode || isDesktop ? 16 : 60) +
-      (isWeb ? 20 : clamp(safeAreaInsets.bottom, 15, 60))
-  const animatedStyle = useAnimatedStyle(() => ({
-    bottom: withTiming(bottom, {duration: 150}),
-  }))
+  const {isDesktop, isTablet, isMobile} = useWebMediaQueries()
+  const {fabMinimalShellTransform} = useMinimalShellMode()
+
   return (
     <AnimatedTouchableOpacity
       style={[
@@ -45,7 +32,7 @@ export const LoadLatestBtn = observer(function LoadLatestBtnImpl({
         isTablet && styles.loadLatestTablet,
         pal.borderDark,
         pal.view,
-        animatedStyle,
+        isMobile && fabMinimalShellTransform,
       ]}
       onPress={onPress}
       hitSlop={HITSLOP_20}
@@ -73,13 +60,11 @@ const styles = StyleSheet.create({
   },
   loadLatestTablet: {
     // @ts-ignore web only
-    left: '50vw',
-    transform: [{translateX: -282}],
+    left: 'calc(50vw - 282px)',
   },
   loadLatestDesktop: {
     // @ts-ignore web only
-    left: '50vw',
-    transform: [{translateX: -382}],
+    left: 'calc(50vw - 382px)',
   },
   indicator: {
     position: 'absolute',