about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorJohn Fawcett <jrf0110@gmail.com>2023-04-12 20:27:55 -0500
committerGitHub <noreply@github.com>2023-04-12 18:27:55 -0700
commitf6769b283fe83d7abbc0545077b3dca978184eed (patch)
treefab3973591fd0514d290de18f37280baca5563f9 /src
parent2fed6c402159c6084dd481ab87c5e8b034e910ac (diff)
downloadvoidsky-f6769b283fe83d7abbc0545077b3dca978184eed.tar.zst
Mobile Web (#427)
* WIP

* WIP

* Fix header offset on web

* Remove debug

* Fix web mobile feed and FAB layout

* Fix modals on mobile web

* Remove dead code

* Remove ios config that shouldnt be committed now

* Move bottom bar into its own folder

* Fix web drawer navigation and state behaviors

* Remove dark mode toggle from web drawer for now

* Fix search on mobile web

* Fix the logged out splash screen on mobile web

* Fixes to detox simulator

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/Navigation.tsx2
-rw-r--r--src/lib/hooks/useMinimalShellMode.tsx32
-rw-r--r--src/lib/hooks/useNavigationTabState.ts13
-rw-r--r--src/lib/hooks/useNavigationTabState.web.ts13
-rw-r--r--src/lib/hooks/useWebMediaQueries.tsx8
-rw-r--r--src/view/com/auth/SplashScreen.web.tsx48
-rw-r--r--src/view/com/pager/FeedsTabBar.tsx70
-rw-r--r--src/view/com/pager/FeedsTabBar.web.tsx8
-rw-r--r--src/view/com/pager/FeedsTabBarMobile.tsx69
-rw-r--r--src/view/com/util/FAB.tsx71
-rw-r--r--src/view/com/util/FAB.web.tsx8
-rw-r--r--src/view/com/util/Toast.web.tsx5
-rw-r--r--src/view/com/util/fab/FAB.tsx1
-rw-r--r--src/view/com/util/fab/FAB.web.tsx14
-rw-r--r--src/view/com/util/fab/FABInner.tsx72
-rw-r--r--src/view/screens/Home.tsx5
-rw-r--r--src/view/screens/Profile.tsx2
-rw-r--r--src/view/screens/Search.web.tsx21
-rw-r--r--src/view/screens/SearchMobile.tsx195
-rw-r--r--src/view/shell/Composer.web.tsx3
-rw-r--r--src/view/shell/Drawer.tsx68
-rw-r--r--src/view/shell/bottom-bar/BottomBar.tsx (renamed from src/view/shell/BottomBar.tsx)86
-rw-r--r--src/view/shell/bottom-bar/BottomBarStyles.tsx61
-rw-r--r--src/view/shell/bottom-bar/BottomBarWeb.tsx101
-rw-r--r--src/view/shell/index.web.tsx43
25 files changed, 705 insertions, 314 deletions
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index c10a9c249..648859f16 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -14,7 +14,7 @@ import {
   FlatNavigatorParams,
   AllNavigatorParams,
 } from 'lib/routes/types'
-import {BottomBar} from './view/shell/BottomBar'
+import {BottomBar} from './view/shell/bottom-bar/BottomBar'
 import {buildStateObject} from 'lib/routes/helpers'
 import {State, RouteParams} from 'lib/routes/types'
 import {colors} from 'lib/styles'
diff --git a/src/lib/hooks/useMinimalShellMode.tsx b/src/lib/hooks/useMinimalShellMode.tsx
new file mode 100644
index 000000000..e28a0e884
--- /dev/null
+++ b/src/lib/hooks/useMinimalShellMode.tsx
@@ -0,0 +1,32 @@
+import React from 'react'
+import {useStores} from 'state/index'
+import {Animated} from 'react-native'
+import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
+
+export function useMinimalShellMode() {
+  const store = useStores()
+  const minimalShellInterp = useAnimatedValue(0)
+  const footerMinimalShellTransform = {
+    transform: [{translateY: Animated.multiply(minimalShellInterp, 100)}],
+  }
+
+  React.useEffect(() => {
+    if (store.shell.minimalShellMode) {
+      Animated.timing(minimalShellInterp, {
+        toValue: 1,
+        duration: 100,
+        useNativeDriver: true,
+        isInteraction: false,
+      }).start()
+    } else {
+      Animated.timing(minimalShellInterp, {
+        toValue: 0,
+        duration: 100,
+        useNativeDriver: true,
+        isInteraction: false,
+      }).start()
+    }
+  }, [minimalShellInterp, store.shell.minimalShellMode])
+
+  return {footerMinimalShellTransform}
+}
diff --git a/src/lib/hooks/useNavigationTabState.ts b/src/lib/hooks/useNavigationTabState.ts
new file mode 100644
index 000000000..8afc799eb
--- /dev/null
+++ b/src/lib/hooks/useNavigationTabState.ts
@@ -0,0 +1,13 @@
+import {useNavigationState} from '@react-navigation/native'
+import {getTabState, TabState} from 'lib/routes/helpers'
+
+export function useNavigationTabState() {
+  return useNavigationState(state => {
+    return {
+      isAtHome: getTabState(state, 'Home') !== TabState.Outside,
+      isAtSearch: getTabState(state, 'Search') !== TabState.Outside,
+      isAtNotifications:
+        getTabState(state, 'Notifications') !== TabState.Outside,
+    }
+  })
+}
diff --git a/src/lib/hooks/useNavigationTabState.web.ts b/src/lib/hooks/useNavigationTabState.web.ts
new file mode 100644
index 000000000..d0173aa0f
--- /dev/null
+++ b/src/lib/hooks/useNavigationTabState.web.ts
@@ -0,0 +1,13 @@
+import {useNavigationState} from '@react-navigation/native'
+import {getCurrentRoute} from 'lib/routes/helpers'
+
+export function useNavigationTabState() {
+  return useNavigationState(state => {
+    let currentRoute = state ? getCurrentRoute(state).name : 'Home'
+    return {
+      isAtHome: currentRoute === 'Home',
+      isAtSearch: currentRoute === 'Search',
+      isAtNotifications: currentRoute === 'Notifications',
+    }
+  })
+}
diff --git a/src/lib/hooks/useWebMediaQueries.tsx b/src/lib/hooks/useWebMediaQueries.tsx
new file mode 100644
index 000000000..441585442
--- /dev/null
+++ b/src/lib/hooks/useWebMediaQueries.tsx
@@ -0,0 +1,8 @@
+import {useMediaQuery} from 'react-responsive'
+
+export function useWebMediaQueries() {
+  const isDesktop = useMediaQuery({
+    query: '(min-width: 1230px)',
+  })
+  return {isDesktop}
+}
diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx
index a20f2d49e..7fac5a8c0 100644
--- a/src/view/com/auth/SplashScreen.web.tsx
+++ b/src/view/com/auth/SplashScreen.web.tsx
@@ -7,8 +7,7 @@ import {s, colors} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useStores} from 'state/index'
 import {CenteredView} from '../util/Views'
