[Chat] Split view layout for web (#10258)
This commit is contained in:
@@ -15,10 +15,8 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DISCOVER_FEED_URI, VIDEO_FEED_URIS} from '#/lib/constants'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {ComposeIcon2} from '#/lib/icons'
|
||||
import {getRootNavigation, getTabState, TabState} from '#/lib/routes/helpers'
|
||||
import {type AllNavigatorParams} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {listenSoftReset} from '#/state/events'
|
||||
import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback'
|
||||
import {useSetHomeBadge} from '#/state/home-badge'
|
||||
@@ -30,14 +28,16 @@ import {
|
||||
} from '#/state/queries/post-feed'
|
||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {useSession} from '#/state/session'
|
||||
import {PostFeed} from '#/view/com/posts/PostFeed'
|
||||
import {FAB} from '#/view/com/util/fab/FAB'
|
||||
import {type ListMethods} from '#/view/com/util/List'
|
||||
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
||||
import {MainScrollProvider} from '#/view/com/util/MainScrollProvider'
|
||||
import {useTheme} from '#/alf'
|
||||
import {useHeaderOffset} from '#/components/hooks/useHeaderOffset'
|
||||
import {EditBig_Stroke2_Corner2_Rounded as EditBigIcon} from '#/components/icons/EditBig'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_NATIVE} from '#/env'
|
||||
import {PostFeed} from '../posts/PostFeed'
|
||||
import {FAB} from '../util/fab/FAB'
|
||||
import {type ListMethods} from '../util/List'
|
||||
import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn'
|
||||
import {MainScrollProvider} from '../util/MainScrollProvider'
|
||||
|
||||
const POLL_FREQ = 60e3 // 60sec
|
||||
|
||||
@@ -81,6 +81,7 @@ export function FeedPage({
|
||||
const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode
|
||||
return IS_NATIVE && _isVideoFeed
|
||||
}, [feedInfo])
|
||||
const t = useTheme()
|
||||
|
||||
useEffect(() => {
|
||||
if (isPageFocused) {
|
||||
@@ -173,7 +174,7 @@ export function FeedPage({
|
||||
<FAB
|
||||
testID="composeFAB"
|
||||
onPress={onPressCompose}
|
||||
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
|
||||
icon={<EditBigIcon size="lg" fill={t.palette.white} />}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg({message: `New post`, context: 'action'}))}
|
||||
accessibilityHint=""
|
||||
|
||||
@@ -2,45 +2,53 @@ import {isValidElement} from 'react'
|
||||
import {type StyleProp, type TextStyle, type ViewStyle} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {Button, type ButtonProps, ButtonText} from '#/components/Button'
|
||||
import {
|
||||
Button,
|
||||
ButtonIcon,
|
||||
type ButtonProps,
|
||||
ButtonText,
|
||||
} from '#/components/Button'
|
||||
import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export type EmptyStateButtonProps = Omit<ButtonProps, 'children' | 'label'> & {
|
||||
label: string
|
||||
text: string
|
||||
icon?: React.ComponentProps<typeof ButtonIcon>['icon']
|
||||
}
|
||||
|
||||
export function EmptyState({
|
||||
testID,
|
||||
icon,
|
||||
iconSize = '3xl',
|
||||
iconColor,
|
||||
message,
|
||||
style,
|
||||
textStyle,
|
||||
button,
|
||||
}: {
|
||||
testID?: string
|
||||
icon?: React.ComponentType<any> | React.ReactElement
|
||||
iconSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'
|
||||
icon?: React.ComponentType<any> | React.ReactElement | null
|
||||
iconSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'
|
||||
iconColor?: string
|
||||
message: string
|
||||
style?: StyleProp<ViewStyle>
|
||||
textStyle?: StyleProp<TextStyle>
|
||||
button?: EmptyStateButtonProps
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {gtMobile, gtTablet} = useBreakpoints()
|
||||
|
||||
const placeholderIcon = (
|
||||
<EditIcon size="2xl" fill={t.atoms.text_contrast_medium.color} />
|
||||
)
|
||||
|
||||
const renderIcon = () => {
|
||||
if (icon === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!icon) {
|
||||
return placeholderIcon
|
||||
}
|
||||
@@ -57,8 +65,7 @@ export function EmptyState({
|
||||
return (
|
||||
<IconComponent
|
||||
size={iconSize}
|
||||
fill={t.atoms.text_contrast_medium.color}
|
||||
style={{color: t.atoms.text_contrast_low.color}}
|
||||
style={{color: iconColor ?? t.atoms.text_contrast_low.color}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -79,16 +86,14 @@ export function EmptyState({
|
||||
{height: 64, width: 64},
|
||||
isValidElement(icon)
|
||||
? a.bg_transparent
|
||||
: [isTabletOrDesktop && {marginTop: 50}],
|
||||
: [gtTablet && {marginTop: 50}],
|
||||
]}>
|
||||
{renderIcon()}
|
||||
</View>
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
color: pal.colors.textLight,
|
||||
maxWidth: gtMobile ? '40%' : '60%',
|
||||
},
|
||||
t.atoms.text_contrast_high,
|
||||
{maxWidth: gtMobile ? '40%' : '60%'},
|
||||
a.pt_xs,
|
||||
a.font_medium,
|
||||
a.text_md,
|
||||
@@ -101,8 +106,9 @@ export function EmptyState({
|
||||
{message}
|
||||
</Text>
|
||||
{button && (
|
||||
<View style={[a.flex_shrink, a.mt_xl, a.self_center, a.mb_5xl]}>
|
||||
<View style={[a.flex_shrink, a.mt_md, a.self_center, a.mb_5xl]}>
|
||||
<Button {...button}>
|
||||
{button.icon && <ButtonIcon icon={button.icon} />}
|
||||
<ButtonText>{button.text}</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
@@ -22,6 +22,8 @@ import {batchedUpdates} from '#/lib/batchedUpdates'
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {useScrollHandlers} from '#/lib/ScrollContext'
|
||||
import {addStyle} from '#/lib/styles'
|
||||
import {useIsWithinSplitView} from '#/screens/Messages/components/splitView/context'
|
||||
import {useTheme, web} from '#/alf'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
export type ListMethods = {
|
||||
@@ -60,7 +62,7 @@ function ListImpl<ItemT>(
|
||||
ListHeaderComponent,
|
||||
ListFooterComponent,
|
||||
ListEmptyComponent,
|
||||
disableFullWindowScroll,
|
||||
disableFullWindowScroll: disableFullWindowScrollProp,
|
||||
contentContainerStyle,
|
||||
data,
|
||||
desktopFixedHeight,
|
||||
@@ -83,6 +85,12 @@ function ListImpl<ItemT>(
|
||||
ref: React.Ref<ListMethods>,
|
||||
) {
|
||||
const contextScrollHandlers = useScrollHandlers()
|
||||
const {isWithinSplitView} = useIsWithinSplitView()
|
||||
const t = useTheme()
|
||||
|
||||
// automatically disable full window scroll when within split view
|
||||
const disableFullWindowScroll =
|
||||
disableFullWindowScrollProp ?? isWithinSplitView
|
||||
|
||||
const isEmpty = !data || data.length === 0
|
||||
|
||||
@@ -329,11 +337,15 @@ function ListImpl<ItemT>(
|
||||
<View
|
||||
{...props}
|
||||
style={[
|
||||
isWithinSplitView &&
|
||||
web({
|
||||
scrollbarWidth: 'thin',
|
||||
scrollbarColor: `${t.palette.contrast_100} transparent`,
|
||||
}),
|
||||
style,
|
||||
disableFullWindowScroll && {
|
||||
flex: 1,
|
||||
// @ts-expect-error web only
|
||||
'overflow-y': 'scroll',
|
||||
overflowY: 'scroll',
|
||||
},
|
||||
]}
|
||||
ref={nativeRef as unknown as React.RefObject<View>}>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {useBreakpoints} from '#/alf'
|
||||
import {FABInner, type FABProps} from './FABInner'
|
||||
|
||||
export const FAB = (_opts: FABProps) => {
|
||||
const {isDesktop} = useWebMediaQueries()
|
||||
export const FAB = (props: FABProps) => {
|
||||
const {gtMobile} = useBreakpoints()
|
||||
|
||||
if (!isDesktop) {
|
||||
return <FABInner {..._opts} />
|
||||
if (!gtMobile) {
|
||||
return <FABInner {...props} />
|
||||
}
|
||||
|
||||
return <View />
|
||||
|
||||
@@ -9,7 +9,6 @@ import debounce from 'lodash.debounce'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {ComposeIcon2} from '#/lib/icons'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
@@ -37,6 +36,7 @@ import * as FeedCard from '#/components/FeedCard'
|
||||
import {SearchInput} from '#/components/forms/SearchInput'
|
||||
import {IconCircle} from '#/components/IconCircle'
|
||||
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
|
||||
import {EditBig_Stroke2_Corner2_Rounded as EditBigIcon} from '#/components/icons/EditBig'
|
||||
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
|
||||
import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass'
|
||||
import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle'
|
||||
@@ -104,6 +104,7 @@ type FlatlistSlice =
|
||||
|
||||
export function FeedsScreen(_props: Props) {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const [query, setQuery] = useState('')
|
||||
@@ -539,7 +540,7 @@ export function FeedsScreen(_props: Props) {
|
||||
<FAB
|
||||
testID="composeFAB"
|
||||
onPress={onPressCompose}
|
||||
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
|
||||
icon={<EditBigIcon size="lg" fill={t.palette.white} />}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`New post`)}
|
||||
accessibilityHint=""
|
||||
|
||||
@@ -8,12 +8,10 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {ComposeIcon2} from '#/lib/icons'
|
||||
import {
|
||||
type NativeStackScreenProps,
|
||||
type NotificationsTabNavigatorParams,
|
||||
} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {logger} from '#/logger'
|
||||
import {emitSoftReset, listenSoftReset} from '#/state/events'
|
||||
import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
|
||||
@@ -33,6 +31,7 @@ import {MainScrollProvider} from '#/view/com/util/MainScrollProvider'
|
||||
import {atoms as a, useTheme, web} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {ButtonIcon} from '#/components/Button'
|
||||
import {EditBig_Stroke2_Corner2_Rounded as EditBigIcon} from '#/components/icons/EditBig'
|
||||
import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {InlineLinkText, Link} from '#/components/Link'
|
||||
@@ -50,6 +49,7 @@ type Props = NativeStackScreenProps<
|
||||
>
|
||||
export function NotificationsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const unreadNotifs = useUnreadNotifications()
|
||||
const hasNew = !!unreadNotifs
|
||||
@@ -161,7 +161,7 @@ export function NotificationsScreen({}: Props) {
|
||||
<FAB
|
||||
testID="composeFAB"
|
||||
onPress={() => openComposer({logContext: 'Fab'})}
|
||||
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
|
||||
icon={<EditBigIcon size="lg" fill={t.palette.white} />}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`New post`)}
|
||||
accessibilityHint=""
|
||||
|
||||
@@ -16,7 +16,6 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
|
||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||
import {ComposeIcon2} from '#/lib/icons'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
@@ -25,7 +24,7 @@ import {
|
||||
import {combinedDisplayName} from '#/lib/strings/display-names'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||
import {colors, s} from '#/lib/styles'
|
||||
import {colors} from '#/lib/styles'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {listenSoftReset} from '#/state/events'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
@@ -43,8 +42,9 @@ import {type ListRef} from '#/view/com/util/List'
|
||||
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
|
||||
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
|
||||
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Circle_And_Square_Stroke1_Corner0_Rounded_Filled as CircleAndSquareIcon} from '#/components/icons/CircleAndSquare'
|
||||
import {EditBig_Stroke2_Corner2_Rounded as EditBigIcon} from '#/components/icons/EditBig'
|
||||
import {Heart2_Stroke1_Corner0_Rounded as HeartIcon} from '#/components/icons/Heart2'
|
||||
import {Image_Stroke1_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
|
||||
import {Message_Stroke1_Corner0_Rounded_Filled as MessageIcon} from '#/components/icons/Message'
|
||||
@@ -172,6 +172,7 @@ function ProfileScreenLoaded({
|
||||
hideBackButton: boolean
|
||||
isPlaceholderProfile: boolean
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const profile = useProfileShadow(profileUnshadowed)
|
||||
const {hasSession, currentAccount} = useSession()
|
||||
const {openComposer} = useOpenComposer()
|
||||
@@ -587,7 +588,7 @@ function ProfileScreenLoaded({
|
||||
<FAB
|
||||
testID="composeFAB"
|
||||
onPress={onPressCompose}
|
||||
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
|
||||
icon={<EditBigIcon size="lg" fill={t.palette.white} />}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`New post`)}
|
||||
accessibilityHint=""
|
||||
|
||||
@@ -46,9 +46,10 @@ import {DesktopRightNav} from './desktop/RightNav'
|
||||
// Older screens are unmounted to prevent memory growth during long sessions.
|
||||
const WEB_MAX_CACHED_SCREENS = 5
|
||||
|
||||
type NativeStackNavigationOptionsWithAuth = NativeStackNavigationOptions & {
|
||||
requireAuth?: boolean
|
||||
}
|
||||
export type NativeStackNavigationOptionsWithAuth =
|
||||
NativeStackNavigationOptions & {
|
||||
requireAuth?: boolean
|
||||
}
|
||||
|
||||
function NativeStackNavigator({
|
||||
id,
|
||||
@@ -200,7 +201,11 @@ function NativeStackNavigator({
|
||||
</View>
|
||||
{IS_WEB && (
|
||||
<>
|
||||
{showBottomBar ? <BottomBarWeb /> : <DesktopLeftNav />}
|
||||
{showBottomBar ? (
|
||||
<BottomBarWeb />
|
||||
) : (
|
||||
<DesktopLeftNav routeName={activeRoute.name} />
|
||||
)}
|
||||
{!isMobile && <DesktopRightNav routeName={activeRoute.name} />}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -8,8 +8,6 @@ import {useNavigation, useNavigationState} from '@react-navigation/native'
|
||||
|
||||
import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {getCurrentRoute, isTab} from '#/lib/routes/helpers'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {
|
||||
@@ -30,7 +28,14 @@ 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, useLayoutBreakpoints, useTheme, web} from '#/alf'
|
||||
import {
|
||||
atoms as a,
|
||||
tokens,
|
||||
useBreakpoints,
|
||||
useLayoutBreakpoints,
|
||||
useTheme,
|
||||
web,
|
||||
} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {type DialogControlProps} from '#/components/Dialog'
|
||||
import {ArrowBoxLeft_Stroke2_Corner0_Rounded as LeaveIcon} from '#/components/icons/ArrowBoxLeft'
|
||||
@@ -44,7 +49,7 @@ import {
|
||||
BulletList_Stroke2_Corner0_Rounded as List,
|
||||
} from '#/components/icons/BulletList'
|
||||
import {DotGrid3x1_Stroke2_Corner0_Rounded as EllipsisIcon} from '#/components/icons/DotGrid'
|
||||
import {EditBig_Stroke2_Corner0_Rounded as EditBig} from '#/components/icons/EditBig'
|
||||
import {EditBig_Stroke2_Corner2_Rounded as EditBigIcon} from '#/components/icons/EditBig'
|
||||
import {
|
||||
Hashtag_Filled_Corner0_Rounded as HashtagFilled,
|
||||
Hashtag_Stroke2_Corner0_Rounded as Hashtag,
|
||||
@@ -76,12 +81,12 @@ import * as Prompt from '#/components/Prompt'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAgeAssurance} from '#/ageAssurance'
|
||||
import {useActorStatus} from '#/features/liveNow'
|
||||
import {router} from '#/routes'
|
||||
import {PlatformInfo} from '../../../../modules/expo-bluesky-swiss-army'
|
||||
import {router} from '../../../routes'
|
||||
|
||||
const NAV_ICON_WIDTH = 28
|
||||
|
||||
function ProfileCard() {
|
||||
function ProfileCard({minimal}: {minimal: boolean}) {
|
||||
const {currentAccount, accounts} = useSession()
|
||||
const {logoutEveryAccount} = useSessionApi()
|
||||
const {isLoading, data} = useProfilesQuery({
|
||||
@@ -89,7 +94,6 @@ function ProfileCard() {
|
||||
})
|
||||
const profiles = data?.profiles
|
||||
const signOutPromptControl = Prompt.usePromptControl()
|
||||
const {leftNavMinimal} = useLayoutBreakpoints()
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
|
||||
@@ -106,7 +110,7 @@ function ProfileCard() {
|
||||
const {isActive: live} = useActorStatus(profile)
|
||||
|
||||
return (
|
||||
<View style={[a.my_md, !leftNavMinimal && [a.w_full, a.align_start]]}>
|
||||
<View style={[a.my_md, !minimal && [a.w_full, a.align_start]]}>
|
||||
{!isLoading && profile ? (
|
||||
<Menu.Root>
|
||||
<Menu.Trigger label={_(msg`Switch accounts`)}>
|
||||
@@ -125,7 +129,7 @@ function ProfileCard() {
|
||||
a.align_center,
|
||||
a.flex_row,
|
||||
{gap: 6},
|
||||
!leftNavMinimal && [a.pl_lg, a.pr_md],
|
||||
!minimal && [a.pl_lg, a.pr_md],
|
||||
]}>
|
||||
<View
|
||||
style={[
|
||||
@@ -138,8 +142,8 @@ function ProfileCard() {
|
||||
a.z_10,
|
||||
active && {
|
||||
transform: [
|
||||
{scale: !leftNavMinimal ? 2 / 3 : 0.8},
|
||||
{translateX: !leftNavMinimal ? -22 : 0},
|
||||
{scale: !minimal ? 2 / 3 : 0.8},
|
||||
{translateX: !minimal ? -22 : 0},
|
||||
],
|
||||
},
|
||||
]}>
|
||||
@@ -150,7 +154,7 @@ function ProfileCard() {
|
||||
live={live}
|
||||
/>
|
||||
</View>
|
||||
{!leftNavMinimal && (
|
||||
{!minimal && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
@@ -203,7 +207,7 @@ function ProfileCard() {
|
||||
<LoadingPlaceholder
|
||||
width={size}
|
||||
height={size}
|
||||
style={[{borderRadius: size}, !leftNavMinimal && a.ml_lg]}
|
||||
style={[{borderRadius: size}, !minimal && a.ml_lg]}
|
||||
/>
|
||||
)}
|
||||
<Prompt.Basic
|
||||
@@ -291,12 +295,18 @@ function SwitcherMenuProfileLink() {
|
||||
}
|
||||
return getCurrentRoute(state)
|
||||
})
|
||||
let isCurrent =
|
||||
currentRouteInfo.name === 'Profile'
|
||||
? isTab(currentRouteInfo.name, pathName) &&
|
||||
const isCurrent = useMemo(() => {
|
||||
if (currentRouteInfo.name === 'Profile') {
|
||||
return (
|
||||
isTab(currentRouteInfo.name, pathName) &&
|
||||
(currentRouteInfo.params as CommonNavigatorParams['Profile']).name ===
|
||||
currentAccount?.handle
|
||||
: isTab(currentRouteInfo.name, pathName)
|
||||
)
|
||||
} else {
|
||||
return isTab(currentRouteInfo.name, pathName)
|
||||
}
|
||||
}, [currentAccount?.handle, currentRouteInfo, pathName])
|
||||
|
||||
const onProfilePress = useCallback(
|
||||
(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
if (e.ctrlKey || e.metaKey || e.altKey) {
|
||||
@@ -374,12 +384,21 @@ interface NavItemProps {
|
||||
icon: JSX.Element
|
||||
iconFilled: JSX.Element
|
||||
label: string
|
||||
minimal: boolean
|
||||
}
|
||||
function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
|
||||
function NavItem({
|
||||
count,
|
||||
hasNew,
|
||||
href,
|
||||
icon,
|
||||
iconFilled,
|
||||
label,
|
||||
minimal,
|
||||
}: NavItemProps) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
const {leftNavMinimal} = useLayoutBreakpoints()
|
||||
|
||||
const [pathName] = useMemo(() => router.matchPath(href), [href])
|
||||
const currentRouteInfo = useNavigationState(state => {
|
||||
if (!state) {
|
||||
@@ -393,6 +412,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
|
||||
(currentRouteInfo.params as CommonNavigatorParams['Profile']).name ===
|
||||
currentAccount?.handle
|
||||
: isTab(currentRouteInfo.name, pathName)
|
||||
const isRelated = currentRouteInfo.name.startsWith(pathName)
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const onPressWrapped = useCallback(
|
||||
(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
@@ -417,7 +437,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.p_md,
|
||||
a.rounded_sm,
|
||||
a.rounded_full,
|
||||
a.gap_sm,
|
||||
a.outline_inset_1,
|
||||
a.transition_color,
|
||||
@@ -438,12 +458,8 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
leftNavMinimal && {
|
||||
width: 40,
|
||||
height: 40,
|
||||
},
|
||||
]}>
|
||||
{isCurrent ? iconFilled : icon}
|
||||
{isCurrent || isRelated ? iconFilled : icon}
|
||||
{typeof count === 'string' && count ? (
|
||||
<View
|
||||
style={[
|
||||
@@ -479,12 +495,6 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
|
||||
paddingVertical: 1,
|
||||
minWidth: 16,
|
||||
},
|
||||
leftNavMinimal && [
|
||||
{
|
||||
top: '10%',
|
||||
left: count.length === 1 ? 20 : 16,
|
||||
},
|
||||
],
|
||||
]}>
|
||||
{count}
|
||||
</Text>
|
||||
@@ -502,15 +512,11 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
|
||||
right: -2,
|
||||
top: -4,
|
||||
},
|
||||
leftNavMinimal && {
|
||||
right: 4,
|
||||
top: 2,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
{!leftNavMinimal && (
|
||||
{!minimal && (
|
||||
<Text style={[a.text_xl, isCurrent ? a.font_bold : a.font_normal]}>
|
||||
{label}
|
||||
</Text>
|
||||
@@ -519,12 +525,11 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
|
||||
)
|
||||
}
|
||||
|
||||
function ComposeBtn() {
|
||||
function ComposeBtn({minimal}: {minimal: boolean}) {
|
||||
const {currentAccount} = useSession()
|
||||
const {getState} = useNavigation()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const {_} = useLingui()
|
||||
const {leftNavMinimal} = useLayoutBreakpoints()
|
||||
const [isFetchingHandle, setIsFetchingHandle] = useState(false)
|
||||
const fetchHandle = useFetchHandle()
|
||||
|
||||
@@ -564,31 +569,28 @@ function ComposeBtn() {
|
||||
const onPressCompose = async () =>
|
||||
openComposer({mention: await getProfileHandle(), logContext: 'Fab'})
|
||||
|
||||
if (leftNavMinimal) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[a.flex_row, a.pl_md, a.pt_xl]}>
|
||||
<View style={minimal ? [a.px_sm, a.pt_lg] : [a.flex_row, a.pl_md, a.pt_lg]}>
|
||||
<Button
|
||||
disabled={isFetchingHandle}
|
||||
label={_(msg`Compose new post`)}
|
||||
onPress={() => void onPressCompose()}
|
||||
size="large"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
style={[a.rounded_full]}>
|
||||
<ButtonIcon icon={EditBig} position="left" />
|
||||
<ButtonText>
|
||||
<Trans context="action">New post</Trans>
|
||||
</ButtonText>
|
||||
style={[a.rounded_full, minimal && {width: 48, height: 48}]}>
|
||||
<ButtonIcon icon={EditBigIcon} size={minimal ? 'lg' : 'sm'} />
|
||||
{!minimal && (
|
||||
<ButtonText>
|
||||
<Trans context="action">New post</Trans>
|
||||
</ButtonText>
|
||||
)}
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function ChatNavItem() {
|
||||
const pal = usePalette('default')
|
||||
function ChatNavItem({minimal}: {minimal: boolean}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const numUnreadMessages = useUnreadMessageCount()
|
||||
const aa = useAgeAssurance()
|
||||
@@ -596,14 +598,19 @@ function ChatNavItem() {
|
||||
return (
|
||||
<NavItem
|
||||
href="/messages"
|
||||
minimal={minimal}
|
||||
count={aa.flags.chatDisabled ? undefined : numUnreadMessages.numUnread}
|
||||
hasNew={aa.flags.chatDisabled ? false : numUnreadMessages.hasNew}
|
||||
icon={
|
||||
<Message style={pal.text} aria-hidden={true} width={NAV_ICON_WIDTH} />
|
||||
<Message
|
||||
style={t.atoms.text}
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
/>
|
||||
}
|
||||
iconFilled={
|
||||
<MessageFilled
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
/>
|
||||
@@ -613,15 +620,24 @@ function ChatNavItem() {
|
||||
)
|
||||
}
|
||||
|
||||
export function DesktopLeftNav() {
|
||||
export function DesktopLeftNav({routeName}: {routeName: string}) {
|
||||
const {hasSession, currentAccount} = useSession()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {isDesktop} = useWebMediaQueries()
|
||||
const {leftNavMinimal, centerColumnOffset} = useLayoutBreakpoints()
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
|
||||
const aa = useAgeAssurance()
|
||||
// splitview uses the minimal variant of the leftnav. unfortunately there's no easy
|
||||
// way to thread this data through because of the view hierarchy, so just check the route name
|
||||
const isMessagesRelatedScreen =
|
||||
routeName.startsWith('Messages') && aa.state.access === aa.Access.Full
|
||||
const {leftNavMinimal: leftNavMinimalBreakpoint, centerColumnOffset} =
|
||||
useLayoutBreakpoints()
|
||||
const numUnreadNotifications = useUnreadNotifications()
|
||||
|
||||
if (!hasSession && !isDesktop) {
|
||||
const leftNavMinimal = isMessagesRelatedScreen || leftNavMinimalBreakpoint
|
||||
|
||||
if (!hasSession && !gtMobile) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -637,7 +653,11 @@ export function DesktopLeftNav() {
|
||||
transform: [
|
||||
{
|
||||
translateX:
|
||||
-300 + (centerColumnOffset ? CENTER_COLUMN_OFFSET : 0),
|
||||
-300 +
|
||||
(centerColumnOffset ? CENTER_COLUMN_OFFSET : 0) +
|
||||
(isMessagesRelatedScreen && !leftNavMinimalBreakpoint
|
||||
? -153
|
||||
: 0),
|
||||
},
|
||||
{translateX: '-100%'},
|
||||
...a.scrollbar_offset.transform,
|
||||
@@ -645,7 +665,7 @@ export function DesktopLeftNav() {
|
||||
},
|
||||
]}>
|
||||
{hasSession ? (
|
||||
<ProfileCard />
|
||||
<ProfileCard minimal={leftNavMinimal} />
|
||||
) : !leftNavMinimal ? (
|
||||
<View style={[a.pt_xl]}>
|
||||
<NavSignupCard />
|
||||
@@ -656,34 +676,36 @@ export function DesktopLeftNav() {
|
||||
<>
|
||||
<NavItem
|
||||
href="/"
|
||||
minimal={leftNavMinimal}
|
||||
icon={
|
||||
<Home
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
/>
|
||||
}
|
||||
iconFilled={
|
||||
<HomeFilled
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
/>
|
||||
}
|
||||
label={_(msg`Home`)}
|
||||
/>
|
||||
<NavItem
|
||||
href="/search"
|
||||
minimal={leftNavMinimal}
|
||||
icon={
|
||||
<MagnifyingGlass
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
/>
|
||||
}
|
||||
iconFilled={
|
||||
<MagnifyingGlassFilled
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
/>
|
||||
@@ -692,36 +714,38 @@ export function DesktopLeftNav() {
|
||||
/>
|
||||
<NavItem
|
||||
href="/notifications"
|
||||
minimal={leftNavMinimal}
|
||||
count={numUnreadNotifications}
|
||||
icon={
|
||||
<Bell
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
/>
|
||||
}
|
||||
iconFilled={
|
||||
<BellFilled
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
/>
|
||||
}
|
||||
label={_(msg`Notifications`)}
|
||||
/>
|
||||
<ChatNavItem />
|
||||
<ChatNavItem minimal={leftNavMinimal} />
|
||||
<NavItem
|
||||
href="/feeds"
|
||||
minimal={leftNavMinimal}
|
||||
icon={
|
||||
<Hashtag
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
/>
|
||||
}
|
||||
iconFilled={
|
||||
<HashtagFilled
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
/>
|
||||
@@ -730,16 +754,17 @@ export function DesktopLeftNav() {
|
||||
/>
|
||||
<NavItem
|
||||
href="/lists"
|
||||
minimal={leftNavMinimal}
|
||||
icon={
|
||||
<List
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
/>
|
||||
}
|
||||
iconFilled={
|
||||
<ListFilled
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
/>
|
||||
@@ -748,16 +773,17 @@ export function DesktopLeftNav() {
|
||||
/>
|
||||
<NavItem
|
||||
href="/saved"
|
||||
minimal={leftNavMinimal}
|
||||
icon={
|
||||
<Bookmark
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
/>
|
||||
}
|
||||
iconFilled={
|
||||
<BookmarkFilled
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
/>
|
||||
@@ -771,42 +797,44 @@ export function DesktopLeftNav() {
|
||||
/>
|
||||
<NavItem
|
||||
href={currentAccount ? makeProfileLink(currentAccount) : '/'}
|
||||
minimal={leftNavMinimal}
|
||||
icon={
|
||||
<UserCircle
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
/>
|
||||
}
|
||||
iconFilled={
|
||||
<UserCircleFilled
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
/>
|
||||
}
|
||||
label={_(msg`Profile`)}
|
||||
/>
|
||||
<NavItem
|
||||
href="/settings"
|
||||
minimal={leftNavMinimal}
|
||||
icon={
|
||||
<Settings
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
/>
|
||||
}
|
||||
iconFilled={
|
||||
<SettingsFilled
|
||||
aria-hidden={true}
|
||||
width={NAV_ICON_WIDTH}
|
||||
style={pal.text}
|
||||
style={t.atoms.text}
|
||||
/>
|
||||
}
|
||||
label={_(msg`Settings`)}
|
||||
/>
|
||||
|
||||
<ComposeBtn />
|
||||
<ComposeBtn minimal={leftNavMinimal} />
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
@@ -824,15 +852,12 @@ const styles = StyleSheet.create({
|
||||
// @ts-expect-error web only
|
||||
maxHeight: '100vh',
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'thin',
|
||||
},
|
||||
leftNavWide: {
|
||||
width: 245,
|
||||
},
|
||||
leftNavMinimal: {
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
height: '100%',
|
||||
width: 86,
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -49,6 +49,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
||||
const kawaii = useKawaiiMode()
|
||||
const gutters = useGutters(['base', 0, 'base', 'wide'])
|
||||
const isSearchScreen = routeName === 'Search'
|
||||
const isMessagesRelatedScreen = routeName.startsWith('Messages')
|
||||
const webqueryParams = useWebQueryParams()
|
||||
const searchQuery = webqueryParams?.q
|
||||
const showExploreScreenDuplicatedContent =
|
||||
@@ -56,7 +57,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
||||
const {rightNavVisible, centerColumnOffset, leftNavMinimal} =
|
||||
useLayoutBreakpoints()
|
||||
|
||||
if (!rightNavVisible) {
|
||||
if (!rightNavVisible || isMessagesRelatedScreen) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user