diff options
-rw-r--r-- | src/alf/breakpoints.ts | 15 | ||||
-rw-r--r-- | src/components/Dialog/context.ts | 1 | ||||
-rw-r--r-- | src/components/Dialog/index.tsx | 1 | ||||
-rw-r--r-- | src/components/Dialog/index.web.tsx | 1 | ||||
-rw-r--r-- | src/components/Dialog/types.ts | 2 | ||||
-rw-r--r-- | src/components/Layout/Header/index.tsx | 10 | ||||
-rw-r--r-- | src/components/Layout/index.tsx | 43 | ||||
-rw-r--r-- | src/screens/Deactivated.tsx | 1 | ||||
-rw-r--r-- | src/screens/Profile/Sections/Labels.tsx | 10 | ||||
-rw-r--r-- | src/view/com/auth/SplashScreen.web.tsx | 8 | ||||
-rw-r--r-- | src/view/com/posts/PostFeed.tsx | 9 | ||||
-rw-r--r-- | src/view/com/util/Views.web.tsx | 33 | ||||
-rw-r--r-- | src/view/com/util/load-latest/LoadLatestBtn.tsx | 64 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 6 | ||||
-rw-r--r-- | src/view/shell/createNativeStackNavigatorWithAuth.tsx | 7 | ||||
-rw-r--r-- | src/view/shell/desktop/LeftNav.tsx | 86 | ||||
-rw-r--r-- | src/view/shell/desktop/RightNav.tsx | 36 |
17 files changed, 211 insertions, 122 deletions
diff --git a/src/alf/breakpoints.ts b/src/alf/breakpoints.ts index 934585644..f30a4b489 100644 --- a/src/alf/breakpoints.ts +++ b/src/alf/breakpoints.ts @@ -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, + } +} diff --git a/src/components/Dialog/context.ts b/src/components/Dialog/context.ts index b479bc7f0..331ff3f33 100644 --- a/src/components/Dialog/context.ts +++ b/src/components/Dialog/context.ts @@ -14,6 +14,7 @@ export const Context = React.createContext<DialogContextProps>({ nativeSnapPoint: BottomSheetSnapPoint.Hidden, disableDrag: false, setDisableDrag: () => {}, + isWithinDialog: false, }) export function useDialogContext() { diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index e70e4aef4..463cadf3c 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -154,6 +154,7 @@ export function Outer({ nativeSnapPoint: snapPoint, disableDrag, setDisableDrag, + isWithinDialog: true, }), [close, snapPoint, disableDrag, setDisableDrag], ) diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx index a27222229..153954691 100644 --- a/src/components/Dialog/index.web.tsx +++ b/src/components/Dialog/index.web.tsx @@ -97,6 +97,7 @@ export function Outer({ nativeSnapPoint: 0, disableDrag: false, setDisableDrag: () => {}, + isWithinDialog: true, }), [close], ) diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts index b87bfe2b7..32886f3ce 100644 --- a/src/components/Dialog/types.ts +++ b/src/components/Dialog/types.ts @@ -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 = { diff --git a/src/components/Layout/Header/index.tsx b/src/components/Layout/Header/index.tsx index 3af0215c5..8ef114b44 100644 --- a/src/components/Layout/Header/index.tsx +++ b/src/components/Layout/Header/index.tsx @@ -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> diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index 489ebb225..623478a6a 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -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, ], }), diff --git a/src/screens/Deactivated.tsx b/src/screens/Deactivated.tsx index de03a8d68..602012f08 100644 --- a/src/screens/Deactivated.tsx +++ b/src/screens/Deactivated.tsx @@ -106,6 +106,7 @@ export function Deactivated() { return ( <View style={[a.util_screen_outer, a.flex_1]}> <Layout.Content + ignoreTabletLayoutOffset contentContainerStyle={[ a.px_2xl, { diff --git a/src/screens/Profile/Sections/Labels.tsx b/src/screens/Profile/Sections/Labels.tsx index 6c76d7b15..770464a71 100644 --- a/src/screens/Profile/Sections/Labels.tsx +++ b/src/screens/Profile/Sections/Labels.tsx @@ -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> ) } diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx index eded80358..21b289e2c 100644 --- a/src/view/com/auth/SplashScreen.web.tsx +++ b/src/view/com/auth/SplashScreen.web.tsx @@ -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} diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx index c50d7f979..8ad97e2c1 100644 --- a/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx @@ -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, diff --git a/src/view/com/util/Views.web.tsx b/src/view/com/util/Views.web.tsx index e64b0ce9a..94cadb13e 100644 --- a/src/view/com/util/Views.web.tsx +++ b/src/view/com/util/Views.web.tsx @@ -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', }, }) diff --git a/src/view/com/util/load-latest/LoadLatestBtn.tsx b/src/view/com/util/load-latest/LoadLatestBtn.tsx index b502f0b68..89e5784b7 100644 --- a/src/view/com/util/load-latest/LoadLatestBtn.tsx +++ b/src/view/com/util/load-latest/LoadLatestBtn.tsx @@ -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: { diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index ef672f7fb..a6e2595ee 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -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> ) } diff --git a/src/view/shell/createNativeStackNavigatorWithAuth.tsx b/src/view/shell/createNativeStackNavigatorWithAuth.tsx index 35a46b427..018ff3d97 100644 --- a/src/view/shell/createNativeStackNavigatorWithAuth.tsx +++ b/src/view/shell/createNativeStackNavigatorWithAuth.tsx @@ -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> diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 62fbf5cae..59055c6dc 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -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', diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 363294aa5..510d505cd 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -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> ) } |