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:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export const Context = React.createContext<DialogContextProps>({
|
||||
nativeSnapPoint: BottomSheetSnapPoint.Hidden,
|
||||
disableDrag: false,
|
||||
setDisableDrag: () => {},
|
||||
isWithinDialog: false,
|
||||
})
|
||||
|
||||
export function useDialogContext() {
|
||||
|
||||
@@ -154,6 +154,7 @@ export function Outer({
|
||||
nativeSnapPoint: snapPoint,
|
||||
disableDrag,
|
||||
setDisableDrag,
|
||||
isWithinDialog: true,
|
||||
}),
|
||||
[close, snapPoint, disableDrag, setDisableDrag],
|
||||
)
|
||||
|
||||
@@ -97,6 +97,7 @@ export function Outer({
|
||||
nativeSnapPoint: 0,
|
||||
disableDrag: false,
|
||||
setDisableDrag: () => {},
|
||||
isWithinDialog: true,
|
||||
}),
|
||||
[close],
|
||||
)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -106,6 +106,7 @@ export function Deactivated() {
|
||||
return (
|
||||
<View style={[a.util_screen_outer, a.flex_1]}>
|
||||
<Layout.Content
|
||||
ignoreTabletLayoutOffset
|
||||
contentContainerStyle={[
|
||||
a.px_2xl,
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user