-import {isDesktopWeb, isMobileWeb} from 'platform/detection'
-import {HelpTip} from './util/HelpTip'
+import {isMobileWeb} from 'platform/detection'
 
 export const SplashScreen = ({
   onPressSignin,
@@ -40,24 +39,22 @@ export const SplashScreen = ({
           <Text style={isMobileWeb ? styles.subtitleMobile : styles.subtitle}>
             See what's next
           </Text>
-          {isDesktopWeb && (
-            <View testID="signinOrCreateAccount" style={styles.btns}>
-              <TouchableOpacity
-                testID="createAccountButton"
-                style={[styles.btn, {backgroundColor: colors.blue3}]}
-                onPress={onPressCreateAccount}>
-                <Text style={[s.white, styles.btnLabel]}>
-                  Create a new account
-                </Text>
-              </TouchableOpacity>
-              <TouchableOpacity
-                testID="signInButton"
-                style={[styles.btn, pal.btn]}
-                onPress={onPressSignin}>
-                <Text style={[pal.text, styles.btnLabel]}>Sign in</Text>
-              </TouchableOpacity>
-            </View>
-          )}
+          <View testID="signinOrCreateAccount" style={styles.btns}>
+            <TouchableOpacity
+              testID="createAccountButton"
+              style={[styles.btn, {backgroundColor: colors.blue3}]}
+              onPress={onPressCreateAccount}>
+              <Text style={[s.white, styles.btnLabel]}>
+                Create a new account
+              </Text>
+            </TouchableOpacity>
+            <TouchableOpacity
+              testID="signInButton"
+              style={[styles.btn, pal.btn]}
+              onPress={onPressSignin}>
+              <Text style={[pal.text, styles.btnLabel]}>Sign in</Text>
+            </TouchableOpacity>
+          </View>
           <Text
             type="xl"
             style={[styles.notice, pal.textLight]}
@@ -70,13 +67,6 @@ export const SplashScreen = ({
             </TouchableOpacity>{' '}
             to try the beta before it's publicly available.
           </Text>
-          {isMobileWeb && (
-            <>
-              <View style={[s.p20, s.mt10]}>
-                <HelpTip text="Beta testers: the mobile web app isn't quite ready yet. Log in on desktop web or using the iPhone app." />
-              </View>
-            </>
-          )}
         </ErrorBoundary>
       </View>
       <Footer />
@@ -148,7 +138,8 @@ const styles = StyleSheet.create({
     paddingBottom: 30,
   },
   btns: {
-    flexDirection: 'row',
+    flexDirection: isMobileWeb ? 'column' : 'row',
+    gap: 20,
     justifyContent: 'center',
     paddingBottom: 40,
   },
@@ -156,7 +147,6 @@ const styles = StyleSheet.create({
     borderRadius: 30,
     paddingHorizontal: 24,
     paddingVertical: 12,
-    marginHorizontal: 10,
     minWidth: 220,
   },
   btnLabel: {
diff --git a/src/view/com/pager/FeedsTabBar.tsx b/src/view/com/pager/FeedsTabBar.tsx
index 76e0a6fc6..aa0ba7b24 100644
--- a/src/view/com/pager/FeedsTabBar.tsx
+++ b/src/view/com/pager/FeedsTabBar.tsx
@@ -1,69 +1 @@
-import React from 'react'
-import {Animated, StyleSheet, TouchableOpacity} from 'react-native'
-import {observer} from 'mobx-react-lite'
-import {TabBar} from 'view/com/pager/TabBar'
-import {RenderTabBarFnProps} from 'view/com/pager/Pager'
-import {UserAvatar} from '../util/UserAvatar'
-import {useStores} from 'state/index'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
-
-export const FeedsTabBar = observer(
-  (
-    props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
-  ) => {
-    const store = useStores()
-    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: [{translateY: Animated.multiply(interp, -100)}],
-    }
-
-    const onPressAvi = React.useCallback(() => {
-      store.shell.openDrawer()
-    }, [store])
-
-    return (
-      <Animated.View style={[pal.view, styles.tabBar, transform]}>
-        <TouchableOpacity
-          testID="viewHeaderDrawerBtn"
-          style={styles.tabBarAvi}
-          onPress={onPressAvi}>
-          <UserAvatar avatar={store.me.avatar} size={30} />
-        </TouchableOpacity>
-        <TabBar
-          {...props}
-          items={['Following', "What's hot"]}
-          indicatorPosition="bottom"
-          indicatorColor={pal.colors.link}
-        />
-      </Animated.View>
-    )
-  },
-)
-
-const styles = StyleSheet.create({
-  tabBar: {
-    position: 'absolute',
-    zIndex: 1,
-    left: 0,
-    right: 0,
-    top: 0,
-    flexDirection: 'row',
-    alignItems: 'center',
-    paddingHorizontal: 18,
-  },
-  tabBarAvi: {
-    marginTop: 1,
-    marginRight: 18,
-  },
-})
+export * from './FeedsTabBarMobile'
diff --git a/src/view/com/pager/FeedsTabBar.web.tsx b/src/view/com/pager/FeedsTabBar.web.tsx
index fc5932883..5cee2fd6d 100644
--- a/src/view/com/pager/FeedsTabBar.web.tsx
+++ b/src/view/com/pager/FeedsTabBar.web.tsx
@@ -4,10 +4,18 @@ import {TabBar} from 'view/com/pager/TabBar'
 import {CenteredView} from 'view/com/util/Views'
 import {RenderTabBarFnProps} from 'view/com/pager/Pager'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
+import {FeedsTabBar as FeedsTabBarMobile} from './FeedsTabBarMobile'
 
 export const FeedsTabBar = observer(
   (props: RenderTabBarFnProps & {onPressSelected: () => void}) => {
     const pal = usePalette('default')
+    const {isDesktop} = useWebMediaQueries()
+
+    if (!isDesktop) {
+      return <FeedsTabBarMobile {...props} />
+    }
+
     return (
       <CenteredView>
         <TabBar
diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/pager/FeedsTabBarMobile.tsx
new file mode 100644
index 000000000..76e0a6fc6
--- /dev/null
+++ b/src/view/com/pager/FeedsTabBarMobile.tsx
@@ -0,0 +1,69 @@
+import React from 'react'
+import {Animated, StyleSheet, TouchableOpacity} from 'react-native'
+import {observer} from 'mobx-react-lite'
+import {TabBar} from 'view/com/pager/TabBar'
+import {RenderTabBarFnProps} from 'view/com/pager/Pager'
+import {UserAvatar} from '../util/UserAvatar'
+import {useStores} from 'state/index'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
+
+export const FeedsTabBar = observer(
+  (
+    props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
+  ) => {
+    const store = useStores()
+    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: [{translateY: Animated.multiply(interp, -100)}],
+    }
+
+    const onPressAvi = React.useCallback(() => {
+      store.shell.openDrawer()
+    }, [store])
+
+    return (
+      <Animated.View style={[pal.view, styles.tabBar, transform]}>
+        <TouchableOpacity
+          testID="viewHeaderDrawerBtn"
+          style={styles.tabBarAvi}
+          onPress={onPressAvi}>
+          <UserAvatar avatar={store.me.avatar} size={30} />
+        </TouchableOpacity>
+        <TabBar
+          {...props}
+          items={['Following', "What's hot"]}
+          indicatorPosition="bottom"
+          indicatorColor={pal.colors.link}
+        />
+      </Animated.View>
+    )
+  },
+)
+
+const styles = StyleSheet.create({
+  tabBar: {
+    position: 'absolute',
+    zIndex: 1,
+    left: 0,
+    right: 0,
+    top: 0,
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingHorizontal: 18,
+  },
+  tabBarAvi: {
+    marginTop: 1,
+    marginRight: 18,
+  },
+})
diff --git a/src/view/com/util/FAB.tsx b/src/view/com/util/FAB.tsx
deleted file mode 100644
index 3427d368e..000000000
--- a/src/view/com/util/FAB.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import React from 'react'
-import {observer} from 'mobx-react-lite'
-import {
-  Animated,
-  GestureResponderEvent,
-  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'
-
-type OnPress = ((event: GestureResponderEvent) => void) | undefined
-export const FAB = observer(
-  ({
-    testID,
-    icon,
-    onPress,
-  }: {
-    testID?: string
-    icon: JSX.Element
-    onPress: OnPress
-  }) => {
-    const store = useStores()
-    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: [{translateY: Animated.multiply(interp, 60)}],
-    }
-    return (
-      <TouchableWithoutFeedback testID={testID} onPress={onPress}>
-        <Animated.View style={[styles.outer, transform]}>
-          <LinearGradient
-            colors={[gradients.blueLight.start, gradients.blueLight.end]}
-            start={{x: 0, y: 0}}
-            end={{x: 1, y: 1}}
-            style={styles.inner}>
-            {icon}
-          </LinearGradient>
-        </Animated.View>
-      </TouchableWithoutFeedback>
-    )
-  },
-)
-
-const styles = StyleSheet.create({
-  outer: {
-    position: 'absolute',
-    zIndex: 1,
-    right: 28,
-    bottom: 94,
-    width: 60,
-    height: 60,
-    borderRadius: 30,
-  },
-  inner: {
-    width: 60,
-    height: 60,
-    borderRadius: 30,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-})
diff --git a/src/view/com/util/FAB.web.tsx b/src/view/com/util/FAB.web.tsx
deleted file mode 100644
index dcffef29e..000000000
--- a/src/view/com/util/FAB.web.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import React from 'react'
-import {GestureResponderEvent, View} from 'react-native'
-import {IconProp} from '@fortawesome/fontawesome-svg-core'
-
-type OnPress = ((event: GestureResponderEvent) => void) | undefined
-export const FAB = (_opts: {icon: IconProp; onPress: OnPress}) => {
-  return <View />
-}
diff --git a/src/view/com/util/Toast.web.tsx b/src/view/com/util/Toast.web.tsx
index bce178b4c..cfde68536 100644
--- a/src/view/com/util/Toast.web.tsx
+++ b/src/view/com/util/Toast.web.tsx
@@ -55,9 +55,10 @@ export function show(text: string) {
 const styles = StyleSheet.create({
   container: {
     position: 'absolute',
-    right: 20,
+    left: 20,
     bottom: 20,
-    width: 350,
+    width: 'calc(100% - 40px)',
+    maxWidth: 350,
     padding: 20,
     flexDirection: 'row',
     alignItems: 'center',
diff --git a/src/view/com/util/fab/FAB.tsx b/src/view/com/util/fab/FAB.tsx
new file mode 100644
index 000000000..b222fe45c
--- /dev/null
+++ b/src/view/com/util/fab/FAB.tsx
@@ -0,0 +1 @@
+export {FABInner as FAB} from './FABInner'
diff --git a/src/view/com/util/fab/FAB.web.tsx b/src/view/com/util/fab/FAB.web.tsx
new file mode 100644
index 000000000..0a8831fa9
--- /dev/null
+++ b/src/view/com/util/fab/FAB.web.tsx
@@ -0,0 +1,14 @@
+import React from 'react'
+import {View} from 'react-native'
+import {FABInner, FABProps} from './FABInner'
+import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
+
+export const FAB = (_opts: FABProps) => {
+  const {isDesktop} = useWebMediaQueries()
+
+  if (!isDesktop) {
+    return <FABInner {..._opts} />
+  }
+
+  return <View />
+}
diff --git a/src/view/com/util/fab/FABInner.tsx b/src/view/com/util/fab/FABInner.tsx
new file mode 100644
index 000000000..3d44c0dd4
--- /dev/null
+++ b/src/view/com/util/fab/FABInner.tsx
@@ -0,0 +1,72 @@
+import React from 'react'
+import {observer} from 'mobx-react-lite'
+import {
+  Animated,
+  GestureResponderEvent,
+  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 {isMobileWeb} from 'platform/detection'
+
+type OnPress = ((event: GestureResponderEvent) => void) | undefined
+export interface FABProps {
+  testID?: string
+  icon: JSX.Element
+  onPress: OnPress
+}
+
+export const FABInner = observer(({testID, icon, onPress}: FABProps) => {
+  const store = useStores()
+  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: [{translateY: Animated.multiply(interp, 60)}],
+  }
+  return (
+    <TouchableWithoutFeedback testID={testID} onPress={onPress}>
+      <Animated.View
+        style={[styles.outer, isMobileWeb && styles.mobileWebOuter, transform]}>
+        <LinearGradient
+          colors={[gradients.blueLight.start, gradients.blueLight.end]}
+          start={{x: 0, y: 0}}
+          end={{x: 1, y: 1}}
+          style={styles.inner}>
+          {icon}
+        </LinearGradient>
+      </Animated.View>
+    </TouchableWithoutFeedback>
+  )
+})
+
+const styles = StyleSheet.create({
+  outer: {
+    position: 'absolute',
+    zIndex: 1,
+    right: 28,
+    bottom: 94,
+    width: 60,
+    height: 60,
+    borderRadius: 30,
+  },
+  mobileWebOuter: {
+    bottom: 114,
+  },
+  inner: {
+    width: 60,
+    height: 60,
+    borderRadius: 30,
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+})
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index fac522c68..23d1e2b9d 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -11,14 +11,15 @@ import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
 import {LoadLatestBtn} from '../com/util/LoadLatestBtn'
 import {FeedsTabBar} from '../com/pager/FeedsTabBar'
 import {Pager, RenderTabBarFnProps} from 'view/com/pager/Pager'
-import {FAB} from '../com/util/FAB'
+import {FAB} from '../com/util/fab/FAB'
 import {useStores} from 'state/index'
 import {s} from 'lib/styles'
 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 import {useAnalytics} from 'lib/analytics'
 import {ComposeIcon2} from 'lib/icons'
+import {isDesktopWeb, isMobileWeb} from 'platform/detection'
 
-const HEADER_OFFSET = 40
+const HEADER_OFFSET = isDesktopWeb ? 0 : isMobileWeb ? 20 : 40
 
 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
 export const HomeScreen = withAuthRequired((_opts: Props) => {
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index e3158a973..dfee6f12a 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -16,7 +16,7 @@ import {ErrorScreen} from '../com/util/error/ErrorScreen'
 import {ErrorMessage} from '../com/util/error/ErrorMessage'
 import {EmptyState} from '../com/util/EmptyState'
 import {Text} from '../com/util/text/Text'
-import {FAB} from '../com/util/FAB'
+import {FAB} from '../com/util/fab/FAB'
 import {s, colors} from 'lib/styles'
 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 import {useAnalytics} from 'lib/analytics'
diff --git a/src/view/screens/Search.web.tsx b/src/view/screens/Search.web.tsx
index d12cbc1be..92df1d920 100644
--- a/src/view/screens/Search.web.tsx
+++ b/src/view/screens/Search.web.tsx
@@ -15,12 +15,15 @@ import {
 import {useStores} from 'state/index'
 import {s} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
+import * as Mobile from './SearchMobile'
+import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 
 type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'>
 export const SearchScreen = withAuthRequired(
-  observer(({route}: Props) => {
+  observer(({navigation, route}: Props) => {
     const pal = usePalette('default')
     const store = useStores()
+    const params = route.params || {}
     const foafs = React.useMemo<FoafsModel>(
       () => new FoafsModel(store),
       [store],
@@ -30,13 +33,13 @@ export const SearchScreen = withAuthRequired(
       [store],
     )
     const searchUIModel = React.useMemo<SearchUIModel | undefined>(
-      () => (route.params.q ? new SearchUIModel(store) : undefined),
-      [route.params.q, store],
+      () => (params.q ? new SearchUIModel(store) : undefined),
+      [params.q, store],
     )
 
     React.useEffect(() => {
-      if (route.params.q && searchUIModel) {
-        searchUIModel.fetch(route.params.q)
+      if (params.q && searchUIModel) {
+        searchUIModel.fetch(params.q)
       }
       if (!foafs.hasData) {
         foafs.fetch()
@@ -44,12 +47,18 @@ export const SearchScreen = withAuthRequired(
       if (!suggestedActors.hasLoaded) {
         suggestedActors.loadMore(true)
       }
-    }, [foafs, suggestedActors, searchUIModel, route.params.q])
+    }, [foafs, suggestedActors, searchUIModel, params.q])
 
     if (searchUIModel) {
       return <SearchResults model={searchUIModel} />
     }
 
+    const {isDesktop} = useWebMediaQueries()
+
+    if (!isDesktop) {
+      return <Mobile.SearchScreen navigation={navigation} route={route} />
+    }
+
     return (
       <ScrollView
         testID="searchScrollView"
diff --git a/src/view/screens/SearchMobile.tsx b/src/view/screens/SearchMobile.tsx
new file mode 100644
index 000000000..e1fb3ec0a
--- /dev/null
+++ b/src/view/screens/SearchMobile.tsx
@@ -0,0 +1,195 @@
+import React from 'react'
+import {
+  Keyboard,
+  RefreshControl,
+  StyleSheet,
+  TouchableWithoutFeedback,
+  View,
+} from 'react-native'
+import {useFocusEffect} from '@react-navigation/native'
+import {withAuthRequired} from 'view/com/auth/withAuthRequired'
+import {ScrollView} from 'view/com/util/Views'
+import {
+  NativeStackScreenProps,
+  SearchTabNavigatorParams,
+} from 'lib/routes/types'
+import {observer} from 'mobx-react-lite'
+import {Text} from 'view/com/util/text/Text'
+import {useStores} from 'state/index'
+import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete'
+import {SearchUIModel} from 'state/models/ui/search'
+import {FoafsModel} from 'state/models/discovery/foafs'
+import {SuggestedActorsModel} from 'state/models/discovery/suggested-actors'
+import {HeaderWithInput} from 'view/com/search/HeaderWithInput'
+import {Suggestions} from 'view/com/search/Suggestions'
+import {SearchResults} from 'view/com/search/SearchResults'
+import {s} from 'lib/styles'
+import {ProfileCard} from 'view/com/profile/ProfileCard'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
+
+type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'>
+export const SearchScreen = withAuthRequired(
+  observer<Props>(({}: Props) => {
+    const pal = usePalette('default')
+    const store = useStores()
+    const scrollElRef = React.useRef<ScrollView>(null)
+    const onMainScroll = useOnMainScroll(store)
+    const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false)
+    const [query, setQuery] = React.useState<string>('')
+    const autocompleteView = React.useMemo<UserAutocompleteModel>(
+      () => new UserAutocompleteModel(store),
+      [store],
+    )
+    const foafs = React.useMemo<FoafsModel>(
+      () => new FoafsModel(store),
+      [store],
+    )
+    const suggestedActors = React.useMemo<SuggestedActorsModel>(
+      () => new SuggestedActorsModel(store),
+      [store],
+    )
+    const [searchUIModel, setSearchUIModel] = React.useState<
+      SearchUIModel | undefined
+    >()
+    const [refreshing, setRefreshing] = React.useState(false)
+
+    const onSoftReset = () => {
+      scrollElRef.current?.scrollTo({x: 0, y: 0})
+    }
+
+    useFocusEffect(
+      React.useCallback(() => {
+        const softResetSub = store.onScreenSoftReset(onSoftReset)
+        const cleanup = () => {
+          softResetSub.remove()
+        }
+
+        store.shell.setMinimalShellMode(false)
+        autocompleteView.setup()
+        if (!foafs.hasData) {
+          foafs.fetch()
+        }
+        if (!suggestedActors.hasLoaded) {
+          suggestedActors.loadMore(true)
+        }
+
+        return cleanup
+      }, [store, autocompleteView, foafs, suggestedActors]),
+    )
+
+    const onChangeQuery = React.useCallback(
+      (text: string) => {
+        setQuery(text)
+        if (text.length > 0) {
+          autocompleteView.setActive(true)
+          autocompleteView.setPrefix(text)
+        } else {
+          autocompleteView.setActive(false)
+        }
+      },
+      [setQuery, autocompleteView],
+    )
+
+    const onPressClearQuery = React.useCallback(() => {
+      setQuery('')
+    }, [setQuery])
+
+    const onPressCancelSearch = React.useCallback(() => {
+      setQuery('')
+      autocompleteView.setActive(false)
+      setSearchUIModel(undefined)
+      store.shell.setIsDrawerSwipeDisabled(false)
+    }, [setQuery, autocompleteView, store])
+
+    const onSubmitQuery = React.useCallback(() => {
+      const model = new SearchUIModel(store)
+      model.fetch(query)
+      setSearchUIModel(model)
+      store.shell.setIsDrawerSwipeDisabled(true)
+    }, [query, setSearchUIModel, store])
+
+    const onRefresh = React.useCallback(async () => {
+      setRefreshing(true)
+      try {
+        await foafs.fetch()
+      } finally {
+        setRefreshing(false)
+      }
+    }, [foafs, setRefreshing])
+
+    return (
+      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
+        <View style={[pal.view, styles.container]}>
+          <HeaderWithInput
+            isInputFocused={isInputFocused}
+            query={query}
+            setIsInputFocused={setIsInputFocused}
+            onChangeQuery={onChangeQuery}
+            onPressClearQuery={onPressClearQuery}
+            onPressCancelSearch={onPressCancelSearch}
+            onSubmitQuery={onSubmitQuery}
+          />
+          {searchUIModel ? (
+            <SearchResults model={searchUIModel} />
+          ) : (
+            <ScrollView
+              ref={scrollElRef}
+              testID="searchScrollView"
+              style={pal.view}
+              onScroll={onMainScroll}
+              scrollEventThrottle={100}
+              refreshControl={
+                <RefreshControl
+                  refreshing={refreshing}
+                  onRefresh={onRefresh}
+                  tintColor={pal.colors.text}
+                  titleColor={pal.colors.text}
+                />
+              }>
+              {query && autocompleteView.searchRes.length ? (
+                <>
+                  {autocompleteView.searchRes.map(item => (
+                    <ProfileCard
+                      key={item.did}
+                      testID={`searchAutoCompleteResult-${item.handle}`}
+                      handle={item.handle}
+                      displayName={item.displayName}
+                      avatar={item.avatar}
+                    />
+                  ))}
+                </>
+              ) : query && !autocompleteView.searchRes.length ? (
+                <View>
+                  <Text style={[pal.textLight, styles.searchPrompt]}>
+                    No results found for {autocompleteView.prefix}
+                  </Text>
+                </View>
+              ) : isInputFocused ? (
+                <View>
+                  <Text style={[pal.textLight, styles.searchPrompt]}>
+                    Search for users on the network
+                  </Text>
+                </View>
+              ) : (
+                <Suggestions foafs={foafs} suggestedActors={suggestedActors} />
+              )}
+              <View style={s.footerSpacer} />
+            </ScrollView>
+          )}
+        </View>
+      </TouchableWithoutFeedback>
+    )
+  }),
+)
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+  },
+
+  searchPrompt: {
+    textAlign: 'center',
+    paddingTop: 10,
+  },
+})
diff --git a/src/view/shell/Composer.web.tsx b/src/view/shell/Composer.web.tsx
index e3994bb23..ed0450c01 100644
--- a/src/view/shell/Composer.web.tsx
+++ b/src/view/shell/Composer.web.tsx
@@ -4,6 +4,7 @@ import {StyleSheet, View} from 'react-native'
 import {ComposePost} from '../com/composer/Composer'
 import {ComposerOpts} from 'state/models/ui/shell'
 import {usePalette} from 'lib/hooks/usePalette'
+import {isMobileWeb} from 'platform/detection'
 
 export const Composer = observer(
   ({
@@ -60,7 +61,7 @@ const styles = StyleSheet.create({
     width: '100%',
     paddingVertical: 0,
     paddingHorizontal: 2,
-    borderRadius: 8,
+    borderRadius: isMobileWeb ? 0 : 8,
     marginBottom: '10vh',
   },
 })
diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
index ebadb2126..de36463e1 100644
--- a/src/view/shell/Drawer.tsx
+++ b/src/view/shell/Drawer.tsx
@@ -8,11 +8,7 @@ import {
   View,
   ViewStyle,
 } from 'react-native'
-import {
-  useNavigation,
-  useNavigationState,
-  StackActions,
-} from '@react-navigation/native'
+import {useNavigation, StackActions} from '@react-navigation/native'
 import {observer} from 'mobx-react-lite'
 import {
   FontAwesomeIcon,
@@ -40,6 +36,8 @@ import {useAnalytics} from 'lib/analytics'
 import {pluralize} from 'lib/strings/helpers'
 import {getTabState, TabState} from 'lib/routes/helpers'
 import {NavigationProp} from 'lib/routes/types'
+import {useNavigationTabState} from 'lib/hooks/useNavigationTabState'
+import {isWeb} from 'platform/detection'
 
 export const DrawerContent = observer(() => {
   const theme = useTheme()
@@ -47,16 +45,7 @@ export const DrawerContent = observer(() => {
   const store = useStores()
   const navigation = useNavigation<NavigationProp>()
   const {track} = useAnalytics()
-  const {isAtHome, isAtSearch, isAtNotifications} = useNavigationState(
-    state => {
-      return {
-        isAtHome: getTabState(state, 'Home') !== TabState.Outside,
-        isAtSearch: getTabState(state, 'Search') !== TabState.Outside,
-        isAtNotifications:
-          getTabState(state, 'Notifications') !== TabState.Outside,
-      }
-    },
-  )
+  const {isAtHome, isAtSearch, isAtNotifications} = useNavigationTabState()
 
   // events
   // =
@@ -66,14 +55,19 @@ export const DrawerContent = observer(() => {
       track('Menu:ItemClicked', {url: tab})
       const state = navigation.getState()
       store.shell.closeDrawer()
-      const tabState = getTabState(state, tab)
-      if (tabState === TabState.InsideAtRoot) {
-        store.emitScreenSoftReset()
-      } else if (tabState === TabState.Inside) {
-        navigation.dispatch(StackActions.popToTop())
-      } else {
+      if (isWeb) {
         // @ts-ignore must be Home, Search, or Notifications
-        navigation.navigate(`${tab}Tab`)
+        navigation.navigate(tab)
+      } else {
+        const tabState = getTabState(state, tab)
+        if (tabState === TabState.InsideAtRoot) {
+          store.emitScreenSoftReset()
+        } else if (tabState === TabState.Inside) {
+          navigation.dispatch(StackActions.popToTop())
+        } else {
+          // @ts-ignore must be Home, Search, or Notifications
+          navigation.navigate(`${tab}Tab`)
+        }
       }
     },
     [store, track, navigation],
@@ -240,20 +234,22 @@ export const DrawerContent = observer(() => {
         </View>
         <View style={s.flex1} />
         <View style={styles.footer}>
-          <TouchableOpacity
-            onPress={onDarkmodePress}
-            style={[
-              styles.footerBtn,
-              theme.colorScheme === 'light'
-                ? pal.btn
-                : styles.footerBtnDarkMode,
-            ]}>
-            <MoonIcon
-              size={22}
-              style={pal.text as StyleProp<ViewStyle>}
-              strokeWidth={2}
-            />
-          </TouchableOpacity>
+          {!isWeb && (
+            <TouchableOpacity
+              onPress={onDarkmodePress}
+              style={[
+                styles.footerBtn,
+                theme.colorScheme === 'light'
+                  ? pal.btn
+                  : styles.footerBtnDarkMode,
+              ]}>
+              <MoonIcon
+                size={22}
+                style={pal.text as StyleProp<ViewStyle>}
+                strokeWidth={2}
+              />
+            </TouchableOpacity>
+          )}
           <TouchableOpacity
             onPress={onPressFeedback}
             style={[
diff --git a/src/view/shell/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx
index b01366b2b..59b21968d 100644
--- a/src/view/shell/BottomBar.tsx
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -2,7 +2,6 @@ import React from 'react'
 import {
   Animated,
   GestureResponderEvent,
-  StyleSheet,
   TouchableOpacity,
   View,
 } from 'react-native'
@@ -13,7 +12,6 @@ import {observer} from 'mobx-react-lite'
 import {Text} from 'view/com/util/text/Text'
 import {useStores} from 'state/index'
 import {useAnalytics} from 'lib/analytics'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
 import {clamp} from 'lib/numbers'
 import {
   HomeIcon,
@@ -24,14 +22,14 @@ import {
   BellIconSolid,
   UserIcon,
 } from 'lib/icons'
-import {colors} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
 import {getTabState, TabState} from 'lib/routes/helpers'
+import {styles} from './BottomBarStyles'
+import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
 
 export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
   const store = useStores()
   const pal = usePalette('default')
-  const minimalShellInterp = useAnimatedValue(0)
   const safeAreaInsets = useSafeAreaInsets()
   const {track} = useAnalytics()
   const {isAtHome, isAtSearch, isAtNotifications} = useNavigationState(
@@ -52,26 +50,7 @@ export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
     },
   )
 
-  React.useEffect(() => {
-    if (store.shell.minimalShellMode) {
-      Animated.timing(minimalShellInterp, {
-        toValue: 1,
-        duration: 100,
-        useNativeDriver: true,
-        isInteraction: false,
-      }).start()
-    } else {
-      Animated.timing(minimalShellInterp, {
-        toValue: 0,
-        duration: 100,
-        useNativeDriver: true,
-        isInteraction: false,
-      }).start()
-    }
-  }, [minimalShellInterp, store.shell.minimalShellMode])
-  const footerMinimalShellTransform = {
-    transform: [{translateY: Animated.multiply(minimalShellInterp, 100)}],
-  }
+  const {footerMinimalShellTransform} = useMinimalShellMode()
 
   const onPressTab = React.useCallback(
     (tab: string) => {
@@ -217,62 +196,3 @@ function Btn({
     </TouchableOpacity>
   )
 }
-
-const styles = StyleSheet.create({
-  bottomBar: {
-    position: 'absolute',
-    bottom: 0,
-    left: 0,
-    right: 0,
-    flexDirection: 'row',
-    borderTopWidth: 1,
-    paddingLeft: 5,
-    paddingRight: 10,
-  },
-  ctrl: {
-    flex: 1,
-    paddingTop: 13,
-    paddingBottom: 4,
-  },
-  notificationCount: {
-    position: 'absolute',
-    left: '52%',
-    top: 8,
-    backgroundColor: colors.blue3,
-    paddingHorizontal: 4,
-    paddingBottom: 1,
-    borderRadius: 6,
-    zIndex: 1,
-  },
-  notificationCountLight: {
-    borderColor: colors.white,
-  },
-  notificationCountDark: {
-    borderColor: colors.gray8,
-  },
-  notificationCountLabel: {
-    fontSize: 12,
-    fontWeight: 'bold',
-    color: colors.white,
-    fontVariant: ['tabular-nums'],
-  },
-  ctrlIcon: {
-    marginLeft: 'auto',
-    marginRight: 'auto',
-  },
-  ctrlIconSizingWrapper: {
-    height: 27,
-  },
-  homeIcon: {
-    top: 0,
-  },
-  searchIcon: {
-    top: -2,
-  },
-  bellIcon: {
-    top: -2.5,
-  },
-  profileIcon: {
-    top: -4,
-  },
-})
diff --git a/src/view/shell/bottom-bar/BottomBarStyles.tsx b/src/view/shell/bottom-bar/BottomBarStyles.tsx
new file mode 100644
index 000000000..3d5adbc9e
--- /dev/null
+++ b/src/view/shell/bottom-bar/BottomBarStyles.tsx
@@ -0,0 +1,61 @@
+import {StyleSheet} from 'react-native'
+import {colors} from 'lib/styles'
+
+export const styles = StyleSheet.create({
+  bottomBar: {
+    position: 'absolute',
+    bottom: 0,
+    left: 0,
+    right: 0,
+    flexDirection: 'row',
+    borderTopWidth: 1,
+    paddingLeft: 5,
+    paddingRight: 10,
+  },
+  ctrl: {
+    flex: 1,
+    paddingTop: 13,
+    paddingBottom: 4,
+  },
+  notificationCount: {
+    position: 'absolute',
+    left: '52%',
+    top: 8,
+    backgroundColor: colors.blue3,
+    paddingHorizontal: 4,
+    paddingBottom: 1,
+    borderRadius: 6,
+    zIndex: 1,
+  },
+  notificationCountLight: {
+    borderColor: colors.white,
+  },
+  notificationCountDark: {
+    borderColor: colors.gray8,
+  },
+  notificationCountLabel: {
+    fontSize: 12,
+    fontWeight: 'bold',
+    color: colors.white,
+    fontVariant: ['tabular-nums'],
+  },
+  ctrlIcon: {
+    marginLeft: 'auto',
+    marginRight: 'auto',
+  },
+  ctrlIconSizingWrapper: {
+    height: 27,
+  },
+  homeIcon: {
+    top: 0,
+  },
+  searchIcon: {
+    top: -2,
+  },
+  bellIcon: {
+    top: -2.5,
+  },
+  profileIcon: {
+    top: -4,
+  },
+})
diff --git a/src/view/shell/bottom-bar/BottomBarWeb.tsx b/src/view/shell/bottom-bar/BottomBarWeb.tsx
new file mode 100644
index 000000000..b7daac5af
--- /dev/null
+++ b/src/view/shell/bottom-bar/BottomBarWeb.tsx
@@ -0,0 +1,101 @@
+import React from 'react'
+import {observer} from 'mobx-react-lite'
+import {useStores} from 'state/index'
+import {usePalette} from 'lib/hooks/usePalette'
+import {Animated} from 'react-native'
+import {useNavigationState} from '@react-navigation/native'
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
+import {getCurrentRoute, isTab} from 'lib/routes/helpers'
+import {styles} from './BottomBarStyles'
+import {clamp} from 'lib/numbers'
+import {
+  BellIcon,
+  BellIconSolid,
+  HomeIcon,
+  HomeIconSolid,
+  MagnifyingGlassIcon2,
+  MagnifyingGlassIcon2Solid,
+  UserIcon,
+} from 'lib/icons'
+import {Link} from 'view/com/util/Link'
+import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
+
+export const BottomBarWeb = observer(() => {
+  const store = useStores()
+  const pal = usePalette('default')
+  const safeAreaInsets = useSafeAreaInsets()
+  const {footerMinimalShellTransform} = useMinimalShellMode()
+
+  return (
+    <Animated.View
+      style={[
+        styles.bottomBar,
+        pal.view,
+        pal.border,
+        {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
+        footerMinimalShellTransform,
+      ]}>
+      <NavItem routeName="Home" href="/">
+        {({isActive}) => {
+          const Icon = isActive ? HomeIconSolid : HomeIcon
+          return (
+            <Icon
+              strokeWidth={4}
+              size={24}
+              style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
+            />
+          )
+        }}
+      </NavItem>
+      <NavItem routeName="Search" href="/search">
+        {({isActive}) => {
+          const Icon = isActive
+            ? MagnifyingGlassIcon2Solid
+            : MagnifyingGlassIcon2
+          return (
+            <Icon
+              size={25}
+              style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
+              strokeWidth={1.8}
+            />
+          )
+        }}
+      </NavItem>
+      <NavItem routeName="Notifications" href="/notifications">
+        {({isActive}) => {
+          const Icon = isActive ? BellIconSolid : BellIcon
+          return (
+            <Icon
+              size={24}
+              strokeWidth={1.9}
+              style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
+            />
+          )
+        }}
+      </NavItem>
+      <NavItem routeName="Profile" href={`/profile/${store.me.handle}`}>
+        {() => (
+          <UserIcon
+            size={28}
+            strokeWidth={1.5}
+            style={[styles.ctrlIcon, pal.text, styles.profileIcon]}
+          />
+        )}
+      </NavItem>
+    </Animated.View>
+  )
+})
+
+const NavItem: React.FC<{
+  children: (props: {isActive: boolean}) => React.ReactChild
+  href: string
+  routeName: string
+}> = ({children, href, routeName}) => {
+  const currentRoute = useNavigationState(getCurrentRoute)
+  const isActive = isTab(currentRoute.name, routeName)
+  return (
+    <Link href={href} style={styles.ctrl}>
+      {children({isActive})}
+    </Link>
+  )
+}
diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx
index 96a120642..86d120127 100644
--- a/src/view/shell/index.web.tsx
+++ b/src/view/shell/index.web.tsx
@@ -1,6 +1,6 @@
 import React from 'react'
 import {observer} from 'mobx-react-lite'
-import {View, StyleSheet} from 'react-native'
+import {View, StyleSheet, TouchableOpacity} from 'react-native'
 import {useStores} from 'state/index'
 import {DesktopLeftNav} from './desktop/LeftNav'
 import {DesktopRightNav} from './desktop/RightNav'
@@ -11,9 +11,13 @@ import {Composer} from './Composer.web'
 import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
 import {s, colors} from 'lib/styles'
 import {RoutesContainer, FlatNavigator} from '../../Navigation'
+import {DrawerContent} from './Drawer'
+import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries'
+import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
 
 const ShellInner = observer(() => {
   const store = useStores()
+  const {isDesktop} = useWebMediaQueries()
 
   return (
     <>
@@ -22,10 +26,14 @@ const ShellInner = observer(() => {
           <FlatNavigator />
         </ErrorBoundary>
       </View>
-      <DesktopLeftNav />
-      <DesktopRightNav />
-      <View style={[styles.viewBorder, styles.viewBorderLeft]} />
-      <View style={[styles.viewBorder, styles.viewBorderRight]} />
+      {isDesktop && (
+        <>
+          <DesktopLeftNav />
+          <DesktopRightNav />
+          <View style={[styles.viewBorder, styles.viewBorderLeft]} />
+          <View style={[styles.viewBorder, styles.viewBorderRight]} />
+        </>
+      )}
       <Composer
         active={store.shell.isComposerActive}
         onClose={() => store.shell.closeComposer()}
@@ -34,8 +42,18 @@ const ShellInner = observer(() => {
         quote={store.shell.composerOpts?.quote}
         onPost={store.shell.composerOpts?.onPost}
       />
+      {!isDesktop && <BottomBarWeb />}
       <ModalsContainer />
       <Lightbox />
+      {!isDesktop && store.shell.isDrawerOpen && (
+        <TouchableOpacity
+          onPress={() => store.shell.closeDrawer()}
+          style={styles.drawerMask}>
+          <View style={styles.drawerContainer}>
+            <DrawerContent />
+          </View>
+        </TouchableOpacity>
+      )}
     </>
   )
 })
@@ -71,4 +89,19 @@ const styles = StyleSheet.create({
   viewBorderRight: {
     left: 'calc(50vw + 300px)',
   },
+  drawerMask: {
+    position: 'absolute',
+    width: '100%',
+    height: '100%',
+    top: 0,
+    left: 0,
+    backgroundColor: 'rgba(0,0,0,0.25)',
+  },
+  drawerContainer: {
+    display: 'flex',
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    height: '100%',
+  },
 })