Better tablet layout (#7656)

* better tablet layout

* adjust left nav spacing

* add right nav to pwi

* clearer logic

* fix a couple screens that don't need the tablet layout

* fix horiz scroll bar

* fix double trending

* fix ts-ignore

* fix labeller screen

* don't offset things within dialogs

* fix load latest button (and add scale animation)

* center loader on home screen

* adjust break points

* adjust left nav spacing

* fix load latest btn (again)

* add lang select to right nav if left nav is minimal

* fix double scrollbar on tiny screens

* fix scrollbar

* fix type errors
This commit is contained in:
Samuel Newman
2025-02-25 09:20:37 -08:00
committed by GitHub
parent 0437838649
commit cc8369e868
17 changed files with 211 additions and 122 deletions
+15
View File
@@ -26,3 +26,18 @@ export function useBreakpoints(): Record<Breakpoint, boolean> & {
}
}, [gtPhone, gtMobile, gtTablet])
}
/**
* Fine-tuned breakpoints for the shell layout
*/
export function useLayoutBreakpoints() {
const rightNavVisible = useMediaQuery({minWidth: 1075})
const centerColumnOffset = useMediaQuery({minWidth: 1075, maxWidth: 1300})
const leftNavMinimal = useMediaQuery({maxWidth: 1300})
return {
rightNavVisible,
centerColumnOffset,
leftNavMinimal,
}
}
+1
View File
@@ -14,6 +14,7 @@ export const Context = React.createContext<DialogContextProps>({
nativeSnapPoint: BottomSheetSnapPoint.Hidden,
disableDrag: false,
setDisableDrag: () => {},
isWithinDialog: false,
})
export function useDialogContext() {
+1
View File
@@ -154,6 +154,7 @@ export function Outer({
nativeSnapPoint: snapPoint,
disableDrag,
setDisableDrag,
isWithinDialog: true,
}),
[close, snapPoint, disableDrag, setDisableDrag],
)
+1
View File
@@ -97,6 +97,7 @@ export function Outer({
nativeSnapPoint: 0,
disableDrag: false,
setDisableDrag: () => {},
isWithinDialog: true,
}),
[close],
)
+2
View File
@@ -44,6 +44,8 @@ export type DialogContextProps = {
nativeSnapPoint: BottomSheetSnapPoint
disableDrag: boolean
setDisableDrag: React.Dispatch<React.SetStateAction<boolean>>
// in the event that the hook is used outside of a dialog
isWithinDialog: boolean
}
export type DialogControlOpenOptions = {
+9 -1
View File
@@ -14,6 +14,7 @@ import {
TextStyleProp,
useBreakpoints,
useGutters,
useLayoutBreakpoints,
useTheme,
web,
} from '#/alf'
@@ -23,6 +24,7 @@ import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
import {
BUTTON_VISUAL_ALIGNMENT_OFFSET,
HEADER_SLOT_SIZE,
SCROLLBAR_OFFSET,
} from '#/components/Layout/const'
import {ScrollbarOffsetContext} from '#/components/Layout/context'
import {Text} from '#/components/Typography'
@@ -42,6 +44,7 @@ export function Outer({
const gutters = useGutters([0, 'base'])
const {gtMobile} = useBreakpoints()
const {isWithinOffsetView} = useContext(ScrollbarOffsetContext)
const {centerColumnOffset} = useLayoutBreakpoints()
return (
<View
@@ -60,7 +63,12 @@ export function Outer({
}),
t.atoms.border_contrast_low,
gtMobile && [a.mx_auto, {maxWidth: 600}],
!isWithinOffsetView && a.scrollbar_offset,
!isWithinOffsetView && {
transform: [
{translateX: centerColumnOffset ? -150 : 0},
{translateX: web(SCROLLBAR_OFFSET) ?? 0},
],
},
]}>
{children}
</View>
+35 -8
View File
@@ -13,7 +13,15 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {isWeb} from '#/platform/detection'
import {useShellLayout} from '#/state/shell/shell-layout'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {
atoms as a,
useBreakpoints,
useLayoutBreakpoints,
useTheme,
web,
} from '#/alf'
import {useDialogContext} from '#/components/Dialog'
import {SCROLLBAR_OFFSET} from '#/components/Layout/const'
import {ScrollbarOffsetContext} from '#/components/Layout/context'
export * from '#/components/Layout/const'
@@ -47,6 +55,7 @@ export const Screen = React.memo(function Screen({
export type ContentProps = AnimatedScrollViewProps & {
style?: StyleProp<ViewStyle>
contentContainerStyle?: StyleProp<ViewStyle>
ignoreTabletLayoutOffset?: boolean
}
/**
@@ -56,6 +65,7 @@ export const Content = React.memo(function Content({
children,
style,
contentContainerStyle,
ignoreTabletLayoutOffset,
...props
}: ContentProps) {
const t = useTheme()
@@ -84,8 +94,10 @@ export const Content = React.memo(function Content({
]}
{...props}>
{isWeb ? (
// @ts-ignore web only -esb
<Center>{children}</Center>
<Center ignoreTabletLayoutOffset={ignoreTabletLayoutOffset}>
{/* @ts-expect-error web only -esb */}
{children}
</Center>
) : (
children
)}
@@ -138,10 +150,13 @@ export const KeyboardAwareContent = React.memo(function LayoutScrollView({
export const Center = React.memo(function LayoutContent({
children,
style,
ignoreTabletLayoutOffset,
...props
}: ViewProps) {
}: ViewProps & {ignoreTabletLayoutOffset?: boolean}) {
const {isWithinOffsetView} = useContext(ScrollbarOffsetContext)
const {gtMobile} = useBreakpoints()
const {centerColumnOffset} = useLayoutBreakpoints()
const {isWithinDialog} = useDialogContext()
const ctx = useMemo(() => ({isWithinOffsetView: true}), [])
return (
<View
@@ -151,8 +166,20 @@ export const Center = React.memo(function LayoutContent({
gtMobile && {
maxWidth: 600,
},
!isWithinOffsetView && {
transform: [
{
translateX:
centerColumnOffset &&
!ignoreTabletLayoutOffset &&
!isWithinDialog
? -150
: 0,
},
{translateX: web(SCROLLBAR_OFFSET) ?? 0},
],
},
style,
!isWithinOffsetView && a.scrollbar_offset,
]}
{...props}>
<ScrollbarOffsetContext.Provider value={ctx}>
@@ -168,6 +195,7 @@ export const Center = React.memo(function LayoutContent({
const WebCenterBorders = React.memo(function LayoutContent() {
const t = useTheme()
const {gtMobile} = useBreakpoints()
const {centerColumnOffset} = useLayoutBreakpoints()
return gtMobile ? (
<View
style={[
@@ -180,9 +208,8 @@ const WebCenterBorders = React.memo(function LayoutContent() {
width: 602,
left: '50%',
transform: [
{
translateX: '-50%',
},
{translateX: '-50%'},
{translateX: centerColumnOffset ? -150 : 0},
...a.scrollbar_offset.transform,
],
}),
+1
View File
@@ -106,6 +106,7 @@ export function Deactivated() {
return (
<View style={[a.util_screen_outer, a.flex_1]}>
<Layout.Content
ignoreTabletLayoutOffset
contentContainerStyle={[
a.px_2xl,
{
+4 -6
View File
@@ -15,7 +15,6 @@ import {isLabelerSubscribed, lookupLabelValueDefinition} from '#/lib/moderation'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {isNative} from '#/platform/detection'
import {ListRef} from '#/view/com/util/List'
import {ScrollView} from '#/view/com/util/Views'
import {atoms as a, useTheme} from '#/alf'
import {Divider} from '#/components/Divider'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
@@ -148,8 +147,8 @@ export function ProfileLabelsSectionInner({
}, [labelerInfo, labelValues])
return (
<ScrollView
// @ts-ignore TODO fix this
<Layout.Content
// @ts-expect-error TODO fix this
ref={scrollElRef}
scrollEventThrottle={1}
contentContainerStyle={{
@@ -228,9 +227,8 @@ export function ProfileLabelsSectionInner({
})}
</View>
)}
<View style={{height: 400}} />
<View style={{height: 100}} />
</View>
</ScrollView>
</Layout.Content>
)
}
+4 -4
View File
@@ -16,9 +16,9 @@ import {
import {atoms as a, useTheme} from '#/alf'
import {AppLanguageDropdown} from '#/components/AppLanguageDropdown'
import {Button, ButtonText} from '#/components/Button'
import * as Layout from '#/components/Layout'
import {InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography'
import {CenteredView} from '../util/Views'
export const SplashScreen = ({
onDismiss,
@@ -70,13 +70,13 @@ export const SplashScreen = ({
</Pressable>
)}
<CenteredView style={[a.h_full, a.flex_1]}>
<Layout.Center style={[a.h_full, a.flex_1]} ignoreTabletLayoutOffset>
<View
testID="noSessionView"
style={[
a.h_full,
a.justify_center,
// @ts-ignore web only
// @ts-expect-error web only
{paddingBottom: '20vh'},
isMobileWeb && a.pb_5xl,
t.atoms.border_contrast_medium,
@@ -135,7 +135,7 @@ export const SplashScreen = ({
</ErrorBoundary>
</View>
<Footer />
</CenteredView>
</Layout.Center>
<AppClipOverlay
visible={showClipOverlay}
setIsVisible={setShowClipOverlay}
+5 -4
View File
@@ -40,7 +40,7 @@ import {List, ListRef} from '#/view/com/util/List'
import {PostFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
import {VideoFeedSourceContext} from '#/screens/VideoFeed/types'
import {useBreakpoints} from '#/alf'
import {useBreakpoints, useLayoutBreakpoints} from '#/alf'
import {ProgressGuide, SuggestedFollows} from '#/components/FeedInterstitials'
import {
PostFeedVideoGridRow,
@@ -197,7 +197,8 @@ let PostFeed = ({
const checkForNewRef = React.useRef<(() => void) | null>(null)
const lastFetchRef = React.useRef<number>(Date.now())
const [feedType, feedUriOrActorDid, feedTab] = feed.split('|')
const {gtMobile, gtTablet} = useBreakpoints()
const {gtMobile} = useBreakpoints()
const {rightNavVisible} = useLayoutBreakpoints()
const areVideoFeedsEnabled = isNative
const feedCacheKey = feedParams?.feedCacheKey
@@ -396,7 +397,7 @@ let PostFeed = ({
key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
})
}
if (!gtTablet && !trendingDisabled) {
if (!rightNavVisible && !trendingDisabled) {
arr.push({
type: 'interstitialTrending',
key:
@@ -512,7 +513,7 @@ let PostFeed = ({
showProgressIntersitial,
trendingDisabled,
trendingVideoDisabled,
gtTablet,
rightNavVisible,
gtMobile,
isVideoFeed,
areVideoFeedsEnabled,
+27 -6
View File
@@ -26,6 +26,8 @@ import Animated from 'react-native-reanimated'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {addStyle} from '#/lib/styles'
import {useLayoutBreakpoints} from '#/alf'
import {useDialogContext} from '#/components/Dialog'
interface AddedProps {
desktopFixedHeight?: boolean | number
@@ -46,9 +48,14 @@ export const CenteredView = React.forwardRef(function CenteredView(
) {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
const {centerColumnOffset} = useLayoutBreakpoints()
const {isWithinDialog} = useDialogContext()
if (!isMobile) {
style = addStyle(style, styles.container)
}
if (centerColumnOffset && !isWithinDialog) {
style = addStyle(style, styles.containerOffset)
}
if (topBorder) {
style = addStyle(style, {
borderTopWidth: 1,
@@ -71,12 +78,17 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
ref: React.Ref<FlatList<ItemT>>,
) {
const {isMobile} = useWebMediaQueries()
const {centerColumnOffset} = useLayoutBreakpoints()
const {isWithinDialog} = useDialogContext()
if (!isMobile) {
contentContainerStyle = addStyle(
contentContainerStyle,
styles.containerScroll,
)
}
if (centerColumnOffset && !isWithinDialog) {
style = addStyle(style, styles.containerOffset)
}
if (contentOffset && contentOffset?.y !== 0) {
// NOTE
// we use paddingTop & contentOffset to space around the floating header
@@ -92,7 +104,7 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
}
if (desktopFixedHeight) {
if (typeof desktopFixedHeight === 'number') {
// @ts-ignore Web only -prf
// @ts-expect-error Web only -prf
style = addStyle(style, {
height: `calc(100vh - ${desktopFixedHeight}px)`,
})
@@ -108,9 +120,9 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
// around this, we set data-stable-gutters which can then be
// styled in our external CSS.
// -prf
// @ts-ignore web only -prf
// @ts-expect-error web only -prf
props.dataSet = props.dataSet || {}
// @ts-ignore web only -prf
// @ts-expect-error web only -prf
props.dataSet.stableGutters = '1'
}
}
@@ -133,16 +145,22 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl(
ref: React.Ref<Animated.ScrollView>,
) {
const {isMobile} = useWebMediaQueries()
const {centerColumnOffset} = useLayoutBreakpoints()
if (!isMobile) {
contentContainerStyle = addStyle(
contentContainerStyle,
styles.containerScroll,
)
}
if (centerColumnOffset) {
contentContainerStyle = addStyle(
contentContainerStyle,
styles.containerOffset,
)
}
return (
<Animated.ScrollView
contentContainerStyle={[styles.contentContainer, contentContainerStyle]}
// @ts-ignore something is wrong with the reanimated types -prf
ref={ref}
{...props}
/>
@@ -151,7 +169,7 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl(
const styles = StyleSheet.create({
contentContainer: {
// @ts-ignore web only
// @ts-expect-error web only
minHeight: '100vh',
},
container: {
@@ -160,6 +178,9 @@ const styles = StyleSheet.create({
marginLeft: 'auto',
marginRight: 'auto',
},
containerOffset: {
transform: [{translateX: -150}],
},
containerScroll: {
width: '100%',
maxWidth: 600,
@@ -167,7 +188,7 @@ const styles = StyleSheet.create({
marginRight: 'auto',
},
fixedHeight: {
// @ts-ignore web only
// @ts-expect-error web only
height: '100vh',
},
})
+36 -28
View File
@@ -1,10 +1,11 @@
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {StyleSheet, View} from 'react-native'
import Animated from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useMediaQuery} from 'react-responsive'
import {HITSLOP_20} from '#/lib/constants'
import {PressableScale} from '#/lib/custom-animations/PressableScale'
import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
@@ -13,9 +14,7 @@ import {useGate} from '#/lib/statsig/statsig'
import {colors} from '#/lib/styles'
import {isWeb} from '#/platform/detection'
import {useSession} from '#/state/session'
const AnimatedTouchableOpacity =
Animated.createAnimatedComponent(TouchableOpacity)
import {useLayoutBreakpoints} from '#/alf'
export function LoadLatestBtn({
onPress,
@@ -29,6 +28,7 @@ export function LoadLatestBtn({
const pal = usePalette('default')
const {hasSession} = useSession()
const {isDesktop, isTablet, isMobile, isTabletOrMobile} = useWebMediaQueries()
const {centerColumnOffset} = useLayoutBreakpoints()
const fabMinimalShellTransform = useMinimalShellFabTransform()
const insets = useSafeAreaInsets()
@@ -49,33 +49,37 @@ export function LoadLatestBtn({
: {bottom: clamp(insets.bottom, 15, 60) + 15}
return (
<AnimatedTouchableOpacity
style={[
styles.loadLatest,
isDesktop &&
(isTallViewport
? styles.loadLatestOutOfLine
: styles.loadLatestInline),
isTablet && styles.loadLatestInline,
pal.borderDark,
pal.view,
bottomPosition,
showBottomBar && fabMinimalShellTransform,
]}
onPress={onPress}
hitSlop={HITSLOP_20}
accessibilityRole="button"
accessibilityLabel={label}
accessibilityHint="">
<FontAwesomeIcon icon="angle-up" color={pal.colors.text} size={19} />
{showIndicator && <View style={[styles.indicator, pal.borderDark]} />}
</AnimatedTouchableOpacity>
<Animated.View style={[showBottomBar && fabMinimalShellTransform]}>
<PressableScale
style={[
styles.loadLatest,
isDesktop &&
(isTallViewport
? styles.loadLatestOutOfLine
: styles.loadLatestInline),
isTablet &&
(centerColumnOffset
? styles.loadLatestInlineOffset
: styles.loadLatestInline),
pal.borderDark,
pal.view,
bottomPosition,
]}
onPress={onPress}
hitSlop={HITSLOP_20}
accessibilityLabel={label}
accessibilityHint=""
targetScale={0.9}>
<FontAwesomeIcon icon="angle-up" color={pal.colors.text} size={19} />
{showIndicator && <View style={[styles.indicator, pal.borderDark]} />}
</PressableScale>
</Animated.View>
)
}
const styles = StyleSheet.create({
loadLatest: {
// @ts-ignore 'fixed' is web only -prf
zIndex: 20,
position: isWeb ? 'fixed' : 'absolute',
left: 18,
borderWidth: StyleSheet.hairlineWidth,
@@ -87,11 +91,15 @@ const styles = StyleSheet.create({
justifyContent: 'center',
},
loadLatestInline: {
// @ts-ignore web only
// @ts-expect-error web only
left: 'calc(50vw - 282px)',
},
loadLatestInlineOffset: {
// @ts-expect-error web only
left: 'calc(50vw - 432px)',
},
loadLatestOutOfLine: {
// @ts-ignore web only
// @ts-expect-error web only
left: 'calc(50vw - 382px)',
},
indicator: {
+4 -2
View File
@@ -81,8 +81,10 @@ export function HomeScreen(props: Props) {
)
} else {
return (
<Layout.Screen style={styles.loading}>
<ActivityIndicator size="large" />
<Layout.Screen>
<Layout.Center style={styles.loading}>
<ActivityIndicator size="large" />
</Layout.Center>
</Layout.Screen>
)
}
@@ -150,11 +150,10 @@ function NativeStackNavigator({
descriptors={newDescriptors}
/>
</View>
{isWeb && showBottomBar && <BottomBarWeb />}
{isWeb && !showBottomBar && (
{isWeb && (
<>
<DesktopLeftNav />
<DesktopRightNav routeName={activeRoute.name} />
{showBottomBar ? <BottomBarWeb /> : <DesktopLeftNav />}
{!isMobile && <DesktopRightNav routeName={activeRoute.name} />}
</>
)}
</NavigationContent>
+40 -46
View File
@@ -1,7 +1,6 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api'
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
import {msg, plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {
@@ -33,7 +32,7 @@ import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {PressableWithHover} from '#/view/com/util/PressableWithHover'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {NavSignupCard} from '#/view/shell/NavSignupCard'
import {atoms as a, tokens, useBreakpoints, useTheme} from '#/alf'
import {atoms as a, tokens, useLayoutBreakpoints, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {DialogControlProps} from '#/components/Dialog'
import {ArrowBoxLeft_Stroke2_Corner0_Rounded as LeaveIcon} from '#/components/icons/ArrowBoxLeft'
@@ -86,7 +85,7 @@ function ProfileCard() {
})
const profiles = data?.profiles
const signOutPromptControl = Prompt.usePromptControl()
const {gtTablet} = useBreakpoints()
const {leftNavMinimal} = useLayoutBreakpoints()
const {_} = useLingui()
const t = useTheme()
@@ -101,7 +100,7 @@ function ProfileCard() {
}))
return (
<View style={[a.my_md, gtTablet && [a.w_full, a.align_start]]}>
<View style={[a.my_md, !leftNavMinimal && [a.w_full, a.align_start]]}>
{!isLoading && profile ? (
<Menu.Root>
<Menu.Trigger label={_(msg`Switch accounts`)}>
@@ -120,7 +119,7 @@ function ProfileCard() {
a.align_center,
a.flex_row,
{gap: 6},
gtTablet && [a.pl_lg, a.pr_md],
!leftNavMinimal && [a.pl_lg, a.pr_md],
]}>
<View
style={[
@@ -133,8 +132,8 @@ function ProfileCard() {
a.z_10,
active && {
transform: [
{scale: gtTablet ? 2 / 3 : 0.8},
{translateX: gtTablet ? -22 : 0},
{scale: !leftNavMinimal ? 2 / 3 : 0.8},
{translateX: !leftNavMinimal ? -22 : 0},
],
},
]}>
@@ -144,7 +143,7 @@ function ProfileCard() {
type={profile?.associated?.labeler ? 'labeler' : 'user'}
/>
</View>
{gtTablet && (
{!leftNavMinimal && (
<>
<View
style={[
@@ -197,7 +196,7 @@ function ProfileCard() {
<LoadingPlaceholder
width={size}
height={size}
style={[{borderRadius: size}, gtTablet && a.ml_lg]}
style={[{borderRadius: size}, !leftNavMinimal && a.ml_lg]}
/>
)}
<Prompt.Basic
@@ -307,8 +306,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
const t = useTheme()
const {_} = useLingui()
const {currentAccount} = useSession()
const {gtMobile, gtTablet} = useBreakpoints()
const isTablet = gtMobile && !gtTablet
const {leftNavMinimal} = useLayoutBreakpoints()
const [pathName] = React.useMemo(() => router.matchPath(href), [href])
const currentRouteInfo = useNavigationState(state => {
if (!state) {
@@ -350,9 +348,8 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
a.transition_color,
]}
hoverStyle={t.atoms.bg_contrast_25}
// @ts-ignore the function signature differs on web -prf
// @ts-expect-error the function signature differs on web -prf
onPress={onPressWrapped}
// @ts-ignore web only -prf
href={href}
dataSet={{noUnderline: 1}}
role="link"
@@ -367,7 +364,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
width: 24,
height: 24,
},
isTablet && {
leftNavMinimal && {
width: 40,
height: 40,
},
@@ -407,7 +404,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
paddingVertical: 1,
minWidth: 16,
},
isTablet && [
leftNavMinimal && [
{
top: '10%',
left: count.length === 1 ? 20 : 16,
@@ -429,7 +426,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
right: -1,
top: -3,
},
isTablet && {
leftNavMinimal && {
right: 6,
top: 4,
},
@@ -437,7 +434,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
/>
) : null}
</View>
{gtTablet && (
{!leftNavMinimal && (
<Text style={[a.text_xl, isCurrent ? a.font_heavy : a.font_normal]}>
{label}
</Text>
@@ -451,7 +448,7 @@ function ComposeBtn() {
const {getState} = useNavigation()
const {openComposer} = useComposerControls()
const {_} = useLingui()
const {isTablet} = useWebMediaQueries()
const {leftNavMinimal} = useLayoutBreakpoints()
const [isFetchingHandle, setIsFetchingHandle] = React.useState(false)
const fetchHandle = useFetchHandle()
@@ -491,9 +488,10 @@ function ComposeBtn() {
const onPressCompose = async () =>
openComposer({mention: await getProfileHandle()})
if (isTablet) {
if (leftNavMinimal) {
return null
}
return (
<View style={[a.flex_row, a.pl_md, a.pt_xl]}>
<Button
@@ -541,7 +539,8 @@ export function DesktopLeftNav() {
const {hasSession, currentAccount} = useSession()
const pal = usePalette('default')
const {_} = useLingui()
const {isDesktop, isTablet} = useWebMediaQueries()
const {isDesktop} = useWebMediaQueries()
const {leftNavMinimal, centerColumnOffset} = useLayoutBreakpoints()
const numUnreadNotifications = useUnreadNotifications()
const hasHomeBadge = useHomeBadge()
const gate = useGate()
@@ -556,8 +555,14 @@ export function DesktopLeftNav() {
style={[
a.px_xl,
styles.leftNav,
isTablet && styles.leftNavTablet,
pal.border,
leftNavMinimal && styles.leftNavMinimal,
{
transform: [
{translateX: centerColumnOffset ? -450 : -300},
{translateX: '-100%'},
...a.scrollbar_offset.transform,
],
},
]}>
{hasSession ? (
<ProfileCard />
@@ -630,14 +635,14 @@ export function DesktopLeftNav() {
href="/feeds"
icon={
<Hashtag
style={pal.text as FontAwesomeIconStyle}
style={pal.text}
aria-hidden={true}
width={NAV_ICON_WIDTH}
/>
}
iconFilled={
<HashtagFilled
style={pal.text as FontAwesomeIconStyle}
style={pal.text}
aria-hidden={true}
width={NAV_ICON_WIDTH}
/>
@@ -708,36 +713,25 @@ export function DesktopLeftNav() {
const styles = StyleSheet.create({
leftNav: {
// @ts-ignore web only
position: 'fixed',
top: 10,
// @ts-ignore web only
top: 0,
paddingTop: 10,
paddingBottom: 10,
left: '50%',
transform: [
{
translateX: -300,
},
{
translateX: '-100%',
},
...a.scrollbar_offset.transform,
],
width: 240,
// @ts-ignore web only
maxHeight: 'calc(100vh - 10px)',
// @ts-expect-error web only
maxHeight: '100vh',
overflowY: 'auto',
},
leftNavTablet: {
top: 0,
left: 0,
right: 'auto',
borderRightWidth: 1,
height: '100%',
width: 76,
leftNavMinimal: {
paddingTop: 0,
paddingBottom: 0,
paddingLeft: 0,
paddingRight: 0,
height: '100%',
width: 86,
alignItems: 'center',
transform: [],
overflowX: 'hidden',
},
backBtn: {
position: 'absolute',
+23 -13
View File
@@ -1,17 +1,23 @@
import React from 'react'
import {useEffect, useState} from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/core'
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {useKawaiiMode} from '#/state/preferences/kawaii'
import {useSession} from '#/state/session'
import {DesktopFeeds} from '#/view/shell/desktop/Feeds'
import {DesktopSearch} from '#/view/shell/desktop/Search'
import {SidebarTrendingTopics} from '#/view/shell/desktop/SidebarTrendingTopics'
import {atoms as a, useGutters, useTheme, web} from '#/alf'
import {
atoms as a,
useGutters,
useLayoutBreakpoints,
useTheme,
web,
} from '#/alf'
import {AppLanguageDropdown} from '#/components/AppLanguageDropdown'
import {Divider} from '#/components/Divider'
import {InlineLinkText} from '#/components/Link'
import {ProgressGuideList} from '#/components/ProgressGuide/List'
@@ -19,16 +25,15 @@ import {Text} from '#/components/Typography'
function useWebQueryParams() {
const navigation = useNavigation()
const [params, setParams] = React.useState<Record<string, string>>({})
const [params, setParams] = useState<Record<string, string>>({})
React.useEffect(() => {
useEffect(() => {
return navigation.addListener('state', e => {
try {
const {state} = e.data
const lastRoute = state.routes[state.routes.length - 1]
const {params} = lastRoute
setParams(params)
} catch (e) {}
setParams(lastRoute.params)
} catch (err) {}
})
}, [navigation, setParams])
@@ -45,9 +50,10 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
const webqueryParams = useWebQueryParams()
const searchQuery = webqueryParams?.q
const showTrending = !isSearchScreen || (isSearchScreen && !!searchQuery)
const {rightNavVisible, centerColumnOffset, leftNavMinimal} =
useLayoutBreakpoints()
const {isTablet} = useWebMediaQueries()
if (isTablet) {
if (!rightNavVisible) {
return null
}
@@ -60,9 +66,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
position: 'fixed',
left: '50%',
transform: [
{
translateX: 300,
},
{translateX: centerColumnOffset ? 150 : 300},
...a.scrollbar_offset.transform,
],
width: 300 + gutters.paddingLeft,
@@ -125,6 +129,12 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
</Trans>
</Text>
)}
{!hasSession && leftNavMinimal && (
<View style={[a.w_full, {height: 32}]}>
<AppLanguageDropdown style={{marginTop: 0}} />
</View>
)}
</View>
)
}